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