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

505 lines
18 KiB
C
Raw Normal View History

#include "putty.h"
#include "terminal.h"
void modalfatalbox(const char *p, ...)
{
va_list ap;
fprintf(stderr, "FATAL ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
const char *const appname = "test_lineedit";
char *platform_default_s(const char *name)
{ return NULL; }
bool platform_default_b(const char *name, bool def)
{ return def; }
int platform_default_i(const char *name, int def)
{ return def; }
FontSpec *platform_default_fontspec(const char *name)
{ return fontspec_new_default(); }
Filename *platform_default_filename(const char *name)
{ return filename_from_str(""); }
const struct BackendVtable *const backends[] = { NULL };
typedef struct Mock {
Terminal *term;
Conf *conf;
struct unicode_data ucsdata[1];
strbuf *context;
bool any_test_failed;
TermWin tw;
} Mock;
static bool mock_setup_draw_ctx(TermWin *win) { return false; }
static void mock_draw_text(TermWin *win, int x, int y, wchar_t *text, int len,
unsigned long attrs, int lattrs, truecolour tc) {}
static void mock_draw_cursor(TermWin *win, int x, int y, wchar_t *text,
int len, unsigned long attrs, int lattrs,
truecolour tc) {}
static void mock_set_raw_mouse_mode(TermWin *win, bool enable) {}
static void mock_set_raw_mouse_mode_pointer(TermWin *win, bool enable) {}
static void mock_palette_set(TermWin *win, unsigned start, unsigned ncolours,
const rgb *colours) {}
static void mock_palette_get_overrides(TermWin *tw, Terminal *term) {}
static const TermWinVtable mock_termwin_vt = {
.setup_draw_ctx = mock_setup_draw_ctx,
.draw_text = mock_draw_text,
.draw_cursor = mock_draw_cursor,
.set_raw_mouse_mode = mock_set_raw_mouse_mode,
.set_raw_mouse_mode_pointer = mock_set_raw_mouse_mode_pointer,
.palette_set = mock_palette_set,
.palette_get_overrides = mock_palette_get_overrides,
};
static Mock *mock_new(void)
{
Mock *mk = snew(Mock);
memset(mk, 0, sizeof(*mk));
mk->conf = conf_new();
do_defaults(NULL, mk->conf);
init_ucs_generic(mk->conf, mk->ucsdata);
mk->ucsdata->line_codepage = CP_ISO8859_1;
mk->context = strbuf_new();
mk->tw.vt = &mock_termwin_vt;
return mk;
}
static void mock_free(Mock *mk)
{
strbuf_free(mk->context);
conf_free(mk->conf);
term_free(mk->term);
sfree(mk);
}
static void reset(Mock *mk)
{
term_pwron(mk->term, true);
term_size(mk->term, 24, 80, 0);
term_set_trust_status(mk->term, false);
strbuf_clear(mk->context);
}
#if 0
static void test_context(Mock *mk, const char *fmt, ...)
{
strbuf_clear(mk->context);
va_list ap;
va_start(ap, fmt);
put_fmtv(mk->context, fmt, ap);
va_end(ap);
}
#endif
static void report_fail(Mock *mk, const char *file, int line,
const char *fmt, ...)
{
printf("%s:%d", file, line);
if (mk->context->len)
printf(" (%s)", mk->context->s);
printf(": ");
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf("\n");
mk->any_test_failed = true;
}
static inline void check_iequal(Mock *mk, const char *file, int line,
long long lhs, long long rhs)
{
if (lhs != rhs)
report_fail(mk, file, line, "%lld != %lld / %#llx != %#llx",
lhs, rhs, lhs, rhs);
}
#define IEQUAL(lhs, rhs) check_iequal(mk, __FILE__, __LINE__, lhs, rhs)
static inline void term_datapl(Terminal *term, ptrlen pl)
{
term_data(term, pl.ptr, pl.len);
}
static struct termchar get_termchar(Terminal *term, int x, int y)
{
termline *tl = term_get_line(term, y);
termchar tc;
if (0 <= x && x < tl->cols)
tc = tl->chars[x];
else
tc = term->erase_char;
term_release_line(tl);
return tc;
}
static unsigned short get_lineattr(Terminal *term, int y)
{
termline *tl = term_get_line(term, y);
unsigned short lattr = tl->lattr;
term_release_line(tl);
return lattr;
}
static void test_hello_world(Mock *mk)
{
/* A trivial test just to kick off this test framework */
mk->ucsdata->line_codepage = CP_ISO8859_1;
reset(mk);
term_datapl(mk->term, PTRLEN_LITERAL("hello, world"));
IEQUAL(mk->term->curs.x, 12);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'h');
IEQUAL(get_termchar(mk->term, 1, 0).chr, CSET_ASCII | 'e');
IEQUAL(get_termchar(mk->term, 2, 0).chr, CSET_ASCII | 'l');
IEQUAL(get_termchar(mk->term, 3, 0).chr, CSET_ASCII | 'l');
IEQUAL(get_termchar(mk->term, 4, 0).chr, CSET_ASCII | 'o');
IEQUAL(get_termchar(mk->term, 5, 0).chr, CSET_ASCII | ',');
IEQUAL(get_termchar(mk->term, 6, 0).chr, CSET_ASCII | ' ');
IEQUAL(get_termchar(mk->term, 7, 0).chr, CSET_ASCII | 'w');
IEQUAL(get_termchar(mk->term, 8, 0).chr, CSET_ASCII | 'o');
IEQUAL(get_termchar(mk->term, 9, 0).chr, CSET_ASCII | 'r');
IEQUAL(get_termchar(mk->term, 10, 0).chr, CSET_ASCII | 'l');
IEQUAL(get_termchar(mk->term, 11, 0).chr, CSET_ASCII | 'd');
}
static void test_wrap(Mock *mk)
{
/* Test behaviour when printing characters wrap to the next line */
mk->ucsdata->line_codepage = CP_UTF8;
/* Print 'abc' without enough space for the c, in wrapping mode */
reset(mk);
mk->term->curs.x = 78;
mk->term->curs.y = 0;
mk->term->wrap = true;
/* 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');
/* And now the 'c' causes a deferred wrap and goes to the next line */
term_datapl(mk->term, PTRLEN_LITERAL("c"));
IEQUAL(mk->term->curs.x, 1);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED);
IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b');
IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c');
/* If we backspace once, the cursor moves back on to the c */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
/* Now backspace again, and the cursor returns to the b */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 79);
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 = true;
/* 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);
/* And the 'c' causes a deferred wrap as before */
term_datapl(mk->term, PTRLEN_LITERAL("c"));
IEQUAL(mk->term->curs.x, 1);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED);
IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00);
IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE);
IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c');
/* If we backspace once, the cursor moves back on to the c */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
/* Now backspace again, and the cursor goes to the RHS of the DW char */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 79);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(mk->term->wrapnext, 0);
/* Now put the DW character in place of bc */
reset(mk);
mk->term->curs.x = 78;
mk->term->curs.y = 0;
mk->term->wrap = true;
/* 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 character wraps, setting LATTR_WRAPPED2 */
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80"));
IEQUAL(mk->term->curs.x, 2);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED | LATTR_WRAPPED2);
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a');
IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | ' ');
IEQUAL(get_termchar(mk->term, 0, 1).chr, 0xAC00);
IEQUAL(get_termchar(mk->term, 1, 1).chr, UCSWIDE);
/* If we backspace once, cursor moves to the RHS of the DW char */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 1);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
/* Backspace again, and cursor moves from RHS to LHS of that char */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
Make backspace take account of LATTR_WRAPPED2. Suppose an application tries to print a double-width character starting in the rightmost column of the screen, so that we apply our emergency fix of wrapping to the next line immediately and printing the character in the first two columns. Suppose they then backspace twice, taking the cursor to the RHS and then the LHS of that character. What should happen if they backspace a third time? Our previous behaviour was to completely ignore the unusual situation, and do the same thing we'd do in any other backspace from column 0: anti-wrap the cursor to the last column of the previous line, leaving it in the empty character cell that was skipped when the DW char couldn't be printed in it. But I think this isn't the best response, because it breaks the invariant that printing N columns' worth of graphic characters and then backspacing N times should leave the cursor on the first of those characters. If I print "a가" (for example) and then backspace three times, I want the cursor on the a, _even_ if weird line wrapping behaviour happened somewhere in that sequence. (Rationale: this helps naïve terminal applications which don't even know what the terminal width is, and aren't tracking their absolute x position. In particular, the simplistic line-based input systems that appear in OS kernels and our own lineedit.c will want to emit a fixed number of backspace-space-backspace sequences to delete characters previously entered on to the line by the user. They still need to check the wcwidth of the characters they're emitting, so that they can BSB twice for a DW character or 0 times for a combining one, but it would be *hugely* more awkward for them to ask the terminal where the cursor is so that they can take account of difficult line wraps!) We already have the ability to _recognise_ this situation: on a line that was wrapped in this unusual way, we set the LATTR_WRAPPED2 line attribute flag, to prevent the empty rightmost column from injecting an unwanted space into copy-pastes from the terminal. Now we also use the same flag to cause the backspace control character to do something interesting. This was the fix that inspired me to start writing test_terminal, because I knew it was touching a delicate area. However, in the course of writing this fix and its tests, I encountered two (!) further bugs, which I'll fix in followup commits!
2023-03-05 09:31:06 +00:00
/* Now backspace again, and the cursor skips the empty column so
* that it can return to the previous logical character, to wit, the a */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
Make backspace take account of LATTR_WRAPPED2. Suppose an application tries to print a double-width character starting in the rightmost column of the screen, so that we apply our emergency fix of wrapping to the next line immediately and printing the character in the first two columns. Suppose they then backspace twice, taking the cursor to the RHS and then the LHS of that character. What should happen if they backspace a third time? Our previous behaviour was to completely ignore the unusual situation, and do the same thing we'd do in any other backspace from column 0: anti-wrap the cursor to the last column of the previous line, leaving it in the empty character cell that was skipped when the DW char couldn't be printed in it. But I think this isn't the best response, because it breaks the invariant that printing N columns' worth of graphic characters and then backspacing N times should leave the cursor on the first of those characters. If I print "a가" (for example) and then backspace three times, I want the cursor on the a, _even_ if weird line wrapping behaviour happened somewhere in that sequence. (Rationale: this helps naïve terminal applications which don't even know what the terminal width is, and aren't tracking their absolute x position. In particular, the simplistic line-based input systems that appear in OS kernels and our own lineedit.c will want to emit a fixed number of backspace-space-backspace sequences to delete characters previously entered on to the line by the user. They still need to check the wcwidth of the characters they're emitting, so that they can BSB twice for a DW character or 0 times for a combining one, but it would be *hugely* more awkward for them to ask the terminal where the cursor is so that they can take account of difficult line wraps!) We already have the ability to _recognise_ this situation: on a line that was wrapped in this unusual way, we set the LATTR_WRAPPED2 line attribute flag, to prevent the empty rightmost column from injecting an unwanted space into copy-pastes from the terminal. Now we also use the same flag to cause the backspace control character to do something interesting. This was the fix that inspired me to start writing test_terminal, because I knew it was touching a delicate area. However, in the course of writing this fix and its tests, I encountered two (!) further bugs, which I'll fix in followup commits!
2023-03-05 09:31:06 +00:00
IEQUAL(mk->term->curs.x, 78);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(mk->term->wrapnext, 0);
/* Print 'ab' up to the rightmost column, and then backspace */
reset(mk);
mk->term->curs.x = 78;
mk->term->curs.y = 0;
mk->term->wrap = true;
/* As before, the 'ab' put us in the rightmost column with wrapnext set */
term_datapl(mk->term, PTRLEN_LITERAL("ab"));
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 | 'b');
/* Backspacing just clears the wrapnext flag, so we're logically
* back on the b again */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 79);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(mk->term->wrapnext, 0);
/* For completeness, the easy case: just print 'a' then backspace */
reset(mk);
mk->term->curs.x = 78;
mk->term->curs.y = 0;
mk->term->wrap = true;
/* 'a' printed in column n-1 takes us to column n */
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_lineattr(mk->term, 0), 0);
IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a');
/* Backspacing moves us back a space on 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 test the special cases that arise when the terminal is only
* one column wide!
*/
reset(mk);
term_size(mk->term, 24, 1, 0);
mk->term->curs.x = 0;
mk->term->curs.y = 0;
mk->term->wrap = true;
/* Printing a single-width character takes us into wrapnext immediately */
term_datapl(mk->term, PTRLEN_LITERAL("a"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(mk->term->wrapnext, 1);
IEQUAL(get_lineattr(mk->term, 0), 0);
IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a');
/* Printing a second one wraps, and takes us _back_ to wrapnext */
term_datapl(mk->term, PTRLEN_LITERAL("b"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 1);
IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED);
IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a');
IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'b');
/* Backspacing once clears the wrapnext flag, putting us on the b */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 1);
IEQUAL(mk->term->wrapnext, 0);
/* Backspacing again returns to the previous line, putting us on the a */
term_datapl(mk->term, PTRLEN_LITERAL("\b"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(mk->term->wrapnext, 0);
/* And now try with a double-width character */
reset(mk);
term_size(mk->term, 24, 1, 0);
mk->term->curs.x = 0;
mk->term->curs.y = 0;
mk->term->wrap = true;
/* DW character won't fit at all, so it transforms into U+FFFD
* REPLACEMENT CHARACTER and then behaves like a SW char */
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80"));
IEQUAL(mk->term->curs.x, 0);
IEQUAL(mk->term->curs.y, 0);
IEQUAL(mk->term->wrapnext, 1);
IEQUAL(get_lineattr(mk->term, 0), 0);
IEQUAL(get_termchar(mk->term, 0, 0).chr, 0xFFFD);
}
static void test_nonwrap(Mock *mk)
{
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
/* Test behaviour when printing characters hit end of line without wrap.
* The wrapnext flag is never set in this mode. */
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');
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
/* The 'b' prints, leaving the cursor where it is */
term_datapl(mk->term, PTRLEN_LITERAL("b"));
IEQUAL(mk->term->curs.x, 79);
IEQUAL(mk->term->curs.y, 0);
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
IEQUAL(mk->term->wrapnext, 0);
IEQUAL(get_lineattr(mk->term, 0), 0);
IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b');
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
/* The 'c' overwrites the b */
term_datapl(mk->term, PTRLEN_LITERAL("c"));
IEQUAL(mk->term->curs.x, 79);
IEQUAL(mk->term->curs.y, 0);
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
IEQUAL(mk->term->wrapnext, 0);
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');
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
/* Since wrapnext was never set, backspacing returns us 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;
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
/* The DW character occupies the rightmost two columns */
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80"));
IEQUAL(mk->term->curs.x, 79);
IEQUAL(mk->term->curs.y, 0);
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
IEQUAL(mk->term->wrapnext, 0);
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);
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
IEQUAL(mk->term->wrapnext, 0);
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);
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
IEQUAL(mk->term->wrapnext, 0);
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;
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
/* First DW character occupies the rightmost columns */
term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80"));
IEQUAL(mk->term->curs.x, 79);
IEQUAL(mk->term->curs.y, 0);
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
IEQUAL(mk->term->wrapnext, 0);
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);
Don't set term->wrapnext when not in auto-wrapping mode. A user sent a transcript from a curses-based tool 'ncmpc', which carefully disables terminal autowrap when printing a character in the bottom right corner of the display, and then turns it back on again. After that, it expects that sending the backspace character really moves the cursor back a space, instead of clearing the wrapnext flag. But in PuTTY, we set the wrapnext flag even if we're not in wrapping mode - it just doesn't _do_ anything when the next character is sent. But it remains set, and still affects backspace. So the display is corrupted by this change of expectation. (Specifically, ncmpc is printing a time display [m:ss] in the very bottom right, so it disables wrap in order to print the final ']'. Then the next thing it needs to do is to update the low-order digit of the seconds field, so it sends \b as the simplest way to get to that character. The effect on the display is that the updated seconds digit appears where the ] was, instead of overwriting the old seconds digit.) This is a tradeoff in desirable behaviours. The point of having a backspace operation cancel the wrapnext flag and not actually move the cursor is to preserve the invariant that sending 'x', backspace, 'y' causes the y to overprint the x, even if that happens near the end of the terminal's line length. In non-wrapping mode that invariant was bound to break _eventually_, but with this change, it breaks one character earlier than before. However, I think that's less bad than breaking the expectations of curses-based full-screen applications, especially since the _main_ need for that invariant arises from naïve applications that don't want to have to think about the terminal width at all - and those applications generally run in _wrapping_ mode, where it's possible to continue the invariant across multiple lines in any case.
2024-08-10 09:38:02 +00:00
IEQUAL(mk->term->wrapnext, 0);
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();
mk->term = term_init(mk->conf, mk->ucsdata, &mk->tw);
test_hello_world(mk);
test_wrap(mk);
test_nonwrap(mk);
bool failed = mk->any_test_failed;
mock_free(mk);
if (failed) {
printf("Test suite FAILED!\n");
return 1;
} else {
printf("Test suite passed\n");
return 0;
}
}