diff options
Diffstat (limited to 'libbb')
| -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'): |
