| /* |
| * 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 256 |
| 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) |
| { |
| |
| 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); |
| } |