Fix issue #9834: preserve trailing spaces on soft-wrapped lines during copy

Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/343539f7-deab-4eeb-9829-99bec57c3523

Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-08 17:17:20 +00:00 committed by GitHub
parent 2ee1b85147
commit f6f1cae3b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 26 additions and 5 deletions

View file

@ -229,6 +229,8 @@ Detailed list of changes
- hints kitten: Fix trailing punctuation not being removed from URLs (:pull:`9828`)
- Fix copy/paste dropping spaces at soft-wrap boundaries when ``strip_trailing_spaces`` is set (:iss:`9834`)
0.46.2 [2026-03-21]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -3967,7 +3967,10 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
while (x_limit && !line->cpu_cells[x_limit - 1].temp_flag) x_limit--;
while (x_start < x_limit && !line->cpu_cells[x_start].temp_flag) x_start++;
bool is_only_whitespace_line = false;
if (strip_trailing_whitespace) {
// Don't strip trailing whitespace from soft-wrapped lines as those spaces
// are part of the original text content that continues on the next line
const bool line_is_continued = x_limit == line->xnum && line->cpu_cells[line->xnum-1].next_char_was_wrapped;
if (strip_trailing_whitespace && !line_is_continued) {
index_type new_limit = limit_without_trailing_whitespace(line, x_limit);
if (new_limit != x_limit) {
x_limit = new_limit;
@ -4018,7 +4021,10 @@ ansi_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
while (x_limit && !line->cpu_cells[x_limit - 1].temp_flag) x_limit--;
while (x_start < x_limit && !line->cpu_cells[x_start].temp_flag) x_start++;
bool is_only_whitespace_line = false;
if (strip_trailing_whitespace) {
// Don't strip trailing whitespace from soft-wrapped lines as those spaces
// are part of the original text content that continues on the next line
const bool line_is_continued = x_limit == line->xnum && line->cpu_cells[line->xnum-1].next_char_was_wrapped;
if (strip_trailing_whitespace && !line_is_continued) {
index_type new_limit = limit_without_trailing_whitespace(line, x_limit);
if (new_limit != x_limit) {
x_limit = new_limit;

View file

@ -702,13 +702,15 @@ def ts(*args):
s.start_selection(0, 0)
s.update_selection(1, 3)
self.ae(ts(), ''.join(('ab ', 'cd')))
self.ae(ts(False, True), ''.join(('ab', 'cd')))
# soft-wrapped lines preserve trailing spaces (issue #9834)
self.ae(ts(False, True), 'ab cd')
s.reset()
s.draw('ab cd')
s.start_selection(0, 0)
s.update_selection(3, 4)
self.ae(s.text_for_selection(), ('ab ', ' ', 'cd'))
self.ae(s.text_for_selection(False, True), ('ab', '\n', 'cd'))
# soft-wrapped lines preserve trailing spaces (issue #9834)
self.ae(s.text_for_selection(False, True), ('ab ', ' ', 'cd'))
s.reset()
s.draw('a')
s.select_graphic_rendition(32)
@ -719,7 +721,8 @@ def ts(*args):
s.update_selection(1, 3)
self.ae(s.text_for_selection(), ('abc ', 'xy'))
self.ae(s.text_for_selection(True), ('a\x1b[32mb\x1b[39mc ', 'xy', '\x1b[m'))
self.ae(s.text_for_selection(True, True), ('a\x1b[32mb\x1b[39mc', 'xy', '\x1b[m'))
# soft-wrapped lines preserve trailing spaces in ANSI mode too (issue #9834)
self.ae(s.text_for_selection(True, True), ('a\x1b[32mb\x1b[39mc ', 'xy', '\x1b[m'))
# ]]]]]]]]]]]]]]]]]]]]
s.reset()
s.draw('a'), s.carriage_return(), s.linefeed(), s.linefeed(), s.draw('b')
@ -727,6 +730,16 @@ def ts(*args):
s.update_selection(4, 4)
self.ae(''.join(s.text_for_selection()), 'a\n\nb')
self.ae(''.join(s.text_for_selection(True)), 'a\n\nb')
# Trailing spaces on soft-wrapped lines must be preserved when strip_trailing_whitespace
# is set: a space at the line-wrap boundary is word-separator content, not padding
# (regression test for issue #9834)
s.reset()
s.draw('1234 5') # "1234 " soft-wraps at col 4; space must survive stripping
s.start_selection(0, 0)
s.update_selection(4, 4)
self.ae(s.text_for_selection(), ('1234 ', '5'))
self.ae(s.text_for_selection(False, True), ('1234 ', '5'))
self.ae(s.text_for_selection(True, True), ('1234 ', '5', ''))
def test_soft_hyphen(self):
s = self.create_screen()