mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
widget: track only Editor caret start in runes
The other information can be queries at use. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+75
-58
@@ -74,12 +74,12 @@ type Editor struct {
|
|||||||
scroll bool
|
scroll bool
|
||||||
// xoff is the offset to the current position when moving between lines.
|
// xoff is the offset to the current position when moving between lines.
|
||||||
xoff fixed.Int26_6
|
xoff fixed.Int26_6
|
||||||
// start is the current caret position, and also the start position of
|
// start is the current caret position in runes, and also the start position of
|
||||||
// selected text. end is the end position of selected text. If start.ofs
|
// selected text. end is the end position of selected text. If start
|
||||||
// == end.ofs, then there's no selection. Note that it's possible (and
|
// == end, then there's no selection. Note that it's possible (and
|
||||||
// common) that the caret (start) is after the end, e.g. after
|
// common) that the caret (start) is after the end, e.g. after
|
||||||
// Shift-DownArrow.
|
// Shift-DownArrow.
|
||||||
start combinedPos
|
start int
|
||||||
end int
|
end int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,11 +214,11 @@ func (e *Editor) processEvents(gtx layout.Context) {
|
|||||||
// Can't process events without a shaper.
|
// Can't process events without a shaper.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
oldStart, oldLen := min(e.caret.start.runes, e.caret.end), e.SelectionLen()
|
oldStart, oldLen := min(e.caret.start, e.caret.end), e.SelectionLen()
|
||||||
e.processPointer(gtx)
|
e.processPointer(gtx)
|
||||||
e.processKey(gtx)
|
e.processKey(gtx)
|
||||||
// Queue a SelectEvent if the selection changed, including if it went away.
|
// Queue a SelectEvent if the selection changed, including if it went away.
|
||||||
if newStart, newLen := min(e.caret.start.runes, e.caret.end), e.SelectionLen(); oldStart != newStart || oldLen != newLen {
|
if newStart, newLen := min(e.caret.start, e.caret.end), e.SelectionLen(); oldStart != newStart || oldLen != newLen {
|
||||||
e.events = append(e.events, SelectEvent{})
|
e.events = append(e.events, SelectEvent{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ func (e *Editor) makeValid() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.lines, e.dims = e.layoutText(e.shaper)
|
e.lines, e.dims = e.layoutText(e.shaper)
|
||||||
e.caret.start = e.closestPosition(combinedPos{runes: e.caret.start.runes})
|
e.caret.start = e.closestPosition(combinedPos{runes: e.caret.start}).runes
|
||||||
e.caret.end = e.closestPosition(combinedPos{runes: e.caret.end}).runes
|
e.caret.end = e.closestPosition(combinedPos{runes: e.caret.end}).runes
|
||||||
e.valid = true
|
e.valid = true
|
||||||
}
|
}
|
||||||
@@ -273,8 +273,8 @@ func (e *Editor) processPointer(gtx layout.Context) {
|
|||||||
if evt.Modifiers == key.ModShift {
|
if evt.Modifiers == key.ModShift {
|
||||||
// If they clicked closer to the end, then change the end to
|
// If they clicked closer to the end, then change the end to
|
||||||
// where the caret used to be (effectively swapping start & end).
|
// where the caret used to be (effectively swapping start & end).
|
||||||
if abs(e.caret.end-e.caret.start.runes) < abs(e.caret.start.runes-prevCaretPos.runes) {
|
if abs(e.caret.end-e.caret.start) < abs(e.caret.start-prevCaretPos) {
|
||||||
e.caret.end = prevCaretPos.runes
|
e.caret.end = prevCaretPos
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
e.ClearSelection()
|
e.ClearSelection()
|
||||||
@@ -369,12 +369,13 @@ func (e *Editor) processKey(gtx layout.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveLines(distance int, selAct selectionAction) {
|
func (e *Editor) moveLines(distance int, selAct selectionAction) {
|
||||||
x := e.caret.start.x + e.caret.xoff
|
caretStart := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
|
x := caretStart.x + e.caret.xoff
|
||||||
// Seek to line.
|
// Seek to line.
|
||||||
pos := e.closestPosition(combinedPos{lineCol: screenPos{Y: e.caret.start.lineCol.Y + distance}})
|
pos := e.closestPosition(combinedPos{lineCol: screenPos{Y: caretStart.lineCol.Y + distance}})
|
||||||
pos = e.closestPosition(combinedPos{x: x, y: pos.y})
|
pos = e.closestPosition(combinedPos{x: x, y: pos.y})
|
||||||
e.caret.start = pos
|
e.caret.start = pos.runes
|
||||||
e.caret.xoff = x - e.caret.start.x
|
e.caret.xoff = x - caretStart.x
|
||||||
e.updateSelection(selAct)
|
e.updateSelection(selAct)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +458,7 @@ func (e *Editor) command(gtx layout.Context, k key.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
e.caret.end = 0
|
e.caret.end = 0
|
||||||
e.caret.start = e.closestPosition(combinedPos{runes: math.MaxInt})
|
e.caret.start = e.Len()
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -527,8 +528,9 @@ func (e *Editor) layout(gtx layout.Context, content layout.Widget) layout.Dimens
|
|||||||
}
|
}
|
||||||
cl := textPadding(e.lines)
|
cl := textPadding(e.lines)
|
||||||
cl.Max = cl.Max.Add(e.viewSize)
|
cl.Max = cl.Max.Add(e.viewSize)
|
||||||
|
caretStart := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
caretEnd := e.closestPosition(combinedPos{runes: e.caret.end})
|
caretEnd := e.closestPosition(combinedPos{runes: e.caret.end})
|
||||||
startSel, endSel := sortPoints(e.caret.start.lineCol, caretEnd.lineCol)
|
startSel, endSel := sortPoints(caretStart.lineCol, caretEnd.lineCol)
|
||||||
it := segmentIterator{
|
it := segmentIterator{
|
||||||
startSel: startSel,
|
startSel: startSel,
|
||||||
endSel: endSel,
|
endSel: endSel,
|
||||||
@@ -633,11 +635,12 @@ func (e *Editor) PaintCaret(gtx layout.Context) {
|
|||||||
}
|
}
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
carWidth := fixed.I(gtx.Px(unit.Dp(1)))
|
carWidth := fixed.I(gtx.Px(unit.Dp(1)))
|
||||||
carX := e.caret.start.x
|
caretStart := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
carY := e.caret.start.y
|
carX := caretStart.x
|
||||||
|
carY := caretStart.y
|
||||||
|
|
||||||
carX -= carWidth / 2
|
carX -= carWidth / 2
|
||||||
carAsc, carDesc := -e.lines[e.caret.start.lineCol.Y].Bounds.Min.Y, e.lines[e.caret.start.lineCol.Y].Bounds.Max.Y
|
carAsc, carDesc := -e.lines[caretStart.lineCol.Y].Bounds.Min.Y, e.lines[caretStart.lineCol.Y].Bounds.Max.Y
|
||||||
carRect := image.Rectangle{
|
carRect := image.Rectangle{
|
||||||
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
|
Min: image.Point{X: carX.Ceil(), Y: carY - carAsc.Ceil()},
|
||||||
Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
|
Max: image.Point{X: carX.Ceil() + carWidth.Ceil(), Y: carY + carDesc.Ceil()},
|
||||||
@@ -678,9 +681,9 @@ func (e *Editor) Text() string {
|
|||||||
// SetText replaces the contents of the editor, clearing any selection first.
|
// SetText replaces the contents of the editor, clearing any selection first.
|
||||||
func (e *Editor) SetText(s string) {
|
func (e *Editor) SetText(s string) {
|
||||||
e.rr = editBuffer{}
|
e.rr = editBuffer{}
|
||||||
e.caret.start = combinedPos{}
|
e.caret.start = 0
|
||||||
e.caret.end = 0
|
e.caret.end = 0
|
||||||
e.replace(e.caret.start.runes, e.caret.end, s)
|
e.replace(e.caret.start, e.caret.end, s)
|
||||||
e.caret.xoff = 0
|
e.caret.xoff = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,7 +728,7 @@ func (e *Editor) scrollAbs(x, y int) {
|
|||||||
func (e *Editor) moveCoord(pos image.Point) {
|
func (e *Editor) moveCoord(pos image.Point) {
|
||||||
x := fixed.I(pos.X + e.scrollOff.X)
|
x := fixed.I(pos.X + e.scrollOff.X)
|
||||||
y := pos.Y + e.scrollOff.Y
|
y := pos.Y + e.scrollOff.Y
|
||||||
e.caret.start = e.closestPosition(combinedPos{x: x, y: y})
|
e.caret.start = e.closestPosition(combinedPos{x: x, y: y}).runes
|
||||||
e.caret.xoff = 0
|
e.caret.xoff = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,14 +763,16 @@ func (e *Editor) layoutText(s text.Shaper) ([]text.Line, layout.Dimensions) {
|
|||||||
// CaretPos returns the line & column numbers of the caret.
|
// CaretPos returns the line & column numbers of the caret.
|
||||||
func (e *Editor) CaretPos() (line, col int) {
|
func (e *Editor) CaretPos() (line, col int) {
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
return e.caret.start.lineCol.Y, e.caret.start.lineCol.X
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
|
return caret.lineCol.Y, caret.lineCol.X
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaretCoords returns the coordinates of the caret, relative to the
|
// CaretCoords returns the coordinates of the caret, relative to the
|
||||||
// editor itself.
|
// editor itself.
|
||||||
func (e *Editor) CaretCoords() f32.Point {
|
func (e *Editor) CaretCoords() f32.Point {
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
return f32.Pt(float32(e.caret.start.x)/64, float32(e.caret.start.y))
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
|
return f32.Pt(float32(caret.x)/64, float32(caret.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
// indexPosition returns the latest position from the index no later than pos.
|
// indexPosition returns the latest position from the index no later than pos.
|
||||||
@@ -880,7 +885,7 @@ func (e *Editor) Delete(runes int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
start := e.caret.start.runes
|
start := e.caret.start
|
||||||
end := e.caret.end
|
end := e.caret.end
|
||||||
if start != end {
|
if start != end {
|
||||||
runes -= sign(runes)
|
runes -= sign(runes)
|
||||||
@@ -903,11 +908,10 @@ func (e *Editor) Insert(s string) {
|
|||||||
// there is a selection, append overwrites it.
|
// there is a selection, append overwrites it.
|
||||||
// xxx|yyy + append zzz => xxxzzz|yyy
|
// xxx|yyy + append zzz => xxxzzz|yyy
|
||||||
func (e *Editor) append(s string) {
|
func (e *Editor) append(s string) {
|
||||||
e.replace(e.caret.start.runes, e.caret.end, s)
|
e.replace(e.caret.start, e.caret.end, s)
|
||||||
e.caret.xoff = 0
|
e.caret.xoff = 0
|
||||||
e.caret.start.ofs += len(s)
|
e.caret.start += utf8.RuneCountInString(s)
|
||||||
e.caret.start.runes += utf8.RuneCountInString(s)
|
e.caret.end = e.caret.start
|
||||||
e.caret.end = e.caret.start.runes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace the text between start and end with s. Indices are in runes.
|
// replace the text between start and end with s. Indices are in runes.
|
||||||
@@ -934,17 +938,19 @@ func (e *Editor) replace(start, end int, s string) {
|
|||||||
}
|
}
|
||||||
return pos
|
return pos
|
||||||
}
|
}
|
||||||
e.caret.start = e.closestPosition(combinedPos{runes: adjust(e.caret.start.runes)})
|
e.caret.start = adjust(e.caret.start)
|
||||||
e.caret.end = adjust(e.caret.end)
|
e.caret.end = adjust(e.caret.end)
|
||||||
e.invalidate()
|
e.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) movePages(pages int, selAct selectionAction) {
|
func (e *Editor) movePages(pages int, selAct selectionAction) {
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
x := e.caret.start.x + e.caret.xoff
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
y := e.caret.start.y + pages*e.viewSize.Y
|
x := caret.x + e.caret.xoff
|
||||||
e.caret.start = e.closestPosition(combinedPos{x: x, y: y})
|
y := caret.y + pages*e.viewSize.Y
|
||||||
e.caret.xoff = x - e.caret.start.x
|
pos := e.closestPosition(combinedPos{x: x, y: y})
|
||||||
|
e.caret.start = pos.runes
|
||||||
|
e.caret.xoff = x - pos.x
|
||||||
e.updateSelection(selAct)
|
e.updateSelection(selAct)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -954,21 +960,25 @@ func (e *Editor) movePages(pages int, selAct selectionAction) {
|
|||||||
func (e *Editor) MoveCaret(startDelta, endDelta int) {
|
func (e *Editor) MoveCaret(startDelta, endDelta int) {
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
e.caret.xoff = 0
|
e.caret.xoff = 0
|
||||||
e.caret.start = e.closestPosition(combinedPos{runes: e.caret.start.runes + startDelta})
|
e.caret.start = e.closestPosition(combinedPos{runes: e.caret.start + startDelta}).runes
|
||||||
e.caret.end = e.closestPosition(combinedPos{runes: e.caret.end + endDelta}).runes
|
e.caret.end = e.closestPosition(combinedPos{runes: e.caret.end + endDelta}).runes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveStart(selAct selectionAction) {
|
func (e *Editor) moveStart(selAct selectionAction) {
|
||||||
e.caret.start = e.closestPosition(combinedPos{lineCol: screenPos{Y: e.caret.start.lineCol.Y}})
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
e.caret.xoff = -e.caret.start.x
|
caret = e.closestPosition(combinedPos{lineCol: screenPos{Y: caret.lineCol.Y}})
|
||||||
|
e.caret.start = caret.runes
|
||||||
|
e.caret.xoff = -caret.x
|
||||||
e.updateSelection(selAct)
|
e.updateSelection(selAct)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Editor) moveEnd(selAct selectionAction) {
|
func (e *Editor) moveEnd(selAct selectionAction) {
|
||||||
e.caret.start = e.closestPosition(combinedPos{lineCol: screenPos{X: math.MaxInt, Y: e.caret.start.lineCol.Y}})
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
l := e.lines[e.caret.start.lineCol.Y]
|
caret = e.closestPosition(combinedPos{lineCol: screenPos{X: math.MaxInt, Y: caret.lineCol.Y}})
|
||||||
|
e.caret.start = caret.runes
|
||||||
|
l := e.lines[caret.lineCol.Y]
|
||||||
a := align(e.Alignment, l.Width, e.viewSize.X)
|
a := align(e.Alignment, l.Width, e.viewSize.X)
|
||||||
e.caret.xoff = l.Width + a - e.caret.start.x
|
e.caret.xoff = l.Width + a - caret.x
|
||||||
e.updateSelection(selAct)
|
e.updateSelection(selAct)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,25 +994,29 @@ func (e *Editor) moveWord(distance int, selAct selectionAction) {
|
|||||||
words, direction = distance*-1, -1
|
words, direction = distance*-1, -1
|
||||||
}
|
}
|
||||||
// atEnd if caret is at either side of the buffer.
|
// atEnd if caret is at either side of the buffer.
|
||||||
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
atEnd := func() bool {
|
atEnd := func() bool {
|
||||||
return e.caret.start.ofs == 0 || e.caret.start.ofs == e.rr.len()
|
return caret.ofs == 0 || caret.ofs == e.rr.len()
|
||||||
}
|
}
|
||||||
// next returns the appropriate rune given the direction.
|
// next returns the appropriate rune given the direction.
|
||||||
next := func() (r rune) {
|
next := func() (r rune) {
|
||||||
if direction < 0 {
|
if direction < 0 {
|
||||||
r, _ = e.rr.runeBefore(e.caret.start.ofs)
|
r, _ = e.rr.runeBefore(caret.ofs)
|
||||||
} else {
|
} else {
|
||||||
r, _ = e.rr.runeAt(e.caret.start.ofs)
|
r, _ = e.rr.runeAt(caret.ofs)
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
for ii := 0; ii < words; ii++ {
|
for ii := 0; ii < words; ii++ {
|
||||||
for r := next(); unicode.IsSpace(r) && !atEnd(); r = next() {
|
for r := next(); unicode.IsSpace(r) && !atEnd(); r = next() {
|
||||||
e.MoveCaret(direction, 0)
|
e.MoveCaret(direction, 0)
|
||||||
|
caret = e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
}
|
}
|
||||||
e.MoveCaret(direction, 0)
|
e.MoveCaret(direction, 0)
|
||||||
|
caret = e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
for r := next(); !unicode.IsSpace(r) && !atEnd(); r = next() {
|
for r := next(); !unicode.IsSpace(r) && !atEnd(); r = next() {
|
||||||
e.MoveCaret(direction, 0)
|
e.MoveCaret(direction, 0)
|
||||||
|
caret = e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e.updateSelection(selAct)
|
e.updateSelection(selAct)
|
||||||
@@ -1020,7 +1034,7 @@ func (e *Editor) deleteWord(distance int) {
|
|||||||
|
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
|
|
||||||
if e.caret.start.runes != e.caret.end {
|
if e.caret.start != e.caret.end {
|
||||||
e.Delete(1)
|
e.Delete(1)
|
||||||
distance -= sign(distance)
|
distance -= sign(distance)
|
||||||
}
|
}
|
||||||
@@ -1035,13 +1049,14 @@ func (e *Editor) deleteWord(distance int) {
|
|||||||
words, direction = distance*-1, -1
|
words, direction = distance*-1, -1
|
||||||
}
|
}
|
||||||
// atEnd if offset is at or beyond either side of the buffer.
|
// atEnd if offset is at or beyond either side of the buffer.
|
||||||
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
atEnd := func(offset int) bool {
|
atEnd := func(offset int) bool {
|
||||||
idx := e.caret.start.ofs + offset*direction
|
idx := caret.ofs + offset*direction
|
||||||
return idx <= 0 || idx >= e.rr.len()
|
return idx <= 0 || idx >= e.rr.len()
|
||||||
}
|
}
|
||||||
// next returns the appropriate rune and length given the direction and offset (in bytes).
|
// next returns the appropriate rune and length given the direction and offset (in bytes).
|
||||||
next := func(offset int) (r rune, l int) {
|
next := func(offset int) (r rune, l int) {
|
||||||
idx := e.caret.start.ofs + offset*direction
|
idx := caret.ofs + offset*direction
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
idx = 0
|
idx = 0
|
||||||
} else if idx > e.rr.len() {
|
} else if idx > e.rr.len() {
|
||||||
@@ -1055,9 +1070,9 @@ func (e *Editor) deleteWord(distance int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var runes = 1
|
var runes = 1
|
||||||
_, bytes := e.rr.runeAt(e.caret.start.ofs)
|
_, bytes := e.rr.runeAt(caret.ofs)
|
||||||
if direction < 0 {
|
if direction < 0 {
|
||||||
_, bytes = e.rr.runeBefore(e.caret.start.ofs)
|
_, bytes = e.rr.runeBefore(caret.ofs)
|
||||||
}
|
}
|
||||||
for ii := 0; ii < words; ii++ {
|
for ii := 0; ii < words; ii++ {
|
||||||
if r, _ := next(bytes); unicode.IsSpace(r) {
|
if r, _ := next(bytes); unicode.IsSpace(r) {
|
||||||
@@ -1077,18 +1092,19 @@ func (e *Editor) deleteWord(distance int) {
|
|||||||
|
|
||||||
func (e *Editor) scrollToCaret() {
|
func (e *Editor) scrollToCaret() {
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
l := e.lines[e.caret.start.lineCol.Y]
|
caret := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
|
l := e.lines[caret.lineCol.Y]
|
||||||
if e.SingleLine {
|
if e.SingleLine {
|
||||||
var dist int
|
var dist int
|
||||||
if d := e.caret.start.x.Floor() - e.scrollOff.X; d < 0 {
|
if d := caret.x.Floor() - e.scrollOff.X; d < 0 {
|
||||||
dist = d
|
dist = d
|
||||||
} else if d := e.caret.start.x.Ceil() - (e.scrollOff.X + e.viewSize.X); d > 0 {
|
} else if d := caret.x.Ceil() - (e.scrollOff.X + e.viewSize.X); d > 0 {
|
||||||
dist = d
|
dist = d
|
||||||
}
|
}
|
||||||
e.scrollRel(dist, 0)
|
e.scrollRel(dist, 0)
|
||||||
} else {
|
} else {
|
||||||
miny := e.caret.start.y - l.Ascent.Ceil()
|
miny := caret.y - l.Ascent.Ceil()
|
||||||
maxy := e.caret.start.y + l.Descent.Ceil()
|
maxy := caret.y + l.Descent.Ceil()
|
||||||
var dist int
|
var dist int
|
||||||
if d := miny - e.scrollOff.Y; d < 0 {
|
if d := miny - e.scrollOff.Y; d < 0 {
|
||||||
dist = d
|
dist = d
|
||||||
@@ -1108,20 +1124,20 @@ func (e *Editor) NumLines() int {
|
|||||||
// SelectionLen returns the length of the selection, in runes; it is
|
// SelectionLen returns the length of the selection, in runes; it is
|
||||||
// equivalent to utf8.RuneCountInString(e.SelectedText()).
|
// equivalent to utf8.RuneCountInString(e.SelectedText()).
|
||||||
func (e *Editor) SelectionLen() int {
|
func (e *Editor) SelectionLen() int {
|
||||||
return abs(e.caret.start.runes - e.caret.end)
|
return abs(e.caret.start - e.caret.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection returns the start and end of the selection, as rune offsets.
|
// Selection returns the start and end of the selection, as rune offsets.
|
||||||
// start can be > end.
|
// start can be > end.
|
||||||
func (e *Editor) Selection() (start, end int) {
|
func (e *Editor) Selection() (start, end int) {
|
||||||
return e.caret.start.runes, e.caret.end
|
return e.caret.start, e.caret.end
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCaret moves the caret to start, and sets the selection end to end. start
|
// SetCaret moves the caret to start, and sets the selection end to end. start
|
||||||
// and end are in runes, and represent offsets into the editor text.
|
// and end are in runes, and represent offsets into the editor text.
|
||||||
func (e *Editor) SetCaret(start, end int) {
|
func (e *Editor) SetCaret(start, end int) {
|
||||||
e.makeValid()
|
e.makeValid()
|
||||||
e.caret.start = e.closestPosition(combinedPos{runes: start})
|
e.caret.start = e.closestPosition(combinedPos{runes: start}).runes
|
||||||
e.caret.end = e.closestPosition(combinedPos{runes: end}).runes
|
e.caret.end = e.closestPosition(combinedPos{runes: end}).runes
|
||||||
e.caret.scroll = true
|
e.caret.scroll = true
|
||||||
e.scroller.Stop()
|
e.scroller.Stop()
|
||||||
@@ -1129,9 +1145,10 @@ func (e *Editor) SetCaret(start, end int) {
|
|||||||
|
|
||||||
// SelectedText returns the currently selected text (if any) from the editor.
|
// SelectedText returns the currently selected text (if any) from the editor.
|
||||||
func (e *Editor) SelectedText() string {
|
func (e *Editor) SelectedText() string {
|
||||||
|
caretStart := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
caretEnd := e.closestPosition(combinedPos{runes: e.caret.end})
|
caretEnd := e.closestPosition(combinedPos{runes: e.caret.end})
|
||||||
start := min(e.caret.start.ofs, caretEnd.ofs)
|
start := min(caretStart.ofs, caretEnd.ofs)
|
||||||
end := max(e.caret.start.ofs, caretEnd.ofs)
|
end := max(caretStart.ofs, caretEnd.ofs)
|
||||||
buf := make([]byte, end-start)
|
buf := make([]byte, end-start)
|
||||||
e.rr.Seek(int64(start), io.SeekStart)
|
e.rr.Seek(int64(start), io.SeekStart)
|
||||||
_, err := e.rr.Read(buf)
|
_, err := e.rr.Read(buf)
|
||||||
@@ -1152,7 +1169,7 @@ func (e *Editor) updateSelection(selAct selectionAction) {
|
|||||||
// ClearSelection clears the selection, by setting the selection end equal to
|
// ClearSelection clears the selection, by setting the selection end equal to
|
||||||
// the selection start.
|
// the selection start.
|
||||||
func (e *Editor) ClearSelection() {
|
func (e *Editor) ClearSelection() {
|
||||||
e.caret.end = e.caret.start.runes
|
e.caret.end = e.caret.start
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo implements io.WriterTo.
|
// WriteTo implements io.WriterTo.
|
||||||
|
|||||||
+13
-9
@@ -117,8 +117,9 @@ func assertCaret(t *testing.T, e *Editor, line, col, bytes int) {
|
|||||||
if gotLine != line || gotCol != col {
|
if gotLine != line || gotCol != col {
|
||||||
t.Errorf("caret at (%d, %d), expected (%d, %d)", gotLine, gotCol, line, col)
|
t.Errorf("caret at (%d, %d), expected (%d, %d)", gotLine, gotCol, line, col)
|
||||||
}
|
}
|
||||||
if bytes != e.caret.start.ofs {
|
caretBytes := e.closestPosition(combinedPos{runes: e.caret.start}).ofs
|
||||||
t.Errorf("caret at buffer position %d, expected %d", e.caret.start.ofs, bytes)
|
if bytes != caretBytes {
|
||||||
|
t.Errorf("caret at buffer position %d, expected %d", caretBytes, bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ func TestEditorCaretConsistency(t *testing.T) {
|
|||||||
gotCoords := e.CaretCoords()
|
gotCoords := e.CaretCoords()
|
||||||
// Blow away index to re-compute position from scratch.
|
// Blow away index to re-compute position from scratch.
|
||||||
e.invalidate()
|
e.invalidate()
|
||||||
want := e.closestPosition(combinedPos{runes: e.caret.start.runes})
|
want := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
wantCoords := f32.Pt(float32(want.x)/64, float32(want.y))
|
wantCoords := f32.Pt(float32(want.x)/64, float32(want.y))
|
||||||
if want.lineCol.Y != gotLine || want.lineCol.X != gotCol || gotCoords != wantCoords {
|
if want.lineCol.Y != gotLine || want.lineCol.X != gotCol || gotCoords != wantCoords {
|
||||||
return fmt.Errorf("caret (%d,%d) pos %s, want (%d,%d) pos %s",
|
return fmt.Errorf("caret (%d,%d) pos %s, want (%d,%d) pos %s",
|
||||||
@@ -245,8 +246,9 @@ func TestEditorMoveWord(t *testing.T) {
|
|||||||
e := setup(tt.Text)
|
e := setup(tt.Text)
|
||||||
e.MoveCaret(tt.Start, tt.Start)
|
e.MoveCaret(tt.Start, tt.Start)
|
||||||
e.moveWord(tt.Skip, selectionClear)
|
e.moveWord(tt.Skip, selectionClear)
|
||||||
if e.caret.start.ofs != tt.Want {
|
caretBytes := e.closestPosition(combinedPos{runes: e.caret.start}).ofs
|
||||||
t.Fatalf("[%d] moveWord: bad caret position: got %d, want %d", ii, e.caret.start.ofs, tt.Want)
|
if caretBytes != tt.Want {
|
||||||
|
t.Fatalf("[%d] moveWord: bad caret position: got %d, want %d", ii, caretBytes, tt.Want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,8 +440,9 @@ func TestEditorDeleteWord(t *testing.T) {
|
|||||||
e.MoveCaret(tt.Start, tt.Start)
|
e.MoveCaret(tt.Start, tt.Start)
|
||||||
e.MoveCaret(0, tt.Selection)
|
e.MoveCaret(0, tt.Selection)
|
||||||
e.deleteWord(tt.Delete)
|
e.deleteWord(tt.Delete)
|
||||||
if e.caret.start.ofs != tt.Want {
|
caretBytes := e.closestPosition(combinedPos{runes: e.caret.start}).ofs
|
||||||
t.Fatalf("[%d] deleteWord: bad caret position: got %d, want %d", ii, e.caret.start.ofs, tt.Want)
|
if caretBytes != tt.Want {
|
||||||
|
t.Fatalf("[%d] deleteWord: bad caret position: got %d, want %d", ii, caretBytes, tt.Want)
|
||||||
}
|
}
|
||||||
if e.Text() != tt.Result {
|
if e.Text() != tt.Result {
|
||||||
t.Fatalf("[%d] deleteWord: invalid result: got %q, want %q", ii, e.Text(), tt.Result)
|
t.Fatalf("[%d] deleteWord: invalid result: got %q, want %q", ii, e.Text(), tt.Result)
|
||||||
@@ -547,11 +550,12 @@ g123456789g
|
|||||||
gtx.Queue = nil
|
gtx.Queue = nil
|
||||||
e.Layout(gtx, cache, font, fontSize, nil)
|
e.Layout(gtx, cache, font, fontSize, nil)
|
||||||
|
|
||||||
|
caretStart := e.closestPosition(combinedPos{runes: e.caret.start})
|
||||||
caretEnd := e.closestPosition(combinedPos{runes: e.caret.end})
|
caretEnd := e.closestPosition(combinedPos{runes: e.caret.end})
|
||||||
if caretEnd.lineCol != tst.startPos || e.caret.start.lineCol != tst.endPos {
|
if caretEnd.lineCol != tst.startPos || caretStart.lineCol != tst.endPos {
|
||||||
t.Errorf("Test %d pt2: Expected %#v, %#v; got %#v, %#v",
|
t.Errorf("Test %d pt2: Expected %#v, %#v; got %#v, %#v",
|
||||||
n,
|
n,
|
||||||
caretEnd.lineCol, e.caret.start.lineCol,
|
caretEnd.lineCol, caretStart.lineCol,
|
||||||
tst.startPos, tst.endPos)
|
tst.startPos, tst.endPos)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user