Browse Source
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1034 c046a42c-6fe2-441c-8c8c-71466251a162stable-0.10
2 changed files with 521 additions and 385 deletions
@ -0,0 +1,425 @@ |
|||
/*
|
|||
* 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 "vl.h" |
|||
|
|||
#define TERM_CMD_BUF_SIZE 4095 |
|||
#define TERM_MAX_CMDS 64 |
|||
#define NB_COMPLETIONS_MAX 256 |
|||
|
|||
#define IS_NORM 0 |
|||
#define IS_ESC 1 |
|||
#define IS_CSI 2 |
|||
|
|||
#define printf do_not_use_printf |
|||
|
|||
static char term_cmd_buf[TERM_CMD_BUF_SIZE + 1]; |
|||
static int term_cmd_buf_index; |
|||
static int term_cmd_buf_size; |
|||
|
|||
static char term_last_cmd_buf[TERM_CMD_BUF_SIZE + 1]; |
|||
static int term_last_cmd_buf_index; |
|||
static int term_last_cmd_buf_size; |
|||
|
|||
static int term_esc_state; |
|||
static int term_esc_param; |
|||
|
|||
static char *term_history[TERM_MAX_CMDS]; |
|||
static int term_hist_entry = -1; |
|||
|
|||
static int nb_completions; |
|||
int completion_index; |
|||
static char *completions[NB_COMPLETIONS_MAX]; |
|||
|
|||
static ReadLineFunc *term_readline_func; |
|||
static int term_is_password; |
|||
static char term_prompt[256]; |
|||
static void *term_readline_opaque; |
|||
|
|||
static void term_show_prompt2(void) |
|||
{ |
|||
term_printf("%s", term_prompt); |
|||
term_flush(); |
|||
term_last_cmd_buf_index = 0; |
|||
term_last_cmd_buf_size = 0; |
|||
term_esc_state = IS_NORM; |
|||
} |
|||
|
|||
static void term_show_prompt(void) |
|||
{ |
|||
term_show_prompt2(); |
|||
term_cmd_buf_index = 0; |
|||
term_cmd_buf_size = 0; |
|||
} |
|||
|
|||
/* update the displayed command line */ |
|||
static void term_update(void) |
|||
{ |
|||
int i, delta, len; |
|||
|
|||
if (term_cmd_buf_size != term_last_cmd_buf_size || |
|||
memcmp(term_cmd_buf, term_last_cmd_buf, term_cmd_buf_size) != 0) { |
|||
for(i = 0; i < term_last_cmd_buf_index; i++) { |
|||
term_printf("\033[D"); |
|||
} |
|||
term_cmd_buf[term_cmd_buf_size] = '\0'; |
|||
if (term_is_password) { |
|||
len = strlen(term_cmd_buf); |
|||
for(i = 0; i < len; i++) |
|||
term_printf("*"); |
|||
} else { |
|||
term_printf("%s", term_cmd_buf); |
|||
} |
|||
term_printf("\033[K"); |
|||
memcpy(term_last_cmd_buf, term_cmd_buf, term_cmd_buf_size); |
|||
term_last_cmd_buf_size = term_cmd_buf_size; |
|||
term_last_cmd_buf_index = term_cmd_buf_size; |
|||
} |
|||
if (term_cmd_buf_index != term_last_cmd_buf_index) { |
|||
delta = term_cmd_buf_index - term_last_cmd_buf_index; |
|||
if (delta > 0) { |
|||
for(i = 0;i < delta; i++) { |
|||
term_printf("\033[C"); |
|||
} |
|||
} else { |
|||
delta = -delta; |
|||
for(i = 0;i < delta; i++) { |
|||
term_printf("\033[D"); |
|||
} |
|||
} |
|||
term_last_cmd_buf_index = term_cmd_buf_index; |
|||
} |
|||
term_flush(); |
|||
} |
|||
|
|||
static void term_insert_char(int ch) |
|||
{ |
|||
if (term_cmd_buf_index < TERM_CMD_BUF_SIZE) { |
|||
memmove(term_cmd_buf + term_cmd_buf_index + 1, |
|||
term_cmd_buf + term_cmd_buf_index, |
|||
term_cmd_buf_size - term_cmd_buf_index); |
|||
term_cmd_buf[term_cmd_buf_index] = ch; |
|||
term_cmd_buf_size++; |
|||
term_cmd_buf_index++; |
|||
} |
|||
} |
|||
|
|||
static void term_backward_char(void) |
|||
{ |
|||
if (term_cmd_buf_index > 0) { |
|||
term_cmd_buf_index--; |
|||
} |
|||
} |
|||
|
|||
static void term_forward_char(void) |
|||
{ |
|||
if (term_cmd_buf_index < term_cmd_buf_size) { |
|||
term_cmd_buf_index++; |
|||
} |
|||
} |
|||
|
|||
static void term_delete_char(void) |
|||
{ |
|||
if (term_cmd_buf_index < term_cmd_buf_size) { |
|||
memmove(term_cmd_buf + term_cmd_buf_index, |
|||
term_cmd_buf + term_cmd_buf_index + 1, |
|||
term_cmd_buf_size - term_cmd_buf_index - 1); |
|||
term_cmd_buf_size--; |
|||
} |
|||
} |
|||
|
|||
static void term_backspace(void) |
|||
{ |
|||
if (term_cmd_buf_index > 0) { |
|||
term_backward_char(); |
|||
term_delete_char(); |
|||
} |
|||
} |
|||
|
|||
static void term_bol(void) |
|||
{ |
|||
term_cmd_buf_index = 0; |
|||
} |
|||
|
|||
static void term_eol(void) |
|||
{ |
|||
term_cmd_buf_index = term_cmd_buf_size; |
|||
} |
|||
|
|||
static void term_up_char(void) |
|||
{ |
|||
int idx; |
|||
|
|||
if (term_hist_entry == 0) |
|||
return; |
|||
if (term_hist_entry == -1) { |
|||
/* Find latest entry */ |
|||
for (idx = 0; idx < TERM_MAX_CMDS; idx++) { |
|||
if (term_history[idx] == NULL) |
|||
break; |
|||
} |
|||
term_hist_entry = idx; |
|||
} |
|||
term_hist_entry--; |
|||
if (term_hist_entry >= 0) { |
|||
pstrcpy(term_cmd_buf, sizeof(term_cmd_buf), |
|||
term_history[term_hist_entry]); |
|||
term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf); |
|||
} |
|||
} |
|||
|
|||
static void term_down_char(void) |
|||
{ |
|||
if (term_hist_entry == TERM_MAX_CMDS - 1 || term_hist_entry == -1) |
|||
return; |
|||
if (term_history[++term_hist_entry] != NULL) { |
|||
pstrcpy(term_cmd_buf, sizeof(term_cmd_buf), |
|||
term_history[term_hist_entry]); |
|||
} else { |
|||
term_hist_entry = -1; |
|||
} |
|||
term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf); |
|||
} |
|||
|
|||
static void term_hist_add(const char *cmdline) |
|||
{ |
|||
char *hist_entry, *new_entry; |
|||
int idx; |
|||
|
|||
if (cmdline[0] == '\0') |
|||
return; |
|||
new_entry = NULL; |
|||
if (term_hist_entry != -1) { |
|||
/* We were editing an existing history entry: replace it */ |
|||
hist_entry = term_history[term_hist_entry]; |
|||
idx = term_hist_entry; |
|||
if (strcmp(hist_entry, cmdline) == 0) { |
|||
goto same_entry; |
|||
} |
|||
} |
|||
/* Search cmdline in history buffers */ |
|||
for (idx = 0; idx < TERM_MAX_CMDS; idx++) { |
|||
hist_entry = term_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(&term_history[idx], &term_history[idx + 1], |
|||
&term_history[TERM_MAX_CMDS] - &term_history[idx + 1]); |
|||
term_history[TERM_MAX_CMDS - 1] = NULL; |
|||
for (; idx < TERM_MAX_CMDS; idx++) { |
|||
if (term_history[idx] == NULL) |
|||
break; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
if (idx == TERM_MAX_CMDS) { |
|||
/* Need to get one free slot */ |
|||
free(term_history[0]); |
|||
memcpy(term_history, &term_history[1], |
|||
&term_history[TERM_MAX_CMDS] - &term_history[1]); |
|||
term_history[TERM_MAX_CMDS - 1] = NULL; |
|||
idx = TERM_MAX_CMDS - 1; |
|||
} |
|||
if (new_entry == NULL) |
|||
new_entry = strdup(cmdline); |
|||
term_history[idx] = new_entry; |
|||
term_hist_entry = -1; |
|||
} |
|||
|
|||
/* completion support */ |
|||
|
|||
void add_completion(const char *str) |
|||
{ |
|||
if (nb_completions < NB_COMPLETIONS_MAX) { |
|||
completions[nb_completions++] = qemu_strdup(str); |
|||
} |
|||
} |
|||
|
|||
static void term_completion(void) |
|||
{ |
|||
int len, i, j, max_width, nb_cols; |
|||
char *cmdline; |
|||
|
|||
nb_completions = 0; |
|||
|
|||
cmdline = qemu_malloc(term_cmd_buf_index + 1); |
|||
if (!cmdline) |
|||
return; |
|||
memcpy(cmdline, term_cmd_buf, term_cmd_buf_index); |
|||
cmdline[term_cmd_buf_index] = '\0'; |
|||
readline_find_completion(cmdline); |
|||
qemu_free(cmdline); |
|||
|
|||
/* no completion found */ |
|||
if (nb_completions <= 0) |
|||
return; |
|||
if (nb_completions == 1) { |
|||
len = strlen(completions[0]); |
|||
for(i = completion_index; i < len; i++) { |
|||
term_insert_char(completions[0][i]); |
|||
} |
|||
/* extra space for next argument. XXX: make it more generic */ |
|||
if (len > 0 && completions[0][len - 1] != '/') |
|||
term_insert_char(' '); |
|||
} else { |
|||
term_printf("\n"); |
|||
max_width = 0; |
|||
for(i = 0; i < nb_completions; i++) { |
|||
len = strlen(completions[i]); |
|||
if (len > max_width) |
|||
max_width = len; |
|||
} |
|||
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 < nb_completions; i++) { |
|||
term_printf("%-*s", max_width, completions[i]); |
|||
if (++j == nb_cols || i == (nb_completions - 1)) { |
|||
term_printf("\n"); |
|||
j = 0; |
|||
} |
|||
} |
|||
term_show_prompt2(); |
|||
} |
|||
} |
|||
|
|||
/* return true if command handled */ |
|||
void readline_handle_byte(int ch) |
|||
{ |
|||
switch(term_esc_state) { |
|||
case IS_NORM: |
|||
switch(ch) { |
|||
case 1: |
|||
term_bol(); |
|||
break; |
|||
case 4: |
|||
term_delete_char(); |
|||
break; |
|||
case 5: |
|||
term_eol(); |
|||
break; |
|||
case 9: |
|||
term_completion(); |
|||
break; |
|||
case 10: |
|||
case 13: |
|||
term_cmd_buf[term_cmd_buf_size] = '\0'; |
|||
if (!term_is_password) |
|||
term_hist_add(term_cmd_buf); |
|||
term_printf("\n"); |
|||
/* NOTE: readline_start can be called here */ |
|||
term_readline_func(term_readline_opaque, term_cmd_buf); |
|||
break; |
|||
case 27: |
|||
term_esc_state = IS_ESC; |
|||
break; |
|||
case 127: |
|||
case 8: |
|||
term_backspace(); |
|||
break; |
|||
case 155: |
|||
term_esc_state = IS_CSI; |
|||
break; |
|||
default: |
|||
if (ch >= 32) { |
|||
term_insert_char(ch); |
|||
} |
|||
break; |
|||
} |
|||
break; |
|||
case IS_ESC: |
|||
if (ch == '[') { |
|||
term_esc_state = IS_CSI; |
|||
term_esc_param = 0; |
|||
} else { |
|||
term_esc_state = IS_NORM; |
|||
} |
|||
break; |
|||
case IS_CSI: |
|||
switch(ch) { |
|||
case 'A': |
|||
case 'F': |
|||
term_up_char(); |
|||
break; |
|||
case 'B': |
|||
case 'E': |
|||
term_down_char(); |
|||
break; |
|||
case 'D': |
|||
term_backward_char(); |
|||
break; |
|||
case 'C': |
|||
term_forward_char(); |
|||
break; |
|||
case '0' ... '9': |
|||
term_esc_param = term_esc_param * 10 + (ch - '0'); |
|||
goto the_end; |
|||
case '~': |
|||
switch(term_esc_param) { |
|||
case 1: |
|||
term_bol(); |
|||
break; |
|||
case 3: |
|||
term_delete_char(); |
|||
break; |
|||
case 4: |
|||
term_eol(); |
|||
break; |
|||
} |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
term_esc_state = IS_NORM; |
|||
the_end: |
|||
break; |
|||
} |
|||
term_update(); |
|||
} |
|||
|
|||
void readline_start(const char *prompt, int is_password, |
|||
ReadLineFunc *readline_func, void *opaque) |
|||
{ |
|||
pstrcpy(term_prompt, sizeof(term_prompt), prompt); |
|||
term_readline_func = readline_func; |
|||
term_readline_opaque = opaque; |
|||
term_is_password = is_password; |
|||
term_show_prompt(); |
|||
} |
|||
|
|||
const char *readline_get_history(unsigned int index) |
|||
{ |
|||
if (index >= TERM_MAX_CMDS) |
|||
return NULL; |
|||
return term_history[index]; |
|||
} |
|||
|
|||
|
|||
Loading…
Reference in new issue