editor.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package gel
   4  
   5  import (
   6  	"bufio"
   7  	"bytes"
   8  	"image"
   9  	"io"
  10  	"math"
  11  	"runtime"
  12  	"sort"
  13  	"strings"
  14  	"time"
  15  	"unicode"
  16  	"unicode/utf8"
  17  
  18  	"github.com/p9c/gio/f32"
  19  	"github.com/p9c/gio/io/clipboard"
  20  	"github.com/p9c/gio/io/event"
  21  	"github.com/p9c/gio/io/key"
  22  	"github.com/p9c/gio/io/pointer"
  23  	"github.com/p9c/gio/layout"
  24  	"github.com/p9c/gio/op"
  25  	"github.com/p9c/gio/op/clip"
  26  	"github.com/p9c/gio/op/paint"
  27  	"github.com/p9c/gio/text"
  28  	"github.com/p9c/gio/unit"
  29  
  30  	"github.com/p9c/gio/gesture"
  31  
  32  	"golang.org/x/image/math/fixed"
  33  
  34  	clipboard3 "github.com/p9c/gel/clipboard"
  35  )
  36  
  37  func (w *Window) Editor() *Editor {
  38  	e := &Editor{
  39  		submitHook: func(string) {},
  40  		changeHook: func(string) {},
  41  		focusHook:  func(bool) {},
  42  	}
  43  	return e
  44  }
  45  
  46  // Editor implements an editable and scrollable text area.
  47  type Editor struct {
  48  	alignment text.Alignment
  49  	// singleLine force the text to stay on a single line. singleLine also sets the scrolling direction to horizontal.
  50  	singleLine bool
  51  	// submit enabled translation of carriage return keys to SubmitEvents. If not enabled, carriage returns are inserted
  52  	// as newlines in the text.
  53  	submit bool
  54  	// mask replaces the visual display of each rune in the contents with the given rune. Newline characters are not
  55  	// masked. When non-zero, the unmasked contents are accessed by Len, Text, and SetText.
  56  	mask rune
  57  
  58  	eventKey     int
  59  	font         text.Font
  60  	shaper       text.Shaper
  61  	textSize     fixed.Int26_6
  62  	blinkStart   time.Time
  63  	focused      bool
  64  	editBuffer   editBuffer
  65  	maskReader   maskReader
  66  	lastMask     rune
  67  	maxWidth     int
  68  	viewSize     image.Point
  69  	valid        bool
  70  	lines        []text.Line
  71  	shapes       []line
  72  	dims         layout.Dimensions
  73  	requestFocus bool
  74  	caret        struct {
  75  		on     bool
  76  		scroll bool
  77  		// start is the current caret position, and also the start position of
  78  		// selected text. end is the end positon of selected text. If start.ofs
  79  		// == end.ofs, then there's no selection. Note that it's possible (and
  80  		// common) that the caret (start) is after the end, e.g. after
  81  		// Shift-DownArrow.
  82  		start combinedPos
  83  		end   combinedPos
  84  	}
  85  	dragging  bool
  86  	dragger   gesture.Drag
  87  	scroller  gesture.Scroll
  88  	scrollOff image.Point
  89  
  90  	clicker gesture.Click
  91  
  92  	// events is the list of events not yet processed.
  93  	events []EditorEvent
  94  	// prevEvents is the number of events from the previous frame.
  95  	prevEvents int
  96  	// the following are hooks for change events on the editor
  97  	submitHook func(string)
  98  	changeHook func(string)
  99  	focusHook  func(bool)
 100  }
 101  
 102  type maskReader struct {
 103  	// rr is the underlying reader.
 104  	rr      io.RuneReader
 105  	maskBuf [utf8.UTFMax]byte
 106  	// mask is the utf-8 encoded mask rune.
 107  	mask []byte
 108  	// overflow contains excess mask bytes left over after the last Read call.
 109  	overflow []byte
 110  }
 111  
 112  // combinedPos is a point in the editor.
 113  type combinedPos struct {
 114  	// editorBuffer offset. The other three fields are based off of this one.
 115  	ofs int
 116  
 117  	// lineCol.Y = line (offset into Editor.lines), and X = col (offset into
 118  	// Editor.lines[Y])
 119  	lineCol screenPos
 120  
 121  	// Pixel coordinates
 122  	x fixed.Int26_6
 123  	y int
 124  
 125  	// xoff is the offset to the current position when moving between lines.
 126  	xoff fixed.Int26_6
 127  }
 128  
 129  type selectionAction int
 130  
 131  const (
 132  	selectionExtend selectionAction = iota
 133  	selectionClear
 134  )
 135  
 136  func (m *maskReader) Reset(r io.RuneReader, mr rune) {
 137  	m.rr = r
 138  	n := utf8.EncodeRune(m.maskBuf[:], mr)
 139  	m.mask = m.maskBuf[:n]
 140  }
 141  
 142  // Read reads from the underlying reader and replaces every rune with the mask rune.
 143  func (m *maskReader) Read(b []byte) (n int, e error) {
 144  	for len(b) > 0 {
 145  		var replacement []byte
 146  		if len(m.overflow) > 0 {
 147  			replacement = m.overflow
 148  		} else {
 149  			var r rune
 150  			r, _, e = m.rr.ReadRune()
 151  			if e != nil {
 152  				break
 153  			}
 154  			if r == '\n' {
 155  				replacement = []byte{'\n'}
 156  			} else {
 157  				replacement = m.mask
 158  			}
 159  		}
 160  		nn := copy(b, replacement)
 161  		m.overflow = replacement[nn:]
 162  		n += nn
 163  		b = b[nn:]
 164  	}
 165  	return n, e
 166  }
 167  
 168  type EditorEvent interface {
 169  	isEditorEvent()
 170  }
 171  
 172  // A ChangeEvent is generated for every user change to the text.
 173  type ChangeEvent struct{}
 174  
 175  // A SubmitEvent is generated when submit is set and a carriage return key is pressed.
 176  type SubmitEvent struct {
 177  	Text string
 178  }
 179  
 180  // A SelectEvent is generated when the user selects some text, or changes the
 181  // selection (e.g. with a shift-click), including if they remove the
 182  // selection. The selected text is not part of the event, on the theory that
 183  // it could be a relatively expensive operation (for a large editor), most
 184  // applications won't actually care about it, and those that do can call
 185  // Editor.SelectedText() (which can be empty).
 186  type SelectEvent struct{}
 187  
 188  type line struct {
 189  	offset         image.Point
 190  	clip           op.CallOp
 191  	selected       bool
 192  	selectionYOffs int
 193  	selectionSize  image.Point
 194  }
 195  
 196  const (
 197  	blinksPerSecond  = 1
 198  	maxBlinkDuration = 10 * time.Second
 199  )
 200  
 201  // Events returns available editor events.
 202  func (e *Editor) Events() []EditorEvent {
 203  	events := e.events
 204  	e.events = nil
 205  	e.prevEvents = 0
 206  	return events
 207  }
 208  
 209  func (e *Editor) processEvents(gtx layout.Context) {
 210  	// Flush events from before the previous layout.
 211  	n := copy(e.events, e.events[e.prevEvents:])
 212  	e.events = e.events[:n]
 213  	e.prevEvents = n
 214  	if e.shaper == nil {
 215  		// Can't process events without a shaper.
 216  		return
 217  	}
 218  	oldStart, oldLen := min(e.caret.start.ofs, e.caret.end.ofs), e.SelectionLen()
 219  	e.processPointer(gtx)
 220  	e.processKey(gtx)
 221  	if newStart, newLen := min(e.caret.start.ofs, e.caret.end.ofs),
 222  		e.SelectionLen(); oldStart != newStart || oldLen != newLen {
 223  		e.events = append(e.events, SelectEvent{})
 224  		if e.SelectionLen() != 0 {
 225  			st := e.SelectedText()
 226  			_ = clipboard3.SetPrimary(st)
 227  			// I.F("new primary buffer string: '%s'", st)
 228  		}
 229  	}
 230  }
 231  
 232  func (e *Editor) makeValid(positions ...*combinedPos) {
 233  	if e.valid {
 234  		return
 235  	}
 236  	e.lines, e.dims = e.layoutText(e.shaper)
 237  	e.makeValidCaret(positions...)
 238  	e.valid = true
 239  }
 240  
 241  func (e *Editor) processPointer(gtx layout.Context) {
 242  	sbounds := e.scrollBounds()
 243  	var smin, smax int
 244  	var axis gesture.Axis
 245  	if e.singleLine {
 246  		axis = gesture.Horizontal
 247  		smin, smax = sbounds.Min.X, sbounds.Max.X
 248  	} else {
 249  		axis = gesture.Vertical
 250  		smin, smax = sbounds.Min.Y, sbounds.Max.Y
 251  	}
 252  	sdist := e.scroller.Scroll(gtx.Metric, gtx, gtx.Now, axis)
 253  	var soff int
 254  	if e.singleLine {
 255  		e.scrollRel(sdist, 0)
 256  		soff = e.scrollOff.X
 257  	} else {
 258  		e.scrollRel(0, sdist)
 259  		soff = e.scrollOff.Y
 260  	}
 261  	for _, evt := range e.clickDragEvents(gtx) {
 262  		switch evt := evt.(type) {
 263  		case gesture.ClickEvent:
 264  			switch {
 265  			case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
 266  				evt.Type == gesture.TypeClick:
 267  				if evt.Button == pointer.ButtonPrimary {
 268  					prevCaretPos := e.caret.start
 269  					e.blinkStart = gtx.Now
 270  					e.moveCoord(image.Point{
 271  						X: int(math.Round(float64(evt.Position.X))),
 272  						Y: int(math.Round(float64(evt.Position.Y))),
 273  					})
 274  					e.requestFocus = true
 275  					if e.scroller.State() != gesture.StateFlinging {
 276  						e.caret.scroll = true
 277  					}
 278  					if evt.Modifiers == key.ModShift {
 279  						// If they clicked closer to the end, then change the end to
 280  						// where the caret used to be (effectively swapping start & end).
 281  						if abs(e.caret.end.ofs-e.caret.start.ofs) < abs(e.caret.start.ofs-prevCaretPos.ofs) {
 282  							e.caret.end = prevCaretPos
 283  						}
 284  					} else {
 285  						e.ClearSelection()
 286  					}
 287  				}
 288  				e.dragging = true
 289  				// I.S(evt)
 290  				// Process a double-click.
 291  				// Double and triple clicks are primary button only
 292  				if evt.Button == pointer.ButtonPrimary {
 293  					if evt.NumClicks == 2 {
 294  						e.moveWord(-1, selectionClear)
 295  						e.moveWord(1, selectionExtend)
 296  						e.dragging = false
 297  					}
 298  					// process a triple click - select all. This required forking gioui.org/gesture
 299  					if evt.NumClicks == 3 {
 300  						e.dragging = false
 301  						e.caret.end, e.caret.start = e.offsetToScreenPos2(0, e.Len())
 302  						evt.NumClicks = 0
 303  					}
 304  				}
 305  				if evt.Button == pointer.ButtonTertiary && evt.Type == gesture.TypeClick {
 306  					e.blinkStart = gtx.Now
 307  					e.moveCoord(image.Point{
 308  						X: int(math.Round(float64(evt.Position.X))),
 309  						Y: int(math.Round(float64(evt.Position.Y))),
 310  					})
 311  					e.ClearSelection()
 312  					primary := clipboard3.GetPrimary()
 313  					e.prepend(primary)
 314  					distance := utf8.RuneCountInString(primary)
 315  					e.MoveCaret(distance, int(selectionExtend))
 316  				}
 317  			}
 318  		case pointer.Event:
 319  			release := false
 320  			switch {
 321  			// on X11 process middle click as insert Primary at pointer position
 322  			case evt.Buttons == pointer.ButtonTertiary && evt.Source == pointer.Mouse:
 323  				e.moveCoord(image.Point{
 324  					X: int(math.Round(float64(evt.Position.X))),
 325  					Y: int(math.Round(float64(evt.Position.Y))),
 326  				})
 327  				e.prepend(clipboard3.GetPrimary())
 328  			case evt.Type == pointer.Release && evt.Source == pointer.Mouse:
 329  				release = true
 330  				fallthrough
 331  			case evt.Type == pointer.Drag && evt.Source == pointer.Mouse:
 332  				if e.dragging {
 333  					e.blinkStart = gtx.Now
 334  					e.moveCoord(image.Point{
 335  						X: int(math.Round(float64(evt.Position.X))),
 336  						Y: int(math.Round(float64(evt.Position.Y))),
 337  					})
 338  					e.caret.scroll = true
 339  
 340  					if release {
 341  						e.dragging = false
 342  					}
 343  				}
 344  			default:
 345  				// I.S(evt)
 346  			}
 347  		}
 348  	}
 349  
 350  	if (sdist > 0 && soff >= smax) || (sdist < 0 && soff <= smin) {
 351  		e.scroller.Stop()
 352  	}
 353  }
 354  
 355  func (e *Editor) clickDragEvents(gtx layout.Context) []event.Event {
 356  	var combinedEvents []event.Event
 357  	for _, evt := range e.clicker.Events(gtx) {
 358  		combinedEvents = append(combinedEvents, evt)
 359  	}
 360  	for _, evt := range e.dragger.Events(gtx.Metric, gtx, gesture.Both) {
 361  		combinedEvents = append(combinedEvents, evt)
 362  	}
 363  	return combinedEvents
 364  }
 365  
 366  func (e *Editor) processKey(gtx layout.Context) {
 367  	if e.editBuffer.Changed() {
 368  		e.events = append(e.events, ChangeEvent{})
 369  	}
 370  	for _, ke := range gtx.Events(&e.eventKey) {
 371  		e.blinkStart = gtx.Now
 372  		switch ke := ke.(type) {
 373  		case key.FocusEvent:
 374  			e.focused = ke.Focus
 375  			e.focusHook(ke.Focus)
 376  		case key.Event:
 377  			if !e.focused || ke.State != key.Press {
 378  				break
 379  			}
 380  			if e.submit && (ke.Name == key.NameReturn || ke.Name == key.NameEnter) {
 381  				if !ke.Modifiers.Contain(key.ModShift) {
 382  					e.events = append(e.events, SubmitEvent{
 383  						Text: e.Text(),
 384  					})
 385  					continue
 386  				}
 387  			}
 388  			if e.command(gtx, ke) {
 389  				e.caret.scroll = true
 390  				e.scroller.Stop()
 391  			}
 392  		case key.EditEvent:
 393  			e.caret.scroll = true
 394  			e.scroller.Stop()
 395  			e.append(ke.Text)
 396  		// Complete a paste event, initiated by Shortcut-V in Editor.command().
 397  		case clipboard.Event:
 398  			e.caret.scroll = true
 399  			e.scroller.Stop()
 400  			e.append(ke.Text)
 401  		}
 402  		if e.editBuffer.Changed() {
 403  			e.events = append(e.events, ChangeEvent{})
 404  			e.changeHook(e.Text())
 405  		}
 406  	}
 407  }
 408  
 409  func (e *Editor) moveLines(distance int, selAct selectionAction) {
 410  	e.caret.start = e.movePosToLine(e.caret.start, e.caret.start.x+e.caret.start.xoff,
 411  		e.caret.start.lineCol.Y+distance)
 412  	e.updateSelection(selAct)
 413  }
 414  
 415  func (e *Editor) command(gtx layout.Context, k key.Event) bool {
 416  	modSkip := key.ModCtrl
 417  	if runtime.GOOS == "darwin" {
 418  		modSkip = key.ModAlt
 419  	}
 420  	moveByWord := k.Modifiers.Contain(modSkip)
 421  	selAct := selectionClear
 422  	if k.Modifiers.Contain(key.ModShift) {
 423  		selAct = selectionExtend
 424  	}
 425  	switch k.Name {
 426  	case key.NameReturn, key.NameEnter:
 427  		e.append("\n")
 428  	case key.NameDeleteBackward:
 429  		if moveByWord {
 430  			e.deleteWord(-1)
 431  		} else {
 432  			e.Delete(-1)
 433  		}
 434  	case key.NameDeleteForward:
 435  		if moveByWord {
 436  			e.deleteWord(1)
 437  		} else {
 438  			e.Delete(1)
 439  		}
 440  	case key.NameUpArrow:
 441  		e.moveLines(-1, selAct)
 442  	case key.NameDownArrow:
 443  		e.moveLines(+1, selAct)
 444  	case key.NameLeftArrow:
 445  		if moveByWord {
 446  			e.moveWord(-1, selAct)
 447  		} else {
 448  			if selAct == selectionClear {
 449  				e.ClearSelection()
 450  			}
 451  			e.MoveCaret(-1, -1*int(selAct))
 452  		}
 453  	case key.NameRightArrow:
 454  		if moveByWord {
 455  			e.moveWord(1, selAct)
 456  		} else {
 457  			if selAct == selectionClear {
 458  				e.ClearSelection()
 459  			}
 460  			e.MoveCaret(1, int(selAct))
 461  		}
 462  	case key.NamePageUp:
 463  		e.movePages(-1, selAct)
 464  	case key.NamePageDown:
 465  		e.movePages(+1, selAct)
 466  	case key.NameHome:
 467  		e.moveStart(selAct)
 468  	case key.NameEnd:
 469  		e.moveEnd(selAct)
 470  	// Initiate a paste operation, by requesting the clipboard contents; other
 471  	// half is in Editor.processKey() under clipboard.Event.
 472  	case "V":
 473  		if k.Modifiers != key.ModShortcut {
 474  			return false
 475  		}
 476  		clipboard.ReadOp{Tag: &e.eventKey}.Add(gtx.Ops)
 477  	// Copy or Cut selection -- ignored if nothing selected.
 478  	case "C", "X":
 479  		if k.Modifiers != key.ModShortcut {
 480  			return false
 481  		}
 482  		if text := e.SelectedText(); text != "" {
 483  			clipboard.WriteOp{Text: text}.Add(gtx.Ops)
 484  			if k.Name == "X" {
 485  				e.Delete(1)
 486  			}
 487  		}
 488  	// Select all
 489  	case "A":
 490  		if k.Modifiers != key.ModShortcut {
 491  			return false
 492  		}
 493  		e.caret.end, e.caret.start = e.offsetToScreenPos2(0, e.Len())
 494  	default:
 495  		return false
 496  	}
 497  	return true
 498  }
 499  
 500  // Focus requests the input focus for the _editor.
 501  func (e *Editor) Focus() {
 502  	e.requestFocus = true
 503  }
 504  
 505  // Focused returns whether the editor is focused or not.
 506  func (e *Editor) Focused() bool {
 507  	return e.focused
 508  }
 509  
 510  // Layout lays out the editor.
 511  func (e *Editor) Layout(gtx layout.Context, sh text.Shaper, font text.Font, size unit.Value) layout.Dimensions {
 512  	textSize := fixed.I(gtx.Px(size))
 513  	if e.font != font || e.textSize != textSize {
 514  		e.invalidate()
 515  		e.font = font
 516  		e.textSize = textSize
 517  	}
 518  	maxWidth := gtx.Constraints.Max.X
 519  	if e.singleLine {
 520  		maxWidth = Inf
 521  	}
 522  	if maxWidth != e.maxWidth {
 523  		e.maxWidth = maxWidth
 524  		e.invalidate()
 525  	}
 526  	if sh != e.shaper {
 527  		e.shaper = sh
 528  		e.invalidate()
 529  	}
 530  	if e.mask != e.lastMask {
 531  		e.lastMask = e.mask
 532  		e.invalidate()
 533  	}
 534  	e.makeValid()
 535  	e.processEvents(gtx)
 536  	e.makeValid()
 537  	if viewSize := gtx.Constraints.Constrain(e.dims.Size); viewSize != e.viewSize {
 538  		e.viewSize = viewSize
 539  		e.invalidate()
 540  	}
 541  	e.makeValid()
 542  	return e.layout(gtx)
 543  }
 544  
 545  func (e *Editor) layout(gtx layout.Context) layout.Dimensions {
 546  	// Adjust scrolling for new viewport and layout.
 547  	e.scrollRel(0, 0)
 548  	if e.caret.scroll {
 549  		e.caret.scroll = false
 550  		e.scrollToCaret()
 551  	}
 552  	off := image.Point{
 553  		X: -e.scrollOff.X,
 554  		Y: -e.scrollOff.Y,
 555  	}
 556  	cl := textPadding(e.lines)
 557  	cl.Max = cl.Max.Add(e.viewSize)
 558  	startSel, endSel := sortPoints(e.caret.start.lineCol, e.caret.end.lineCol)
 559  	it := segmentIterator{
 560  		startSel:  startSel,
 561  		endSel:    endSel,
 562  		Lines:     e.lines,
 563  		Clip:      cl,
 564  		Alignment: e.alignment,
 565  		Width:     e.viewSize.X,
 566  		Offset:    off,
 567  	}
 568  	e.shapes = e.shapes[:0]
 569  	for {
 570  		lo, off, selected, yOffs, size, ok := it.Next()
 571  		if !ok {
 572  			break
 573  		}
 574  		path := e.shaper.Shape(e.font, e.textSize, lo)
 575  		e.shapes = append(e.shapes, line{off, path, selected, yOffs, size})
 576  	}
 577  	key.InputOp{Tag: &e.eventKey}.Add(gtx.Ops)
 578  	if e.requestFocus {
 579  		key.FocusOp{Tag: &e.eventKey}.Add(gtx.Ops)
 580  		key.SoftKeyboardOp{Show: true}.Add(gtx.Ops)
 581  	}
 582  	e.requestFocus = false
 583  	// todo: this should be scaled
 584  	pointerPadding := gtx.Px(unit.Dp(4))
 585  	r := image.Rectangle{Max: e.viewSize}
 586  	r.Min.X -= pointerPadding
 587  	r.Min.Y -= pointerPadding
 588  	r.Max.X += pointerPadding
 589  	r.Max.X += pointerPadding
 590  	pointer.Rect(r).Add(gtx.Ops)
 591  	pointer.CursorNameOp{Name: pointer.CursorText}.Add(gtx.Ops)
 592  	var scrollRange image.Rectangle
 593  	if e.singleLine {
 594  		scrollRange.Min.X = -e.scrollOff.X
 595  		scrollRange.Max.X = max(0, e.dims.Size.X-(e.scrollOff.X+e.viewSize.X))
 596  	} else {
 597  		scrollRange.Min.Y = -e.scrollOff.Y
 598  		scrollRange.Max.Y = max(0, e.dims.Size.Y-(e.scrollOff.Y+e.viewSize.Y))
 599  	}
 600  	e.scroller.Add(gtx.Ops, scrollRange)
 601  
 602  	e.clicker.Add(gtx.Ops)
 603  	e.dragger.Add(gtx.Ops)
 604  	e.caret.on = false
 605  	if e.focused {
 606  		now := gtx.Now
 607  		dt := now.Sub(e.blinkStart)
 608  		blinking := dt < maxBlinkDuration
 609  		const timePerBlink = time.Second / blinksPerSecond
 610  		nextBlink := now.Add(timePerBlink/2 - dt%(timePerBlink/2))
 611  		if blinking {
 612  			redraw := op.InvalidateOp{At: nextBlink}
 613  			redraw.Add(gtx.Ops)
 614  		}
 615  		e.caret.on = e.focused && (!blinking || dt%timePerBlink < timePerBlink/2)
 616  	}
 617  
 618  	return layout.Dimensions{Size: e.viewSize, Baseline: e.dims.Baseline}
 619  }
 620  
 621  // PaintSelection paints the contrasting background for selected text.
 622  func (e *Editor) PaintSelection(gtx layout.Context) {
 623  	cl := textPadding(e.lines)
 624  	cl.Max = cl.Max.Add(e.viewSize)
 625  	clip.Rect(cl).Add(gtx.Ops)
 626  	for _, shape := range e.shapes {
 627  		if !shape.selected {
 628  			continue
 629  		}
 630  		stack := op.Save(gtx.Ops)
 631  		offset := shape.offset
 632  		offset.Y += shape.selectionYOffs
 633  		op.Offset(layout.FPt(offset)).Add(gtx.Ops)
 634  		clip.Rect(image.Rectangle{Max: shape.selectionSize}).Add(gtx.Ops)
 635  		paint.PaintOp{}.Add(gtx.Ops)
 636  		stack.Load()
 637  	}
 638  }
 639  
 640  func (e *Editor) PaintText(gtx layout.Context) {
 641  	cl := textPadding(e.lines)
 642  	cl.Max = cl.Max.Add(e.viewSize)
 643  	clip.Rect(cl).Add(gtx.Ops)
 644  	for _, shape := range e.shapes {
 645  		stack := op.Save(gtx.Ops)
 646  		op.Offset(layout.FPt(shape.offset)).Add(gtx.Ops)
 647  		shape.clip.Add(gtx.Ops)
 648  		paint.PaintOp{}.Add(gtx.Ops)
 649  		stack.Load()
 650  	}
 651  }
 652  
 653  func (e *Editor) PaintCaret(gtx layout.Context) {
 654  	if !e.caret.on {
 655  		return
 656  	}
 657  	e.makeValid()
 658  	// todo: this should also be scaled and like blink/sec, configured in theme
 659  	carWidth := fixed.I(gtx.Px(unit.Dp(1)))
 660  	carX := e.caret.start.x
 661  	carY := e.caret.start.y
 662  
 663  	defer op.Save(gtx.Ops).Load()
 664  	carX -= carWidth / 2
 665  	carAsc, carDesc := -e.lines[e.caret.start.lineCol.Y].Bounds.Min.Y,
 666  		e.lines[e.caret.start.lineCol.Y].Bounds.Max.Y
 667  	carRect := image.Rectangle{
 668  		Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
 669  		Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
 670  	}
 671  	carRect = carRect.Add(image.Point{
 672  		X: -e.scrollOff.X,
 673  		Y: -e.scrollOff.Y,
 674  	})
 675  	cl := textPadding(e.lines)
 676  	// Account for caret width to each side.
 677  	whalf := (carWidth / 2).Ceil()
 678  	if cl.Max.X < whalf {
 679  		cl.Max.X = whalf
 680  	}
 681  	if cl.Min.X > -whalf {
 682  		cl.Min.X = -whalf
 683  	}
 684  	cl.Max = cl.Max.Add(e.viewSize)
 685  	carRect = cl.Intersect(carRect)
 686  	if !carRect.Empty() {
 687  		st := op.Save(gtx.Ops)
 688  		clip.Rect(carRect).Add(gtx.Ops)
 689  		paint.PaintOp{}.Add(gtx.Ops)
 690  		st.Load()
 691  	}
 692  }
 693  
 694  // Len is the length of the editor contents.
 695  func (e *Editor) Len() int {
 696  	return e.editBuffer.len()
 697  }
 698  
 699  // Text returns the contents of the editor.
 700  func (e *Editor) Text() string {
 701  	return e.editBuffer.String()
 702  }
 703  
 704  // SetText replaces the contents of the editor, clearing any selection first.
 705  func (e *Editor) SetText(s string) *Editor {
 706  	e.editBuffer = editBuffer{}
 707  	e.caret.start = combinedPos{}
 708  	e.caret.end = combinedPos{}
 709  	e.prepend(s)
 710  	return e
 711  }
 712  
 713  func (e *Editor) scrollBounds() image.Rectangle {
 714  	var b image.Rectangle
 715  	if e.singleLine {
 716  		if len(e.lines) > 0 {
 717  			b.Min.X = align(e.alignment, e.lines[0].Width, e.viewSize.X).Floor()
 718  			if b.Min.X > 0 {
 719  				b.Min.X = 0
 720  			}
 721  		}
 722  		b.Max.X = e.dims.Size.X + b.Min.X - e.viewSize.X
 723  	} else {
 724  		b.Max.Y = e.dims.Size.Y - e.viewSize.Y
 725  	}
 726  	return b
 727  }
 728  
 729  func (e *Editor) scrollRel(dx, dy int) {
 730  	e.scrollAbs(e.scrollOff.X+dx, e.scrollOff.Y+dy)
 731  }
 732  
 733  func (e *Editor) scrollAbs(x, y int) {
 734  	e.scrollOff.X = x
 735  	e.scrollOff.Y = y
 736  	b := e.scrollBounds()
 737  	if e.scrollOff.X > b.Max.X {
 738  		e.scrollOff.X = b.Max.X
 739  	}
 740  	if e.scrollOff.X < b.Min.X {
 741  		e.scrollOff.X = b.Min.X
 742  	}
 743  	if e.scrollOff.Y > b.Max.Y {
 744  		e.scrollOff.Y = b.Max.Y
 745  	}
 746  	if e.scrollOff.Y < b.Min.Y {
 747  		e.scrollOff.Y = b.Min.Y
 748  	}
 749  }
 750  
 751  func (e *Editor) moveCoord(pos image.Point) {
 752  	var (
 753  		prevDesc fixed.Int26_6
 754  		carLine  int
 755  		y        int
 756  	)
 757  	for _, l := range e.lines {
 758  		y += (prevDesc + l.Ascent).Ceil()
 759  		prevDesc = l.Descent
 760  		if y+prevDesc.Ceil() >= pos.Y+e.scrollOff.Y {
 761  			break
 762  		}
 763  		carLine++
 764  	}
 765  	x := fixed.I(pos.X + e.scrollOff.X)
 766  	e.caret.start = e.movePosToLine(e.caret.start, x, carLine)
 767  	e.caret.start.xoff = 0
 768  }
 769  
 770  func (e *Editor) layoutText(s text.Shaper) ([]text.Line, layout.Dimensions) {
 771  	e.editBuffer.Reset()
 772  	var r io.Reader = &e.editBuffer
 773  	if e.mask != 0 {
 774  		e.maskReader.Reset(&e.editBuffer, e.mask)
 775  		r = &e.maskReader
 776  	}
 777  	var lines []text.Line
 778  	if s != nil {
 779  		lines, _ = s.Layout(e.font, e.textSize, e.maxWidth, r)
 780  	} else {
 781  		lines, _ = nullLayout(r)
 782  	}
 783  	dims := linesDimens(lines)
 784  	for i := 0; i < len(lines)-1; i++ {
 785  		// To avoid l flickering while editing, assume a soft newline takes up all available space.
 786  		if lay := lines[i].Layout; len(lay.Text) > 0 {
 787  			r := lay.Text[len(lay.Text)-1]
 788  			if r != '\n' {
 789  				dims.Size.X = e.maxWidth
 790  				break
 791  			}
 792  		}
 793  	}
 794  	return lines, dims
 795  }
 796  
 797  // CaretPos returns the line & column numbers of the caret.
 798  func (e *Editor) CaretPos() (line, col int) {
 799  	e.makeValid()
 800  	return e.caret.start.lineCol.Y, e.caret.start.lineCol.X
 801  }
 802  
 803  // CaretCoords returns the coordinates of the caret, relative to the
 804  // editor itself.
 805  func (e *Editor) CaretCoords() f32.Point {
 806  	e.makeValid()
 807  	return f32.Pt(float32(e.caret.start.x)/64, float32(e.caret.start.y))
 808  }
 809  
 810  // offsetToScreenPos2 is a utility function to shortcut the common case of
 811  // wanting the positions of exactly two offsets.
 812  func (e *Editor) offsetToScreenPos2(o1, o2 int) (combinedPos, combinedPos) {
 813  	cp1, iter := e.offsetToScreenPos(o1)
 814  	return cp1, iter(o2)
 815  }
 816  
 817  // offsetToScreenPos takes an offset into the editor text (e.g.
 818  // e.caret.end.ofs) and returns a combinedPos that corresponds to its current
 819  // screen position, as well as an iterator that lets you get the combinedPos
 820  // of a later offset. The offsets given to offsetToScreenPos and to the
 821  // returned iterator must be sorted, lowest first, and they must be valid (0
 822  // <= offset <= e.Len()).
 823  //
 824  // This function is written this way to take advantage of previous work done
 825  // for offsets after the first. Otherwise you have to start from the top each
 826  // time.
 827  func (e *Editor) offsetToScreenPos(offset int) (combinedPos, func(int) combinedPos) {
 828  	var col, line, idx int
 829  	var x fixed.Int26_6
 830  
 831  	l := e.lines[line]
 832  	y := l.Ascent.Ceil()
 833  	prevDesc := l.Descent
 834  
 835  	iter := func(offset int) combinedPos {
 836  	LOOP:
 837  		for {
 838  			for ; col < len(l.Layout.Advances); col++ {
 839  				if idx >= offset {
 840  					break LOOP
 841  				}
 842  
 843  				x += l.Layout.Advances[col]
 844  				_, s := e.editBuffer.runeAt(idx)
 845  				idx += s
 846  			}
 847  			if lastLine := line == len(e.lines)-1; lastLine || idx > offset {
 848  				break LOOP
 849  			}
 850  
 851  			line++
 852  			x = 0
 853  			col = 0
 854  			l = e.lines[line]
 855  			y += (prevDesc + l.Ascent).Ceil()
 856  			prevDesc = l.Descent
 857  		}
 858  		return combinedPos{
 859  			lineCol: screenPos{Y: line, X: col},
 860  			x:       x + align(e.alignment, e.lines[line].Width, e.viewSize.X),
 861  			y:       y,
 862  			ofs:     offset,
 863  		}
 864  	}
 865  	return iter(offset), iter
 866  }
 867  
 868  func (e *Editor) invalidate() {
 869  	e.valid = false
 870  }
 871  
 872  // Delete runes from the caret position. The sign of runes specifies the
 873  // direction to delete: positive is forward, negative is backward.
 874  //
 875  // If there is a selection, it is deleted and counts as a single rune.
 876  func (e *Editor) Delete(runes int) {
 877  	if runes == 0 {
 878  		return
 879  	}
 880  
 881  	if l := e.caret.end.ofs - e.caret.start.ofs; l != 0 {
 882  		e.caret.start.ofs = e.editBuffer.deleteRunes(e.caret.start.ofs, l)
 883  		runes -= sign(runes)
 884  	}
 885  
 886  	e.caret.start.ofs = e.editBuffer.deleteRunes(e.caret.start.ofs, runes)
 887  	e.caret.start.xoff = 0
 888  	e.ClearSelection()
 889  	e.invalidate()
 890  }
 891  
 892  // Insert inserts text at the caret, moving the caret forward. If there is a
 893  // selection, Insert overwrites it.
 894  func (e *Editor) Insert(s string) {
 895  	e.append(s)
 896  	e.caret.scroll = true
 897  }
 898  
 899  func (e *Editor) append(s string) {
 900  	e.prepend(s)
 901  	e.caret.start.ofs += len(s)
 902  	e.caret.end.ofs = e.caret.start.ofs
 903  }
 904  
 905  // prepend inserts s after the cursor; the caret does not change. If there is
 906  // a selection, prepend overwrites it.
 907  // xxx|yyy + prepend zzz => xxx|zzzyyy
 908  func (e *Editor) prepend(s string) {
 909  	if e.singleLine {
 910  		s = strings.ReplaceAll(s, "\n", " ")
 911  	}
 912  	e.caret.start.ofs = e.editBuffer.deleteRunes(e.caret.start.ofs,
 913  		e.caret.end.ofs-e.caret.start.ofs) // Delete any selection first.
 914  	e.editBuffer.prepend(e.caret.start.ofs, s)
 915  	e.caret.start.xoff = 0
 916  	e.invalidate()
 917  }
 918  
 919  func (e *Editor) movePages(pages int, selAct selectionAction) {
 920  	e.makeValid()
 921  	y := e.caret.start.y + pages*e.viewSize.Y
 922  	var (
 923  		prevDesc fixed.Int26_6
 924  		carLine2 int
 925  	)
 926  	y2 := e.lines[0].Ascent.Ceil()
 927  	for i := 1; i < len(e.lines); i++ {
 928  		if y2 >= y {
 929  			break
 930  		}
 931  		l := e.lines[i]
 932  		h := (prevDesc + l.Ascent).Ceil()
 933  		prevDesc = l.Descent
 934  		if y2+h-y >= y-y2 {
 935  			break
 936  		}
 937  		y2 += h
 938  		carLine2++
 939  	}
 940  	e.caret.start = e.movePosToLine(e.caret.start, e.caret.start.x+e.caret.start.xoff, carLine2)
 941  	e.updateSelection(selAct)
 942  }
 943  
 944  func (e *Editor) movePosToLine(pos combinedPos, x fixed.Int26_6, line int) combinedPos {
 945  	e.makeValid(&pos)
 946  	if line < 0 {
 947  		line = 0
 948  	}
 949  	if line >= len(e.lines) {
 950  		line = len(e.lines) - 1
 951  	}
 952  
 953  	prevDesc := e.lines[line].Descent
 954  	for pos.lineCol.Y < line {
 955  		pos = e.movePosToEnd(pos)
 956  		l := e.lines[pos.lineCol.Y]
 957  		_, s := e.editBuffer.runeAt(pos.ofs)
 958  		pos.ofs += s
 959  		pos.y += (prevDesc + l.Ascent).Ceil()
 960  		pos.lineCol.X = 0
 961  		prevDesc = l.Descent
 962  		pos.lineCol.Y++
 963  	}
 964  	for pos.lineCol.Y > line {
 965  		pos = e.movePosToStart(pos)
 966  		l := e.lines[pos.lineCol.Y]
 967  		_, s := e.editBuffer.runeBefore(pos.ofs)
 968  		pos.ofs -= s
 969  		pos.y -= (prevDesc + l.Ascent).Ceil()
 970  		prevDesc = l.Descent
 971  		pos.lineCol.Y--
 972  		l = e.lines[pos.lineCol.Y]
 973  		pos.lineCol.X = len(l.Layout.Advances) - 1
 974  	}
 975  
 976  	pos = e.movePosToStart(pos)
 977  	l := e.lines[line]
 978  	pos.x = align(e.alignment, l.Width, e.viewSize.X)
 979  	// Only move past the end of the last line
 980  	end := 0
 981  	if line < len(e.lines)-1 {
 982  		end = 1
 983  	}
 984  	// Move to rune closest to x.
 985  	for i := 0; i < len(l.Layout.Advances)-end; i++ {
 986  		adv := l.Layout.Advances[i]
 987  		if pos.x >= x {
 988  			break
 989  		}
 990  		if pos.x+adv-x >= x-pos.x {
 991  			break
 992  		}
 993  		pos.x += adv
 994  		_, s := e.editBuffer.runeAt(pos.ofs)
 995  		pos.ofs += s
 996  		pos.lineCol.X++
 997  	}
 998  	pos.xoff = x - pos.x
 999  	return pos
1000  }
1001  
1002  // MoveCaret moves the caret (aka selection start) and the selection end
1003  // relative to their current positions. Positive distances moves forward,
1004  // negative distances moves backward. Distances are in runes.
1005  func (e *Editor) MoveCaret(startDelta, endDelta int) {
1006  	e.makeValid()
1007  	keepSame := e.caret.start.ofs == e.caret.end.ofs && startDelta == endDelta
1008  	e.caret.start = e.movePos(e.caret.start, startDelta)
1009  	e.caret.start.xoff = 0
1010  	// If they were in the same place, and we're moving them the same distance,
1011  	// just assign the new position, instead of recalculating it.
1012  	if keepSame {
1013  		e.caret.end = e.caret.start
1014  	} else {
1015  		e.caret.end = e.movePos(e.caret.end, endDelta)
1016  		e.caret.end.xoff = 0
1017  	}
1018  }
1019  
1020  func (e *Editor) movePos(pos combinedPos, distance int) combinedPos {
1021  	for ; distance < 0 && pos.ofs > 0; distance++ {
1022  		if pos.lineCol.X == 0 {
1023  			// Move to end of previous line.
1024  			pos = e.movePosToLine(pos, fixed.I(e.maxWidth), pos.lineCol.Y-1)
1025  			continue
1026  		}
1027  		l := e.lines[pos.lineCol.Y].Layout
1028  		_, s := e.editBuffer.runeBefore(pos.ofs)
1029  		pos.ofs -= s
1030  		pos.lineCol.X--
1031  		pos.x -= l.Advances[pos.lineCol.X]
1032  	}
1033  	for ; distance > 0 && pos.ofs < e.editBuffer.len(); distance-- {
1034  		l := e.lines[pos.lineCol.Y].Layout
1035  		// Only move past the end of the last line
1036  		end := 0
1037  		if pos.lineCol.Y < len(e.lines)-1 {
1038  			end = 1
1039  		}
1040  		if pos.lineCol.X >= len(l.Advances)-end {
1041  			// Move to start of next line.
1042  			pos = e.movePosToLine(pos, 0, pos.lineCol.Y+1)
1043  			continue
1044  		}
1045  		pos.x += l.Advances[pos.lineCol.X]
1046  		_, s := e.editBuffer.runeAt(pos.ofs)
1047  		pos.ofs += s
1048  		pos.lineCol.X++
1049  	}
1050  	return pos
1051  }
1052  
1053  func (e *Editor) moveStart(selAct selectionAction) {
1054  	e.caret.start = e.movePosToStart(e.caret.start)
1055  	e.updateSelection(selAct)
1056  }
1057  
1058  func (e *Editor) movePosToStart(pos combinedPos) combinedPos {
1059  	e.makeValid(&pos)
1060  	layout := e.lines[pos.lineCol.Y].Layout
1061  	for i := pos.lineCol.X - 1; i >= 0; i-- {
1062  		_, s := e.editBuffer.runeBefore(pos.ofs)
1063  		pos.ofs -= s
1064  		pos.x -= layout.Advances[i]
1065  	}
1066  	pos.lineCol.X = 0
1067  	pos.xoff = -pos.x
1068  	return pos
1069  }
1070  
1071  func (e *Editor) moveEnd(selAct selectionAction) {
1072  	e.caret.start = e.movePosToEnd(e.caret.start)
1073  	e.updateSelection(selAct)
1074  }
1075  
1076  func (e *Editor) movePosToEnd(pos combinedPos) combinedPos {
1077  	e.makeValid(&pos)
1078  	l := e.lines[pos.lineCol.Y]
1079  	// Only move past the end of the last line
1080  	end := 0
1081  	if pos.lineCol.Y < len(e.lines)-1 {
1082  		end = 1
1083  	}
1084  	layout := l.Layout
1085  	for i := pos.lineCol.X; i < len(layout.Advances)-end; i++ {
1086  		adv := layout.Advances[i]
1087  		_, s := e.editBuffer.runeAt(pos.ofs)
1088  		pos.ofs += s
1089  		pos.x += adv
1090  		pos.lineCol.X++
1091  	}
1092  	a := align(e.alignment, l.Width, e.viewSize.X)
1093  	pos.xoff = l.Width + a - pos.x
1094  	return pos
1095  }
1096  
1097  // moveWord moves the caret to the next word in the specified direction.
1098  // Positive is forward, negative is backward.
1099  // Absolute values greater than one will skip that many words.
1100  func (e *Editor) moveWord(distance int, selAct selectionAction) {
1101  	e.makeValid()
1102  	// split the distance information into constituent parts to be
1103  	// used independently.
1104  	words, direction := distance, 1
1105  	if distance < 0 {
1106  		words, direction = distance*-1, -1
1107  	}
1108  	// atEnd if caret is at either side of the buffer.
1109  	atEnd := func() bool {
1110  		return e.caret.start.ofs == 0 || e.caret.start.ofs == e.editBuffer.len()
1111  	}
1112  	// next returns the appropriate rune given the direction.
1113  	next := func() (r rune) {
1114  		if direction < 0 {
1115  			r, _ = e.editBuffer.runeBefore(e.caret.start.ofs)
1116  		} else {
1117  			r, _ = e.editBuffer.runeAt(e.caret.start.ofs)
1118  		}
1119  		return r
1120  	}
1121  	for ii := 0; ii < words; ii++ {
1122  		for r := next(); unicode.IsSpace(r) && !atEnd(); r = next() {
1123  			e.MoveCaret(direction, 0)
1124  		}
1125  		e.MoveCaret(direction, 0)
1126  		for r := next(); !unicode.IsSpace(r) && !atEnd(); r = next() {
1127  			e.MoveCaret(direction, 0)
1128  		}
1129  	}
1130  	e.updateSelection(selAct)
1131  }
1132  
1133  // deleteWord the next word(s) in the specified direction. Unlike moveWord, deleteWord treats whitespace as a word
1134  // itself.
1135  //
1136  // Positive is forward, negative is backward.
1137  //
1138  // Absolute values greater than one will delete that many words.
1139  func (e *Editor) deleteWord(distance int) {
1140  	if distance == 0 {
1141  		return
1142  	}
1143  
1144  	e.makeValid()
1145  
1146  	if e.caret.start.ofs != e.caret.end.ofs {
1147  		e.Delete(1)
1148  		distance -= sign(distance)
1149  	}
1150  	if distance == 0 {
1151  		return
1152  	}
1153  
1154  	// split the distance information into constituent parts to be
1155  	// used independently.
1156  	words, direction := distance, 1
1157  	if distance < 0 {
1158  		words, direction = distance*-1, -1
1159  	}
1160  	// atEnd if offset is at or beyond either side of the buffer.
1161  	atEnd := func(offset int) bool {
1162  		idx := e.caret.start.ofs + offset*direction
1163  		return idx <= 0 || idx >= e.editBuffer.len()
1164  	}
1165  	// next returns the appropriate rune given the direction and offset.
1166  	next := func(offset int) (r rune) {
1167  		idx := e.caret.start.ofs + offset*direction
1168  		if idx < 0 {
1169  			idx = 0
1170  		} else if idx > e.editBuffer.len() {
1171  			idx = e.editBuffer.len()
1172  		}
1173  		if direction < 0 {
1174  			r, _ = e.editBuffer.runeBefore(idx)
1175  		} else {
1176  			r, _ = e.editBuffer.runeAt(idx)
1177  		}
1178  		return r
1179  	}
1180  	var runes = 1
1181  	for ii := 0; ii < words; ii++ {
1182  		if r := next(runes); unicode.IsSpace(r) {
1183  			for r := next(runes); unicode.IsSpace(r) && !atEnd(runes); r = next(runes) {
1184  				runes += 1
1185  			}
1186  		} else {
1187  			for r := next(runes); !unicode.IsSpace(r) && !atEnd(runes); r = next(runes) {
1188  				runes += 1
1189  			}
1190  		}
1191  	}
1192  	e.Delete(runes * direction)
1193  }
1194  
1195  func (e *Editor) scrollToCaret() {
1196  	e.makeValid()
1197  	l := e.lines[e.caret.start.lineCol.Y]
1198  	if e.singleLine {
1199  		var dist int
1200  		if d := e.caret.start.x.Floor() - e.scrollOff.X; d < 0 {
1201  			dist = d
1202  		} else if d = e.caret.start.x.Ceil() - (e.scrollOff.X + e.viewSize.X); d > 0 {
1203  			dist = d
1204  		}
1205  		e.scrollRel(dist, 0)
1206  	} else {
1207  		miny := e.caret.start.y - l.Ascent.Ceil()
1208  		maxy := e.caret.start.y + l.Descent.Ceil()
1209  		var dist int
1210  		if d := miny - e.scrollOff.Y; d < 0 {
1211  			dist = d
1212  		} else if d = maxy - (e.scrollOff.Y + e.viewSize.Y); d > 0 {
1213  			dist = d
1214  		}
1215  		e.scrollRel(0, dist)
1216  	}
1217  }
1218  
1219  // NumLines returns the number of lines in the editor.
1220  func (e *Editor) NumLines() int {
1221  	e.makeValid()
1222  	return len(e.lines)
1223  }
1224  
1225  // SelectionLen returns the length of the selection, in bytes; it is
1226  // equivalent to len(e.SelectedText()).
1227  func (e *Editor) SelectionLen() int {
1228  	return abs(e.caret.start.ofs - e.caret.end.ofs)
1229  }
1230  
1231  // Selection returns the start and end of the selection, as offsets into the
1232  // editor text. start can be > end.
1233  func (e *Editor) Selection() (start, end int) {
1234  	return e.caret.start.ofs, e.caret.end.ofs
1235  }
1236  
1237  // SetCaret moves the caret to start, and sets the selection end to end. start
1238  // and end are in bytes, and represent offsets into the editor text. start and
1239  // end must be at a rune boundary.
1240  func (e *Editor) SetCaret(start, end int) {
1241  	e.makeValid()
1242  	// Constrain start and end to [0, e.Len()].
1243  	l := e.Len()
1244  	start = max(min(start, l), 0)
1245  	end = max(min(end, l), 0)
1246  	e.caret.start.ofs, e.caret.end.ofs = start, end
1247  	e.makeValidCaret()
1248  	e.caret.scroll = true
1249  	e.scroller.Stop()
1250  }
1251  
1252  func (e *Editor) makeValidCaret(positions ...*combinedPos) {
1253  	// Jump through some hoops to order the offsets given to offsetToScreenPos,
1254  	// but still be able to update them correctly with the results thereof.
1255  	positions = append(positions, &e.caret.start, &e.caret.end)
1256  	sort.Slice(positions, func(i, j int) bool {
1257  		return positions[i].ofs < positions[j].ofs
1258  	})
1259  	var iter func(offset int) combinedPos
1260  	*positions[0], iter = e.offsetToScreenPos(positions[0].ofs)
1261  	for _, cp := range positions[1:] {
1262  		*cp = iter(cp.ofs)
1263  	}
1264  }
1265  
1266  // SelectedText returns the currently selected text (if any) from the editor.
1267  func (e *Editor) SelectedText() string {
1268  	l := e.SelectionLen()
1269  	if l == 0 {
1270  		return ""
1271  	}
1272  	buf := make([]byte, l)
1273  	e.editBuffer.Seek(int64(min(e.caret.start.ofs, e.caret.end.ofs)), io.SeekStart)
1274  	_, err := e.editBuffer.Read(buf)
1275  	if err != nil {
1276  		// The only error that rr.Read can return is EOF, which just means no
1277  		// selection, but we've already made sure that shouldn't happen.
1278  		panic("impossible error because end is before e.rr.Len()")
1279  	}
1280  	return string(buf)
1281  }
1282  
1283  func (e *Editor) updateSelection(selAct selectionAction) {
1284  	if selAct == selectionClear {
1285  		e.ClearSelection()
1286  	}
1287  }
1288  
1289  // ClearSelection clears the selection, by setting the selection end equal to
1290  // the selection start.
1291  func (e *Editor) ClearSelection() {
1292  	e.caret.end = e.caret.start
1293  }
1294  
1295  func max(a, b int) int {
1296  	if a > b {
1297  		return a
1298  	}
1299  	return b
1300  }
1301  
1302  func min(a, b int) int {
1303  	if a < b {
1304  		return a
1305  	}
1306  	return b
1307  }
1308  
1309  func abs(n int) int {
1310  	if n < 0 {
1311  		return -n
1312  	}
1313  	return n
1314  }
1315  
1316  func sign(n int) int {
1317  	switch {
1318  	case n < 0:
1319  		return -1
1320  	case n > 0:
1321  		return 1
1322  	default:
1323  		return 0
1324  	}
1325  }
1326  
1327  // sortPoints returns a and b sorted such that a2 <= b2.
1328  func sortPoints(a, b screenPos) (a2, b2 screenPos) {
1329  	if b.Less(a) {
1330  		return b, a
1331  	}
1332  	return a, b
1333  }
1334  
1335  func nullLayout(r io.Reader) ([]text.Line, error) {
1336  	rr := bufio.NewReader(r)
1337  	var rerr error
1338  	var n int
1339  	var buf bytes.Buffer
1340  	for {
1341  		r, s, e := rr.ReadRune()
1342  		n += s
1343  		buf.WriteRune(r)
1344  		if e != nil {
1345  			rerr = e
1346  			break
1347  		}
1348  	}
1349  	return []text.Line{
1350  		{
1351  			Layout: text.Layout{
1352  				Text:     buf.String(),
1353  				Advances: make([]fixed.Int26_6, n),
1354  			},
1355  		},
1356  	}, rerr
1357  }
1358  
1359  func (s ChangeEvent) isEditorEvent() {}
1360  func (s SubmitEvent) isEditorEvent() {}
1361  func (s SelectEvent) isEditorEvent() {}
1362  
1363  // func (e *Editor) moveToLine(x fixed.Int26_6, line int) {
1364  // 	e.makeValid()
1365  // 	if line < 0 {
1366  // 		line = 0
1367  // 	}
1368  // 	if line >= len(e.lines) {
1369  // 		line = len(e.lines) - 1
1370  // 	}
1371  //
1372  // 	prevDesc := e.lines[line].Descent
1373  // 	for e.caret.Line < line {
1374  // 		e.moveEnd()
1375  // 		l := e.lines[e.caret.Line]
1376  // 		_, s := e.editBuffer.runeAt(e.editBuffer.caret)
1377  // 		e.editBuffer.caret += s
1378  // 		e.caret.y += (prevDesc + l.Ascent).Ceil()
1379  // 		e.caret.Col = 0
1380  // 		prevDesc = l.Descent
1381  // 		e.caret.Line++
1382  // 	}
1383  // 	for e.caret.Line > line {
1384  // 		e.moveStart()
1385  // 		l := e.lines[e.caret.Line]
1386  // 		_, s := e.editBuffer.runeBefore(e.editBuffer.caret)
1387  // 		e.editBuffer.caret -= s
1388  // 		e.caret.y -= (prevDesc + l.Ascent).Ceil()
1389  // 		prevDesc = l.Descent
1390  // 		e.caret.Line--
1391  // 		l = e.lines[e.caret.Line]
1392  // 		e.caret.Col = len(l.Layout.Advances) - 1
1393  // 	}
1394  //
1395  // 	e.moveStart()
1396  // 	l := e.lines[line]
1397  // 	e.caret.x = align(e.alignment, l.Width, e.viewSize.X)
1398  // 	// Only move past the end of the last line
1399  // 	end := 0
1400  // 	if line < len(e.lines)-1 {
1401  // 		end = 1
1402  // 	}
1403  // 	// Move to rune closest to x.
1404  // 	for i := 0; i < len(l.Layout.Advances)-end; i++ {
1405  // 		adv := l.Layout.Advances[i]
1406  // 		if e.caret.x >= x {
1407  // 			break
1408  // 		}
1409  // 		if e.caret.x+adv-x >= x-e.caret.x {
1410  // 			break
1411  // 		}
1412  // 		e.caret.x += adv
1413  // 		_, s := e.editBuffer.runeAt(e.editBuffer.caret)
1414  // 		e.editBuffer.caret += s
1415  // 		e.caret.Col++
1416  // 	}
1417  // 	e.caret.xoff = x - e.caret.x
1418  // }
1419  //
1420  // // Move the caret: positive distance moves forward, negative distance moves
1421  // // backward.
1422  // func (e *Editor) Move(distance int) {
1423  // 	e.makeValid()
1424  // 	for ; distance < 0 && e.editBuffer.caret > 0; distance++ {
1425  // 		if e.caret.Col == 0 {
1426  // 			// Move to end of previous line.
1427  // 			e.moveToLine(fixed.I(e.maxWidth), e.caret.Line-1)
1428  // 			continue
1429  // 		}
1430  // 		l := e.lines[e.caret.Line].Layout
1431  // 		_, s := e.editBuffer.runeBefore(e.editBuffer.caret)
1432  // 		e.editBuffer.caret -= s
1433  // 		e.caret.Col--
1434  // 		e.caret.x -= l.Advances[e.caret.Col]
1435  // 	}
1436  // 	for ; distance > 0 && e.editBuffer.caret < e.editBuffer.len(); distance-- {
1437  // 		l := e.lines[e.caret.Line].Layout
1438  // 		// Only move past the end of the last line
1439  // 		end := 0
1440  // 		if e.caret.Line < len(e.lines)-1 {
1441  // 			end = 1
1442  // 		}
1443  // 		if e.caret.Col >= len(l.Advances)-end {
1444  // 			// Move to start of next line.
1445  // 			e.moveToLine(0, e.caret.Line+1)
1446  // 			continue
1447  // 		}
1448  // 		e.caret.x += l.Advances[e.caret.Col]
1449  // 		_, s := e.editBuffer.runeAt(e.editBuffer.caret)
1450  // 		e.editBuffer.caret += s
1451  // 		e.caret.Col++
1452  // 	}
1453  // 	e.caret.xoff = 0
1454  // }
1455  
1456  func (e *Editor) layoutCaret() (line, col int, x fixed.Int26_6, y int) {
1457  	var idx int
1458  	var prevDesc fixed.Int26_6
1459  loop:
1460  	for {
1461  		x = 0
1462  		col = 0
1463  		l := e.lines[line]
1464  		y += (prevDesc + l.Ascent).Ceil()
1465  		prevDesc = l.Descent
1466  		for _, adv := range l.Layout.Advances {
1467  			if idx == e.editBuffer.caret {
1468  				break loop
1469  			}
1470  			x += adv
1471  			_, s := e.editBuffer.runeAt(idx)
1472  			idx += s
1473  			col++
1474  		}
1475  		if line == len(e.lines)-1 || idx > e.editBuffer.caret {
1476  			break
1477  		}
1478  		line++
1479  	}
1480  	x += align(e.alignment, e.lines[line].Width, e.viewSize.X)
1481  	return
1482  }
1483  
1484  func (e *Editor) SingleLine() *Editor {
1485  	e.singleLine = true
1486  	return e
1487  }
1488  
1489  func (e *Editor) Submit(submit bool) *Editor {
1490  	e.submit = submit
1491  	return e
1492  }
1493  
1494  func (e *Editor) Mask(mask rune) *Editor {
1495  	e.mask = mask
1496  	return e
1497  }
1498  
1499  func (e *Editor) SetSubmit(submitFn func(txt string)) *Editor {
1500  	e.submitHook = submitFn
1501  	return e
1502  }
1503  
1504  func (e *Editor) SetChange(changeFn func(txt string)) *Editor {
1505  	e.changeHook = changeFn
1506  	return e
1507  }
1508  
1509  func (e *Editor) SetFocus(focusFn func(is bool)) *Editor {
1510  	e.focusHook = focusFn
1511  	return e
1512  }
1513  
1514  func (e *Editor) Alignment(alignment text.Alignment) *Editor {
1515  	e.alignment = alignment
1516  	return e
1517  }
1518