Skip to content

Commit dd306bc

Browse files
committed
slash: optimize screen refresh
Erasing the line and rewriting everything on every change is simple, but works poorly with slow serial speeds and can still create visible refresh artifacts with faster speeds. Rewrite the refresh routine to keep track of the screen cursor and line length and try to refresh fewer bytes. We still need a boolean to force a full reset after we print completion lists. A full buffer reset is also used when browsing the history, which could probably be optimized further. Signed-off-by: Jeppe Ledet-Pedersen <jlp@satlab.com>
1 parent 90417b7 commit dd306bc

2 files changed

Lines changed: 71 additions & 60 deletions

File tree

include/slash/slash.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,13 @@ struct slash_command {
267267
* @line_size: Size in bytes of the line buffer.
268268
* @prompt: Current prompt string.
269269
* @prompt_length: Length in bytes of the prompt string.
270-
* @prompt_print_length: Length in printable characters of the prompt string.
271270
* @buffer: Pointer to line buffer memory.
272271
* @cursor: Current index of cursor in line buffer.
272+
* @cursor_screen: Current index of cursor in line buffer on screen.
273273
* @length: Current amount of used bytes in line buffer.
274+
* @length_screen: Current amount of used bytes in line buffer on screen.
275+
* @refresh_full: Force a full screen refresh including prompt.
276+
* @refresh_buffer: Force a screen refresh of buffer.
274277
* @last_char: Last input character.
275278
* @history_size: Size in byte of the history buffer.
276279
* @history_depth: Number of history entries browsed back.
@@ -306,10 +309,13 @@ struct slash {
306309
size_t line_size;
307310
const char *prompt;
308311
size_t prompt_length;
309-
size_t prompt_print_length;
310312
char *buffer;
311313
size_t cursor;
314+
size_t cursor_screen;
312315
size_t length;
316+
size_t length_screen;
317+
bool refresh_full;
318+
bool refresh_buffer;
313319
char last_char;
314320

315321
/* History */

src/slash.c

Lines changed: 63 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -113,26 +113,6 @@ int slash_getopt(struct slash *slash, const char *opts)
113113
}
114114

115115
/* Terminal handling */
116-
static size_t slash_escaped_strlen(const char *s)
117-
{
118-
int len = 0;
119-
bool escaped = false;
120-
121-
while (*s) {
122-
if (escaped) {
123-
if (*s == 'm')
124-
escaped = false;
125-
} else if (*s == ESC) {
126-
escaped = true;
127-
} else {
128-
len++;
129-
}
130-
s++;
131-
}
132-
133-
return len;
134-
}
135-
136116
static int slash_rawmode_enable(struct slash *slash)
137117
{
138118
#ifdef SLASH_HAVE_TERMIOS_H
@@ -697,6 +677,7 @@ static void slash_complete(struct slash *slash)
697677
if (command) {
698678
slash_printf(slash, "\n");
699679
slash_command_usage(slash, command);
680+
slash->refresh_full = true;
700681
} else {
701682
slash_bell(slash);
702683
}
@@ -717,6 +698,7 @@ static void slash_complete(struct slash *slash)
717698
slash_command_description(slash, cur);
718699
}
719700
}
701+
slash->refresh_full = true;
720702
}
721703
}
722704

@@ -888,6 +870,7 @@ static bool slash_history_next(struct slash *slash)
888870
slash->buffer[srclen] = '\0';
889871
slash->history_cursor = src;
890872
slash->cursor = slash->length = srclen;
873+
slash->refresh_buffer = true;
891874

892875
/* Rewind if use to store buffer temporarily */
893876
if (!slash->history_depth && slash->history_cursor != slash->history_tail)
@@ -917,33 +900,64 @@ static bool slash_history_previous(struct slash *slash)
917900
slash->buffer[srclen] = '\0';
918901
slash->history_cursor = src;
919902
slash->cursor = slash->length = srclen;
903+
slash->refresh_buffer = true;
920904

921905
return true;
922906
}
923907

