mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
Fix printing double-width char in rightmost column without wrap.
Another bug turned up by writing tests. The code that spots that the character won't fit, and wraps it to the next line setting LATTR_WRAPPED2, was not checking that wrap mode was _enabled_ before doing that. So if you printed a DW character in the rightmost column while the terminal was in non-auto-wrap mode, you'd get an unwanted wrap. Other terminals disagree on what to do here. xterm leaves the cursor in the same place and doesn't print any character at all. gnome-terminal, on the other hand, backspaces by a character so that it _can_ print the requested DW character, in the rightmost _two_ columns. I think I don't much like either of those, so instead I'm using the same fallback we use for displaying a DW character when the whole terminal is only one column wide: if there is physically no room to print the requested character, turn it into U+FFFD REPLACEMENT CHARACTER.
This commit is contained in:
parent
069f7c8b21
commit
ed5bf9b3b8
@ -3364,34 +3364,43 @@ static void term_display_graphic_char(Terminal *term, unsigned long c)
|
||||
linecols -= TRUST_SIGIL_WIDTH;
|
||||
|
||||
/*
|
||||
* Preliminary check: if the terminal is only one character cell
|
||||
* wide, then we cannot display any double-width character at all.
|
||||
* Substitute single-width REPLACEMENT CHARACTER instead.
|
||||
* Before we switch on the character width, do a preliminary check for
|
||||
* cases where we might have no room at all to display a double-width
|
||||
* character. Our fallback is to substitute REPLACEMENT CHARACTER,
|
||||
* which is single-width, and it's easiest to do that _before_ having
|
||||
* to 'goto' from one switch case to another.
|
||||
*/
|
||||
if (width == 2 && linecols < 2) {
|
||||
width = 1;
|
||||
c = 0xFFFD;
|
||||
if (width == 2 && term->curs.x >= linecols-1) {
|
||||
/*
|
||||
* If we're in wrapping mode and the terminal is at least 2 cells
|
||||
* wide, it's OK, we have a fallback. But otherwise, substitute.
|
||||
*/
|
||||
if (linecols < 2 || !term->wrap) {
|
||||
width = 1;
|
||||
c = 0xFFFD;
|
||||
}
|
||||
}
|
||||
|
||||
switch (width) {
|
||||
case 2:
|
||||
/*
|
||||
* If we're about to display a double-width character starting
|
||||
* in the rightmost column, then we do something special
|
||||
* instead. We must print a space in the last column of the
|
||||
* screen, then wrap; and we also set LATTR_WRAPPED2 which
|
||||
* instructs subsequent cut-and-pasting not only to splice
|
||||
* this line to the one after it, but to ignore the space in
|
||||
* the last character position as well. (Because what was
|
||||
* actually output to the terminal was presumably just a
|
||||
* sequence of CJK characters, and we don't want a space to be
|
||||
* pasted in the middle of those just because they had the
|
||||
* misfortune to start in the wrong parity column. xterm
|
||||
* concurs.)
|
||||
* If we're about to display a double-width character starting in
|
||||
* the rightmost column (and we're in wrapping mode - the other
|
||||
* case was disposed of above), then we do something special
|
||||
* instead. We must print a space in the last column of the screen,
|
||||
* then wrap; and we also set LATTR_WRAPPED2 which instructs
|
||||
* subsequent cut-and-pasting not only to splice this line to the
|
||||
* one after it, but to ignore the space in the last character
|
||||
* position as well. (Because what was actually output to the
|
||||
* terminal was presumably just a sequence of CJK characters, and
|
||||
* we don't want a space to be pasted in the middle of those just
|
||||
* because they had the misfortune to start in the wrong parity
|
||||
* column. xterm concurs.)
|
||||
*/
|
||||
check_boundary(term, term->curs.x, term->curs.y);
|
||||
check_boundary(term, term->curs.x+2, term->curs.y);
|
||||
if (term->curs.x >= linecols-1) {
|
||||
assert(term->wrap); /* we handled the non-wrapping case above */
|
||||
copy_termchar(cline, term->curs.x,
|
||||
&term->erase_char);
|
||||
cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2;
|
||||
|
@ -379,6 +379,110 @@ static void test_wrap(Mock *mk)
|
||||
IEQUAL(get_termchar(mk->term, 0, 0).chr, 0xFFFD);
|
||||
}
|
||||
|
||||
static void test_nonwrap(Mock *mk)
|
||||
{
|
||||
/* Test behaviour when printing characters hit end of line without wrap */
|
||||
mk->ucsdata->line_codepage = CP_UTF8;
|
||||
|
||||
/* Print 'abc' without enough space for the c */
|
||||
reset(mk);
|
||||
mk->term->curs.x = 78;
|
||||
mk->term->curs.y = 0;
|
||||
mk->term->wrap = false;
|
||||
/* The 'a' prints without anything unusual happening */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("a"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 0);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a');
|
||||
/* The 'b' prints, leaving the cursor where it is with wrapnext set */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("b"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 1);
|
||||
IEQUAL(get_lineattr(mk->term, 0), 0);
|
||||
IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b');
|
||||
/* The 'c' overwrites the b, leaving wrapnext still set */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("c"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 1);
|
||||
IEQUAL(get_lineattr(mk->term, 0), 0);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a');
|
||||
IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c');
|
||||
/* So backspacing clears wrapnext, leaving us on the c */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 0);
|
||||
/* And backspacing again returns the cursor to the a */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
|
||||
IEQUAL(mk->term->curs.x, 78);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 0);
|
||||
|
||||
/* Now try it with a double-width character in place of ab */
|
||||
mk->term->curs.x = 78;
|
||||
mk->term->curs.y = 0;
|
||||
mk->term->wrap = false;
|
||||
/* The DW character goes directly to the wrapnext state */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 1);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00);
|
||||
IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE);
|
||||
/* The 'c' must overprint the RHS of the DW char, clearing the LHS */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("c"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 1);
|
||||
IEQUAL(get_lineattr(mk->term, 0), 0);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' ');
|
||||
IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c');
|
||||
|
||||
/* Now put the DW char in place of the bc */
|
||||
reset(mk);
|
||||
mk->term->curs.x = 78;
|
||||
mk->term->curs.y = 0;
|
||||
mk->term->wrap = false;
|
||||
/* The 'a' prints as before */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("a"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 0);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a');
|
||||
/* The DW char won't fit, so turns into U+FFFD REPLACEMENT CHARACTER */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 1);
|
||||
IEQUAL(get_lineattr(mk->term, 0), 0);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a');
|
||||
IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD);
|
||||
|
||||
/* Just for completeness, try both of those together */
|
||||
reset(mk);
|
||||
mk->term->curs.x = 78;
|
||||
mk->term->curs.y = 0;
|
||||
mk->term->wrap = false;
|
||||
/* First DW character goes directly to the wrapnext state */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 1);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00);
|
||||
IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE);
|
||||
/* Second DW char becomes U+FFFD, overwriting RHS of the first one */
|
||||
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x81"));
|
||||
IEQUAL(mk->term->curs.x, 79);
|
||||
IEQUAL(mk->term->curs.y, 0);
|
||||
IEQUAL(mk->term->wrapnext, 1);
|
||||
IEQUAL(get_lineattr(mk->term, 0), 0);
|
||||
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' ');
|
||||
IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Mock *mk = mock_new();
|
||||
@ -386,6 +490,7 @@ int main(void)
|
||||
|
||||
test_hello_world(mk);
|
||||
test_wrap(mk);
|
||||
test_nonwrap(mk);
|
||||
|
||||
mock_free(mk);
|
||||
return 0;
|
||||
|
Loading…
Reference in New Issue
Block a user