1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Fix double display glitch in erase_lots().

If the cursor is on the rightmost column of the terminal and
term->wrapnext is set, and the user asks to erase from the current
position to the end of (at least) the line, what should happen?

PuTTY's previous behaviour was to ignore term->wrapnext, and do the
same thing we would have done without it: erase from the current
physical cursor position to EOL inclusive, i.e. blank the character
cell we just printed.

But this is unfortunate if a program writes an interleaving of
printing characters and ESC[K, which I recently found out is what gcc
does in its colour-highlighted error messages: if the last printed
char just before an ESC[K pushes the cursor into the deferred-wrap
state, then the ESC[K blanks that character, and then we wrap to the
next line. So one character of the error message ends up missing.

xfce4-terminal and gnome-terminal take the approach in this situation
of regarding the cursor position as _right_ at the end of the line, so
no character cells get cleared at all, and the error message displays
as intended. I think that's more sensible, so I've switched to doing
the same thing.

(xterm has different behaviour again: it blanks the character cell and
also clears its analogue of the wrapnext flag. So in _their_ handling
of this sequence of output, one character of the error message is
still missing, but it looks as if it's been _omitted_ rather than
replaced by a space.)

Secondly, in the course of fixing that, I looked at the check_boundary
call in erase_lots, which is supposed to ensure that if a wide CJK
character straddles the boundary between what's being erased and what
isn't, then both halves of the character are deleted. I had to modify
that anyway because I was moving that very boundary, and in doing so,
I noticed that even according to the previous behaviour, it had an
off-by-one error. In the case where you send ESC[1K (meaning erase up
to and including the cursor position), the call to check_boundary was
performed on the _left_ edge of the cursor's character cell, when it
should have been the right edge. So you could end up with an
erase_char in the left half (i.e. a space) and still have the magic
value UCSWIDE in the right half, causing the terminal to think you had
a double-width U+0020 on the screen, which isn't supposed to be able
to happen.
This commit is contained in:
Simon Tatham 2019-08-08 18:05:16 +01:00
parent efcf164abe
commit 6f4083e682

View File

@ -2439,15 +2439,48 @@ static void erase_lots(Terminal *term,
end.x = 0;
erase_lattr = true;
}
/* This is the endpoint of the clearing operation that is not
* either the start or end of the line / screen. */
pos boundary = term->curs;
if (!from_begin) {
start = term->curs;
/*
* If we're erasing from the current char to the end of
* line/screen, then we take account of wrapnext, so as to
* maintain the invariant that writing a printing character
* followed by ESC[K should not overwrite the character you
* _just wrote_. That is, when wrapnext says the cursor is
* 'logically' at the very rightmost edge of the screen
* instead of just before the last printing char, ESC[K should
* do nothing at all, and ESC[J should clear the next line but
* leave this one unchanged.
*
* This adjusted position will also be the position we use for
* check_boundary (i.e. the thing we ensure isn't in the
* middle of a double-width printing char).
*/
if (term->wrapnext)
incpos(boundary);
start = boundary;
}
if (!to_end) {
end = term->curs;
incpos(end);
/*
* If we're erasing from the start of (at least) the line _to_
* the current position, then that is taken to mean 'inclusive
* of the cell under the cursor', which means we don't
* consider wrapnext at all: whether it's set or not, we still
* clear the cell under the cursor.
*
* Again, that incremented boundary position is where we
* should be careful of a straddling wide character.
*/
incpos(boundary);
end = boundary;
}
if (!from_begin || !to_end)
check_boundary(term, term->curs.x, term->curs.y);
check_boundary(term, boundary.x, boundary.y);
check_selection(term, start, end);
/* Clear screen also forces a full window redraw, just in case. */