From 3cb1c9c895d69e1d4bd9aa639aad21b9ebfed300 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 16 Jul 2024 14:25:19 +0000 Subject: [PATCH 1/4] Add a 'premove' state-layer callback to inform the higher layer before scroll-based moves happen --- include/vterm.h | 4 ++++ src/state.c | 33 +++++++++++++++++++++++++++++++++ src/vterm_internal.h | 1 + t/12state_scroll.test | 6 +++++- t/13state_edit.test | 6 +++++- t/harness.c | 17 +++++++++++++++++ t/run-test.pl | 2 +- 7 files changed, 66 insertions(+), 3 deletions(-) diff --git a/include/vterm.h b/include/vterm.h index 44e15023..3ad813e1 100644 --- a/include/vterm.h +++ b/include/vterm.h @@ -437,6 +437,8 @@ typedef struct { int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); int (*sb_clear)(void *user); + // ABI-compat only enabled if vterm_state_callbacks_has_premove() is invoked + int (*premove)(VTermRect dest, void *user); } VTermStateCallbacks; typedef struct { @@ -459,6 +461,8 @@ VTermState *vterm_obtain_state(VTerm *vt); void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); void *vterm_state_get_cbdata(VTermState *state); +void vterm_state_callbacks_has_premove(VTermState *state); + void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user); void *vterm_state_get_unrecognised_fbdata(VTermState *state); diff --git a/src/state.c b/src/state.c index ce8e0342..54618d5b 100644 --- a/src/state.c +++ b/src/state.c @@ -73,6 +73,7 @@ static VTermState *vterm_state_new(VTerm *vt) state->callbacks = NULL; state->cbdata = NULL; + state->callbacks_has_premove = false; state->selection.callbacks = NULL; state->selection.user = NULL; @@ -126,6 +127,33 @@ static void scroll(VTermState *state, VTermRect rect, int downward, int rightwar else if(rightward < -cols) rightward = -cols; + if(state->callbacks_has_premove && state->callbacks && state->callbacks->premove) { + // TODO: technically this logic is wrong if both downward != 0 and rightward != 0 + + /* Work out what subsection of the destination area is about to be destroyed */ + if(downward > 0) + /* about to destroy the top */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.start_row, .end_row = rect.start_row + downward, + .start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata); + else if(downward < 0) + /* about to destroy the bottom */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.end_row + downward, .end_row = rect.end_row, + .start_col = rect.start_col, .end_col = rect.end_col}, state->cbdata); + + if(rightward > 0) + /* about to destroy the left */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.start_row, .end_row = rect.end_row, + .start_col = rect.start_col, .end_col = rect.start_col + rightward}, state->cbdata); + else if(rightward < 0) + /* about to destroy the right */ + (*state->callbacks->premove)((VTermRect){ + .start_row = rect.start_row, .end_row = rect.end_row, + .start_col = rect.end_col + rightward, .end_col = rect.end_col}, state->cbdata); + } + // Update lineinfo if full line if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { int height = rect.end_row - rect.start_row - abs(downward); @@ -2156,6 +2184,11 @@ void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *cal } } +void vterm_state_callbacks_has_premove(VTermState *state) +{ + state->callbacks_has_premove = true; +} + void *vterm_state_get_cbdata(VTermState *state) { return state->cbdata; diff --git a/src/vterm_internal.h b/src/vterm_internal.h index e79d74be..4761063a 100644 --- a/src/vterm_internal.h +++ b/src/vterm_internal.h @@ -58,6 +58,7 @@ struct VTermState const VTermStateCallbacks *callbacks; void *cbdata; + bool callbacks_has_premove; const VTermStateFallbacks *fallbacks; void *fbdata; diff --git a/t/12state_scroll.test b/t/12state_scroll.test index c1f2791d..5605131b 100644 --- a/t/12state_scroll.test +++ b/t/12state_scroll.test @@ -127,24 +127,28 @@ PUSH "\e[100;105r\eD" PUSH "\e[5;2r\eD" RESET -WANTSTATE -s+me +WANTSTATE -s+Pme !Scroll Down move+erase emulation PUSH "\e[S" + premove 0..1,0..80 moverect 1..25,0..80 -> 0..24,0..80 erase 24..25,0..80 ?cursor = 0,0 PUSH "\e[2S" + premove 0..2,0..80 moverect 2..25,0..80 -> 0..23,0..80 erase 23..25,0..80 ?cursor = 0,0 !Scroll Up move+erase emulation PUSH "\e[T" + premove 24..25,0..80 moverect 0..24,0..80 -> 1..25,0..80 erase 0..1,0..80 ?cursor = 0,0 PUSH "\e[2T" + premove 23..25,0..80 moverect 0..23,0..80 -> 2..25,0..80 erase 0..2,0..80 ?cursor = 0,0 diff --git a/t/13state_edit.test b/t/13state_edit.test index d3f3e9e4..84d22cc9 100644 --- a/t/13state_edit.test +++ b/t/13state_edit.test @@ -268,7 +268,7 @@ PUSH "\e[2\"q" PUSH "\eP\$q\"q\e\\" output "\eP1\$r2\"q\e\\" -WANTSTATE -s+m +WANTSTATE -s+Pm !ICH move+erase emuation RESET @@ -278,12 +278,14 @@ PUSH "ACD" PUSH "\e[2D" ?cursor = 0,1 PUSH "\e[@" + premove 0..1,79..80 moverect 0..1,1..79 -> 0..1,2..80 erase 0..1,1..2 ?cursor = 0,1 PUSH "B" ?cursor = 0,2 PUSH "\e[3@" + premove 0..1,77..80 moverect 0..1,2..77 -> 0..1,5..80 erase 0..1,2..5 @@ -295,10 +297,12 @@ PUSH "ABBC" PUSH "\e[3D" ?cursor = 0,1 PUSH "\e[P" + premove 0..1,1..2 moverect 0..1,2..80 -> 0..1,1..79 erase 0..1,79..80 ?cursor = 0,1 PUSH "\e[3P" + premove 0..1,1..4 moverect 0..1,4..80 -> 0..1,1..77 erase 0..1,77..80 ?cursor = 0,1 diff --git a/t/harness.c b/t/harness.c index ddd5917b..6c844fe2 100644 --- a/t/harness.c +++ b/t/harness.c @@ -318,6 +318,18 @@ static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) return 1; } +static int want_premove = 0; +static int premove(VTermRect rect, void *user) +{ + if(!want_premove) + return 0; + + printf("premove %d..%d,%d..%d\n", + rect.start_row, rect.end_row, rect.start_col, rect.end_col); + + return 1; +} + static int want_scrollrect = 0; static int scrollrect(VTermRect rect, int downward, int rightward, void *user) { @@ -489,6 +501,7 @@ static int state_sb_clear(void *user) { VTermStateCallbacks state_cbs = { .putglyph = state_putglyph, .movecursor = movecursor, + .premove = premove, .scrollrect = scrollrect, .moverect = moverect, .erase = state_erase, @@ -657,6 +670,7 @@ int main(int argc, char **argv) if(!state) { state = vterm_obtain_state(vt); vterm_state_set_callbacks(state, &state_cbs, NULL); + vterm_state_callbacks_has_premove(state); /* In some tests we want to check the behaviour of overflowing the * buffer, so make it nicely small */ @@ -683,6 +697,9 @@ int main(int argc, char **argv) case 's': want_scrollrect = sense; break; + case 'P': + want_premove = sense; + break; case 'm': want_moverect = sense; break; diff --git a/t/run-test.pl b/t/run-test.pl index 99c36df9..e7c57426 100755 --- a/t/run-test.pl +++ b/t/run-test.pl @@ -125,7 +125,7 @@ sub do_line elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) { $line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2"; } - elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) { + elsif( $line =~ m/^(?:movecursor|scrollrect|premove|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) { # no conversion } elsif( $line =~ m/^(selection-set) (.*?) (\[?)(.*?)(\]?)$/ ) { From c76de0969b0beb4cf8462644606d3fdc0cdee59d Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 16 Jul 2024 14:26:34 +0000 Subject: [PATCH 2/4] Use premove rather than start of moverect to implement scrollback push in screen layer --- src/screen.c | 17 +++++++++++++---- t/62screen_damage.test | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/screen.c b/src/screen.c index cda63fb3..389e198b 100644 --- a/src/screen.c +++ b/src/screen.c @@ -214,18 +214,25 @@ static void sb_pushline_from_row(VTermScreen *screen, int row) (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); } -static int moverect_internal(VTermRect dest, VTermRect src, void *user) +static int premove(VTermRect rect, void *user) { VTermScreen *screen = user; if(screen->callbacks && screen->callbacks->sb_pushline && - dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner - dest.end_col == screen->cols && // full width + rect.start_row == 0 && rect.start_col == 0 && // starts top-left corner + rect.end_col == screen->cols && // full width screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen - for(int row = 0; row < src.start_row; row++) + for(int row = 0; row < rect.end_row; row++) sb_pushline_from_row(screen, row); } + return 1; +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + int cols = src.end_col - src.start_col; int downward = src.start_row - dest.start_row; @@ -840,6 +847,7 @@ static int sb_clear(void *user) { static VTermStateCallbacks state_cbs = { .putglyph = &putglyph, .movecursor = &movecursor, + .premove = &premove, .scrollrect = &scrollrect, .erase = &erase, .setpenattr = &setpenattr, @@ -884,6 +892,7 @@ static VTermScreen *screen_new(VTerm *vt) screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); vterm_state_set_callbacks(screen->state, &state_cbs, screen); + vterm_state_callbacks_has_premove(screen->state); return screen; } diff --git a/t/62screen_damage.test b/t/62screen_damage.test index 3b1b2385..2f11c759 100644 --- a/t/62screen_damage.test +++ b/t/62screen_damage.test @@ -146,9 +146,9 @@ PUSH "\e[25H\r\nABCDE\b\b\b\e[2P\r\n" sb_pushline 80 = moverect 1..25,0..80 -> 0..24,0..80 damage 24..25,0..80 = 24<41 42 43 44 45> + sb_pushline 80 = moverect 24..25,4..80 -> 24..25,2..78 damage 24..25,78..80 - sb_pushline 80 = DAMAGEFLUSH moverect 1..25,0..80 -> 0..24,0..80 damage 24..25,0..80 From 62ec922b6fac0faa3a01ce39c227c63f392743cc Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 16 Jul 2024 14:39:42 +0000 Subject: [PATCH 3/4] Added sb_pushline4 screen callback, which adds an extra 'continuation' argument --- include/vterm.h | 4 ++++ src/screen.c | 32 ++++++++++++++++++++++++-------- t/harness.c | 19 ++++++++++--------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/include/vterm.h b/include/vterm.h index 3ad813e1..1ae20288 100644 --- a/include/vterm.h +++ b/include/vterm.h @@ -545,6 +545,8 @@ typedef struct { int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); int (*sb_clear)(void* user); + /* ABI-compat this is only used if vterm_screen_callbacks_has_pushline4() is called */ + int (*sb_pushline4)(int cols, const VTermScreenCell *cells, bool continuation, void *user); } VTermScreenCallbacks; VTermScreen *vterm_obtain_screen(VTerm *vt); @@ -552,6 +554,8 @@ VTermScreen *vterm_obtain_screen(VTerm *vt); void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); void *vterm_screen_get_cbdata(VTermScreen *screen); +void vterm_screen_callbacks_has_pushline4(VTermScreen *screen); + void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user); void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); diff --git a/src/screen.c b/src/screen.c index 389e198b..5775e678 100644 --- a/src/screen.c +++ b/src/screen.c @@ -48,6 +48,7 @@ struct VTermScreen const VTermScreenCallbacks *callbacks; void *cbdata; + bool callbacks_has_pushline4; VTermDamageSize damage_merge; /* start_row == -1 => no damage */ @@ -205,25 +206,31 @@ static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) return 1; } -static void sb_pushline_from_row(VTermScreen *screen, int row) +static void sb_pushline_from_row(VTermScreen *screen, int row, bool continuation) { VTermPos pos = { .row = row }; for(pos.col = 0; pos.col < screen->cols; pos.col++) vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); - (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); + if(screen->callbacks_has_pushline4 && screen->callbacks->sb_pushline4) + (screen->callbacks->sb_pushline4)(screen->cols, screen->sb_buffer, continuation, screen->cbdata); + else + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); } static int premove(VTermRect rect, void *user) { VTermScreen *screen = user; - if(screen->callbacks && screen->callbacks->sb_pushline && + if(((screen->callbacks && screen->callbacks->sb_pushline) || + (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4)) && rect.start_row == 0 && rect.start_col == 0 && // starts top-left corner rect.end_col == screen->cols && // full width screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen - for(int row = 0; row < rect.end_row; row++) - sb_pushline_from_row(screen, row); + for(int row = 0; row < rect.end_row; row++) { + const VTermLineInfo *lineinfo = vterm_state_get_lineinfo(screen->state, row); + sb_pushline_from_row(screen, row, lineinfo->continuation); + } } return 1; @@ -674,9 +681,12 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) { /* Push spare lines to scrollback buffer */ - if(screen->callbacks && screen->callbacks->sb_pushline) - for(int row = 0; row <= old_row; row++) - sb_pushline_from_row(screen, row); + if((screen->callbacks && screen->callbacks->sb_pushline) || + (screen->callbacks_has_pushline4 && screen->callbacks && screen->callbacks->sb_pushline4)) + for(int row = 0; row <= old_row; row++) { + const VTermLineInfo *lineinfo = old_lineinfo + row; + sb_pushline_from_row(screen, row, lineinfo->continuation); + } if(active) statefields->pos.row -= (old_row + 1); } @@ -884,6 +894,7 @@ static VTermScreen *screen_new(VTerm *vt) screen->callbacks = NULL; screen->cbdata = NULL; + screen->callbacks_has_pushline4 = false; screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); @@ -1070,6 +1081,11 @@ void *vterm_screen_get_cbdata(VTermScreen *screen) return screen->cbdata; } +void vterm_screen_callbacks_has_pushline4(VTermScreen *screen) +{ + screen->callbacks_has_pushline4 = true; +} + void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user) { vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); diff --git a/t/harness.c b/t/harness.c index 6c844fe2..3ec60ee2 100644 --- a/t/harness.c +++ b/t/harness.c @@ -582,7 +582,7 @@ static int screen_damage(VTermRect rect, void *user) } static int want_screen_scrollback = 0; -static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) +static int screen_sb_pushline4(int cols, const VTermScreenCell *cells, bool continuation, void *user) { if(!want_screen_scrollback) return 1; @@ -591,7 +591,7 @@ static int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user while(eol && !cells[eol-1].chars[0]) eol--; - printf("sb_pushline %d =", cols); + printf("sb_pushline %d%s =", cols, continuation ? " cont" : ""); for(int c = 0; c < eol; c++) printf(" %02X", cells[c].chars[0]); printf("\n"); @@ -628,13 +628,13 @@ static int screen_sb_clear(void *user) } VTermScreenCallbacks screen_cbs = { - .damage = screen_damage, - .moverect = moverect, - .movecursor = movecursor, - .settermprop = settermprop, - .sb_pushline = screen_sb_pushline, - .sb_popline = screen_sb_popline, - .sb_clear = screen_sb_clear, + .damage = screen_damage, + .moverect = moverect, + .movecursor = movecursor, + .settermprop = settermprop, + .sb_popline = screen_sb_popline, + .sb_clear = screen_sb_clear, + .sb_pushline4 = screen_sb_pushline4, }; int main(int argc, char **argv) @@ -725,6 +725,7 @@ int main(int argc, char **argv) if(!screen) screen = vterm_obtain_screen(vt); vterm_screen_set_callbacks(screen, &screen_cbs, NULL); + vterm_screen_callbacks_has_pushline4(screen); int i = 10; int sense = 1; From c8a56cbca9ef2932e86da1cb3cc0f4b77a03cef3 Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Tue, 16 Jul 2024 14:49:33 +0000 Subject: [PATCH 4/4] Capture t/69screen_pushline.test --- t/69screen_pushline.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 t/69screen_pushline.test diff --git a/t/69screen_pushline.test b/t/69screen_pushline.test new file mode 100644 index 00000000..735ea294 --- /dev/null +++ b/t/69screen_pushline.test @@ -0,0 +1,17 @@ +INIT +WANTSTATE +WANTSCREEN b + +RESET + +!Spillover text marks continuation on second line +PUSH "A"x85 +PUSH "\r\n" + ?lineinfo 0 = + ?lineinfo 1 = cont + +!Continuation mark sent to sb_pushline +PUSH "\n"x23 + sb_pushline 80 = 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 +PUSH "\n" + sb_pushline 80 cont = 41 41 41 41 41