mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
fix(tui): cursor after multi-line paste lands in wrong column
wrapLine()'s internal newLine() toggled the firstLine flag BEFORE
checking it, so the very first wrap continuation flushed to the
output WITHOUT the cont indent. Second and later continuations
were fine. Visible as:
0: '▌ this is a very long first line that'
1: 'will wrap around terminal boundaries' <- no indent
2: ' still wrapping further past this point' <- indented
Downstream, locateCursor() in the editor assumed continuation rows
always start with cont and stripped its width when counting runes.
When the first continuation didn't actually have it, the stripping
was a no-op but the leadW was still added, so the reported visual
column for the cursor drifted by cont-width (2 cells) to the right.
Effect for the user: after drag-dropping a multi-line payload (or
pasting any text where the first paragraph wraps), the terminal
cursor rendered mid-text instead of at the end of the pasted
content. Typing still appended at the correct logical position,
so keystrokes landed in the right place in the buffer, it was
purely visual drift.
Fix: in newLine(), always write cont to cur after flushing (and
after setting firstLine = false). That makes the second row, and
every subsequent wrap continuation, carry the indent consistently.
Added three regression tests:
- wrapLine directly: every row >= 1 has cont prefix
- editor multi-line paste: cursor lands at logical end with
correct visual (row, col)
- editor long-paste-with-wrap: wrap continuations all indented
AND cursor still lands at correct column
This commit is contained in:
parent
00b3d9dc76
commit
1be3f85a47
2 changed files with 94 additions and 4 deletions
|
|
@ -754,11 +754,16 @@ func wrapLine(s string, width int, cont string) []string {
|
|||
out = append(out, cur.String())
|
||||
cur.Reset()
|
||||
curW = 0
|
||||
if !firstLine {
|
||||
cur.WriteString(cont)
|
||||
curW = contW
|
||||
}
|
||||
// After flushing, everything we append next is a CONTINUATION
|
||||
// row, which must start with the cont indent so the editor's
|
||||
// visual cursor alignment stays consistent. The old code only
|
||||
// wrote cont when !firstLine BEFORE toggling firstLine, which
|
||||
// meant the very first wrap never got its indent. That caused
|
||||
// the terminal cursor to land in the wrong column after a
|
||||
// multi-line paste.
|
||||
firstLine = false
|
||||
cur.WriteString(cont)
|
||||
curW = contW
|
||||
}
|
||||
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
|
|
|
|||
85
internal/tui/wrap_test.go
Normal file
85
internal/tui/wrap_test.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestWrapLineFirstContinuationHasIndent is a regression test for a
|
||||
// bug where wrapLine()'s internal newLine() toggled the firstLine
|
||||
// flag and THEN checked it, so the very first wrap continuation
|
||||
// flushed without the cont indent. Any subsequent continuation
|
||||
// (second wrap onwards) got the indent. That was visible as a
|
||||
// misaligned second row and caused the editor's cursor to land in
|
||||
// the wrong column after a multi-line paste (locateCursor assumes
|
||||
// continuations carry cont, so when they didn't, the cursor drifted
|
||||
// one-indent-worth to the right).
|
||||
func TestWrapLineFirstContinuationHasIndent(t *testing.T) {
|
||||
s := "prefix this is a long line that will wrap around at forty cells"
|
||||
out := wrapLine(s, 40, " ")
|
||||
|
||||
if len(out) < 2 {
|
||||
t.Fatalf("want at least 2 wrapped rows, got %d: %v", len(out), out)
|
||||
}
|
||||
// Row 0 is the first line (no indent; it's the lead).
|
||||
// Every row from index 1 onward is a continuation and MUST start
|
||||
// with the cont prefix.
|
||||
for i := 1; i < len(out); i++ {
|
||||
if !strings.HasPrefix(out[i], " ") {
|
||||
t.Errorf("row %d missing cont indent: %q", i, out[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEditorCursorAfterMultilinePaste is the downstream test: the
|
||||
// rendered editor cursor must land at the logical end of the paste,
|
||||
// with its visual column equal to leadW + runewidth(last-line).
|
||||
func TestEditorCursorAfterMultilinePaste(t *testing.T) {
|
||||
e := NewEditor("▌ ")
|
||||
e.HandleKey(Key{Kind: KeyPaste, Paste: "aaa\nbbb\nccccc"})
|
||||
|
||||
// Logical end: last line "ccccc", cursor past its 5 runes.
|
||||
if e.CursorR != 2 || e.CursorC != 5 {
|
||||
t.Fatalf("logical cursor: want (2, 5), got (%d, %d)", e.CursorR, e.CursorC)
|
||||
}
|
||||
|
||||
lines, row, col := e.Render(80)
|
||||
if len(lines) != 3 {
|
||||
t.Fatalf("want 3 rendered rows, got %d: %v", len(lines), lines)
|
||||
}
|
||||
// Row 0 "▌ aaa", row 1 " bbb", row 2 " ccccc".
|
||||
// Cursor lives at row 2; column = 2 (cont indent) + 5 = 7.
|
||||
if row != 2 {
|
||||
t.Errorf("visual row: want 2, got %d", row)
|
||||
}
|
||||
if col != 7 {
|
||||
t.Errorf("visual col: want 7, got %d", col)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEditorCursorAfterLongPasteWithWrap verifies the cursor lands
|
||||
// correctly when a pasted line is long enough to wrap at the given
|
||||
// render width. This is the scenario that was broken: before the
|
||||
// fix, the first wrap continuation missed its cont indent, so the
|
||||
// terminal cursor drifted when typed after pasting a wrapped path.
|
||||
func TestEditorCursorAfterLongPasteWithWrap(t *testing.T) {
|
||||
e := NewEditor("▌ ")
|
||||
e.HandleKey(Key{Kind: KeyPaste, Paste: "this is a very long line that should wrap\nshort"})
|
||||
|
||||
lines, row, col := e.Render(30)
|
||||
|
||||
// Every continuation row (anything after row 0) must be
|
||||
// cont-indented so locateCursor's rune-counting stays honest.
|
||||
for i := 1; i < len(lines); i++ {
|
||||
if !strings.HasPrefix(lines[i], " ") {
|
||||
t.Errorf("continuation row %d missing indent: %q", i, lines[i])
|
||||
}
|
||||
}
|
||||
// Cursor should be at the end of "short" on the last rendered row.
|
||||
if row != len(lines)-1 {
|
||||
t.Errorf("visual row: want %d (last), got %d", len(lines)-1, row)
|
||||
}
|
||||
if col != 2+5 { // cont indent + len("short")
|
||||
t.Errorf("visual col: want 7, got %d", col)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue