completion support


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1020 c046a42c-6fe2-441c-8c8c-71466251a162
diff --git a/monitor.c b/monitor.c
index 3c55c62..47b047a 100644
--- a/monitor.c
+++ b/monitor.c
@@ -23,8 +23,10 @@
  */
 #include "vl.h"
 #include "disas.h"
+#include <dirent.h>
 
 //#define DEBUG
+//#define DEBUG_COMPLETION
 
 #ifndef offsetof
 #define offsetof(type, field) ((size_t) &((type *)0)->field)
@@ -32,6 +34,7 @@
 
 #define TERM_CMD_BUF_SIZE 4095
 #define TERM_MAX_CMDS 64
+#define NB_COMPLETIONS_MAX 256
 
 #define IS_NORM 0
 #define IS_ESC  1
@@ -42,16 +45,28 @@
 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;
+static CharDriverState *monitor_hd;
+
+static int nb_completions;
+static int completion_index;
+static char *completions[NB_COMPLETIONS_MAX];
+
 
 /*
  * Supported types:
  * 
  * 'F'          filename
+ * 'B'          block device name
  * 's'          string (accept optional quote)
  * 'i'          integer
  * '/'          optional gdb-like print format (like "/10x")
@@ -71,17 +86,20 @@
 static term_cmd_t term_cmds[];
 static term_cmd_t info_cmds[];
 
+static void add_completion(const char *str);
+
 void term_printf(const char *fmt, ...)
 {
+    char buf[4096];
     va_list ap;
     va_start(ap, fmt);
-    vprintf(fmt, ap);
+    vsnprintf(buf, sizeof(buf), fmt, ap);
+    qemu_chr_write(monitor_hd, buf, strlen(buf));
     va_end(ap);
 }
 
 void term_flush(void)
 {
-    fflush(stdout);
 }
 
 static int compare_cmd(const char *name, const char *list)
@@ -232,8 +250,6 @@
 {
     BlockDriverState *bs;
 
-    term_printf("%d %s\n", force, filename);
-
     bs = bdrv_find(filename);
     if (!bs) {
         term_printf("device not found\n");
@@ -674,9 +690,9 @@
       "subcommand", "show various information about the system state" },
     { "q|quit", "", do_quit,
       "", "quit the emulator" },
-    { "eject", "-fs", do_eject,
+    { "eject", "-fB", do_eject,
       "[-f] device", "eject a removable media (use -f to force it)" },
-    { "change", "sF", do_change,
+    { "change", "BF", do_change,
       "device filename", "change a removable media" },
     { "screendump", "F", do_screen_dump, 
       "filename", "save screen into PPM image 'filename'" },
@@ -953,6 +969,16 @@
         }
         next();
         break;
+    case '\'':
+        pch++;
+        if (*pch == '\0')
+            expr_error("character constant expected");
+        n = *pch;
+        pch++;
+        if (*pch != '\'')
+            expr_error("missing terminating \' character");
+        next();
+        break;
     case '$':
         {
             char buf[128], *q;
@@ -1088,15 +1114,16 @@
     char *q;
     int c;
 
+    q = buf;
     p = *pp;
     while (isspace(*p))
         p++;
     if (*p == '\0') {
     fail:
+        *q = '\0';
         *pp = p;
         return -1;
     }
-    q = buf;
     if (*p == '\"') {
         p++;
         while (*p != '\0' && *p != '\"') {
@@ -1140,8 +1167,8 @@
             }
             p++;
         }
-        *q = '\0';
     }
+    *q = '\0';
     *pp = p;
     return 0;
 }
@@ -1204,6 +1231,7 @@
         typestr++;
         switch(c) {
         case 'F':
+        case 'B':
         case 's':
             {
                 int ret;
@@ -1221,10 +1249,17 @@
                 }
                 ret = get_str(buf, sizeof(buf), &p);
                 if (ret < 0) {
-                    if (c == 'F')
+                    switch(c) {
+                    case 'F':
                         term_printf("%s: filename expected\n", cmdname);
-                    else
+                        break;
+                    case 'B':
+                        term_printf("%s: block device name expected\n", cmdname);
+                        break;
+                    default:
                         term_printf("%s: string expected\n", cmdname);
+                        break;
+                    }
                     goto fail;
                 }
                 str = qemu_malloc(strlen(buf) + 1);
@@ -1432,19 +1467,232 @@
     return;
 }
 
-static void term_show_prompt(void)
+static void cmd_completion(const char *name, const char *list)
+{
+    const char *p, *pstart;
+    char cmd[128];
+    int len;
+
+    p = list;
+    for(;;) {
+        pstart = p;
+        p = strchr(p, '|');
+        if (!p)
+            p = pstart + strlen(pstart);
+        len = p - pstart;
+        if (len > sizeof(cmd) - 2)
+            len = sizeof(cmd) - 2;
+        memcpy(cmd, pstart, len);
+        cmd[len] = '\0';
+        if (name[0] == '\0' || !strncmp(name, cmd, strlen(name))) {
+            add_completion(cmd);
+        }
+        if (*p == '\0')
+            break;
+        p++;
+    }
+}
+
+static void file_completion(const char *input)
+{
+    DIR *ffs;
+    struct dirent *d;
+    char path[1024];
+    char file[1024], file_prefix[1024];
+    int input_path_len;
+    const char *p;
+
+    p = strrchr(input, '/'); 
+    if (!p) {
+        input_path_len = 0;
+        pstrcpy(file_prefix, sizeof(file_prefix), input);
+        strcpy(path, ".");
+    } else {
+        input_path_len = p - input + 1;
+        memcpy(path, input, input_path_len);
+        if (input_path_len > sizeof(path) - 1)
+            input_path_len = sizeof(path) - 1;
+        path[input_path_len] = '\0';
+        pstrcpy(file_prefix, sizeof(file_prefix), p + 1);
+    }
+#ifdef DEBUG_COMPLETION
+    term_printf("input='%s' path='%s' prefix='%s'\n", input, path, file_prefix);
+#endif
+    ffs = opendir(path);
+    if (!ffs)
+        return;
+    for(;;) {
+        struct stat sb;
+        d = readdir(ffs);
+        if (!d)
+            break;
+        if (strstart(d->d_name, file_prefix, NULL)) {
+            memcpy(file, input, input_path_len);
+            strcpy(file + input_path_len, d->d_name);
+            /* stat the file to find out if it's a directory.
+             * In that case add a slash to speed up typing long paths
+             */
+            stat(file, &sb);
+            if(S_ISDIR(sb.st_mode))
+                strcat(file, "/");
+            add_completion(file);
+        }
+    }
+    closedir(ffs);
+}
+
+static void block_completion_it(void *opaque, const char *name)
+{
+    const char *input = opaque;
+
+    if (input[0] == '\0' ||
+        !strncmp(name, (char *)input, strlen(input))) {
+        add_completion(name);
+    }
+}
+
+/* NOTE: this parser is an approximate form of the real command parser */
+static void parse_cmdline(const char *cmdline,
+                         int *pnb_args, char **args)
+{
+    const char *p;
+    int nb_args, ret;
+    char buf[1024];
+
+    p = cmdline;
+    nb_args = 0;
+    for(;;) {
+        while (isspace(*p))
+            p++;
+        if (*p == '\0')
+            break;
+        if (nb_args >= MAX_ARGS)
+            break;
+        ret = get_str(buf, sizeof(buf), &p);
+        args[nb_args] = qemu_strdup(buf);
+        nb_args++;
+        if (ret < 0)
+            break;
+    }
+    *pnb_args = nb_args;
+}
+
+static void find_completion(const char *cmdline)
+{
+    const char *cmdname;
+    char *args[MAX_ARGS];
+    int nb_args, i, len;
+    const char *ptype, *str;
+    term_cmd_t *cmd;
+
+    parse_cmdline(cmdline, &nb_args, args);
+#ifdef DEBUG_COMPLETION
+    for(i = 0; i < nb_args; i++) {
+        term_printf("arg%d = '%s'\n", i, (char *)args[i]);
+    }
+#endif
+
+    /* if the line ends with a space, it means we want to complete the
+       next arg */
+    len = strlen(cmdline);
+    if (len > 0 && isspace(cmdline[len - 1])) {
+        if (nb_args >= MAX_ARGS)
+            return;
+        args[nb_args++] = qemu_strdup("");
+    }
+    if (nb_args <= 1) {
+        /* command completion */
+        if (nb_args == 0)
+            cmdname = "";
+        else
+            cmdname = args[0];
+        completion_index = strlen(cmdname);
+        for(cmd = term_cmds; cmd->name != NULL; cmd++) {
+            cmd_completion(cmdname, cmd->name);
+        }
+    } else {
+        /* find the command */
+        for(cmd = term_cmds; cmd->name != NULL; cmd++) {
+            if (compare_cmd(args[0], cmd->name))
+                goto found;
+        }
+        return;
+    found:
+        ptype = cmd->args_type;
+        for(i = 0; i < nb_args - 2; i++) {
+            if (*ptype != '\0') {
+                ptype++;
+                while (*ptype == '?')
+                    ptype++;
+            }
+        }
+        str = args[nb_args - 1];
+        switch(*ptype) {
+        case 'F':
+            /* file completion */
+            completion_index = strlen(str);
+            file_completion(str);
+            break;
+        case 'B':
+            /* block device name completion */
+            completion_index = strlen(str);
+            bdrv_iterate(block_completion_it, (void *)str);
+            break;
+        default:
+            break;
+        }
+    }
+    for(i = 0; i < nb_args; i++)
+        qemu_free(args[i]);
+}
+
+static void term_show_prompt2(void)
 {
     term_printf("(qemu) ");
     fflush(stdout);
-    term_cmd_buf_index = 0;
-    term_cmd_buf_size = 0;
+    term_last_cmd_buf_index = 0;
+    term_last_cmd_buf_size = 0;
     term_esc_state = IS_NORM;
 }
 
-static void term_print_cmdline (const char *cmdline)
+static void term_show_prompt(void)
 {
-    term_show_prompt();
-    term_printf("%s", cmdline);
+    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;
+
+    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';
+        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();
 }
 
@@ -1456,9 +1704,7 @@
                 term_cmd_buf_size - term_cmd_buf_index);
         term_cmd_buf[term_cmd_buf_index] = ch;
         term_cmd_buf_size++;
-        term_printf("\033[@%c", ch);
         term_cmd_buf_index++;
-        term_flush();
     }
 }
 
@@ -1466,8 +1712,6 @@
 {
     if (term_cmd_buf_index > 0) {
         term_cmd_buf_index--;
-        term_printf("\033[D");
-        term_flush();
     }
 }
 
@@ -1475,8 +1719,6 @@
 {
     if (term_cmd_buf_index < term_cmd_buf_size) {
         term_cmd_buf_index++;
-        term_printf("\033[C");
-        term_flush();
     }
 }
 
@@ -1486,9 +1728,7 @@
         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_printf("\033[P");
         term_cmd_buf_size--;
-        term_flush();
     }
 }
 
@@ -1502,14 +1742,12 @@
 
 static void term_bol(void)
 {
-    while (term_cmd_buf_index > 0)
-        term_backward_char();
+    term_cmd_buf_index = 0;
 }
 
 static void term_eol(void)
 {
-    while (term_cmd_buf_index < term_cmd_buf_size)
-        term_forward_char();
+    term_cmd_buf_index = term_cmd_buf_size;
 }
 
 static void term_up_char(void)
