diff options
Diffstat (limited to 'avr/cli_readline.c')
-rw-r--r-- | avr/cli_readline.c | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/avr/cli_readline.c b/avr/cli_readline.c new file mode 100644 index 0000000..81f68df --- /dev/null +++ b/avr/cli_readline.c @@ -0,0 +1,697 @@ +/* + * (C) Copyright 2014-2016 Leo C. <erbl259-lmu@yahoo.de> + * + * (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, <luo.jinhua@gd-linux.com> + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include "cli_readline.h" +#include "common.h" + +#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 = 0; + 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 <nandy@mizi.com> + */ + + +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); +} |