Skip to content

Commit 51c9dbf

Browse files
committed
Backspace when cursor is off right edge of screen should move back two cells
This is extremely counter-intuitive for a terminal like kitty that allows the cursor to be off screen when drawing a char in the last cell. However, most other terminals dont allow the cursor to be off the right edge so it makes sense for them. There is no terminfo property controlling this behavior as far as I know. The execrable ncurses actually relies on this behavior of backspace to position the cursor in some cases, the good lord alone knows why. So in the interests of compatibility lets special case backsapce when cursor is off the right edge. A simple test script to see the behavior: ```py import subprocess cols = int(subprocess.check_output('tput cols'.split()).decode().strip()) print(end='\x1b[?7l') # turn off line wrap print('a' * cols, end='\x08bc') # print a upto last cell then backspace and bc input() print(end='\x1b[?7h') # turn on line wrap print() ``` This should result in a line starting with aaaa and ending with bc. It does so in most terminals, exceptions being foot and kitty before this patch. Note that this also means that backspacing when cursor is off the right edge of the terminal is broken in all terminals with this weird behavior. See the docs in screen_backspace() for details. Sigh. Fixes #8841
1 parent 55a2f2c commit 51c9dbf

File tree

3 files changed

+23
-5
lines changed

3 files changed

+23
-5
lines changed

docs/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ Detailed list of changes
118118
- macOS: Fix hiding quick access terminal window not restoring focus to
119119
previously active application (:disc:`8840`)
120120

121+
- Special case backspacing when cursor is off the right edge of the screen to
122+
backspace two cells. (:iss:`8841`)
123+
121124
0.42.2 [2025-07-16]
122125
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123126

kitty/screen.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,8 +1962,21 @@ screen_set_tab_stop(Screen *self) {
19621962
void
19631963
screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) {
19641964
if (count == 0) count = 1;
1965-
if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0;
1966-
else self->cursor->x += move_direction * count;
1965+
if (move_direction < 0) {
1966+
// we allow the cursor to go off the right edge of the screen when
1967+
// drawing a char in the rightmost cell. This is different form most
1968+
// terminals. Because of the behavior of most terminals, the execrable
1969+
// ncurses assumes that drawing a char on the right most cell and then
1970+
// doing a backspace actually moves the cursor back two spaces. Sigh.
1971+
// Note that doing this means that when inputting in cooked mode, for
1972+
// example, using `read` in a shell and typing to the right edge of the
1973+
// screen and doing backspace, the rightmost cell retains text and the
1974+
// text from the right but one cell is deleted, which is wrong since
1975+
// the kernel buffer only deletes the rightmost cell.
1976+
if (self->cursor->x >= self->columns) self->cursor->x = self->columns-1;
1977+
if (count > self->cursor->x) count = self->cursor->x;
1978+
}
1979+
self->cursor->x += move_direction * count;
19671980
screen_ensure_bounds(self, false, cursor_within_margins(self));
19681981
}
19691982

kitty_tests/screen.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def init():
162162
s.reset(), s.reset_dirty()
163163
s.draw('abcde')
164164
s.cursor.bold = True
165-
s.cursor_back(4)
165+
s.cursor_back(3) # cursor is off the edge of screen but in this situation a single back moves it back two cells
166166
s.reset_dirty()
167167
self.ae(s.cursor.x, 1)
168168

@@ -561,11 +561,13 @@ def nl():
561561
# Simple wrapping
562562
s.cursor_position(region, s.columns), s.draw(chr(ord('A') + i - 1).lower() + ch)
563563
# Backspace at right margin
564-
s.cursor_position(region + 1, s.columns), s.draw(ch), s.backspace(), s.draw(ch.lower())
564+
s.cursor_position(region + 1, s.columns), s.draw(ch), s.backspace()
565+
s.cursor.x += 1
566+
s.draw(ch.lower())
565567
nl()
566568
elif which == 2:
567569
# Tab to right margin
568-
s.cursor_position(region + 1, s.columns), s.draw(ch), s.backspace(), s.backspace(), s.tab(), s.tab(), s.draw(ch.lower())
570+
s.cursor_position(region + 1, s.columns), s.draw(ch), s.backspace(), s.tab(), s.tab(), s.draw(ch.lower())
569571
s.cursor_position(region + 1, 2), s.backspace(), s.draw(ch), nl()
570572
else:
571573
s.cursor_position(region + 1, 1), nl()

0 commit comments

Comments
 (0)