From daead48672e56af966911abc4efe1e54573c02cc Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Fri, 1 Aug 2025 22:53:54 +0300 Subject: implemented the ^W command for refreshing the screen in loops, for sleeping and also the CTRL+L immediate editing command * ^W can be added to loops in order to view progress in interactive mode. It also sleeps for a given number of milliseconds (10ms by default). * In batch mode it is therefore the sleep command. * Since CTRL+W is an immediate editing command, you will usually type it Caret+W. ASCII 23 however will also be accepted. * While ^W only updates the screen, you can force a complete redraw by pressing CTRL+L. This is what most terminal applications use for redrawing. It will make it harder to insert ASCII 12, but this is seldom necessary since it is a form feed. ^L (ASCII 12 and the upcaret variant ) is still a whitespace character and therefore treated as a NOP. * DEC TECO had CTRL+W as the refresh immediate editing command. Video TECO uses as a regular command for refreshign in loops. I'd rather keep ET reserved as a potential terminal configuration command as in DEC TECO, though. --- doc/sciteco.7.template | 10 ++++++++++ src/cmdline.c | 5 +++++ src/core-commands.c | 43 +++++++++++++++++++++++++++++++++++++++- src/interface-curses/interface.c | 24 +++++++++++++--------- src/interface-gtk/interface.c | 39 +++++++++++++++++++++++------------- src/interface.h | 9 +++++++++ tests/testsuite.at | 2 +- 7 files changed, 107 insertions(+), 25 deletions(-) diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template index 965e203..3bf8d2e 100644 --- a/doc/sciteco.7.template +++ b/doc/sciteco.7.template @@ -680,6 +680,16 @@ work as an immediate editing command in the GUI or as a signal dispatched from an associated console or from another process. T} T{ +.SCITECO_TOPIC ^L redraw +Redraw +T};12;^L;T{ +Everywhere +T};T{ +Enforces a complete redraw of the entire window. +This is useful when the display becomes corrupted, +especially when using the Curses UI. +T} +T{ .SCITECO_TOPIC interrupt Interrupt T};3;^C;T{ diff --git a/src/cmdline.c b/src/cmdline.c index 605af73..1f12c7b 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -472,6 +472,11 @@ teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent_ctx, gun raise(SIGTSTP); return TRUE; #endif + + case TECO_CTL_KEY('L'): + /* causes a complete screen redraw */ + teco_interface_refresh(TRUE); + return TRUE; } teco_interface_popup_clear(); diff --git a/src/core-commands.c b/src/core-commands.c index a2d3c92..c71ee95 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -1605,6 +1605,46 @@ teco_state_control_time(teco_machine_main_t *ctx, GError **error) } } +/*$ ^W refresh sleep delay wait + * [n]^W -- Wait and refresh screen + * + * First sleep milliseconds before refreshing the view, + * i.e. drawing it. + * By default it sleeps for 10ms. + * This can be added to loops to make progress visible + * in interactive mode. + * In batch mode this command is useful as a sleep command. + * Sleeps can of course be interrupted with CTRL+C. + * + * Since CTRL+W is an immediate editing command, you may + * have to type this command in upcaret mode. + * To enforce a complete screen redraw you can also + * press CTRL+L. + */ +static void +teco_state_control_refresh(teco_machine_main_t *ctx, GError **error) +{ + teco_int_t ms; + + if (!teco_expressions_pop_num_calc(&ms, 10, error)) + return; + + while (ms > 0 && !teco_interface_is_interrupted()) { + /* + * UNIX' usleep() would also be interrupted by + * SIGINT, but polling for interruptions is + * probably precise enough. + * We need this as a fallback anyway. + */ + g_usleep(MIN(ms*1000, TECO_POLL_INTERVAL)); + ms -= TECO_POLL_INTERVAL/1000; + } + + teco_interface_unfold(); + teco_interface_ssm(SCI_SCROLLCARET, 0, 0); + teco_interface_refresh(FALSE); +} + static teco_state_t * teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) { @@ -1647,7 +1687,8 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error) ['Y'] = {&teco_state_start, teco_state_control_last_range}, ['S'] = {&teco_state_start, teco_state_control_last_length}, ['T'] = {&teco_state_start, teco_state_control_typeout, - .modifier_colon = 1} + .modifier_colon = 1}, + ['W'] = {&teco_state_start, teco_state_control_refresh} }; /* diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index d92eade..381c188 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -134,7 +134,6 @@ teco_console_ctrl_handler(DWORD type) static gint teco_xterm_version(void) G_GNUC_UNUSED; -static void teco_interface_refresh(void); static gint teco_interface_blocking_getch(void); #define UNNAMED_FILE "(Unnamed)" @@ -924,7 +923,7 @@ teco_interface_getch(gboolean widechar) if (!teco_interface.cmdline_window) /* batch mode */ return teco_interface_stdio_getch(widechar); - teco_interface_refresh(); + teco_interface_refresh(FALSE); /* * Signal that we accept input by drawing a real cursor in the message bar. @@ -1819,8 +1818,8 @@ teco_interface_is_interrupted(void) * filtering out CTRL+C. * It's currently necessary as a fallback e.g. for PDCURSES_GUI or XCurses. * - * NOTE: Theoretically, this can be optimized by doing wgetch() only every X - * microseconds like on Gtk+. + * NOTE: Theoretically, this can be optimized by doing wgetch() only every + * TECO_POLL_INTERVAL microseconds like on Gtk+. * But this turned out to slow things down, at least on PDCurses/WinGUI. */ gboolean @@ -1848,9 +1847,16 @@ teco_interface_is_interrupted(void) #endif -static void -teco_interface_refresh(void) +void +teco_interface_refresh(gboolean force) { + if (!teco_interface.cmdline_window) + /* batch mode */ + return; + + if (G_UNLIKELY(force)) + clearok(curscr, TRUE); + /* * Info window is updated very often which is very * costly, especially when using PDC_set_title(), @@ -2124,7 +2130,7 @@ teco_interface_event_loop_iter(void) * in the ^KMOUSE macro, allowing dot to be outside of the view. */ teco_interface_unfold(); - teco_interface_refresh(); + teco_interface_refresh(FALSE); return; #endif @@ -2183,7 +2189,7 @@ teco_interface_event_loop_iter(void) teco_interface_unfold(); teco_interface_ssm(SCI_SCROLLCARET, 0, 0); - teco_interface_refresh(); + teco_interface_refresh(FALSE); } gboolean @@ -2199,7 +2205,7 @@ teco_interface_event_loop(GError **error) teco_interface_cmdline_update(&empty_cmdline); teco_interface_msg_clear(); teco_interface_ssm(SCI_SCROLLCARET, 0, 0); - teco_interface_refresh(); + teco_interface_refresh(FALSE); #ifdef EMCURSES PDC_emscripten_set_handler(teco_interface_event_loop_iter, TRUE); diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index ae1dd74..dcf3660 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -77,12 +77,6 @@ static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny * static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED; static gchar teco_interface_get_ansi_key(GdkEventKey *event); -/** - * Interval between polling for keypresses. - * In other words, this is the maximum latency to detect CTRL+C interruptions. - */ -#define TECO_POLL_INTERVAL 100000 /* microseconds */ - #define UNNAMED_FILE "(Unnamed)" #define USER_CSS_FILE ".teco_css" @@ -437,10 +431,6 @@ teco_interface_getch_commit_cb(GtkIMContext *context, gchar *str, gpointer user_ gtk_main_quit(); } -/* - * FIXME: Redundancies with teco_interface_handle_keypress() - * FIXME: Report function keys - */ static gboolean teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) { @@ -865,6 +855,27 @@ teco_interface_is_interrupted(void) return teco_interrupted != FALSE; } +void +teco_interface_refresh(gboolean force) +{ + if (!gtk_main_level()) /* batch mode */ + return; + + if (G_UNLIKELY(force)) + gtk_widget_queue_draw(teco_interface.window); + + GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window)); + gdk_window_thaw_updates(top_window); + + /* + * FIXME: Why do we need two iterations to see any updates? + */ + for (gint i = 0; i < 2; i++) + gtk_main_iteration_do(FALSE); + + gdk_window_freeze_updates(top_window); +} + static void teco_interface_set_css_variables(teco_view_t *view) { @@ -942,7 +953,7 @@ teco_interface_set_css_variables(teco_view_t *view) } static void -teco_interface_refresh(gboolean current_view_changed) +teco_interface_update(gboolean current_view_changed) { /* * The styles configured via Scintilla might change @@ -1290,7 +1301,7 @@ teco_interface_event_loop(GError **error) GTK_STYLE_PROVIDER(user_css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER); - teco_interface_refresh(TRUE); + teco_interface_update(TRUE); gtk_widget_show_all(teco_interface.window); /* don't show popup by default */ @@ -1512,7 +1523,7 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) } teco_interrupted = FALSE; - teco_interface_refresh(teco_interface_current_view != last_view); + teco_interface_update(teco_interface_current_view != last_view); /* always expand folds, even after mouse clicks */ teco_interface_unfold(); /* @@ -1597,7 +1608,7 @@ teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpoint teco_interface_popup_clear(); teco_interface_cmdline_update(&teco_cmdline); - teco_interface_refresh(teco_interface_current_view != last_view); + teco_interface_update(teco_interface_current_view != last_view); } static gboolean diff --git a/src/interface.h b/src/interface.h index f22c023..02af8a2 100644 --- a/src/interface.h +++ b/src/interface.h @@ -41,6 +41,12 @@ * feature. */ +/** + * Interval between polling for keypresses (if necessary). + * In other words, this is the maximum latency to detect CTRL+C interruptions. + */ +#define TECO_POLL_INTERVAL 100000 /* microseconds */ + /** @protected */ extern teco_view_t *teco_interface_current_view; @@ -179,6 +185,9 @@ void teco_interface_stdio_msg(teco_msg_t type, const gchar *str, gsize len); /** @protected */ teco_int_t teco_interface_stdio_getch(gboolean widechar); +/** @protected */ +void teco_interface_refresh(gboolean force); + /** @pure */ void teco_interface_cleanup(void); diff --git a/tests/testsuite.at b/tests/testsuite.at index 78e6f48..7fecc45 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -357,7 +357,7 @@ AT_CLEANUP AT_SETUP([Timestamps]) # TODO: Test the date (^B) and time (^H and :^H) variants as well. -TE_CHECK([[::^HUa ::^H-Qa"<(0/0)']], 0, ignore, ignore) +TE_CHECK([[::^HUt 100^W (::^H-Qt)-100"<(0/0)']], 0, ignore, ignore) AT_CLEANUP AT_SETUP([Program version]) -- cgit v1.2.3