@@ -1530,8 +1768,6 @@
     if (term_hist_entry >= 0) {
 	pstrcpy(term_cmd_buf, sizeof(term_cmd_buf), 
                 term_history[term_hist_entry]);
-	term_printf("\n");
-	term_print_cmdline(term_cmd_buf);
 	term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf);
     }
 }
@@ -1546,8 +1782,6 @@
     } else {
 	term_hist_entry = -1;
     }
-    term_printf("\n");
-    term_print_cmdline(term_cmd_buf);
     term_cmd_buf_index = term_cmd_buf_size = strlen(term_cmd_buf);
 }
 
@@ -1600,6 +1834,67 @@
     term_hist_entry = -1;
 }
 
+/* completion support */
+
+static 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';
+    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 */
 static void term_handle_byte(int ch)
 {
@@ -1609,9 +1904,15 @@
         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';
@@ -1684,104 +1985,29 @@
     the_end:
         break;
     }
-}
-
-/*************************************************************/
-/* serial console support */
-
-#define TERM_ESCAPE 0x01 /* ctrl-a is used for escape */
-
-static int term_got_escape, term_command;
-
-void term_print_help(void)
-{
-    term_printf("\n"
-                "C-a h    print this help\n"
-                "C-a x    exit emulatior\n"
-                "C-a s    save disk data back to file (if -snapshot)\n"
-                "C-a b    send break (magic sysrq)\n"
-                "C-a c    switch between console and monitor\n"
-                "C-a C-a  send C-a\n"
-                );
-}
-
-/* called when a char is received */
-static void term_received_byte(int ch)
-{
-    if (!serial_console) {
-        /* if no serial console, handle every command */
-        term_handle_byte(ch);
-    } else {
-        if (term_got_escape) {
-            term_got_escape = 0;
-            switch(ch) {
-            case 'h':
-                term_print_help();
-                break;
-            case 'x':
-                exit(0);
-                break;
-            case 's': 
-                {
-                    int i;
-                    for (i = 0; i < MAX_DISKS; i++) {
-                        if (bs_table[i])
-                            bdrv_commit(bs_table[i]);
-                    }
-                }
-                break;
-            case 'b':
-                if (serial_console)
-                    serial_receive_break(serial_console);
-                break;
-            case 'c':
-                if (!term_command) {
-                    term_show_prompt();
-                    term_command = 1;
-                } else {
-                    term_command = 0;
-                }
-                break;
-            case TERM_ESCAPE:
-                goto send_char;
-            }
-        } else if (ch == TERM_ESCAPE) {
-            term_got_escape = 1;
-        } else {
-        send_char:
-            if (term_command) {
-                term_handle_byte(ch);
-            } else {
-                if (serial_console)
-                    serial_receive_byte(serial_console, ch);
-            }
-        }
-    }
+    term_update();
 }
 
 static int term_can_read(void *opaque)
 {
-    if (serial_console) {
-        return serial_can_receive(serial_console);
-    } else {
-        return 128;
-    }
+    return 128;
 }
 
 static void term_read(void *opaque, const uint8_t *buf, int size)
 {
     int i;
     for(i = 0; i < size; i++)
-        term_received_byte(buf[i]);
+        term_handle_byte(buf[i]);
 }
 
-void monitor_init(void)
+void monitor_init(CharDriverState *hd, int show_banner)
 {
-    if (!serial_console) {
+    monitor_hd = hd;
+    if (show_banner) {
         term_printf("QEMU %s monitor - type 'help' for more information\n",
                     QEMU_VERSION);
         term_show_prompt();
     }
     term_hist_entry = -1;
-    qemu_add_fd_read_handler(0, term_can_read, term_read, NULL);
+    qemu_chr_add_read_handler(hd, term_can_read, term_read, NULL);
 }