diff options
-rw-r--r-- | libbb/lineedit.c | 88 |
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 | ||
440 | static 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 | */ | ||
440 | static 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) */ |
450 | static void input_backward(unsigned num) | 453 | static 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 */ |
534 | static void redraw(int y, int back_cursor) | 537 | static 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 |
1787 | static void parse_and_put_prompt(const char *prmt_ptr) | 1820 | static 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'): |