mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
widget: redefine >= and ++ on combinedPos
This commit redefines incrementing a combinedPos to either move a single rune forward, *or* transition from EOL->BOL, *or* both. This allows traversal of lines without a trailing newline character to reach the position after the final glyph of content. Additionally, this commit updates positionGreaterOrEqual to explicitly handle hard newlines via special-case logic, allowing lines without a hard newline to avoid the newline-based short-circuit logic that would prevent them from iteratively reaching the combinedPos following the final glyph on the line. Fixes: https://todo.sr.ht/~eliasnaur/gio/400 Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
+62
-37
@@ -1000,13 +1000,17 @@ func (e *Editor) indexPosition(pos combinedPos) combinedPos {
|
||||
}
|
||||
|
||||
// positionGreaterOrEqual reports whether p1 >= p2 according to the non-zero fields
|
||||
// of p2. All fields of p1 must be a consistent and valid. The clusterIndex field
|
||||
// is never considered, as it is a line-local property.
|
||||
// of p2. All fields of p1 must be consistent and valid.
|
||||
func positionGreaterOrEqual(lines []text.Line, p1, p2 combinedPos) bool {
|
||||
l := lines[p1.lineCol.Y]
|
||||
endCol := l.Layout.Runes.Count - 1
|
||||
if lastLine := p1.lineCol.Y == len(lines)-1; lastLine {
|
||||
endCol++
|
||||
// Check whether the final glyph cluster has no glyphs, indicating a newline
|
||||
// rune that forced the existence of a line break.
|
||||
hardNewLine := len(l.Layout.Clusters) > 0 && l.Layout.Clusters[len(l.Layout.Clusters)-1].Glyphs.Count == 0
|
||||
endCol := l.Layout.Runes.Count
|
||||
if hardNewLine {
|
||||
// If there was a hard newline, prevent the cursor for passing it on
|
||||
// this line.
|
||||
endCol--
|
||||
}
|
||||
eol := p1.lineCol.X == endCol
|
||||
switch {
|
||||
@@ -1031,17 +1035,24 @@ func positionGreaterOrEqual(lines []text.Line, p1, p2 combinedPos) bool {
|
||||
if eol {
|
||||
return true
|
||||
}
|
||||
// If clusterIndex is equal, they could be positions of different
|
||||
// runes within the same cluster, so we fall back to the positional
|
||||
// test below.
|
||||
if p2.clusterIndex != 0 && p1.clusterIndex != p2.clusterIndex {
|
||||
return p1.clusterIndex > p2.clusterIndex
|
||||
}
|
||||
|
||||
// Find the cluster containing the rune position described by p1
|
||||
// in order to determine the width of a rune within it.
|
||||
clusterIdx := clusterIndexFor(l, p1.lineCol.X, p1.clusterIndex)
|
||||
flip := l.Layout.Direction.Progression() == system.TowardOrigin
|
||||
adv := l.Layout.Clusters[clusterIdx].RuneWidth()
|
||||
if p1.clusterIndex == len(l.Layout.Clusters) {
|
||||
return (!flip && p1.x >= p2.x) || (flip && p1.x <= p2.x)
|
||||
} else {
|
||||
adv := l.Layout.Clusters[p1.clusterIndex].RuneWidth()
|
||||
|
||||
left := p1.x + adv - p2.x
|
||||
right := p2.x - p1.x
|
||||
left := p1.x + adv - p2.x
|
||||
right := p2.x - p1.x
|
||||
|
||||
return (!flip && left >= right) || (flip && left <= right)
|
||||
return (!flip && left >= right) || (flip && left <= right)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1114,39 +1125,53 @@ func seekPosition(lines []text.Line, alignment text.Alignment, width int, start,
|
||||
}
|
||||
}
|
||||
|
||||
// incrementPosition updates pos to be one rune further into the text.
|
||||
// incrementLinePosition transitions pos from the end of a line to the beginning of the next one.
|
||||
// If pos is not at the end of a line, it will have no effect. It returns the (possibly modified)
|
||||
// position, whether it handled the transition from the end of a line, and whether the position
|
||||
// is at the end of the text data.
|
||||
func incrementLinePosition(lines []text.Line, alignment text.Alignment, width int, pos combinedPos) (_ combinedPos, eol, eof bool) {
|
||||
l := lines[pos.lineCol.Y]
|
||||
if pos.lineCol.X >= l.Layout.Runes.Count {
|
||||
if pos.lineCol.Y == len(lines)-1 {
|
||||
// End of file.
|
||||
return pos, false, true
|
||||
}
|
||||
// Move to next line.
|
||||
prevDesc := l.Descent
|
||||
pos.lineCol.Y++
|
||||
pos.lineCol.X = 0
|
||||
pos.clusterIndex = 0
|
||||
l = lines[pos.lineCol.Y]
|
||||
// Use firstPos to get the correct x coordinate of the beginning of the line.
|
||||
alignedPos := firstPos(l, alignment, width)
|
||||
pos.x = alignedPos.x
|
||||
pos.y += (prevDesc + l.Ascent).Ceil()
|
||||
return pos, true, false
|
||||
}
|
||||
return pos, false, false
|
||||
}
|
||||
|
||||
// incrementPosition updates pos to be one position further into the text. This will either
|
||||
// move pos one rune further into the text, transition pos from the end of one line to the
|
||||
// beginning of the next, or both.
|
||||
// All fields of pos must be valid before calling incrementPosition. eof will be true when
|
||||
// pos represents the final text position in the lines.
|
||||
func incrementPosition(lines []text.Line, alignment text.Alignment, width int, pos combinedPos) (_ combinedPos, eof bool) {
|
||||
var eol bool
|
||||
pos, eol, eof = incrementLinePosition(lines, alignment, width, pos)
|
||||
if eof || eol {
|
||||
return pos, eof
|
||||
}
|
||||
l := lines[pos.lineCol.Y]
|
||||
handleLineTransition := func() bool {
|
||||
if pos.lineCol.X >= l.Layout.Runes.Count {
|
||||
if pos.lineCol.Y == len(lines)-1 {
|
||||
// End of file.
|
||||
return true
|
||||
}
|
||||
// Move to next line.
|
||||
prevDesc := l.Descent
|
||||
pos.lineCol.Y++
|
||||
pos.lineCol.X = 0
|
||||
pos.clusterIndex = 0
|
||||
l = lines[pos.lineCol.Y]
|
||||
// Use firstPos to get the correct x coordinate of the beginning of the line.
|
||||
alignedPos := firstPos(l, alignment, width)
|
||||
pos.x = alignedPos.x
|
||||
pos.y += (prevDesc + l.Ascent).Ceil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
if handleLineTransition() {
|
||||
return pos, true
|
||||
}
|
||||
isHardNewLine := l.Layout.Clusters[pos.clusterIndex].Glyphs.Count == 0
|
||||
pos.x += l.Layout.Clusters[pos.clusterIndex].RuneWidth()
|
||||
pos.runes++
|
||||
pos.lineCol.X++
|
||||
pos.clusterIndex = clusterIndexFor(l, pos.lineCol.X, pos.clusterIndex)
|
||||
|
||||
return pos, handleLineTransition()
|
||||
if isHardNewLine {
|
||||
pos, _, eof = incrementLinePosition(lines, alignment, width, pos)
|
||||
}
|
||||
return pos, false
|
||||
}
|
||||
|
||||
// indexRune returns the latest rune index and byte offset no later than r.
|
||||
|
||||
+3
-3
@@ -231,6 +231,9 @@ func TestIncrementPosition(t *testing.T) {
|
||||
if input.clusterIndex > output.clusterIndex {
|
||||
t.Errorf("iteration %d advanced clusterIndex incorrectly: input %d output %d", i, input.clusterIndex, output.clusterIndex)
|
||||
}
|
||||
if output.runes != input.runes+1 {
|
||||
t.Errorf("iteration %d advanced runes incorrectly: input %d output %d", i, input.runes, output.runes)
|
||||
}
|
||||
} else {
|
||||
if input.y >= output.y {
|
||||
t.Errorf("iteration %d advanced the wrong way on y axis: input %v(%d) output %v(%d)", i, input.y, input.y, output.y, output.y)
|
||||
@@ -252,9 +255,6 @@ func TestIncrementPosition(t *testing.T) {
|
||||
t.Errorf("iteration %d should have zeroed lineCol.X, got: %d", i, output.lineCol.X)
|
||||
}
|
||||
}
|
||||
if output.runes != input.runes+1 {
|
||||
t.Errorf("iteration %d advanced runes incorrectly: input %d output %d", i, input.runes, output.runes)
|
||||
}
|
||||
input = output
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user