/* * (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" 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 */ struct hist_node_s { struct hist_node_s *next; char line[]; }; typedef struct hist_node_s hist_node; static hist_node *hist_head; static hist_node *hist_cur; static void hist_reset(void) { hist_cur = hist_head; } static hist_node *hist_search_node(char *line) { hist_node *p = hist_head; while (p && strcmp(p->line, line)) p = p->next; return p; } #if 0 static hist_node *hist_insert(char *line) { hist_node *p = (hist_node *) malloc(sizeof (hist_node) + strlen(line) + 1); if (p) { strcpy(p->line, line); p->next = hist_head; hist_head = p; } return p; } #endif static hist_node *hist_new(char *line) { hist_node *p = (hist_node *) malloc(sizeof (hist_node) + strlen(line) + 1); if (p) { strcpy(p->line, line); p->next = NULL; } return p; } static hist_node *hist_delete(void) { hist_node *p = NULL; hist_node *q = hist_head; if (q) { while(q->next) { p = q; q = q->next; } free(q); if (p) p->next = NULL; } return p; } static hist_node *hist_unlink(hist_node *pos) { hist_node *p = NULL; hist_node *q = hist_head; while(q && q != pos) { p = q; q = q->next; } if (q) { if (p) p->next = q->next; else hist_head = q->next; q->next = NULL; } return q; } static uint_fast8_t hist_count(void) { hist_node *p = hist_head; uint_fast8_t n = 0; while (p) { ++n; p = p->next; } return n; } static hist_node *cread_add_to_hist(char *line) { hist_node * p; p = hist_search_node(line); if (p) hist_unlink(p); else p = hist_new(line); if (p) { p->next = hist_head; hist_head = p; } if (hist_count() > CONFIG_SYS_HIST_MAX) hist_delete(); return p; } static char *hist_prev(void) { hist_node *p = hist_cur; if (p == NULL) return NULL; hist_cur = p->next; return p->line; } static char *hist_next(void) { hist_node *p = NULL; hist_node *q = hist_head; if(q == hist_cur) return NULL; while(q->next != hist_cur) { p = q; q = q->next; } hist_cur = q; return p ? p->line : ""; } static char *hist_search_backward(char* buf, uint8_t num) { hist_node *p = hist_cur; if (p == NULL) return NULL; while (p->next && strncmp(p->line, buf, num)) p = p->next; if(!strncmp(p->line, buf, num)) { hist_cur = p->next; return p->line; } return NULL; } static char *hist_search_forward (char* buf, uint8_t num) { hist_node *p = NULL; hist_node *match = NULL; hist_node *q = hist_head; if(q == hist_cur) return NULL; while(q->next != hist_cur) { p = q; q = q->next; if (p && !strncmp(p->line, buf, num)) match = p; } if(match) { hist_cur = match->next; return match->line; } 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 */ if (enable_history && buf[0]) cread_add_to_hist(buf); 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); }