Repository navigation
fix(textarea): preserve newline when deleting the last character - #1072
kimjooyoon wants to merge 1 commit into
Conversation
Check for a cursor already at line end before deleting a rune, so one Delete press cannot also merge the next line. Add eight regression and boundary cases. Prepared and locally verified with Codex assistance. Fixes charmbracelet#1071
by-ae
left a comment
There was a problem hiding this comment.
I checked this against current main. With main's textarea.go, the new test fails in exactly three cases (last_character, only_character, wide_character). With this branch it passes, along with the rest of go test ./textarea/. The reorder is the right fix. The old code deleted the rune first, and then m.col >= len(...) was true for the now-shorter line, so the same keypress also joined the next line.
Two small follow-ups:
-
Overlap with #1067. That PR rewrites this same block for graphemes, and it keeps the old order: delete first, then
if m.col >= len(m.value[m.row]) { m.mergeLineBelow(...) }(#1067 diff, textarea.go around L1327). So whichever lands second needs to keep the "check line end first" order, or the bug comes back for grapheme deletes. Yourwide_charactercase covers a single wide rune, but a multi-rune grapheme case (for example"ae\u0301\nx"at col 1, expecting"a\nx"once #1067 is in) would catch that regression directly. -
The guard is redundant now. Once the line-end case breaks out early,
m.col < len(m.value[m.row])is always true, and so islen(...) > 0. The block can be just:if m.col >= len(m.value[m.row]) { m.mergeLineBelow(m.row) break } m.value[m.row] = slices.Delete(m.value[m.row], m.col, m.col+1)
The sibling cases (
DeleteAfterCursor,DeleteCharacterBackward) also clampm.colbefore the boundary check (textarea.go#L1281). Doing the same here would keep the three paths consistent, though>=already handles an out-of-range column.
|
Thanks @by-ae for checking both revisions. Agreed: whichever PR lands second must keep the line-end check before deletion. The combining-mark regression belongs with the #1067 integration; this patch keeps its current rune scope. I also agree the inner guard is redundant after the early break. I'll track that cleanup and review the sibling-path clamping separately. |
Fixes #1071.
Deleting the last character of a non-final textarea line also deleted the following newline: Delete on
binab\ncdreturnedacd. Check whether the cursor is already at line end before removing the rune, so a keypress either deletes a character or joins lines. Delete at an existing line end, empty lines and final-line behavior stay unchanged.The new eight-case regression fails in three cases on main
c3bde0d1and passes after the three-line reorder. It covers ASCII, a one-character line, a wide character and five boundary controls. The exact public API reproducer in the issue was also executed before and after the change.Local validation (macOS arm64, Go 1.26.0):
go test ./...go test -race ./textareago build ./...go vet ./...golangci-lint v2.13.1 run --timeout 3m— 0 issuesgofmtandgit diff --checkThe eight-case regression also passes on Go 1.27.3. Upstream cross-platform CI remains separate from these local checks.
This touches the same forward-delete block as #1067 but does not change rune/grapheme boundaries or depend on that proposal; the ordering correction applies independently.
AI disclosure: a Codex agent found and reproduced this bug, prepared the patch/tests and ran the listed checks. This description is AI-generated. No human-only review or upstream acceptance is implied.
Independent reproduction: Linux Go 1.27.3 evidence replays the exact original and submitted revisions with eight focused cases: three original failures, eight patched/race passes. A separate Gooo edit also matches all eight. The downloaded original events and all 41 source/log hashes were checked against the exact sources. The reproduction guide lists inputs, cursor units and commands. This focused run does not replace upstream cross-platform CI or approval.
Public evidence: the source-bound snapshot preserves the raw
forward-delete/logs and summaries from Linux CI 39691446155 at the exact submitted revision above. The published copy can be downloaded without GitHub login and checked offline with Python 3.9+, without a Gooo compiler. It retains intentional original failures and scoped results; upstream approval and acceptance remain separate.