|  | /* | 
|  | * Command line editing and history | 
|  | * Copyright (c) 2010-2011, Jouni Malinen <j@w1.fi> | 
|  | * | 
|  | * This software may be distributed under the terms of the BSD license. | 
|  | * See README for more details. | 
|  | */ | 
|  |  | 
|  | #include "includes.h" | 
|  | #include <termios.h> | 
|  |  | 
|  | #include "common.h" | 
|  | #include "eloop.h" | 
|  | #include "list.h" | 
|  | #include "edit.h" | 
|  |  | 
|  | #define CMD_BUF_LEN 4096 | 
|  | static char cmdbuf[CMD_BUF_LEN]; | 
|  | static int cmdbuf_pos = 0; | 
|  | static int cmdbuf_len = 0; | 
|  | static char currbuf[CMD_BUF_LEN]; | 
|  | static int currbuf_valid = 0; | 
|  | static const char *ps2 = NULL; | 
|  |  | 
|  | #define HISTORY_MAX 100 | 
|  |  | 
|  | struct edit_history { | 
|  | struct dl_list list; | 
|  | char str[1]; | 
|  | }; | 
|  |  | 
|  | static struct dl_list history_list; | 
|  | static struct edit_history *history_curr; | 
|  |  | 
|  | static void *edit_cb_ctx; | 
|  | static void (*edit_cmd_cb)(void *ctx, char *cmd); | 
|  | static void (*edit_eof_cb)(void *ctx); | 
|  | static char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) = | 
|  | NULL; | 
|  |  | 
|  | static struct termios prevt, newt; | 
|  |  | 
|  |  | 
|  | #define CLEAR_END_LINE "\e[K" | 
|  |  | 
|  |  | 
|  | void edit_clear_line(void) | 
|  | { | 
|  | int i; | 
|  | putchar('\r'); | 
|  | for (i = 0; i < cmdbuf_len + 2 + (ps2 ? (int) os_strlen(ps2) : 0); i++) | 
|  | putchar(' '); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void move_start(void) | 
|  | { | 
|  | cmdbuf_pos = 0; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void move_end(void) | 
|  | { | 
|  | cmdbuf_pos = cmdbuf_len; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void move_left(void) | 
|  | { | 
|  | if (cmdbuf_pos > 0) { | 
|  | cmdbuf_pos--; | 
|  | edit_redraw(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static void move_right(void) | 
|  | { | 
|  | if (cmdbuf_pos < cmdbuf_len) { | 
|  | cmdbuf_pos++; | 
|  | edit_redraw(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static void move_word_left(void) | 
|  | { | 
|  | while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] == ' ') | 
|  | cmdbuf_pos--; | 
|  | while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] != ' ') | 
|  | cmdbuf_pos--; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void move_word_right(void) | 
|  | { | 
|  | while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] == ' ') | 
|  | cmdbuf_pos++; | 
|  | while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] != ' ') | 
|  | cmdbuf_pos++; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void delete_left(void) | 
|  | { | 
|  | if (cmdbuf_pos == 0) | 
|  | return; | 
|  |  | 
|  | edit_clear_line(); | 
|  | os_memmove(cmdbuf + cmdbuf_pos - 1, cmdbuf + cmdbuf_pos, | 
|  | cmdbuf_len - cmdbuf_pos); | 
|  | cmdbuf_pos--; | 
|  | cmdbuf_len--; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void delete_current(void) | 
|  | { | 
|  | if (cmdbuf_pos == cmdbuf_len) | 
|  | return; | 
|  |  | 
|  | edit_clear_line(); | 
|  | os_memmove(cmdbuf + cmdbuf_pos, cmdbuf + cmdbuf_pos + 1, | 
|  | cmdbuf_len - cmdbuf_pos); | 
|  | cmdbuf_len--; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void delete_word(void) | 
|  | { | 
|  | int pos; | 
|  |  | 
|  | edit_clear_line(); | 
|  | pos = cmdbuf_pos; | 
|  | while (pos > 0 && cmdbuf[pos - 1] == ' ') | 
|  | pos--; | 
|  | while (pos > 0 && cmdbuf[pos - 1] != ' ') | 
|  | pos--; | 
|  | os_memmove(cmdbuf + pos, cmdbuf + cmdbuf_pos, cmdbuf_len - cmdbuf_pos); | 
|  | cmdbuf_len -= cmdbuf_pos - pos; | 
|  | cmdbuf_pos = pos; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void clear_left(void) | 
|  | { | 
|  | if (cmdbuf_pos == 0) | 
|  | return; | 
|  |  | 
|  | edit_clear_line(); | 
|  | os_memmove(cmdbuf, cmdbuf + cmdbuf_pos, cmdbuf_len - cmdbuf_pos); | 
|  | cmdbuf_len -= cmdbuf_pos; | 
|  | cmdbuf_pos = 0; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void clear_right(void) | 
|  | { | 
|  | if (cmdbuf_pos == cmdbuf_len) | 
|  | return; | 
|  |  | 
|  | edit_clear_line(); | 
|  | cmdbuf_len = cmdbuf_pos; | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void history_add(const char *str) | 
|  | { | 
|  | struct edit_history *h, *match = NULL, *last = NULL; | 
|  | size_t len, count = 0; | 
|  |  | 
|  | if (str[0] == '\0') | 
|  | return; | 
|  |  | 
|  | dl_list_for_each(h, &history_list, struct edit_history, list) { | 
|  | if (os_strcmp(str, h->str) == 0) { | 
|  | match = h; | 
|  | break; | 
|  | } | 
|  | last = h; | 
|  | count++; | 
|  | } | 
|  |  | 
|  | if (match) { | 
|  | dl_list_del(&h->list); | 
|  | dl_list_add(&history_list, &h->list); | 
|  | history_curr = h; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (count >= HISTORY_MAX && last) { | 
|  | dl_list_del(&last->list); | 
|  | os_free(last); | 
|  | } | 
|  |  | 
|  | len = os_strlen(str); | 
|  | h = os_zalloc(sizeof(*h) + len); | 
|  | if (h == NULL) | 
|  | return; | 
|  | dl_list_add(&history_list, &h->list); | 
|  | os_strlcpy(h->str, str, len + 1); | 
|  | history_curr = h; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void history_use(void) | 
|  | { | 
|  | edit_clear_line(); | 
|  | cmdbuf_len = cmdbuf_pos = os_strlen(history_curr->str); | 
|  | os_memcpy(cmdbuf, history_curr->str, cmdbuf_len); | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void history_prev(void) | 
|  | { | 
|  | if (history_curr == NULL) | 
|  | return; | 
|  |  | 
|  | if (history_curr == | 
|  | dl_list_first(&history_list, struct edit_history, list)) { | 
|  | if (!currbuf_valid) { | 
|  | cmdbuf[cmdbuf_len] = '\0'; | 
|  | os_memcpy(currbuf, cmdbuf, cmdbuf_len + 1); | 
|  | currbuf_valid = 1; | 
|  | history_use(); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (history_curr == | 
|  | dl_list_last(&history_list, struct edit_history, list)) | 
|  | return; | 
|  |  | 
|  | history_curr = dl_list_entry(history_curr->list.next, | 
|  | struct edit_history, list); | 
|  | history_use(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void history_next(void) | 
|  | { | 
|  | if (history_curr == NULL || | 
|  | history_curr == | 
|  | dl_list_first(&history_list, struct edit_history, list)) { | 
|  | if (currbuf_valid) { | 
|  | currbuf_valid = 0; | 
|  | edit_clear_line(); | 
|  | cmdbuf_len = cmdbuf_pos = os_strlen(currbuf); | 
|  | os_memcpy(cmdbuf, currbuf, cmdbuf_len); | 
|  | edit_redraw(); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | history_curr = dl_list_entry(history_curr->list.prev, | 
|  | struct edit_history, list); | 
|  | history_use(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void history_read(const char *fname) | 
|  | { | 
|  | FILE *f; | 
|  | char buf[CMD_BUF_LEN], *pos; | 
|  |  | 
|  | f = fopen(fname, "r"); | 
|  | if (f == NULL) | 
|  | return; | 
|  |  | 
|  | while (fgets(buf, CMD_BUF_LEN, f)) { | 
|  | for (pos = buf; *pos; pos++) { | 
|  | if (*pos == '\r' || *pos == '\n') { | 
|  | *pos = '\0'; | 
|  | break; | 
|  | } | 
|  | } | 
|  | history_add(buf); | 
|  | } | 
|  |  | 
|  | fclose(f); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void history_write(const char *fname, | 
|  | int (*filter_cb)(void *ctx, const char *cmd)) | 
|  | { | 
|  | FILE *f; | 
|  | struct edit_history *h; | 
|  |  | 
|  | f = fopen(fname, "w"); | 
|  | if (f == NULL) | 
|  | return; | 
|  |  | 
|  | dl_list_for_each_reverse(h, &history_list, struct edit_history, list) { | 
|  | if (filter_cb && filter_cb(edit_cb_ctx, h->str)) | 
|  | continue; | 
|  | fprintf(f, "%s\n", h->str); | 
|  | } | 
|  |  | 
|  | fclose(f); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void history_debug_dump(void) | 
|  | { | 
|  | struct edit_history *h; | 
|  | edit_clear_line(); | 
|  | printf("\r"); | 
|  | dl_list_for_each_reverse(h, &history_list, struct edit_history, list) | 
|  | printf("%s%s\n", h == history_curr ? "[C]" : "", h->str); | 
|  | if (currbuf_valid) | 
|  | printf("{%s}\n", currbuf); | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void insert_char(int c) | 
|  | { | 
|  | if (cmdbuf_len >= (int) sizeof(cmdbuf) - 1) | 
|  | return; | 
|  | if (cmdbuf_len == cmdbuf_pos) { | 
|  | cmdbuf[cmdbuf_pos++] = c; | 
|  | cmdbuf_len++; | 
|  | putchar(c); | 
|  | fflush(stdout); | 
|  | } else { | 
|  | os_memmove(cmdbuf + cmdbuf_pos + 1, cmdbuf + cmdbuf_pos, | 
|  | cmdbuf_len - cmdbuf_pos); | 
|  | cmdbuf[cmdbuf_pos++] = c; | 
|  | cmdbuf_len++; | 
|  | edit_redraw(); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static void process_cmd(void) | 
|  | { | 
|  | currbuf_valid = 0; | 
|  | if (cmdbuf_len == 0) { | 
|  | printf("\n%s> ", ps2 ? ps2 : ""); | 
|  | fflush(stdout); | 
|  | return; | 
|  | } | 
|  | printf("\n"); | 
|  | cmdbuf[cmdbuf_len] = '\0'; | 
|  | history_add(cmdbuf); | 
|  | cmdbuf_pos = 0; | 
|  | cmdbuf_len = 0; | 
|  | edit_cmd_cb(edit_cb_ctx, cmdbuf); | 
|  | printf("%s> ", ps2 ? ps2 : ""); | 
|  | fflush(stdout); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void free_completions(char **c) | 
|  | { | 
|  | int i; | 
|  | if (c == NULL) | 
|  | return; | 
|  | for (i = 0; c[i]; i++) | 
|  | os_free(c[i]); | 
|  | os_free(c); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int filter_strings(char **c, char *str, size_t len) | 
|  | { | 
|  | int i, j; | 
|  |  | 
|  | for (i = 0, j = 0; c[j]; j++) { | 
|  | if (os_strncasecmp(c[j], str, len) == 0) { | 
|  | if (i != j) { | 
|  | c[i] = c[j]; | 
|  | c[j] = NULL; | 
|  | } | 
|  | i++; | 
|  | } else { | 
|  | os_free(c[j]); | 
|  | c[j] = NULL; | 
|  | } | 
|  | } | 
|  | c[i] = NULL; | 
|  | return i; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int common_len(const char *a, const char *b) | 
|  | { | 
|  | int len = 0; | 
|  | while (a[len] && a[len] == b[len]) | 
|  | len++; | 
|  | return len; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int max_common_length(char **c) | 
|  | { | 
|  | int len, i; | 
|  |  | 
|  | len = os_strlen(c[0]); | 
|  | for (i = 1; c[i]; i++) { | 
|  | int same = common_len(c[0], c[i]); | 
|  | if (same < len) | 
|  | len = same; | 
|  | } | 
|  |  | 
|  | return len; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int cmp_str(const void *a, const void *b) | 
|  | { | 
|  | return os_strcmp(* (const char **) a, * (const char **) b); | 
|  | } | 
|  |  | 
|  | static void complete(int list) | 
|  | { | 
|  | char **c; | 
|  | int i, len, count; | 
|  | int start, end; | 
|  | int room, plen, add_space; | 
|  |  | 
|  | if (edit_completion_cb == NULL) | 
|  | return; | 
|  |  | 
|  | cmdbuf[cmdbuf_len] = '\0'; | 
|  | c = edit_completion_cb(edit_cb_ctx, cmdbuf, cmdbuf_pos); | 
|  | if (c == NULL) | 
|  | return; | 
|  |  | 
|  | end = cmdbuf_pos; | 
|  | start = end; | 
|  | while (start > 0 && cmdbuf[start - 1] != ' ') | 
|  | start--; | 
|  | plen = end - start; | 
|  |  | 
|  | count = filter_strings(c, &cmdbuf[start], plen); | 
|  | if (count == 0) { | 
|  | free_completions(c); | 
|  | return; | 
|  | } | 
|  |  | 
|  | len = max_common_length(c); | 
|  | if (len <= plen && count > 1) { | 
|  | if (list) { | 
|  | qsort(c, count, sizeof(char *), cmp_str); | 
|  | edit_clear_line(); | 
|  | printf("\r"); | 
|  | for (i = 0; c[i]; i++) | 
|  | printf("%s%s", i > 0 ? " " : "", c[i]); | 
|  | printf("\n"); | 
|  | edit_redraw(); | 
|  | } | 
|  | free_completions(c); | 
|  | return; | 
|  | } | 
|  | len -= plen; | 
|  |  | 
|  | room = sizeof(cmdbuf) - 1 - cmdbuf_len; | 
|  | if (room < len) | 
|  | len = room; | 
|  | add_space = count == 1 && len < room; | 
|  |  | 
|  | os_memmove(cmdbuf + cmdbuf_pos + len + add_space, cmdbuf + cmdbuf_pos, | 
|  | cmdbuf_len - cmdbuf_pos); | 
|  | os_memcpy(&cmdbuf[cmdbuf_pos - plen], c[0], plen + len); | 
|  | if (add_space) | 
|  | cmdbuf[cmdbuf_pos + len] = ' '; | 
|  |  | 
|  | cmdbuf_pos += len + add_space; | 
|  | cmdbuf_len += len + add_space; | 
|  |  | 
|  | edit_redraw(); | 
|  |  | 
|  | free_completions(c); | 
|  | } | 
|  |  | 
|  |  | 
|  | enum edit_key_code { | 
|  | EDIT_KEY_NONE = 256, | 
|  | EDIT_KEY_TAB, | 
|  | EDIT_KEY_UP, | 
|  | EDIT_KEY_DOWN, | 
|  | EDIT_KEY_RIGHT, | 
|  | EDIT_KEY_LEFT, | 
|  | EDIT_KEY_ENTER, | 
|  | EDIT_KEY_BACKSPACE, | 
|  | EDIT_KEY_INSERT, | 
|  | EDIT_KEY_DELETE, | 
|  | EDIT_KEY_HOME, | 
|  | EDIT_KEY_END, | 
|  | EDIT_KEY_PAGE_UP, | 
|  | EDIT_KEY_PAGE_DOWN, | 
|  | EDIT_KEY_F1, | 
|  | EDIT_KEY_F2, | 
|  | EDIT_KEY_F3, | 
|  | EDIT_KEY_F4, | 
|  | EDIT_KEY_F5, | 
|  | EDIT_KEY_F6, | 
|  | EDIT_KEY_F7, | 
|  | EDIT_KEY_F8, | 
|  | EDIT_KEY_F9, | 
|  | EDIT_KEY_F10, | 
|  | EDIT_KEY_F11, | 
|  | EDIT_KEY_F12, | 
|  | EDIT_KEY_CTRL_UP, | 
|  | EDIT_KEY_CTRL_DOWN, | 
|  | EDIT_KEY_CTRL_RIGHT, | 
|  | EDIT_KEY_CTRL_LEFT, | 
|  | EDIT_KEY_CTRL_A, | 
|  | EDIT_KEY_CTRL_B, | 
|  | EDIT_KEY_CTRL_D, | 
|  | EDIT_KEY_CTRL_E, | 
|  | EDIT_KEY_CTRL_F, | 
|  | EDIT_KEY_CTRL_G, | 
|  | EDIT_KEY_CTRL_H, | 
|  | EDIT_KEY_CTRL_J, | 
|  | EDIT_KEY_CTRL_K, | 
|  | EDIT_KEY_CTRL_L, | 
|  | EDIT_KEY_CTRL_N, | 
|  | EDIT_KEY_CTRL_O, | 
|  | EDIT_KEY_CTRL_P, | 
|  | EDIT_KEY_CTRL_R, | 
|  | EDIT_KEY_CTRL_T, | 
|  | EDIT_KEY_CTRL_U, | 
|  | EDIT_KEY_CTRL_V, | 
|  | EDIT_KEY_CTRL_W, | 
|  | EDIT_KEY_ALT_UP, | 
|  | EDIT_KEY_ALT_DOWN, | 
|  | EDIT_KEY_ALT_RIGHT, | 
|  | EDIT_KEY_ALT_LEFT, | 
|  | EDIT_KEY_SHIFT_UP, | 
|  | EDIT_KEY_SHIFT_DOWN, | 
|  | EDIT_KEY_SHIFT_RIGHT, | 
|  | EDIT_KEY_SHIFT_LEFT, | 
|  | EDIT_KEY_ALT_SHIFT_UP, | 
|  | EDIT_KEY_ALT_SHIFT_DOWN, | 
|  | EDIT_KEY_ALT_SHIFT_RIGHT, | 
|  | EDIT_KEY_ALT_SHIFT_LEFT, | 
|  | EDIT_KEY_EOF | 
|  | }; | 
|  |  | 
|  | static void show_esc_buf(const char *esc_buf, char c, int i) | 
|  | { | 
|  | edit_clear_line(); | 
|  | printf("\rESC buffer '%s' c='%c' [%d]\n", esc_buf, c, i); | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key1_no(char last) | 
|  | { | 
|  | switch (last) { | 
|  | case 'A': | 
|  | return EDIT_KEY_UP; | 
|  | case 'B': | 
|  | return EDIT_KEY_DOWN; | 
|  | case 'C': | 
|  | return EDIT_KEY_RIGHT; | 
|  | case 'D': | 
|  | return EDIT_KEY_LEFT; | 
|  | default: | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key1_shift(char last) | 
|  | { | 
|  | switch (last) { | 
|  | case 'A': | 
|  | return EDIT_KEY_SHIFT_UP; | 
|  | case 'B': | 
|  | return EDIT_KEY_SHIFT_DOWN; | 
|  | case 'C': | 
|  | return EDIT_KEY_SHIFT_RIGHT; | 
|  | case 'D': | 
|  | return EDIT_KEY_SHIFT_LEFT; | 
|  | default: | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key1_alt(char last) | 
|  | { | 
|  | switch (last) { | 
|  | case 'A': | 
|  | return EDIT_KEY_ALT_UP; | 
|  | case 'B': | 
|  | return EDIT_KEY_ALT_DOWN; | 
|  | case 'C': | 
|  | return EDIT_KEY_ALT_RIGHT; | 
|  | case 'D': | 
|  | return EDIT_KEY_ALT_LEFT; | 
|  | default: | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key1_alt_shift(char last) | 
|  | { | 
|  | switch (last) { | 
|  | case 'A': | 
|  | return EDIT_KEY_ALT_SHIFT_UP; | 
|  | case 'B': | 
|  | return EDIT_KEY_ALT_SHIFT_DOWN; | 
|  | case 'C': | 
|  | return EDIT_KEY_ALT_SHIFT_RIGHT; | 
|  | case 'D': | 
|  | return EDIT_KEY_ALT_SHIFT_LEFT; | 
|  | default: | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key1_ctrl(char last) | 
|  | { | 
|  | switch (last) { | 
|  | case 'A': | 
|  | return EDIT_KEY_CTRL_UP; | 
|  | case 'B': | 
|  | return EDIT_KEY_CTRL_DOWN; | 
|  | case 'C': | 
|  | return EDIT_KEY_CTRL_RIGHT; | 
|  | case 'D': | 
|  | return EDIT_KEY_CTRL_LEFT; | 
|  | default: | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key1(int param1, int param2, char last) | 
|  | { | 
|  | /* ESC-[<param1>;<param2><last> */ | 
|  |  | 
|  | if (param1 < 0 && param2 < 0) | 
|  | return esc_seq_to_key1_no(last); | 
|  |  | 
|  | if (param1 == 1 && param2 == 2) | 
|  | return esc_seq_to_key1_shift(last); | 
|  |  | 
|  | if (param1 == 1 && param2 == 3) | 
|  | return esc_seq_to_key1_alt(last); | 
|  |  | 
|  | if (param1 == 1 && param2 == 4) | 
|  | return esc_seq_to_key1_alt_shift(last); | 
|  |  | 
|  | if (param1 == 1 && param2 == 5) | 
|  | return esc_seq_to_key1_ctrl(last); | 
|  |  | 
|  | if (param2 < 0) { | 
|  | if (last != '~') | 
|  | return EDIT_KEY_NONE; | 
|  | switch (param1) { | 
|  | case 2: | 
|  | return EDIT_KEY_INSERT; | 
|  | case 3: | 
|  | return EDIT_KEY_DELETE; | 
|  | case 5: | 
|  | return EDIT_KEY_PAGE_UP; | 
|  | case 6: | 
|  | return EDIT_KEY_PAGE_DOWN; | 
|  | case 15: | 
|  | return EDIT_KEY_F5; | 
|  | case 17: | 
|  | return EDIT_KEY_F6; | 
|  | case 18: | 
|  | return EDIT_KEY_F7; | 
|  | case 19: | 
|  | return EDIT_KEY_F8; | 
|  | case 20: | 
|  | return EDIT_KEY_F9; | 
|  | case 21: | 
|  | return EDIT_KEY_F10; | 
|  | case 23: | 
|  | return EDIT_KEY_F11; | 
|  | case 24: | 
|  | return EDIT_KEY_F12; | 
|  | } | 
|  | } | 
|  |  | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key2(int param1, int param2, char last) | 
|  | { | 
|  | /* ESC-O<param1>;<param2><last> */ | 
|  |  | 
|  | if (param1 >= 0 || param2 >= 0) | 
|  | return EDIT_KEY_NONE; | 
|  |  | 
|  | switch (last) { | 
|  | case 'F': | 
|  | return EDIT_KEY_END; | 
|  | case 'H': | 
|  | return EDIT_KEY_HOME; | 
|  | case 'P': | 
|  | return EDIT_KEY_F1; | 
|  | case 'Q': | 
|  | return EDIT_KEY_F2; | 
|  | case 'R': | 
|  | return EDIT_KEY_F3; | 
|  | case 'S': | 
|  | return EDIT_KEY_F4; | 
|  | default: | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code esc_seq_to_key(char *seq) | 
|  | { | 
|  | char last, *pos; | 
|  | int param1 = -1, param2 = -1; | 
|  | enum edit_key_code ret = EDIT_KEY_NONE; | 
|  |  | 
|  | last = '\0'; | 
|  | for (pos = seq; *pos; pos++) | 
|  | last = *pos; | 
|  |  | 
|  | if (seq[1] >= '0' && seq[1] <= '9') { | 
|  | param1 = atoi(&seq[1]); | 
|  | pos = os_strchr(seq, ';'); | 
|  | if (pos) | 
|  | param2 = atoi(pos + 1); | 
|  | } | 
|  |  | 
|  | if (seq[0] == '[') | 
|  | ret = esc_seq_to_key1(param1, param2, last); | 
|  | else if (seq[0] == 'O') | 
|  | ret = esc_seq_to_key2(param1, param2, last); | 
|  |  | 
|  | if (ret != EDIT_KEY_NONE) | 
|  | return ret; | 
|  |  | 
|  | edit_clear_line(); | 
|  | printf("\rUnknown escape sequence '%s'\n", seq); | 
|  | edit_redraw(); | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code edit_read_key(int sock) | 
|  | { | 
|  | int c; | 
|  | unsigned char buf[1]; | 
|  | int res; | 
|  | static int esc = -1; | 
|  | static char esc_buf[7]; | 
|  |  | 
|  | res = read(sock, buf, 1); | 
|  | if (res < 0) | 
|  | perror("read"); | 
|  | if (res <= 0) | 
|  | return EDIT_KEY_EOF; | 
|  |  | 
|  | c = buf[0]; | 
|  |  | 
|  | if (esc >= 0) { | 
|  | if (c == 27 /* ESC */) { | 
|  | esc = 0; | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  |  | 
|  | if (esc == 6) { | 
|  | show_esc_buf(esc_buf, c, 0); | 
|  | esc = -1; | 
|  | } else { | 
|  | esc_buf[esc++] = c; | 
|  | esc_buf[esc] = '\0'; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (esc == 1) { | 
|  | if (esc_buf[0] != '[' && esc_buf[0] != 'O') { | 
|  | show_esc_buf(esc_buf, c, 1); | 
|  | esc = -1; | 
|  | return EDIT_KEY_NONE; | 
|  | } else | 
|  | return EDIT_KEY_NONE; /* Escape sequence continues */ | 
|  | } | 
|  |  | 
|  | if (esc > 1) { | 
|  | if ((c >= '0' && c <= '9') || c == ';') | 
|  | return EDIT_KEY_NONE; /* Escape sequence continues */ | 
|  |  | 
|  | if (c == '~' || (c >= 'A' && c <= 'Z')) { | 
|  | esc = -1; | 
|  | return esc_seq_to_key(esc_buf); | 
|  | } | 
|  |  | 
|  | show_esc_buf(esc_buf, c, 2); | 
|  | esc = -1; | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  |  | 
|  | switch (c) { | 
|  | case 1: | 
|  | return EDIT_KEY_CTRL_A; | 
|  | case 2: | 
|  | return EDIT_KEY_CTRL_B; | 
|  | case 4: | 
|  | return EDIT_KEY_CTRL_D; | 
|  | case 5: | 
|  | return EDIT_KEY_CTRL_E; | 
|  | case 6: | 
|  | return EDIT_KEY_CTRL_F; | 
|  | case 7: | 
|  | return EDIT_KEY_CTRL_G; | 
|  | case 8: | 
|  | return EDIT_KEY_CTRL_H; | 
|  | case 9: | 
|  | return EDIT_KEY_TAB; | 
|  | case 10: | 
|  | return EDIT_KEY_CTRL_J; | 
|  | case 13: /* CR */ | 
|  | return EDIT_KEY_ENTER; | 
|  | case 11: | 
|  | return EDIT_KEY_CTRL_K; | 
|  | case 12: | 
|  | return EDIT_KEY_CTRL_L; | 
|  | case 14: | 
|  | return EDIT_KEY_CTRL_N; | 
|  | case 15: | 
|  | return EDIT_KEY_CTRL_O; | 
|  | case 16: | 
|  | return EDIT_KEY_CTRL_P; | 
|  | case 18: | 
|  | return EDIT_KEY_CTRL_R; | 
|  | case 20: | 
|  | return EDIT_KEY_CTRL_T; | 
|  | case 21: | 
|  | return EDIT_KEY_CTRL_U; | 
|  | case 22: | 
|  | return EDIT_KEY_CTRL_V; | 
|  | case 23: | 
|  | return EDIT_KEY_CTRL_W; | 
|  | case 27: /* ESC */ | 
|  | esc = 0; | 
|  | return EDIT_KEY_NONE; | 
|  | case 127: | 
|  | return EDIT_KEY_BACKSPACE; | 
|  | default: | 
|  | return c; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static char search_buf[21]; | 
|  | static int search_skip; | 
|  |  | 
|  | static char * search_find(void) | 
|  | { | 
|  | struct edit_history *h; | 
|  | size_t len = os_strlen(search_buf); | 
|  | int skip = search_skip; | 
|  |  | 
|  | if (len == 0) | 
|  | return NULL; | 
|  |  | 
|  | dl_list_for_each(h, &history_list, struct edit_history, list) { | 
|  | if (os_strstr(h->str, search_buf)) { | 
|  | if (skip == 0) | 
|  | return h->str; | 
|  | skip--; | 
|  | } | 
|  | } | 
|  |  | 
|  | search_skip = 0; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_redraw(void) | 
|  | { | 
|  | char *match = search_find(); | 
|  | printf("\rsearch '%s': %s" CLEAR_END_LINE, | 
|  | search_buf, match ? match : ""); | 
|  | printf("\rsearch '%s", search_buf); | 
|  | fflush(stdout); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_start(void) | 
|  | { | 
|  | edit_clear_line(); | 
|  | search_buf[0] = '\0'; | 
|  | search_skip = 0; | 
|  | search_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_clear(void) | 
|  | { | 
|  | search_redraw(); | 
|  | printf("\r" CLEAR_END_LINE); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_stop(void) | 
|  | { | 
|  | char *match = search_find(); | 
|  | search_buf[0] = '\0'; | 
|  | search_clear(); | 
|  | if (match) { | 
|  | os_strlcpy(cmdbuf, match, CMD_BUF_LEN); | 
|  | cmdbuf_len = os_strlen(cmdbuf); | 
|  | cmdbuf_pos = cmdbuf_len; | 
|  | } | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_cancel(void) | 
|  | { | 
|  | search_buf[0] = '\0'; | 
|  | search_clear(); | 
|  | edit_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_backspace(void) | 
|  | { | 
|  | size_t len; | 
|  | len = os_strlen(search_buf); | 
|  | if (len == 0) | 
|  | return; | 
|  | search_buf[len - 1] = '\0'; | 
|  | search_skip = 0; | 
|  | search_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_next(void) | 
|  | { | 
|  | search_skip++; | 
|  | search_find(); | 
|  | search_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void search_char(char c) | 
|  | { | 
|  | size_t len; | 
|  | len = os_strlen(search_buf); | 
|  | if (len == sizeof(search_buf) - 1) | 
|  | return; | 
|  | search_buf[len] = c; | 
|  | search_buf[len + 1] = '\0'; | 
|  | search_skip = 0; | 
|  | search_redraw(); | 
|  | } | 
|  |  | 
|  |  | 
|  | static enum edit_key_code search_key(enum edit_key_code c) | 
|  | { | 
|  | switch (c) { | 
|  | case EDIT_KEY_ENTER: | 
|  | case EDIT_KEY_CTRL_J: | 
|  | case EDIT_KEY_LEFT: | 
|  | case EDIT_KEY_RIGHT: | 
|  | case EDIT_KEY_HOME: | 
|  | case EDIT_KEY_END: | 
|  | case EDIT_KEY_CTRL_A: | 
|  | case EDIT_KEY_CTRL_E: | 
|  | search_stop(); | 
|  | return c; | 
|  | case EDIT_KEY_DOWN: | 
|  | case EDIT_KEY_UP: | 
|  | search_cancel(); | 
|  | return EDIT_KEY_EOF; | 
|  | case EDIT_KEY_CTRL_H: | 
|  | case EDIT_KEY_BACKSPACE: | 
|  | search_backspace(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_R: | 
|  | search_next(); | 
|  | break; | 
|  | default: | 
|  | if (c >= 32 && c <= 255) | 
|  | search_char(c); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return EDIT_KEY_NONE; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx) | 
|  | { | 
|  | static int last_tab = 0; | 
|  | static int search = 0; | 
|  | enum edit_key_code c; | 
|  |  | 
|  | c = edit_read_key(sock); | 
|  |  | 
|  | if (search) { | 
|  | c = search_key(c); | 
|  | if (c == EDIT_KEY_NONE) | 
|  | return; | 
|  | search = 0; | 
|  | if (c == EDIT_KEY_EOF) | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (c != EDIT_KEY_TAB && c != EDIT_KEY_NONE) | 
|  | last_tab = 0; | 
|  |  | 
|  | switch (c) { | 
|  | case EDIT_KEY_NONE: | 
|  | break; | 
|  | case EDIT_KEY_EOF: | 
|  | edit_eof_cb(edit_cb_ctx); | 
|  | break; | 
|  | case EDIT_KEY_TAB: | 
|  | complete(last_tab); | 
|  | last_tab = 1; | 
|  | break; | 
|  | case EDIT_KEY_UP: | 
|  | case EDIT_KEY_CTRL_P: | 
|  | history_prev(); | 
|  | break; | 
|  | case EDIT_KEY_DOWN: | 
|  | case EDIT_KEY_CTRL_N: | 
|  | history_next(); | 
|  | break; | 
|  | case EDIT_KEY_RIGHT: | 
|  | case EDIT_KEY_CTRL_F: | 
|  | move_right(); | 
|  | break; | 
|  | case EDIT_KEY_LEFT: | 
|  | case EDIT_KEY_CTRL_B: | 
|  | move_left(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_RIGHT: | 
|  | move_word_right(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_LEFT: | 
|  | move_word_left(); | 
|  | break; | 
|  | case EDIT_KEY_DELETE: | 
|  | delete_current(); | 
|  | break; | 
|  | case EDIT_KEY_END: | 
|  | move_end(); | 
|  | break; | 
|  | case EDIT_KEY_HOME: | 
|  | case EDIT_KEY_CTRL_A: | 
|  | move_start(); | 
|  | break; | 
|  | case EDIT_KEY_F2: | 
|  | history_debug_dump(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_D: | 
|  | if (cmdbuf_len > 0) { | 
|  | delete_current(); | 
|  | return; | 
|  | } | 
|  | printf("\n"); | 
|  | edit_eof_cb(edit_cb_ctx); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_E: | 
|  | move_end(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_H: | 
|  | case EDIT_KEY_BACKSPACE: | 
|  | delete_left(); | 
|  | break; | 
|  | case EDIT_KEY_ENTER: | 
|  | case EDIT_KEY_CTRL_J: | 
|  | process_cmd(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_K: | 
|  | clear_right(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_L: | 
|  | edit_clear_line(); | 
|  | edit_redraw(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_R: | 
|  | search = 1; | 
|  | search_start(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_U: | 
|  | clear_left(); | 
|  | break; | 
|  | case EDIT_KEY_CTRL_W: | 
|  | delete_word(); | 
|  | break; | 
|  | default: | 
|  | if (c >= 32 && c <= 255) | 
|  | insert_char(c); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | int edit_init(void (*cmd_cb)(void *ctx, char *cmd), | 
|  | void (*eof_cb)(void *ctx), | 
|  | char ** (*completion_cb)(void *ctx, const char *cmd, int pos), | 
|  | void *ctx, const char *history_file, const char *ps) | 
|  | { | 
|  | currbuf[0] = '\0'; | 
|  | dl_list_init(&history_list); | 
|  | history_curr = NULL; | 
|  | if (history_file) | 
|  | history_read(history_file); | 
|  |  | 
|  | edit_cb_ctx = ctx; | 
|  | edit_cmd_cb = cmd_cb; | 
|  | edit_eof_cb = eof_cb; | 
|  | edit_completion_cb = completion_cb; | 
|  |  | 
|  | tcgetattr(STDIN_FILENO, &prevt); | 
|  | newt = prevt; | 
|  | newt.c_lflag &= ~(ICANON | ECHO); | 
|  | tcsetattr(STDIN_FILENO, TCSANOW, &newt); | 
|  |  | 
|  | eloop_register_read_sock(STDIN_FILENO, edit_read_char, NULL, NULL); | 
|  |  | 
|  | ps2 = ps; | 
|  | printf("%s> ", ps2 ? ps2 : ""); | 
|  | fflush(stdout); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | void edit_deinit(const char *history_file, | 
|  | int (*filter_cb)(void *ctx, const char *cmd)) | 
|  | { | 
|  | struct edit_history *h; | 
|  | if (history_file) | 
|  | history_write(history_file, filter_cb); | 
|  | while ((h = dl_list_first(&history_list, struct edit_history, list))) { | 
|  | dl_list_del(&h->list); | 
|  | os_free(h); | 
|  | } | 
|  | edit_clear_line(); | 
|  | putchar('\r'); | 
|  | fflush(stdout); | 
|  | eloop_unregister_read_sock(STDIN_FILENO); | 
|  | tcsetattr(STDIN_FILENO, TCSANOW, &prevt); | 
|  | } | 
|  |  | 
|  |  | 
|  | void edit_redraw(void) | 
|  | { | 
|  | char tmp; | 
|  | cmdbuf[cmdbuf_len] = '\0'; | 
|  | printf("\r%s> %s", ps2 ? ps2 : "", cmdbuf); | 
|  | if (cmdbuf_pos != cmdbuf_len) { | 
|  | tmp = cmdbuf[cmdbuf_pos]; | 
|  | cmdbuf[cmdbuf_pos] = '\0'; | 
|  | printf("\r%s> %s", ps2 ? ps2 : "", cmdbuf); | 
|  | cmdbuf[cmdbuf_pos] = tmp; | 
|  | } | 
|  | fflush(stdout); | 
|  | } |