924908
/* Line editing */
925909
int slash_refresh(struct slash *slash)
926910
{
927-
char esc[16];
911+
const char *esc = ESCAPE("K");
912+
913+
if (slash->refresh_full) {
914+
slash_putchar(slash, '\r');
915+
if (slash_write(slash, slash->prompt, slash->prompt_length) < 0)
916+
return -1;
917+
if (slash_write(slash, esc, strlen(esc)) < 0)
918+
return -1;
919+
slash->cursor_screen = 0;
920+
slash->length_screen = 0;
921+
slash->refresh_full = false;
922+
}
928923

929-
/* Ensure line is zero terminated */
930-
slash->buffer[slash->length] = '\0';
924+
if (slash->refresh_buffer) {
925+
while (slash->cursor_screen > 0) {
926+
slash_putchar(slash, '\b');
927+
slash->cursor_screen--;
928+
}
929+
slash->refresh_buffer = false;
930+
}
931+
932+
while (slash->cursor_screen != slash->cursor) {
933+
if (slash->cursor_screen < slash->cursor) {
934+
slash_putchar(slash, slash->buffer[slash->cursor_screen]);
935+
slash->cursor_screen++;
936+
} else if (slash->cursor_screen > slash->cursor) {
937+
slash_putchar(slash, '\b');
938+
slash->cursor_screen--;
939+
}
940+
}
931941

932-
/* Move cursor to left edge */
933-
if (slash_putchar(slash, '\r') < 0)
934-
return -1;
942+
if (slash->length_screen != slash->length) {
943+
if (slash_write(slash,
944+
&slash->buffer[slash->cursor],
945+
slash->length - slash->cursor) < 0)
946+
return -1;
947+
slash->cursor_screen = slash->length;
935948

936-
/* Write the prompt and the current buffer content */
937-
if (slash_write(slash, slash->prompt, slash->prompt_length) < 0)
938-
return -1;
939-
if (slash_write(slash, slash->buffer, slash->length) < 0)
940-
return -1;
949+
if (slash->length_screen > slash->length) {
950+
if (slash_write(slash, esc, strlen(esc)) < 0)
951+
return -1;
952+
}
941953

942-
/* Erase to the right and move cursor to original position. */
943-
snprintf(esc, sizeof(esc), ESCAPE("K") "\r" ESCAPE("%uC"),
944-
(unsigned int)(slash->cursor + slash->prompt_print_length));
945-
if (slash_write(slash, esc, strlen(esc)) < 0)
946-
return -1;
954+
while (slash->cursor_screen > slash->cursor) {
955+
slash_putchar(slash, '\b');
956+
slash->cursor_screen--;
957+
}
958+
959+
slash->length_screen = slash->length;
960+
}
947961

948962
return 0;
949963
}
@@ -953,30 +967,21 @@ static void slash_insert(struct slash *slash, int c)
953967
if (slash->length + 1 > slash->line_size)
954968
return;
955969

956-
/* Fast path if we're adding a character to the end of the line */
957-
if (slash->cursor == slash->length) {
958-
slash->buffer[slash->cursor] = c;
959-
slash->cursor++;
960-
slash->length++;
961-
slash->buffer[slash->length] = '\0';
962-
slash_putchar(slash, slash->buffer[slash->cursor - 1]);
963-
} else {
964-
memmove(&slash->buffer[slash->cursor + 1],
965-
&slash->buffer[slash->cursor],
966-
slash->length - slash->cursor);
967-
slash->buffer[slash->cursor] = c;
968-
slash->cursor++;
969-
slash->length++;
970-
slash->buffer[slash->length] = '\0';
971-
slash_refresh(slash);
972-
}
970+
memmove(&slash->buffer[slash->cursor + 1],
971+
&slash->buffer[slash->cursor],
972+
slash->length - slash->cursor);
973+
slash->buffer[slash->cursor] = c;
974+
slash->cursor++;
975+
slash->length++;
976+
slash->buffer[slash->length] = '\0';
973977
}
974978

975979
void slash_reset(struct slash *slash)
976980
{
977981
slash->buffer[0] = '\0';
978982
slash->length = 0;
979983
slash->cursor = 0;
984+
slash->refresh_full = true;
980985
}
981986

982987
static void slash_arrow_up(struct slash *slash)
@@ -1018,6 +1023,7 @@ void slash_clear_screen(struct slash *slash)
10181023
{
10191024
const char *esc = ESCAPE("H") ESCAPE("2J");
10201025
slash_write(slash, esc, strlen(esc));
1026+
slash->refresh_full = true;
10211027
}
10221028

10231029
static void slash_backspace(struct slash *slash)
@@ -1065,7 +1071,6 @@ void slash_set_prompt(struct slash *slash, const char *prompt)
10651071
{
10661072
slash->prompt = prompt;
10671073
slash->prompt_length = strlen(prompt);
1068-
slash->prompt_print_length = slash_escaped_strlen(prompt);
10691074
}
10701075

10711076
char *slash_readline(struct slash *slash)
@@ -1108,8 +1113,6 @@ char *slash_readline(struct slash *slash)
11081113
slash->cursor = slash->length;
11091114
}
11101115
escaped = false;
1111-
1112-
slash_refresh(slash);
11131116
} else if (iscntrl(c)) {
11141117
switch (c) {
11151118
case CONTROL('A'):
@@ -1141,6 +1144,7 @@ char *slash_readline(struct slash *slash)
11411144
break;
11421145
case CONTROL('K'):
11431146
slash->length = slash->cursor;
1147+
slash->buffer[slash->length] = '\0';
11441148
break;
11451149
case CONTROL('L'):
11461150
slash_clear_screen(slash);
@@ -1157,6 +1161,7 @@ char *slash_readline(struct slash *slash)
11571161
case CONTROL('U'):
11581162
slash->cursor = 0;
11591163
slash->length = 0;
1164+
slash->buffer[0] = '\0';
11601165
break;
11611166
case CONTROL('W'):
11621167
slash_delete_word(slash);
@@ -1179,13 +1184,13 @@ char *slash_readline(struct slash *slash)
11791184
/* Unknown control */
11801185
break;
11811186
}
1182-
1183-
slash_refresh(slash);
11841187
} else if (isprint(c)) {
11851188
/* Add to buffer */
11861189
slash_insert(slash, c);
11871190
}
11881191

1192+
slash_refresh(slash);
1193+
11891194
slash->last_char = c;
11901195
}
11911196

0 commit comments

Comments
 (0)