/* * (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 "config.h" #include "con-utils.h" #include "print-utils.h" #include "command.h" #include "debug.h" #define DEBUG_READLINE 0 /* set to 1 to debug */ #define debug_readline(fmt, args...) \ debug_cond(DEBUG_READLINE, fmt, ##args) char console_buffer[CONFIG_SYS_CBSIZE + 1]; /* console I/O buffer */ #define CTL_CH(c) ((c) - 'a' + 1) #define CTL_BACKSPACE ('\b') #define DEL ((char)255) #define DEL7 ((char)127) /************************************************************************************************/ /* 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 /* */ FKEY_TBL_ITEM(H, KEY_HOME), // Home key FKEY_TBL_ITEM(F, KEY_END), // End 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 console */ 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, STATE_SS3 } 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; } if (ch == 'O') { state = STATE_SS3; continue; } state = STATE_GROUND; break; case STATE_SS3: if (ch == 'F') ch = KEY_END; if (ch == 'H') ch = KEY_HOME; 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; } /************************************************************************************************/ /* * cmdline-editing related codes from vivi. * Author: Janghoon Lyu */ char histbuf[CONFIG_SYS_HISTSIZE+1]; #define HISTBUFE (histbuf+CONFIG_SYS_HISTSIZE) static char *hist_head; static char *hist_cur; static void hist_reset(void) { if (hist_head == NULL) hist_head = HISTBUFE; hist_cur = hist_head; } static char *hist_entry_next(char *p) { if (*p) while (*p++); return p; } static char *hist_entry_prev(char *p) { if (p == hist_head) return NULL; --p; while (p != hist_head && *(p-1) != 0) --p; return p; } static char *hist_search_entry(char *line) { char *p = hist_head; while (*p && strcmp(p, line)) p = hist_entry_next(p); return *p ? p : NULL; } static char *hist_delete(size_t amount) { char *p; p = HISTBUFE - amount; if (p < hist_head) p = hist_head; while (p > hist_head && *(p-1)) --p; if (p == hist_head) hist_head = HISTBUFE; else { size_t shift = HISTBUFE - p; size_t len = p - hist_head; hist_head = memmove(hist_head + shift, hist_head, len); } return hist_head; } static char *hist_delete_entry(char *entry) { size_t shift = strlen(entry) + 1; size_t len = entry - hist_head; hist_head = memmove(hist_head + shift, hist_head, len); return hist_head; } static char *hist_add_entry(char *line) { char *p = hist_head; if (p == NULL) p = HISTBUFE; char *q = p - strlen(line) - 1; if (q < histbuf) q = histbuf; strlcpy(q, line, p - q); hist_head = q; return hist_head; } static uint_fast8_t hist_get_count(void) { char *p = hist_head; uint_fast8_t n = 0; while (*p) { ++n; while (*p++); } return n; } static int hist_get_size(void) { return HISTBUFE - hist_head; } static char *cread_add_to_hist(char *line) { char *p; p = hist_search_entry(line); if (p) hist_delete_entry(p); else { size_t free = hist_head - histbuf; if (free < strlen(line) + 1) hist_delete(strlen(line) + 1 - free); } hist_add_entry(line); return p; } static char *hist_prev(void) { char *p = hist_cur; if (*p == '\0') return NULL; hist_cur = hist_entry_next(p); return p; } static char *hist_next(void) { char *p = hist_cur; if(p == hist_head) return NULL; p = hist_entry_prev(p); hist_cur = p; return p == hist_head ? "" : hist_entry_prev(p); } static char *hist_search_backward(char* buf, uint8_t num) { char *p = hist_cur; if (*p == '\0') return NULL; while (*p && strncmp(p, buf, num)) p = hist_entry_next(p); if(!strncmp(p, buf, num)) { hist_cur = hist_entry_next(p); return p; } return NULL; } static char *hist_search_forward (char* buf, uint8_t num) { char *p = hist_cur; if(p != hist_head && (p = hist_entry_prev(p)) != NULL) { do { p = hist_entry_prev(p); } while (p && strncmp(p, buf, num) != 0); //if(!strncmp(p, buf, num)) { if(p) { hist_cur = hist_entry_next(p); return p; } } return NULL; } static void putnstr(char *str, int n) { /* printf_P(PSTR("%.*s"), (int)n, str) */ while (n-- && *str) putchar(*str++); } static void getcmd_putch(int ch) { putchar(ch);} static int getcmd_getch(void) { return vt_parse();} static void getcmd_cbeep(void) { getcmd_putch('\a');} static void beginning_of_line(uint8_t *num) { while (*num) { getcmd_putch(CTL_BACKSPACE); (*num)--; } } static void erase_to_eol(uint_fast8_t *num, uint_fast8_t *eol_num) { 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); } } static void refresh_to_eol(char *buf, uint_fast8_t *num, uint_fast8_t *eol_num) { if (*num < *eol_num) { uint_fast8_t 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 */ buf[*num] = ichar; putnstr(buf + *num, 1); (*num)++; } } static void cread_add_str(char *str, bool insert, uint_fast8_t *num, uint_fast8_t *eol_num, char *buf, uint_fast8_t len) { char c; while ((c = *str++) != '\0') cread_add_char(c, insert, num, eol_num, buf, len); } 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; bool insert = 1; (void) prompt; if (buf[0]) cread_add_str(buf, 1, &num, &eol_num, buf, len); hist_reset(); while (1) { int ichar = getcmd_getch(); if ((ichar == '\n') || (ichar == '\r')) { putchar('\n'); break; } switch (ichar) { case KEY_HOME: case CTL_CH('a'): beginning_of_line(&num); break; case CTL_CH('c'): /* ^C - break */ putchar('\n'); *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) { uint_fast8_t 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(&num, &eol_num); break; case KEY_END: case CTL_CH('e'): refresh_to_eol(buf, &num, &eol_num); 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(&num); erase_to_eol(&num, &eol_num); break; case DEL: case DEL7: case 8: /* backward-delete-char */ if (num) { uint_fast8_t wlen = eol_num - --num; buf[eol_num] = ' '; memmove(&buf[num], &buf[num+1], wlen); getcmd_putch(CTL_BACKSPACE); putnstr(buf + num, wlen); 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) { /* first, go home */ beginning_of_line(&num); /* overwrite current line */ cread_add_str(hline, 0, &num, &eol_num, buf, len); /* erase to end of line */ erase_to_eol(&num, &eol_num); } else { getcmd_cbeep(); } } else { getcmd_cbeep(); } break; case KEY_PPAGE: /* history-search-backward */ case KEY_NPAGE: /* history-search-forward */ if (enable_history) { char *hline; if (ichar == KEY_PPAGE) hline = hist_search_backward(buf, num); else hline = hist_search_forward(buf, num); if (hline) { uint_fast8_t num2 = num; /* overwrite current line from cursor position */ cread_add_str(hline+num, 0, &num2, &eol_num, buf, len); /* erase to end of line */ erase_to_eol(&num2, &eol_num); /* cursor back */ while (num2-- > num) getcmd_putch(CTL_BACKSPACE); } 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; } } while (eol_num && buf[eol_num-1] == ' ') --eol_num; /* remove trailing blanks */ buf[eol_num] = '\0'; /* lose the newline */ uint_fast8_t i = 0; while (buf[i] == ' ') ++i; /* remove leading blanks */ if (i) { eol_num -= i; memmove(buf, buf+i, eol_num+1); } debug_readline("### hist_head: %p, hist_cur: %p\n", hist_head, hist_cur); if (enable_history && buf[0]) { cread_add_to_hist(buf); debug_readline("### hist_head: %p, hist_cur: %p, hist_size: %3d, hist_count: %2d\n", hist_head, hist_cur, hist_get_size(), hist_get_count()); } return eol_num; } /****************************************************************************/ 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'; if (prompt) my_puts_P(prompt); return cread_line(prompt, console_buffer, CONFIG_SYS_CBSIZE, enable_history); }