/* * QEMU readline utility * * Copyright (c) 2003-2004 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "readline.h" #include "monitor.h" #define IS_NORM 0 #define IS_ESC 1 #define IS_CSI 2 #define printf do_not_use_printf void readline_show_prompt(ReadLineState *rs) { monitor_printf(rs->mon, "%s", rs->prompt); monitor_flush(rs->mon); rs->last_cmd_buf_index = 0; rs->last_cmd_buf_size = 0; rs->esc_state = IS_NORM; } /* update the displayed command line */ static void readline_update(ReadLineState *rs) { int i, delta, len; if (rs->cmd_buf_size != rs->last_cmd_buf_size || memcmp(rs->cmd_buf, rs->last_cmd_buf, rs->cmd_buf_size) != 0) { for(i = 0; i < rs->last_cmd_buf_index; i++) { monitor_printf(rs->mon, "\033[D"); } rs->cmd_buf[rs->cmd_buf_size] = '\0'; if (rs->read_password) { len = strlen(rs->cmd_buf); for(i = 0; i < len; i++) monitor_printf(rs->mon, "*"); } else { monitor_printf(rs->mon, "%s", rs->cmd_buf); } monitor_printf(rs->mon, "\033[K"); memcpy(rs->last_cmd_buf, rs->cmd_buf, rs->cmd_buf_size); rs->last_cmd_buf_size = rs->cmd_buf_size; rs->last_cmd_buf_index = rs->cmd_buf_size; } if (rs->cmd_buf_index != rs->last_cmd_buf_index) { delta = rs->cmd_buf_index - rs->last_cmd_buf_index; if (delta > 0) { for(i = 0;i < delta; i++) { monitor_printf(rs->mon, "\033[C"); } } else { delta = -delta; for(i = 0;i < delta; i++) { monitor_printf(rs->mon, "\033[D"); } } rs->last_cmd_buf_index = rs->cmd_buf_index; } monitor_flush(rs->mon); } static void readline_insert_char(ReadLineState *rs, int ch) { if (rs->cmd_buf_index < READLINE_CMD_BUF_SIZE) { memmove(rs->cmd_buf + rs->cmd_buf_index + 1, rs->cmd_buf + rs->cmd_buf_index, rs->cmd_buf_size - rs->cmd_buf_index); rs->cmd_buf[rs->cmd_buf_index] = ch; rs->cmd_buf_size++; rs->cmd_buf_index++; } } static void readline_backward_char(ReadLineState *rs) { if (rs->cmd_buf_index > 0) { rs->cmd_buf_index--; } } static void readline_forward_char(ReadLineState *rs) { if (rs->cmd_buf_index < rs->cmd_buf_size) { rs->cmd_buf_index++; } } static void readline_delete_char(ReadLineState *rs) { if (rs->cmd_buf_index < rs->cmd_buf_size) { memmove(rs->cmd_buf + rs->cmd_buf_index, rs->cmd_buf + rs->cmd_buf_index + 1, rs->cmd_buf_size - rs->cmd_buf_index - 1); rs->cmd_buf_size--; } } static void readline_backspace(ReadLineState *rs) { if (rs->cmd_buf_index > 0) { readline_backward_char(rs); readline_delete_char(rs); } } static void readline_backword(ReadLineState *rs) { int start; if (rs->cmd_buf_index == 0 || rs->cmd_buf_index > rs->cmd_buf_size) { return; } start = rs->cmd_buf_index - 1; /* find first word (backwards) */ while (start > 0) { if (!qemu_isspace(rs->cmd_buf[start])) { break; } --start; } /* find first space (backwards) */ while (start > 0) { if (qemu_isspace(rs->cmd_buf[start])) { ++start; break; } --start; } /* remove word */ if (start < rs->cmd_buf_index) { memmove(rs->cmd_buf + start, rs->cmd_buf + rs->cmd_buf_index, rs->cmd_buf_size - rs->cmd_buf_index); rs->cmd_buf_size -= rs->cmd_buf_index - start; rs->cmd_buf_index = start; } } static void readline_bol(ReadLineState *rs) { rs->cmd_buf_index = 0; } static void readline_eol(ReadLineState *rs) { rs->cmd_buf_index = rs->cmd_buf_size; } static void readline_up_char(ReadLineState *rs) { int idx; if (rs->hist_entry == 0) return; if (rs->hist_entry == -1) { /* Find latest entry */ for (idx = 0; idx < READLINE_MAX_CMDS; idx++) { if (rs->history[idx] == NULL) break; } rs->hist_entry = idx; } rs->hist_entry--; if (rs->hist_entry >= 0) { pstrcpy(rs->cmd_buf, sizeof(rs->cmd_buf), rs->history[rs->hist_entry]); rs->cmd_buf_index = rs->cmd_buf_size = strlen(rs->cmd_buf); } } static void readline_down_char(ReadLineState *rs) { if (rs->hist_entry == -1) return; if (rs->hist_entry < READLINE_MAX_CMDS - 1 && rs->history[++rs->hist_entry] != NULL) { pstrcpy(rs->cmd_buf, sizeof(rs->cmd_buf), rs->history[rs->hist_entry]); } else { rs->cmd_buf[0] = 0; rs->hist_entry = -1; } rs->cmd_buf_index = rs->cmd_buf_size = strlen(rs->cmd_buf); } static void readline_hist_add(ReadLineState *rs, const char *cmdline) { char *hist_entry, *new_entry; int idx; if (cmdline[0] == '\0') return; new_entry = NULL; if (rs->hist_entry != -1) { /* We were editing an existing history entry: replace it */ hist_entry = rs->history[rs->hist_entry]; idx = rs->hist_entry; if (strcmp(hist_entry, cmdline) == 0) { goto same_entry; } } /* Search cmdline in history buffers */ for (idx = 0; idx < READLINE_MAX_CMDS; idx++) { hist_entry = rs->history[idx]; if (hist_entry == NULL) break; if (strcmp(hist_entry, cmdline) == 0) { same_entry: new_entry = hist_entry; /* Put this entry at the end of history */ memmove(&rs->history[idx], &rs->history[idx + 1], (READLINE_MAX_CMDS - idx + 1) * sizeof(char *)); rs->history[READLINE_MAX_CMDS - 1] = NULL; for (; idx < READLINE_MAX_CMDS; idx++) { if (rs->history[idx] == NULL) break; } break; } } if (idx == READLINE_MAX_CMDS) { /* Need to get one free slot */ free(rs->history[0]); memcpy(rs->history, &rs->history[1], (READLINE_MAX_CMDS - 1) * sizeof(char *)); rs->history[READLINE_MAX_CMDS - 1] = NULL; idx = READLINE_MAX_CMDS - 1; } if (new_entry == NULL) new_entry = strdup(cmdline); rs->history[idx] = new_entry; rs->hist_entry = -1; } /* completion support */ void readline_add_completion(ReadLineState *rs, const char *str) { if (rs->nb_completions < READLINE_MAX_COMPLETIONS) { rs->completions[rs->nb_completions++] = qemu_strdup(str); } } void readline_set_completion_index(ReadLineState *rs, int index) { rs->completion_index = index; } static void readline_completion(ReadLineState *rs) { Monitor *mon = cur_mon; int len, i, j, max_width, nb_cols, max_prefix; char *cmdline; rs->nb_completions = 0; cmdline = qemu_malloc(rs->cmd_buf_index + 1); memcpy(cmdline, rs->cmd_buf, rs->cmd_buf_index); cmdline[rs->cmd_buf_index] = '\0'; rs->completion_finder(cmdline); qemu_free(cmdline); /* no completion found */ if (rs->nb_completions <= 0) return; if (rs->nb_completions == 1) { len = strlen(rs->completions[0]); for(i = rs->completion_index; i < len; i++) { readline_insert_char(rs, rs->completions[0][i]); } /* extra space for next argument. XXX: make it more generic */ if (len > 0 && rs->completions[0][len - 1] != '/') readline_insert_char(rs, ' '); } else { monitor_printf(mon, "\n"); max_width = 0; max_prefix = 0; for(i = 0; i < rs->nb_completions; i++) { len = strlen(rs->completions[i]); if (i==0) { max_prefix = len; } else { if (len < max_prefix) max_prefix = len; for(j=0; j<max_prefix; j++) { if (rs->completions[i][j] != rs->completions[0][j]) max_prefix = j; } } if (len > max_width) max_width = len; } if (max_prefix > 0) for(i = rs->completion_index; i < max_prefix; i++) { readline_insert_char(rs, rs->completions[0][i]); } max_width += 2; if (max_width < 10) max_width = 10; else if (max_width > 80) max_width = 80; nb_cols = 80 / max_width; j = 0; for(i = 0; i < rs->nb_completions; i++) { monitor_printf(rs->mon, "%-*s", max_width, rs->completions[i]); if (++j == nb_cols || i == (rs->nb_completions - 1)) { monitor_printf(rs->mon, "\n"); j = 0; } } readline_show_prompt(rs); } } /* return true if command handled */ void readline_handle_byte(ReadLineState *rs, int ch) { switch(rs->esc_state) { case IS_NORM: switch(ch) { case 1: readline_bol(rs); break; case 4: readline_delete_char(rs); break; case 5: readline_eol(rs); break; case 9: readline_completion(rs); break; case 10: case 13: rs->cmd_buf[rs->cmd_buf_size] = '\0'; if (!rs->read_password) readline_hist_add(rs, rs->cmd_buf); monitor_printf(rs->mon, "\n"); rs->cmd_buf_index = 0; rs->cmd_buf_size = 0; rs->last_cmd_buf_index = 0; rs->last_cmd_buf_size = 0; rs->readline_func(rs->mon, rs->cmd_buf, rs->readline_opaque); break; case 23: /* ^W */ readline_backword(rs); break; case 27: rs->esc_state = IS_ESC; break; case 127: case 8: readline_backspace(rs); break; case 155: rs->esc_state = IS_CSI; break; default: if (ch >= 32) { readline_insert_char(rs, ch); } break; } break; case IS_ESC: if (ch == '[') { rs->esc_state = IS_CSI; rs->esc_param = 0; } else { rs->esc_state = IS_NORM; } break; case IS_CSI: switch(ch) { case 'A': case 'F': readline_up_char(rs); break; case 'B': case 'E': readline_down_char(rs); break; case 'D': readline_backward_char(rs); break; case 'C': readline_forward_char(rs); break; case '0' ... '9': rs->esc_param = rs->esc_param * 10 + (ch - '0'); goto the_end; case '~': switch(rs->esc_param) { case 1: readline_bol(rs); break; case 3: readline_delete_char(rs); break; case 4: readline_eol(rs); break; } break; default: break; } rs->esc_state = IS_NORM; the_end: break; } readline_update(rs); } void readline_start(ReadLineState *rs, const char *prompt, int read_password, ReadLineFunc *readline_func, void *opaque) { pstrcpy(rs->prompt, sizeof(rs->prompt), prompt); rs->readline_func = readline_func; rs->readline_opaque = opaque; rs->read_password = read_password; readline_restart(rs); } void readline_restart(ReadLineState *rs) { rs->cmd_buf_index = 0; rs->cmd_buf_size = 0; } const char *readline_get_history(ReadLineState *rs, unsigned int index) { if (index >= READLINE_MAX_CMDS) return NULL; return rs->history[index]; } ReadLineState *readline_init(Monitor *mon, ReadLineCompletionFunc *completion_finder) { ReadLineState *rs = qemu_mallocz(sizeof(*rs)); rs->hist_entry = -1; rs->mon = mon; rs->completion_finder = completion_finder; return rs; }