/* * (C) Copyright 2014-2016 Leo C. * * (C) Copyright 2000 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. * * Add to readline cmdline-editing by * (C) Copyright 2005 * JinHua Luo, GuangDong Linux Center, * * SPDX-License-Identifier: GPL-2.0 */ #include "cli_readline.h" #include "common.h" #include #include #include #include #include #include "config.h" #include "con-utils.h" #include "print-utils.h" #include "command.h" /************************************************************************************************/ /* TODO: * */ #define ESC 0x1b #define KEY_TAB '\t' // TAB key #define KEY_CR '\r' // RETURN key #define KEY_BACKSPACE '\b' // Backspace key #define KEY_ESCAPE 0x1B // ESCAPE (pressed twice) #define KEY_DOWN 0x80 // Down arrow key #define KEY_UP 0x81 // Up arrow key #define KEY_LEFT 0x82 // Left arrow key #define KEY_RIGHT 0x83 // Right arrow key #define KEY_HOME 0x84 // Home key #define KEY_DC 0x85 // Delete character key #define KEY_IC 0x86 // Ins char/toggle ins mode key #define KEY_NPAGE 0x87 // Next-page key #define KEY_PPAGE 0x88 // Previous-page key #define KEY_END 0x89 // End key #define KEY_BTAB 0x8A // Back tab key #define KEY_F1 0x8B // Function key F1 #define KEY_F(n) (KEY_F1+(n)-1) // Space for additional 12 function keys struct fkey_tbl_s { const FLASH char *sequence; /* ESC Sequence */ int code; /* Keycode */ }; //typedef const FLASH struct fkey_tbl_s fkey_tbl_t; #define FKEY_TBL_ITEM(_seq, _code) { FSTR(#_seq), _code } static const FLASH struct fkey_tbl_s fkey_table[] = { FKEY_TBL_ITEM(B, KEY_DOWN), // Down arrow key FKEY_TBL_ITEM(A, KEY_UP), // Up arrow key FKEY_TBL_ITEM(D, KEY_LEFT), // Left arrow key FKEY_TBL_ITEM(C, KEY_RIGHT), // Right arrow key FKEY_TBL_ITEM(1~, KEY_HOME), // Home key FKEY_TBL_ITEM(3~, KEY_DC), // Delete character key FKEY_TBL_ITEM(2~, KEY_IC), // Ins char/toggle ins mode key FKEY_TBL_ITEM(6~, KEY_NPAGE), // Next-page key FKEY_TBL_ITEM(5~, KEY_PPAGE), // Previous-page key FKEY_TBL_ITEM(4~, KEY_END), // End key FKEY_TBL_ITEM(Z, KEY_BTAB), // Back tab key /* VT400: */ FKEY_TBL_ITEM(11~, KEY_F(1)), // Function key F1 FKEY_TBL_ITEM(12~, KEY_F(2)), // Function key F2 FKEY_TBL_ITEM(13~, KEY_F(3)), // Function key F3 FKEY_TBL_ITEM(14~, KEY_F(4)), // Function key F4 FKEY_TBL_ITEM(15~, KEY_F(5)), // Function key F5 /* Linux consoe */ FKEY_TBL_ITEM([A, KEY_F(1)), // Function key F1 FKEY_TBL_ITEM([B, KEY_F(2)), // Function key F2 FKEY_TBL_ITEM([C, KEY_F(3)), // Function key F3 FKEY_TBL_ITEM([D, KEY_F(4)), // Function key F4 FKEY_TBL_ITEM([E, KEY_F(5)), // Function key F5 FKEY_TBL_ITEM(17~, KEY_F(6)), // Function key F6 FKEY_TBL_ITEM(18~, KEY_F(7)), // Function key F7 FKEY_TBL_ITEM(19~, KEY_F(8)), // Function key F8 FKEY_TBL_ITEM(20~, KEY_F(9)), // Function key F9 FKEY_TBL_ITEM(21~, KEY_F(10)), // Function key F10 FKEY_TBL_ITEM(23~, KEY_F(11)), // Function key F11 FKEY_TBL_ITEM(24~, KEY_F(12)), // Function key F12 { NULL } /* Mark end of table */ }; typedef enum { STATE_GROUND, STATE_ESCAPE, STATE_CSI_ENTRY } vtparse_state_t; #define CHB_SIZE 15 static int vt_parse (void) { static vtparse_state_t state = STATE_GROUND; char buf[CHB_SIZE+1]; uint8_t param[2]; uint8_t i_buf; uint8_t i_param; int ch; while (1) { ch = my_getchar(1); // debug_getch(state, ch); switch (state) { case STATE_GROUND: if (ch == ESC) { state = STATE_ESCAPE; continue; } if (ch == 0x7F) // BACKSPACE on VT200 sends DEL char ch = KEY_BACKSPACE; // map it to '\b' break; case STATE_ESCAPE: if (ch < 0) continue; if (ch == '[') { state = STATE_CSI_ENTRY; param[0] = param[1] = 0; i_buf = 0; i_param = 0; continue; } state = STATE_GROUND; break; case STATE_CSI_ENTRY: if (ch < 0) continue; buf[i_buf] = ch; if (i_buf < CHB_SIZE) i_buf++; if (ch == ';') { i_param++; continue; } if (isdigit(ch)) { if (i_param < 2) param[i_param] = param[i_param] * 10 + ch - '0'; continue; } if (ch >= '@' && ch <= '~' && ch != '[') { buf[i_buf] = '\0'; int_fast8_t i = 0; while (fkey_table[i].sequence) { if (! strcmp_P (buf, fkey_table[i].sequence)) { ch = fkey_table[i].code; break; } i++; } if (fkey_table[i].sequence == NULL) { ch = '$'; /* KEY_ESCAPE; */ } } state = STATE_GROUND; break; } break; /* while */ } return ch; } /************************************************************************************************/ char console_buffer[CONFIG_SYS_CBSIZE + 1]; /* console I/O buffer */ /* * cmdline-editing related codes from vivi. * Author: Janghoon Lyu */ static void putnstr(char *str, int n) { /* printf_P(PSTR("%.*s"), (int)n, str) */ while (n-- && *str) putchar(*str++); } #define CTL_CH(c) ((c) - 'a' + 1) #define CTL_BACKSPACE ('\b') #define DEL ((char)255) #define DEL7 ((char)127) #define getcmd_putch(ch) putchar(ch) #define getcmd_getch() vt_parse() #define getcmd_cbeep() getcmd_putch('\a') #define HIST_MAX 20 static int_fast8_t hist_max; static int_fast8_t hist_add_idx; static int_fast8_t hist_cur = -1; static char *hist_list[HIST_MAX]; static void cread_add_to_hist(char *line) { if (hist_max) { int_fast8_t last = hist_add_idx; if (--last < 0) last = hist_max; if (!strcmp(line, hist_list[last])) return; } char *p = strdup(line); if (p) { free(hist_list[hist_add_idx]); hist_list[hist_add_idx] = p; if (++hist_add_idx >= HIST_MAX) hist_add_idx = 0; if (hist_add_idx > hist_max) hist_max = hist_add_idx; } } static char *hist_prev(void) { char *ret; int_fast8_t old_cur; if (hist_cur < 0) return NULL; old_cur = hist_cur; if (--hist_cur < 0) hist_cur = hist_max; if (hist_cur == hist_add_idx) { hist_cur = old_cur; ret = NULL; } else { ret = hist_list[hist_cur]; } return ret; } static char *hist_next(void) { char *ret; if (hist_cur < 0) return NULL; if (hist_cur == hist_add_idx) return NULL; if (++hist_cur > hist_max) hist_cur = 0; if (hist_cur == hist_add_idx) ret = ""; else ret = hist_list[hist_cur]; return ret; } #define BEGINNING_OF_LINE() { \ while (num) { \ getcmd_putch(CTL_BACKSPACE); \ num--; \ } \ } #define ERASE_TO_EOL() { \ if (num < eol_num) { \ /* printf_P(PSTR("%*S"), (int)(eol_num - num), PSTR("")); */ \ print_blanks(eol_num - num); \ do { \ getcmd_putch(CTL_BACKSPACE); \ } while (--eol_num > num); \ } \ } #define REFRESH_TO_EOL() { \ if (num < eol_num) { \ wlen = eol_num - num; \ putnstr(buf + num, wlen); \ num = eol_num; \ } \ } static void cread_add_char(char ichar, bool insert, uint_fast8_t *num, uint_fast8_t *eol_num, char *buf, uint_fast8_t len) { uint_fast8_t wlen; /* room ??? */ if (insert || *num == *eol_num) { if (*eol_num > len - 1) { getcmd_cbeep(); return; } (*eol_num)++; } if (insert) { wlen = *eol_num - *num; if (wlen > 1) memmove(&buf[*num+1], &buf[*num], wlen-1); buf[*num] = ichar; putnstr(buf + *num, wlen); (*num)++; while (--wlen) getcmd_putch(CTL_BACKSPACE); } else { /* echo the character */ wlen = 1; buf[*num] = ichar; putnstr(buf + *num, wlen); (*num)++; } } static void cread_add_str(char *str, int strsize, bool insert, uint_fast8_t *num, uint_fast8_t *eol_num, char *buf, uint_fast8_t len) { while (strsize--) { cread_add_char(*str, insert, num, eol_num, buf, len); str++; } } static int cread_line(const FLASH char *const prompt, char *buf, uint_fast8_t *len, bool enable_history) { uint_fast8_t num = 0; uint_fast8_t eol_num = 0; uint_fast8_t wlen; int ichar; bool insert = 1; int init_len = strlen(buf); (void) prompt; if (init_len) cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len); while (1) { ichar = getcmd_getch(); if ((ichar == '\n') || (ichar == '\r')) { putchar('\n'); break; } switch (ichar) { case KEY_HOME: case CTL_CH('a'): BEGINNING_OF_LINE(); break; case CTL_CH('c'): /* ^C - break */ *buf = '\0'; /* discard input */ return -1; case KEY_RIGHT: case CTL_CH('f'): /* forward-char */ if (num < eol_num) { getcmd_putch(buf[num]); num++; } break; case KEY_LEFT: case CTL_CH('b'): /* backward-char */ if (num) { getcmd_putch(CTL_BACKSPACE); num--; } break; case KEY_DC: case CTL_CH('d'): /* delete-char */ if (num < eol_num) { wlen = eol_num - num - 1; if (wlen) { memmove(&buf[num], &buf[num+1], wlen); putnstr(buf + num, wlen); } getcmd_putch(' '); do { getcmd_putch(CTL_BACKSPACE); } while (wlen--); eol_num--; } break; case CTL_CH('k'): /* kill-line */ ERASE_TO_EOL(); break; case KEY_END: case CTL_CH('e'): REFRESH_TO_EOL(); break; case KEY_IC: case CTL_CH('o'): insert = !insert; break; case CTL_CH('x'): case CTL_CH('u'): /* kill-whole-line */ BEGINNING_OF_LINE(); ERASE_TO_EOL(); break; case DEL: case DEL7: case 8: /* backward-delete-char */ if (num) { wlen = eol_num - num; num--; memmove(&buf[num], &buf[num+1], wlen); getcmd_putch(CTL_BACKSPACE); putnstr(buf + num, wlen); getcmd_putch(' '); do { getcmd_putch(CTL_BACKSPACE); } while (wlen--); eol_num--; } break; case KEY_UP: case CTL_CH('p'): /* previous-history */ case KEY_DOWN: case CTL_CH('n'): /* next-history */ if (enable_history) { char *hline; if (ichar == CTL_CH('p') || ichar == KEY_UP) hline = hist_prev(); else hline = hist_next(); if (hline) { /* nuke the current line */ /* first, go home */ BEGINNING_OF_LINE(); /* erase to end of line */ ERASE_TO_EOL(); /* copy new line into place and display */ strcpy(buf, hline); eol_num = strlen(buf); REFRESH_TO_EOL(); } else { getcmd_cbeep(); } } else { getcmd_cbeep(); } break; #ifdef CONFIG_AUTO_COMPLETE case '\t': { int num2, col; /* do not autocomplete when in the middle */ if (num < eol_num) { getcmd_cbeep(); break; } buf[num] = '\0'; col = strlen_P(prompt) + eol_num; num2 = num; if (cmd_auto_complete(prompt, buf, &num2, &col)) { col = num2 - num; num += col; eol_num += col; } break; } #endif default: if (isprint(ichar)) cread_add_char(ichar, insert, &num, &eol_num, buf, *len); break; } } *len = eol_num; buf[eol_num] = '\0'; /* lose the newline */ if (enable_history) { if (buf[0]) cread_add_to_hist(buf); hist_cur = hist_add_idx; } return 0; } /****************************************************************************/ static int cli_readline_into_buffer(const FLASH char *const prompt, char *buffer, bool enable_history) { char *p = buffer; uint_fast8_t len = CONFIG_SYS_CBSIZE; int rc; if (prompt) my_puts_P(prompt); rc = cread_line(prompt, p, &len, enable_history); return rc < 0 ? rc : (int) len; } int cli_readline(const FLASH char *const prompt, bool enable_history) { /* * If console_buffer isn't 0-length the user will be prompted to modify * it instead of entering it from scratch as desired. */ console_buffer[0] = '\0'; return cli_readline_into_buffer(prompt, console_buffer, enable_history); }