aboutsummaryrefslogtreecommitdiff
path: root/libbb/lineedit.c
diff options
context:
space:
mode:
authorAvi Halachmi <avihpit@yahoo.com>2017-10-12 16:38:35 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2017-10-12 18:26:13 +0200
commit0fd5dbba8f34e006aa0e999002d31e79daf2fdf3 (patch)
treec773d6a6de361d3c18cffb18f3a5fb8816ac38d4 /libbb/lineedit.c
parent1121b4e568b340cfe2a9b7fc41fb48f4d684ad47 (diff)
downloadbusybox-w32-0fd5dbba8f34e006aa0e999002d31e79daf2fdf3.tar.gz
busybox-w32-0fd5dbba8f34e006aa0e999002d31e79daf2fdf3.tar.bz2
busybox-w32-0fd5dbba8f34e006aa0e999002d31e79daf2fdf3.zip
lineedit: improve multiline PS1 - redraw using last PS1 line. Closes 10381
This patch only affects prompts with newlines. We redraw the prompt [+ input] occasionally, e.g. during tab completion, history browsing or search, etc, and we expect it to align with prior redraws, such that the visible effect is that only the input changes. With multi-line PS1, redraw always printed the prompt some lines below the old one, which resulted in terminal scroll during every redraw. Now we only redraw the last PS1 line, so vertical alignment is easier to manage (we already calculated it using only the last line, but re-drew all lines - that was the culprit), which fixes those extra scrolls. Notes: - We now use the full prompt for the initial draw, after clear-screen (^L), and after tab-completion choices are displayed. Everything else now redraws using the last/sole prompt line. - During terminal resize we now only redraw the last[/sole] prompt line, which is arguably better because it's hard to do right (and we never did). - Good side effect for reverse-i-search: its prompt now replaces only the last line of the original prompt - like other shells do. function old new delta put_prompt_custom - 66 +66 draw_custom - 66 +66 parse_and_put_prompt 766 806 +40 read_line_input 3867 3884 +17 input_tab 1069 1076 +7 cmdedit_setwidth 61 63 +2 redraw 59 47 -12 put_prompt 46 - -46 ------------------------------------------------------------------------------ (add/remove: 2/1 grow/shrink: 4/1 up/down: 198/-58) Total: 140 bytes Signed-off-by: Avi Halachmi <avihpit@yahoo.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'libbb/lineedit.c')
-rw-r--r--libbb/lineedit.c88
1 files changed, 64 insertions, 24 deletions
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index 3a092ffe2..c0e35bb21 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -37,11 +37,6 @@
37 * 37 *
38 * Unicode in PS1 is not fully supported: prompt length calulation is wrong, 38 * Unicode in PS1 is not fully supported: prompt length calulation is wrong,
39 * resulting in line wrap problems with long (multi-line) input. 39 * resulting in line wrap problems with long (multi-line) input.
40 *
41 * Multi-line PS1 (e.g. PS1="\n[\w]\n$ ") has problems with history
42 * browsing: up/down arrows result in scrolling.
43 * It stems from simplistic "cmdedit_y = cmdedit_prmt_len / cmdedit_termw"
44 * calculation of how many lines the prompt takes.
45 */ 40 */
46#include "busybox.h" 41#include "busybox.h"
47#include "NUM_APPLETS.h" 42#include "NUM_APPLETS.h"
@@ -133,7 +128,7 @@ struct lineedit_statics {
133 128
134 unsigned cmdedit_x; /* real x (col) terminal position */ 129 unsigned cmdedit_x; /* real x (col) terminal position */
135 unsigned cmdedit_y; /* pseudoreal y (row) terminal position */ 130 unsigned cmdedit_y; /* pseudoreal y (row) terminal position */
136 unsigned cmdedit_prmt_len; /* length of prompt (without colors etc) */ 131 unsigned cmdedit_prmt_len; /* on-screen length of last/sole prompt line */
137 132
138 unsigned cursor; 133 unsigned cursor;
139 int command_len; /* must be signed */ 134 int command_len; /* must be signed */
@@ -143,6 +138,7 @@ struct lineedit_statics {
143 CHAR_T *command_ps; 138 CHAR_T *command_ps;
144 139
145 const char *cmdedit_prompt; 140 const char *cmdedit_prompt;
141 const char *prompt_last_line; /* last/sole prompt line */
146 142
147#if ENABLE_USERNAME_OR_HOMEDIR 143#if ENABLE_USERNAME_OR_HOMEDIR
148 char *user_buf; 144 char *user_buf;
@@ -185,6 +181,7 @@ extern struct lineedit_statics *const lineedit_ptr_to_statics;
185#define command_len (S.command_len ) 181#define command_len (S.command_len )
186#define command_ps (S.command_ps ) 182#define command_ps (S.command_ps )
187#define cmdedit_prompt (S.cmdedit_prompt ) 183#define cmdedit_prompt (S.cmdedit_prompt )
184#define prompt_last_line (S.prompt_last_line)
188#define user_buf (S.user_buf ) 185#define user_buf (S.user_buf )
189#define home_pwd_buf (S.home_pwd_buf ) 186#define home_pwd_buf (S.home_pwd_buf )
190#define matches (S.matches ) 187#define matches (S.matches )
@@ -437,14 +434,20 @@ static void beep(void)
437 bb_putchar('\007'); 434 bb_putchar('\007');
438} 435}
439 436
440static void put_prompt(void) 437/* Full or last/sole prompt line, reset edit cursor, calculate terminal cursor.
438 * cmdedit_y is always calculated for the last/sole prompt line.
439 */
440static void put_prompt_custom(bool is_full)
441{ 441{
442 fputs(cmdedit_prompt, stdout); 442 fputs((is_full ? cmdedit_prompt : prompt_last_line), stdout);
443 cursor = 0; 443 cursor = 0;
444 cmdedit_y = cmdedit_prmt_len / cmdedit_termw; /* new quasireal y */ 444 cmdedit_y = cmdedit_prmt_len / cmdedit_termw; /* new quasireal y */
445 cmdedit_x = cmdedit_prmt_len % cmdedit_termw; 445 cmdedit_x = cmdedit_prmt_len % cmdedit_termw;
446} 446}
447 447
448#define put_prompt_last_line() put_prompt_custom(0)
449#define put_prompt() put_prompt_custom(1)
450
448/* Move back one character */ 451/* Move back one character */
449/* (optimized for slow terminals) */ 452/* (optimized for slow terminals) */
450static void input_backward(unsigned num) 453static void input_backward(unsigned num)
@@ -509,7 +512,7 @@ static void input_backward(unsigned num)
509 printf("\r" ESC"[%uA", cmdedit_y); 512 printf("\r" ESC"[%uA", cmdedit_y);
510 cmdedit_y = 0; 513 cmdedit_y = 0;
511 sv_cursor = cursor; 514 sv_cursor = cursor;
512 put_prompt(); /* sets cursor to 0 */ 515 put_prompt_last_line(); /* sets cursor to 0 */
513 while (cursor < sv_cursor) 516 while (cursor < sv_cursor)
514 put_cur_glyph_and_inc_cursor(); 517 put_cur_glyph_and_inc_cursor();
515 } else { 518 } else {
@@ -530,18 +533,27 @@ static void input_backward(unsigned num)
530 } 533 }
531} 534}
532 535
533/* draw prompt, editor line, and clear tail */ 536/* See redraw and draw_full below */
534static void redraw(int y, int back_cursor) 537static void draw_custom(int y, int back_cursor, bool is_full)
535{ 538{
536 if (y > 0) /* up y lines */ 539 if (y > 0) /* up y lines */
537 printf(ESC"[%uA", y); 540 printf(ESC"[%uA", y);
538 bb_putchar('\r'); 541 bb_putchar('\r');
539 put_prompt(); 542 put_prompt_custom(is_full);
540 put_till_end_and_adv_cursor(); 543 put_till_end_and_adv_cursor();
541 printf(SEQ_CLEAR_TILL_END_OF_SCREEN); 544 printf(SEQ_CLEAR_TILL_END_OF_SCREEN);
542 input_backward(back_cursor); 545 input_backward(back_cursor);
543} 546}
544 547
548/* Move y lines up, draw last/sole prompt line, editor line[s], and clear tail.
549 * goal: redraw the prompt+input+cursor in-place, overwriting the previous */
550#define redraw(y, back_cursor) draw_custom((y), (back_cursor), 0)
551
552/* Like above, but without moving up, and while using all the prompt lines.
553 * goal: draw a full prompt+input+cursor unrelated to a previous position.
554 * note: cmdedit_y always ends up relating to the last/sole prompt line */
555#define draw_full(back_cursor) draw_custom(0, (back_cursor), 1)
556
545/* Delete the char in front of the cursor, optionally saving it 557/* Delete the char in front of the cursor, optionally saving it
546 * for later putback */ 558 * for later putback */
547#if !ENABLE_FEATURE_EDITING_VI 559#if !ENABLE_FEATURE_EDITING_VI
@@ -1106,7 +1118,7 @@ static NOINLINE void input_tab(smallint *lastWasTab)
1106 int sav_cursor = cursor; 1118 int sav_cursor = cursor;
1107 goto_new_line(); 1119 goto_new_line();
1108 showfiles(); 1120 showfiles();
1109 redraw(0, command_len - sav_cursor); 1121 draw_full(command_len - sav_cursor);
1110 } 1122 }
1111 return; 1123 return;
1112 } 1124 }
@@ -1782,14 +1794,37 @@ static void ask_terminal(void)
1782#define ask_terminal() ((void)0) 1794#define ask_terminal() ((void)0)
1783#endif 1795#endif
1784 1796
1797/* Note about multi-line PS1 (e.g. "\n\w \u@\h\n> ") and prompt redrawing:
1798 *
1799 * If the prompt has any newlines, after we print it once we use only its last
1800 * line to redraw in-place, which makes it simpler to calculate how many lines
1801 * we should move the cursor up to align the redraw (cmdedit_y). The earlier
1802 * prompt lines just stay on screen and we redraw below them.
1803 *
1804 * Use cases for all prompt lines beyond the initial draw:
1805 * - After clear-screen (^L) or after displaying tab-completion choices, we
1806 * print the full prompt, as it isn't redrawn in-place.
1807 * - During terminal resize we could try to redraw all lines, but we don't,
1808 * because it requires delicate alignment, it's good enough with only the
1809 * last line, and doing it wrong is arguably worse than not doing it at all.
1810 *
1811 * Terminology wise, if it doesn't mention "full", then it means the last/sole
1812 * prompt line. We use the prompt (last/sole line) while redrawing in-place,
1813 * and the full where we need a fresh one unrelated to an earlier position.
1814 *
1815 * If PS1 is not multiline, the last/sole line and the full are the same string.
1816 */
1817
1785/* Called just once at read_line_input() init time */ 1818/* Called just once at read_line_input() init time */
1786#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT 1819#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
1787static void parse_and_put_prompt(const char *prmt_ptr) 1820static void parse_and_put_prompt(const char *prmt_ptr)
1788{ 1821{
1789 const char *p; 1822 const char *p;
1790 cmdedit_prompt = prmt_ptr; 1823 cmdedit_prompt = prompt_last_line = prmt_ptr;
1791 p = strrchr(prmt_ptr, '\n'); 1824 p = strrchr(prmt_ptr, '\n');
1792 cmdedit_prmt_len = unicode_strwidth(p ? p+1 : prmt_ptr); 1825 if (p)
1826 prompt_last_line = p + 1;
1827 cmdedit_prmt_len = unicode_strwidth(prompt_last_line);
1793 put_prompt(); 1828 put_prompt();
1794} 1829}
1795#else 1830#else
@@ -1973,7 +2008,11 @@ static void parse_and_put_prompt(const char *prmt_ptr)
1973 if (cwd_buf != (char *)bb_msg_unknown) 2008 if (cwd_buf != (char *)bb_msg_unknown)
1974 free(cwd_buf); 2009 free(cwd_buf);
1975# endif 2010# endif
1976 cmdedit_prompt = prmt_mem_ptr; 2011 /* see comment (above this function) about multiline prompt redrawing */
2012 cmdedit_prompt = prompt_last_line = prmt_mem_ptr;
2013 prmt_ptr = strrchr(cmdedit_prompt, '\n');
2014 if (prmt_ptr)
2015 prompt_last_line = prmt_ptr + 1;
1977 put_prompt(); 2016 put_prompt();
1978} 2017}
1979#endif 2018#endif
@@ -2145,7 +2184,7 @@ static int32_t reverse_i_search(int timeout)
2145 match_buf[0] = '\0'; 2184 match_buf[0] = '\0';
2146 2185
2147 /* Save and replace the prompt */ 2186 /* Save and replace the prompt */
2148 saved_prompt = cmdedit_prompt; 2187 saved_prompt = prompt_last_line;
2149 saved_prmt_len = cmdedit_prmt_len; 2188 saved_prmt_len = cmdedit_prmt_len;
2150 goto set_prompt; 2189 goto set_prompt;
2151 2190
@@ -2218,10 +2257,10 @@ static int32_t reverse_i_search(int timeout)
2218 cursor = match - matched_history_line; 2257 cursor = match - matched_history_line;
2219//FIXME: cursor position for Unicode case 2258//FIXME: cursor position for Unicode case
2220 2259
2221 free((char*)cmdedit_prompt); 2260 free((char*)prompt_last_line);
2222 set_prompt: 2261 set_prompt:
2223 cmdedit_prompt = xasprintf("(reverse-i-search)'%s': ", match_buf); 2262 prompt_last_line = xasprintf("(reverse-i-search)'%s': ", match_buf);
2224 cmdedit_prmt_len = unicode_strwidth(cmdedit_prompt); 2263 cmdedit_prmt_len = unicode_strwidth(prompt_last_line);
2225 goto do_redraw; 2264 goto do_redraw;
2226 } 2265 }
2227 } 2266 }
@@ -2241,8 +2280,8 @@ static int32_t reverse_i_search(int timeout)
2241 if (matched_history_line) 2280 if (matched_history_line)
2242 command_len = load_string(matched_history_line); 2281 command_len = load_string(matched_history_line);
2243 2282
2244 free((char*)cmdedit_prompt); 2283 free((char*)prompt_last_line);
2245 cmdedit_prompt = saved_prompt; 2284 prompt_last_line = saved_prompt;
2246 cmdedit_prmt_len = saved_prmt_len; 2285 cmdedit_prmt_len = saved_prmt_len;
2247 redraw(cmdedit_y, command_len - cursor); 2286 redraw(cmdedit_y, command_len - cursor);
2248 2287
@@ -2451,8 +2490,9 @@ int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *comman
2451 case CTRL('L'): 2490 case CTRL('L'):
2452 vi_case(CTRL('L')|VI_CMDMODE_BIT:) 2491 vi_case(CTRL('L')|VI_CMDMODE_BIT:)
2453 /* Control-l -- clear screen */ 2492 /* Control-l -- clear screen */
2454 printf(ESC"[H"); /* cursor to top,left */ 2493 /* cursor to top,left; clear to the end of screen */
2455 redraw(0, command_len - cursor); 2494 printf(ESC"[H" ESC"[J");
2495 draw_full(command_len - cursor);
2456 break; 2496 break;
2457#if MAX_HISTORY > 0 2497#if MAX_HISTORY > 0
2458 case CTRL('N'): 2498 case CTRL('N'):