text.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. package widget
  2. import (
  3. "bufio"
  4. "image"
  5. "io"
  6. "math"
  7. "sort"
  8. "unicode"
  9. "unicode/utf8"
  10. "gioui.org/f32"
  11. "gioui.org/font"
  12. "gioui.org/layout"
  13. "gioui.org/op"
  14. "gioui.org/op/clip"
  15. "gioui.org/op/paint"
  16. "gioui.org/text"
  17. "gioui.org/unit"
  18. "golang.org/x/exp/slices"
  19. "golang.org/x/image/math/fixed"
  20. )
  21. // textSource provides text data for use in widgets. If the underlying data type
  22. // can fail due to I/O errors, it is the responsibility of that type to provide
  23. // its own mechanism to surface and handle those errors. They will not always
  24. // be returned by widgets using these functions.
  25. type textSource interface {
  26. io.ReaderAt
  27. // Size returns the total length of the data in bytes.
  28. Size() int64
  29. // Changed returns whether the contents have changed since the last call
  30. // to Changed.
  31. Changed() bool
  32. // ReplaceRunes replaces runeCount runes starting at byteOffset within the
  33. // data with the provided string. Implementations of read-only text sources
  34. // are free to make this a no-op.
  35. ReplaceRunes(byteOffset int64, runeCount int64, replacement string)
  36. }
  37. // textView provides efficient shaping and indexing of interactive text. When provided
  38. // with a TextSource, textView will shape and cache the runes within that source.
  39. // It provides methods for configuring a viewport onto the shaped text which can
  40. // be scrolled, and for configuring and drawing text selection boxes.
  41. type textView struct {
  42. Alignment text.Alignment
  43. // LineHeight controls the distance between the baselines of lines of text.
  44. // If zero, a sensible default will be used.
  45. LineHeight unit.Sp
  46. // LineHeightScale applies a scaling factor to the LineHeight. If zero, a
  47. // sensible default will be used.
  48. LineHeightScale float32
  49. // SingleLine forces the text to stay on a single line.
  50. // SingleLine also sets the scrolling direction to
  51. // horizontal.
  52. SingleLine bool
  53. // MaxLines limits the shaped text to a specific quantity of shaped lines.
  54. MaxLines int
  55. // Truncator is the text that will be shown at the end of the final
  56. // line if MaxLines is exceeded. Defaults to "…" if empty.
  57. Truncator string
  58. // WrapPolicy configures how displayed text will be broken into lines.
  59. WrapPolicy text.WrapPolicy
  60. // DisableSpaceTrim configures whether trailing whitespace on a line will have its
  61. // width zeroed. Set to true for editors, but false for non-editable text.
  62. DisableSpaceTrim bool
  63. // Mask replaces the visual display of each rune in the contents with the given rune.
  64. // Newline characters are not masked. When non-zero, the unmasked contents
  65. // are accessed by Len, Text, and SetText.
  66. Mask rune
  67. params text.Parameters
  68. shaper *text.Shaper
  69. seekCursor int64
  70. rr textSource
  71. maskReader maskReader
  72. // graphemes tracks the indices of grapheme cluster boundaries within rr.
  73. graphemes []int
  74. // paragraphReader is used to populate graphemes.
  75. paragraphReader graphemeReader
  76. lastMask rune
  77. viewSize image.Point
  78. valid bool
  79. regions []Region
  80. dims layout.Dimensions
  81. // offIndex is an index of rune index to byte offsets.
  82. offIndex []offEntry
  83. index glyphIndex
  84. caret struct {
  85. // xoff is the offset to the current position when moving between lines.
  86. xoff fixed.Int26_6
  87. // start is the current caret position in runes, and also the start position of
  88. // selected text. end is the end position of selected text. If start
  89. // == end, then there's no selection. Note that it's possible (and
  90. // common) that the caret (start) is after the end, e.g. after
  91. // Shift-DownArrow.
  92. start int
  93. end int
  94. }
  95. scrollOff image.Point
  96. }
  97. func (e *textView) Changed() bool {
  98. return e.rr.Changed()
  99. }
  100. // Dimensions returns the dimensions of the visible text.
  101. func (e *textView) Dimensions() layout.Dimensions {
  102. basePos := e.dims.Size.Y - e.dims.Baseline
  103. return layout.Dimensions{Size: e.viewSize, Baseline: e.viewSize.Y - basePos}
  104. }
  105. // FullDimensions returns the dimensions of all shaped text, including
  106. // text that isn't visible within the current viewport.
  107. func (e *textView) FullDimensions() layout.Dimensions {
  108. return e.dims
  109. }
  110. // SetSource initializes the underlying data source for the Text. This
  111. // must be done before invoking any other methods on Text.
  112. func (e *textView) SetSource(source textSource) {
  113. e.rr = source
  114. e.invalidate()
  115. e.seekCursor = 0
  116. }
  117. // ReadRuneAt reads the rune starting at the given byte offset, if any.
  118. func (e *textView) ReadRuneAt(off int64) (rune, int, error) {
  119. var buf [utf8.UTFMax]byte
  120. b := buf[:]
  121. n, err := e.rr.ReadAt(b, off)
  122. b = b[:n]
  123. r, s := utf8.DecodeRune(b)
  124. return r, s, err
  125. }
  126. // ReadRuneAt reads the run prior to the given byte offset, if any.
  127. func (e *textView) ReadRuneBefore(off int64) (rune, int, error) {
  128. var buf [utf8.UTFMax]byte
  129. b := buf[:]
  130. if off < utf8.UTFMax {
  131. b = b[:off]
  132. off = 0
  133. } else {
  134. off -= utf8.UTFMax
  135. }
  136. n, err := e.rr.ReadAt(b, off)
  137. b = b[:n]
  138. r, s := utf8.DecodeLastRune(b)
  139. return r, s, err
  140. }
  141. func (e *textView) makeValid() {
  142. if e.valid {
  143. return
  144. }
  145. e.layoutText(e.shaper)
  146. e.valid = true
  147. }
  148. func (e *textView) closestToRune(runeIdx int) combinedPos {
  149. e.makeValid()
  150. pos, _ := e.index.closestToRune(runeIdx)
  151. return pos
  152. }
  153. func (e *textView) closestToLineCol(line, col int) combinedPos {
  154. e.makeValid()
  155. return e.index.closestToLineCol(screenPos{line: line, col: col})
  156. }
  157. func (e *textView) closestToXY(x fixed.Int26_6, y int) combinedPos {
  158. e.makeValid()
  159. return e.index.closestToXY(x, y)
  160. }
  161. func (e *textView) closestToXYGraphemes(x fixed.Int26_6, y int) combinedPos {
  162. // Find the closest existing rune position to the provided coordinates.
  163. pos := e.closestToXY(x, y)
  164. // Resolve cluster boundaries on either side of the rune position.
  165. firstOption := e.moveByGraphemes(pos.runes, 0)
  166. distance := 1
  167. if firstOption > pos.runes {
  168. distance = -1
  169. }
  170. secondOption := e.moveByGraphemes(firstOption, distance)
  171. // Choose the closest grapheme cluster boundary to the desired point.
  172. first := e.closestToRune(firstOption)
  173. firstDist := absFixed(first.x - x)
  174. second := e.closestToRune(secondOption)
  175. secondDist := absFixed(second.x - x)
  176. if firstDist > secondDist {
  177. return second
  178. } else {
  179. return first
  180. }
  181. }
  182. func absFixed(i fixed.Int26_6) fixed.Int26_6 {
  183. if i < 0 {
  184. return -i
  185. }
  186. return i
  187. }
  188. // MaxLines moves the cursor the specified number of lines vertically, ensuring
  189. // that the resulting position is aligned to a grapheme cluster.
  190. func (e *textView) MoveLines(distance int, selAct selectionAction) {
  191. caretStart := e.closestToRune(e.caret.start)
  192. x := caretStart.x + e.caret.xoff
  193. // Seek to line.
  194. pos := e.closestToLineCol(caretStart.lineCol.line+distance, 0)
  195. pos = e.closestToXYGraphemes(x, pos.y)
  196. e.caret.start = pos.runes
  197. e.caret.xoff = x - pos.x
  198. e.updateSelection(selAct)
  199. }
  200. // calculateViewSize determines the size of the current visible content,
  201. // ensuring that even if there is no text content, some space is reserved
  202. // for the caret.
  203. func (e *textView) calculateViewSize(gtx layout.Context) image.Point {
  204. base := e.dims.Size
  205. if caretWidth := e.caretWidth(gtx); base.X < caretWidth {
  206. base.X = caretWidth
  207. }
  208. return gtx.Constraints.Constrain(base)
  209. }
  210. // Layout the text, reshaping it as necessary.
  211. func (e *textView) Layout(gtx layout.Context, lt *text.Shaper, font font.Font, size unit.Sp) {
  212. if e.params.Locale != gtx.Locale {
  213. e.params.Locale = gtx.Locale
  214. e.invalidate()
  215. }
  216. textSize := fixed.I(gtx.Sp(size))
  217. if e.params.Font != font || e.params.PxPerEm != textSize {
  218. e.invalidate()
  219. e.params.Font = font
  220. e.params.PxPerEm = textSize
  221. }
  222. maxWidth := gtx.Constraints.Max.X
  223. if e.SingleLine {
  224. maxWidth = math.MaxInt
  225. }
  226. minWidth := gtx.Constraints.Min.X
  227. if maxWidth != e.params.MaxWidth {
  228. e.params.MaxWidth = maxWidth
  229. e.invalidate()
  230. }
  231. if minWidth != e.params.MinWidth {
  232. e.params.MinWidth = minWidth
  233. e.invalidate()
  234. }
  235. if lt != e.shaper {
  236. e.shaper = lt
  237. e.invalidate()
  238. }
  239. if e.Mask != e.lastMask {
  240. e.lastMask = e.Mask
  241. e.invalidate()
  242. }
  243. if e.Alignment != e.params.Alignment {
  244. e.params.Alignment = e.Alignment
  245. e.invalidate()
  246. }
  247. if e.Truncator != e.params.Truncator {
  248. e.params.Truncator = e.Truncator
  249. e.invalidate()
  250. }
  251. if e.MaxLines != e.params.MaxLines {
  252. e.params.MaxLines = e.MaxLines
  253. e.invalidate()
  254. }
  255. if e.WrapPolicy != e.params.WrapPolicy {
  256. e.params.WrapPolicy = e.WrapPolicy
  257. e.invalidate()
  258. }
  259. if lh := fixed.I(gtx.Sp(e.LineHeight)); lh != e.params.LineHeight {
  260. e.params.LineHeight = lh
  261. e.invalidate()
  262. }
  263. if e.LineHeightScale != e.params.LineHeightScale {
  264. e.params.LineHeightScale = e.LineHeightScale
  265. e.invalidate()
  266. }
  267. if e.DisableSpaceTrim != e.params.DisableSpaceTrim {
  268. e.params.DisableSpaceTrim = e.DisableSpaceTrim
  269. e.invalidate()
  270. }
  271. e.makeValid()
  272. if viewSize := e.calculateViewSize(gtx); viewSize != e.viewSize {
  273. e.viewSize = viewSize
  274. e.invalidate()
  275. }
  276. e.makeValid()
  277. }
  278. // PaintSelection clips and paints the visible text selection rectangles using
  279. // the provided material to fill the rectangles.
  280. func (e *textView) PaintSelection(gtx layout.Context, material op.CallOp) {
  281. localViewport := image.Rectangle{Max: e.viewSize}
  282. docViewport := image.Rectangle{Max: e.viewSize}.Add(e.scrollOff)
  283. defer clip.Rect(localViewport).Push(gtx.Ops).Pop()
  284. e.regions = e.index.locate(docViewport, e.caret.start, e.caret.end, e.regions)
  285. for _, region := range e.regions {
  286. area := clip.Rect(region.Bounds).Push(gtx.Ops)
  287. material.Add(gtx.Ops)
  288. paint.PaintOp{}.Add(gtx.Ops)
  289. area.Pop()
  290. }
  291. }
  292. // PaintText clips and paints the visible text glyph outlines using the provided
  293. // material to fill the glyphs.
  294. func (e *textView) PaintText(gtx layout.Context, material op.CallOp) {
  295. m := op.Record(gtx.Ops)
  296. viewport := image.Rectangle{
  297. Min: e.scrollOff,
  298. Max: e.viewSize.Add(e.scrollOff),
  299. }
  300. it := textIterator{
  301. viewport: viewport,
  302. material: material,
  303. }
  304. startGlyph := 0
  305. for _, line := range e.index.lines {
  306. if line.descent.Ceil()+line.yOff >= viewport.Min.Y {
  307. break
  308. }
  309. startGlyph += line.glyphs
  310. }
  311. var glyphs [32]text.Glyph
  312. line := glyphs[:0]
  313. for _, g := range e.index.glyphs[startGlyph:] {
  314. var ok bool
  315. if line, ok = it.paintGlyph(gtx, e.shaper, g, line); !ok {
  316. break
  317. }
  318. }
  319. call := m.Stop()
  320. viewport.Min = viewport.Min.Add(it.padding.Min)
  321. viewport.Max = viewport.Max.Add(it.padding.Max)
  322. defer clip.Rect(viewport.Sub(e.scrollOff)).Push(gtx.Ops).Pop()
  323. call.Add(gtx.Ops)
  324. }
  325. // caretWidth returns the width occupied by the caret for the current
  326. // gtx.
  327. func (e *textView) caretWidth(gtx layout.Context) int {
  328. carWidth2 := gtx.Dp(1) / 2
  329. if carWidth2 < 1 {
  330. carWidth2 = 1
  331. }
  332. return carWidth2
  333. }
  334. // PaintCaret clips and paints the caret rectangle, adding material immediately
  335. // before painting to set the appropriate paint material.
  336. func (e *textView) PaintCaret(gtx layout.Context, material op.CallOp) {
  337. carWidth2 := e.caretWidth(gtx)
  338. caretPos, carAsc, carDesc := e.CaretInfo()
  339. carRect := image.Rectangle{
  340. Min: caretPos.Sub(image.Pt(carWidth2, carAsc)),
  341. Max: caretPos.Add(image.Pt(carWidth2, carDesc)),
  342. }
  343. cl := image.Rectangle{Max: e.viewSize}
  344. carRect = cl.Intersect(carRect)
  345. if !carRect.Empty() {
  346. defer clip.Rect(carRect).Push(gtx.Ops).Pop()
  347. material.Add(gtx.Ops)
  348. paint.PaintOp{}.Add(gtx.Ops)
  349. }
  350. }
  351. func (e *textView) CaretInfo() (pos image.Point, ascent, descent int) {
  352. caretStart := e.closestToRune(e.caret.start)
  353. ascent = caretStart.ascent.Ceil()
  354. descent = caretStart.descent.Ceil()
  355. pos = image.Point{
  356. X: caretStart.x.Round(),
  357. Y: caretStart.y,
  358. }
  359. pos = pos.Sub(e.scrollOff)
  360. return
  361. }
  362. // ByteOffset returns the start byte of the rune at the given
  363. // rune offset, clamped to the size of the text.
  364. func (e *textView) ByteOffset(runeOffset int) int64 {
  365. return int64(e.runeOffset(e.closestToRune(runeOffset).runes))
  366. }
  367. // Len is the length of the editor contents, in runes.
  368. func (e *textView) Len() int {
  369. e.makeValid()
  370. return e.closestToRune(math.MaxInt).runes
  371. }
  372. // Text returns the contents of the editor. If the provided buf is large enough, it will
  373. // be filled and returned. Otherwise a new buffer will be allocated.
  374. // Callers can guarantee that buf is large enough by giving it capacity e.Len()*utf8.UTFMax.
  375. func (e *textView) Text(buf []byte) []byte {
  376. size := e.rr.Size()
  377. if cap(buf) < int(size) {
  378. buf = make([]byte, size)
  379. }
  380. buf = buf[:size]
  381. e.Seek(0, io.SeekStart)
  382. n, _ := io.ReadFull(e, buf)
  383. buf = buf[:n]
  384. return buf
  385. }
  386. func (e *textView) ScrollBounds() image.Rectangle {
  387. var b image.Rectangle
  388. if e.SingleLine {
  389. if len(e.index.lines) > 0 {
  390. line := e.index.lines[0]
  391. b.Min.X = line.xOff.Floor()
  392. if b.Min.X > 0 {
  393. b.Min.X = 0
  394. }
  395. }
  396. b.Max.X = e.dims.Size.X + b.Min.X - e.viewSize.X
  397. } else {
  398. b.Max.Y = e.dims.Size.Y - e.viewSize.Y
  399. }
  400. return b
  401. }
  402. func (e *textView) ScrollRel(dx, dy int) {
  403. e.scrollAbs(e.scrollOff.X+dx, e.scrollOff.Y+dy)
  404. }
  405. // ScrollOff returns the scroll offset of the text viewport.
  406. func (e *textView) ScrollOff() image.Point {
  407. return e.scrollOff
  408. }
  409. func (e *textView) scrollAbs(x, y int) {
  410. e.scrollOff.X = x
  411. e.scrollOff.Y = y
  412. b := e.ScrollBounds()
  413. if e.scrollOff.X > b.Max.X {
  414. e.scrollOff.X = b.Max.X
  415. }
  416. if e.scrollOff.X < b.Min.X {
  417. e.scrollOff.X = b.Min.X
  418. }
  419. if e.scrollOff.Y > b.Max.Y {
  420. e.scrollOff.Y = b.Max.Y
  421. }
  422. if e.scrollOff.Y < b.Min.Y {
  423. e.scrollOff.Y = b.Min.Y
  424. }
  425. }
  426. // MoveCoord moves the caret to the position closest to the provided
  427. // point that is aligned to a grapheme cluster boundary.
  428. func (e *textView) MoveCoord(pos image.Point) {
  429. x := fixed.I(pos.X + e.scrollOff.X)
  430. y := pos.Y + e.scrollOff.Y
  431. e.caret.start = e.closestToXYGraphemes(x, y).runes
  432. e.caret.xoff = 0
  433. }
  434. // Truncated returns whether the text in the textView is currently
  435. // truncated due to a restriction on the number of lines.
  436. func (e *textView) Truncated() bool {
  437. return e.index.truncated
  438. }
  439. func (e *textView) layoutText(lt *text.Shaper) {
  440. e.Seek(0, io.SeekStart)
  441. var r io.Reader = e
  442. if e.Mask != 0 {
  443. e.maskReader.Reset(e, e.Mask)
  444. r = &e.maskReader
  445. }
  446. e.index.reset()
  447. it := textIterator{viewport: image.Rectangle{Max: image.Point{X: math.MaxInt, Y: math.MaxInt}}}
  448. if lt != nil {
  449. lt.Layout(e.params, r)
  450. for {
  451. g, ok := lt.NextGlyph()
  452. if !it.processGlyph(g, ok) {
  453. break
  454. }
  455. e.index.Glyph(g)
  456. }
  457. } else {
  458. // Make a fake glyph for every rune in the reader.
  459. b := bufio.NewReader(r)
  460. for _, _, err := b.ReadRune(); err != io.EOF; _, _, err = b.ReadRune() {
  461. g := text.Glyph{Runes: 1, Flags: text.FlagClusterBreak}
  462. _ = it.processGlyph(g, true)
  463. e.index.Glyph(g)
  464. }
  465. }
  466. e.paragraphReader.SetSource(e.rr)
  467. e.graphemes = e.graphemes[:0]
  468. for g := e.paragraphReader.Graphemes(); len(g) > 0; g = e.paragraphReader.Graphemes() {
  469. if len(e.graphemes) > 0 && g[0] == e.graphemes[len(e.graphemes)-1] {
  470. g = g[1:]
  471. }
  472. e.graphemes = append(e.graphemes, g...)
  473. }
  474. dims := layout.Dimensions{Size: it.bounds.Size()}
  475. dims.Baseline = dims.Size.Y - it.baseline
  476. e.dims = dims
  477. }
  478. // CaretPos returns the line & column numbers of the caret.
  479. func (e *textView) CaretPos() (line, col int) {
  480. pos := e.closestToRune(e.caret.start)
  481. return pos.lineCol.line, pos.lineCol.col
  482. }
  483. // CaretCoords returns the coordinates of the caret, relative to the
  484. // editor itself.
  485. func (e *textView) CaretCoords() f32.Point {
  486. pos := e.closestToRune(e.caret.start)
  487. return f32.Pt(float32(pos.x)/64-float32(e.scrollOff.X), float32(pos.y-e.scrollOff.Y))
  488. }
  489. // indexRune returns the latest rune index and byte offset no later than r.
  490. func (e *textView) indexRune(r int) offEntry {
  491. // Initialize index.
  492. if len(e.offIndex) == 0 {
  493. e.offIndex = append(e.offIndex, offEntry{})
  494. }
  495. i := sort.Search(len(e.offIndex), func(i int) bool {
  496. entry := e.offIndex[i]
  497. return entry.runes >= r
  498. })
  499. // Return the entry guaranteed to be less than or equal to r.
  500. if i > 0 {
  501. i--
  502. }
  503. return e.offIndex[i]
  504. }
  505. // runeOffset returns the byte offset into e.rr of the r'th rune.
  506. // r must be a valid rune index, usually returned by closestPosition.
  507. func (e *textView) runeOffset(r int) int {
  508. const runesPerIndexEntry = 50
  509. entry := e.indexRune(r)
  510. lastEntry := e.offIndex[len(e.offIndex)-1].runes
  511. for entry.runes < r {
  512. if entry.runes > lastEntry && entry.runes%runesPerIndexEntry == runesPerIndexEntry-1 {
  513. e.offIndex = append(e.offIndex, entry)
  514. }
  515. _, s, _ := e.ReadRuneAt(int64(entry.bytes))
  516. entry.bytes += s
  517. entry.runes++
  518. }
  519. return entry.bytes
  520. }
  521. func (e *textView) invalidate() {
  522. e.offIndex = e.offIndex[:0]
  523. e.valid = false
  524. }
  525. // Replace the text between start and end with s. Indices are in runes.
  526. // It returns the number of runes inserted.
  527. func (e *textView) Replace(start, end int, s string) int {
  528. if start > end {
  529. start, end = end, start
  530. }
  531. startPos := e.closestToRune(start)
  532. endPos := e.closestToRune(end)
  533. startOff := e.runeOffset(startPos.runes)
  534. replaceSize := endPos.runes - startPos.runes
  535. sc := utf8.RuneCountInString(s)
  536. newEnd := startPos.runes + sc
  537. e.rr.ReplaceRunes(int64(startOff), int64(replaceSize), s)
  538. adjust := func(pos int) int {
  539. switch {
  540. case newEnd < pos && pos <= endPos.runes:
  541. pos = newEnd
  542. case endPos.runes < pos:
  543. diff := newEnd - endPos.runes
  544. pos = pos + diff
  545. }
  546. return pos
  547. }
  548. e.caret.start = adjust(e.caret.start)
  549. e.caret.end = adjust(e.caret.end)
  550. e.invalidate()
  551. return sc
  552. }
  553. // MovePages moves the caret position by vertical pages of text, ensuring that
  554. // the final position is aligned to a grapheme cluster boundary.
  555. func (e *textView) MovePages(pages int, selAct selectionAction) {
  556. caret := e.closestToRune(e.caret.start)
  557. x := caret.x + e.caret.xoff
  558. y := caret.y + pages*e.viewSize.Y
  559. pos := e.closestToXYGraphemes(x, y)
  560. e.caret.start = pos.runes
  561. e.caret.xoff = x - pos.x
  562. e.updateSelection(selAct)
  563. }
  564. // moveByGraphemes returns the rune index resulting from moving the
  565. // specified number of grapheme clusters from startRuneidx.
  566. func (e *textView) moveByGraphemes(startRuneidx, graphemes int) int {
  567. if len(e.graphemes) == 0 {
  568. return startRuneidx
  569. }
  570. startGraphemeIdx, _ := slices.BinarySearch(e.graphemes, startRuneidx)
  571. startGraphemeIdx = max(startGraphemeIdx+graphemes, 0)
  572. startGraphemeIdx = min(startGraphemeIdx, len(e.graphemes)-1)
  573. startRuneIdx := e.graphemes[startGraphemeIdx]
  574. return e.closestToRune(startRuneIdx).runes
  575. }
  576. // clampCursorToGraphemes ensures that the final start/end positions of
  577. // the cursor are on grapheme cluster boundaries.
  578. func (e *textView) clampCursorToGraphemes() {
  579. e.caret.start = e.moveByGraphemes(e.caret.start, 0)
  580. e.caret.end = e.moveByGraphemes(e.caret.end, 0)
  581. }
  582. // MoveCaret moves the caret (aka selection start) and the selection end
  583. // relative to their current positions. Positive distances moves forward,
  584. // negative distances moves backward. Distances are in grapheme clusters which
  585. // better match the expectations of users than runes.
  586. func (e *textView) MoveCaret(startDelta, endDelta int) {
  587. e.caret.xoff = 0
  588. e.caret.start = e.moveByGraphemes(e.caret.start, startDelta)
  589. e.caret.end = e.moveByGraphemes(e.caret.end, endDelta)
  590. }
  591. // MoveTextStart moves the caret to the start of the text.
  592. func (e *textView) MoveTextStart(selAct selectionAction) {
  593. caret := e.closestToRune(e.caret.end)
  594. e.caret.start = 0
  595. e.caret.end = caret.runes
  596. e.caret.xoff = -caret.x
  597. e.updateSelection(selAct)
  598. e.clampCursorToGraphemes()
  599. }
  600. // MoveTextEnd moves the caret to the end of the text.
  601. func (e *textView) MoveTextEnd(selAct selectionAction) {
  602. caret := e.closestToRune(math.MaxInt)
  603. e.caret.start = caret.runes
  604. e.caret.xoff = fixed.I(e.params.MaxWidth) - caret.x
  605. e.updateSelection(selAct)
  606. e.clampCursorToGraphemes()
  607. }
  608. // MoveLineStart moves the caret to the start of the current line, ensuring that the resulting
  609. // cursor position is on a grapheme cluster boundary.
  610. func (e *textView) MoveLineStart(selAct selectionAction) {
  611. caret := e.closestToRune(e.caret.start)
  612. caret = e.closestToLineCol(caret.lineCol.line, 0)
  613. e.caret.start = caret.runes
  614. e.caret.xoff = -caret.x
  615. e.updateSelection(selAct)
  616. e.clampCursorToGraphemes()
  617. }
  618. // MoveLineEnd moves the caret to the end of the current line, ensuring that the resulting
  619. // cursor position is on a grapheme cluster boundary.
  620. func (e *textView) MoveLineEnd(selAct selectionAction) {
  621. caret := e.closestToRune(e.caret.start)
  622. caret = e.closestToLineCol(caret.lineCol.line, math.MaxInt)
  623. e.caret.start = caret.runes
  624. e.caret.xoff = fixed.I(e.params.MaxWidth) - caret.x
  625. e.updateSelection(selAct)
  626. e.clampCursorToGraphemes()
  627. }
  628. // MoveWord moves the caret to the next word in the specified direction.
  629. // Positive is forward, negative is backward.
  630. // Absolute values greater than one will skip that many words.
  631. // The final caret position will be aligned to a grapheme cluster boundary.
  632. // BUG(whereswaldon): this method's definition of a "word" is currently
  633. // whitespace-delimited. Languages that do not use whitespace to delimit
  634. // words will experience counter-intuitive behavior when navigating by
  635. // word.
  636. func (e *textView) MoveWord(distance int, selAct selectionAction) {
  637. // split the distance information into constituent parts to be
  638. // used independently.
  639. words, direction := distance, 1
  640. if distance < 0 {
  641. words, direction = distance*-1, -1
  642. }
  643. // atEnd if caret is at either side of the buffer.
  644. caret := e.closestToRune(e.caret.start)
  645. atEnd := func() bool {
  646. return caret.runes == 0 || caret.runes == e.Len()
  647. }
  648. // next returns the appropriate rune given the direction.
  649. next := func() (r rune) {
  650. off := e.runeOffset(caret.runes)
  651. if direction < 0 {
  652. r, _, _ = e.ReadRuneBefore(int64(off))
  653. } else {
  654. r, _, _ = e.ReadRuneAt(int64(off))
  655. }
  656. return r
  657. }
  658. for ii := 0; ii < words; ii++ {
  659. for r := next(); unicode.IsSpace(r) && !atEnd(); r = next() {
  660. e.MoveCaret(direction, 0)
  661. caret = e.closestToRune(e.caret.start)
  662. }
  663. e.MoveCaret(direction, 0)
  664. caret = e.closestToRune(e.caret.start)
  665. for r := next(); !unicode.IsSpace(r) && !atEnd(); r = next() {
  666. e.MoveCaret(direction, 0)
  667. caret = e.closestToRune(e.caret.start)
  668. }
  669. }
  670. e.updateSelection(selAct)
  671. e.clampCursorToGraphemes()
  672. }
  673. func (e *textView) ScrollToCaret() {
  674. caret := e.closestToRune(e.caret.start)
  675. if e.SingleLine {
  676. var dist int
  677. if d := caret.x.Floor() - e.scrollOff.X; d < 0 {
  678. dist = d
  679. } else if d := caret.x.Ceil() - (e.scrollOff.X + e.viewSize.X); d > 0 {
  680. dist = d
  681. }
  682. e.ScrollRel(dist, 0)
  683. } else {
  684. miny := caret.y - caret.ascent.Ceil()
  685. maxy := caret.y + caret.descent.Ceil()
  686. var dist int
  687. if d := miny - e.scrollOff.Y; d < 0 {
  688. dist = d
  689. } else if d := maxy - (e.scrollOff.Y + e.viewSize.Y); d > 0 {
  690. dist = d
  691. }
  692. e.ScrollRel(0, dist)
  693. }
  694. }
  695. // SelectionLen returns the length of the selection, in runes; it is
  696. // equivalent to utf8.RuneCountInString(e.SelectedText()).
  697. func (e *textView) SelectionLen() int {
  698. return abs(e.caret.start - e.caret.end)
  699. }
  700. // Selection returns the start and end of the selection, as rune offsets.
  701. // start can be > end.
  702. func (e *textView) Selection() (start, end int) {
  703. return e.caret.start, e.caret.end
  704. }
  705. // SetCaret moves the caret to start, and sets the selection end to end. Then
  706. // the two ends are clamped to the nearest grapheme cluster boundary. start
  707. // and end are in runes, and represent offsets into the editor text.
  708. func (e *textView) SetCaret(start, end int) {
  709. e.caret.start = e.closestToRune(start).runes
  710. e.caret.end = e.closestToRune(end).runes
  711. e.clampCursorToGraphemes()
  712. }
  713. // SelectedText returns the currently selected text (if any) from the editor,
  714. // filling the provided byte slice if it is large enough or allocating and
  715. // returning a new byte slice if the provided one is insufficient.
  716. // Callers can guarantee that the buf is large enough by providing a buffer
  717. // with capacity e.SelectionLen()*utf8.UTFMax.
  718. func (e *textView) SelectedText(buf []byte) []byte {
  719. startOff := e.runeOffset(e.caret.start)
  720. endOff := e.runeOffset(e.caret.end)
  721. start := min(startOff, endOff)
  722. end := max(startOff, endOff)
  723. if cap(buf) < end-start {
  724. buf = make([]byte, end-start)
  725. }
  726. buf = buf[:end-start]
  727. n, _ := e.rr.ReadAt(buf, int64(start))
  728. // There is no way to reasonably handle a read error here. We rely upon
  729. // implementations of textSource to provide other ways to signal errors
  730. // if the user cares about that, and here we use whatever data we were
  731. // able to read.
  732. return buf[:n]
  733. }
  734. func (e *textView) updateSelection(selAct selectionAction) {
  735. if selAct == selectionClear {
  736. e.ClearSelection()
  737. }
  738. }
  739. // ClearSelection clears the selection, by setting the selection end equal to
  740. // the selection start.
  741. func (e *textView) ClearSelection() {
  742. e.caret.end = e.caret.start
  743. }
  744. // WriteTo implements io.WriterTo.
  745. func (e *textView) WriteTo(w io.Writer) (int64, error) {
  746. e.Seek(0, io.SeekStart)
  747. return io.Copy(w, struct{ io.Reader }{e})
  748. }
  749. // Seek implements io.Seeker.
  750. func (e *textView) Seek(offset int64, whence int) (int64, error) {
  751. switch whence {
  752. case io.SeekStart:
  753. e.seekCursor = offset
  754. case io.SeekCurrent:
  755. e.seekCursor += offset
  756. case io.SeekEnd:
  757. e.seekCursor = e.rr.Size() + offset
  758. }
  759. return e.seekCursor, nil
  760. }
  761. // Read implements io.Reader.
  762. func (e *textView) Read(p []byte) (int, error) {
  763. n, err := e.rr.ReadAt(p, e.seekCursor)
  764. e.seekCursor += int64(n)
  765. return n, err
  766. }
  767. // ReadAt implements io.ReaderAt.
  768. func (e *textView) ReadAt(p []byte, offset int64) (int, error) {
  769. return e.rr.ReadAt(p, offset)
  770. }
  771. // Regions returns visible regions covering the rune range [start,end).
  772. func (e *textView) Regions(start, end int, regions []Region) []Region {
  773. viewport := image.Rectangle{
  774. Min: e.scrollOff,
  775. Max: e.viewSize.Add(e.scrollOff),
  776. }
  777. return e.index.locate(viewport, start, end, regions)
  778. }