Add tab-completion of guest filenames (currently disabled).
authorRichard Jones <rjones@trick.home.annexia.org>
Thu, 18 Jun 2009 20:06:22 +0000 (21:06 +0100)
committerRichard Jones <rjones@trick.home.annexia.org>
Thu, 18 Jun 2009 20:06:22 +0000 (21:06 +0100)
fish/Makefile.am
fish/completion.c
fish/destpaths.c [new file with mode: 0644]
fish/fish.c
fish/fish.h
guestfish.pod
src/generator.ml

index 5f88894..c3fbc6f 100644 (file)
@@ -21,6 +21,7 @@ guestfish_SOURCES = \
        alloc.c \
        cmds.c \
        completion.c \
+       destpaths.c \
        echo.c \
        edit.c \
        fish.c \
index 264c584..43c4f64 100644 (file)
@@ -191,6 +191,8 @@ generator (const char *text, int state)
     len = strlen (text);
   }
 
+  rl_attempted_completion_over = 1;
+
   while ((name = commands[index]) != NULL) {
     index++;
     if (strncasecmp (name, text, len) == 0)
@@ -207,8 +209,12 @@ char **do_completion (const char *text, int start, int end)
   char **matches = NULL;
 
 #ifdef HAVE_LIBREADLINE
+  rl_completion_append_character = ' ';
+
   if (start == 0)
     matches = rl_completion_matches (text, generator);
+  else if (complete_dest_paths)
+    matches = rl_completion_matches (text, complete_dest_paths_generator);
 #endif
 
   return matches;
diff --git a/fish/destpaths.c b/fish/destpaths.c
new file mode 100644 (file)
index 0000000..6cddafa
--- /dev/null
@@ -0,0 +1,173 @@
+/* guestfish - the filesystem interactive shell
+ * Copyright (C) 2009 Red Hat Inc. 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#define _GNU_SOURCE            // for strndup, asprintf
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#endif
+
+#include <guestfs.h>
+
+#include "fish.h"
+
+/* Readline completion for paths on the guest filesystem, also for
+ * devices and LVM names.
+ */
+
+int complete_dest_paths = 0; /* SEE NOTE */
+
+/* NOTE: This is currently disabled by default (with no way to
+ * enable it).  That's because it's not particularly natural.
+ *
+ * Also there is a quite serious performance problem.  When listing
+ * even moderately long directories, this takes many seconds.  The
+ * reason is because it calls guestfs_is_dir on each directory
+ * entry, thus lots of round trips to the server.  We could have
+ * a "readdir and stat each entry" call to ease this.
+ */
+
+char *
+complete_dest_paths_generator (const char *text, int state)
+{
+#ifdef HAVE_LIBREADLINE
+
+  static int len, index;
+  static char **words = NULL;
+  static int nr_words = 0;
+  char *word;
+  guestfs_error_handler_cb old_error_cb;
+  void *old_error_cb_data;
+
+  /* Temporarily replace the error handler so that messages don't
+   * get printed to stderr while we are issuing commands.
+   */
+#define SAVE_ERROR_CB                                                  \
+  old_error_cb = guestfs_get_error_handler (g, &old_error_cb_data);    \
+  guestfs_set_error_handler (g, NULL, NULL);
+
+  /* Restore error handler. */
+#define RESTORE_ERROR_CB                                               \
+  guestfs_set_error_handler (g, old_error_cb, old_error_cb_data);
+
+  if (!state) {
+    char **strs;
+    int i, n;
+
+    len = strlen (text);
+    index = 0;
+
+    if (words) {
+      /* NB. 'words' array is NOT NULL-terminated. */
+      for (i = 0; i < nr_words; ++i)
+       free (words[i]);
+      free (words);
+    }
+
+    words = NULL;
+    nr_words = 0;
+
+    SAVE_ERROR_CB
+
+#define APPEND_STRS_AND_FREE                                           \
+    if (strs) {                                                                \
+      n = count_strings (strs);                                                \
+      words = realloc (words, sizeof (char *) * (nr_words + n));       \
+      for (i = 0; i < n; ++i)                                          \
+       words[nr_words++] = strs[i];                                    \
+      free (strs);                                                     \
+    }
+
+    /* Is it a device? */
+    if (len < 5 || strncmp (text, "/dev/", 5) == 0) {
+      /* Get a list of everything that can possibly begin with /dev/ */
+      strs = guestfs_list_devices (g);
+      APPEND_STRS_AND_FREE
+
+      strs = guestfs_list_partitions (g);
+      APPEND_STRS_AND_FREE
+
+      strs = guestfs_lvs (g);
+      APPEND_STRS_AND_FREE
+    }
+
+    if (len < 1 || text[0] == '/') {
+      /* If we've got a partial path already, we need to list everything
+       * in that directory, otherwise list everything in /
+       */
+      char *p, *dir;
+
+      p = strrchr (text, '/');
+      dir = p && p > text ? strndup (text, p - text) : strdup ("/");
+
+      strs = guestfs_ls (g, dir);
+
+      /* Prepend directory to names. */
+      if (strs) {
+       for (i = 0; strs[i]; ++i) {
+         p = NULL;
+         if (strcmp (dir, "/") == 0)
+           asprintf (&p, "/%s", strs[i]);
+         else
+           asprintf (&p, "%s/%s", dir, strs[i]);
+         free (strs[i]);
+         strs[i] = p;
+       }
+      }
+
+      free (dir);
+      APPEND_STRS_AND_FREE
+    }
+
+    /* else ...  In theory we could complete other things here such as VG
+     * names.  At the moment we don't do that.
+     */
+
+    RESTORE_ERROR_CB
+  }
+
+  /* This inhibits ordinary (local filename) completion. */
+  rl_attempted_completion_over = 1;
+
+  /* Complete the string. */
+  while (index < nr_words) {
+    word = words[index];
+    index++;
+    if (strncasecmp (word, text, len) == 0) {
+      /* Is it a directory? */
+      if (strncmp (word, "/dev/", 5) != 0) {
+       SAVE_ERROR_CB
+       if (guestfs_is_dir (g, word) > 0)
+         rl_completion_append_character = '/';
+       RESTORE_ERROR_CB
+      }
+
+      return strdup (word);
+    }
+  }
+
+#endif /* HAVE_LIBREADLINE */
+
+  return NULL;
+}
index da2d6d2..e66880f 100644 (file)
@@ -102,6 +102,7 @@ usage (void)
             "  -h|--cmd-help        List available commands\n"
             "  -h|--cmd-help cmd    Display detailed help on 'cmd'\n"
             "  -a|--add image       Add image\n"
+            "  -D|--no-dest-paths   Don't tab-complete paths from guest fs\n"
             "  -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n"
             "  -n|--no-sync         Don't autosync\n"
             "  -r|--ro              Mount read-only\n"
@@ -119,6 +120,7 @@ main (int argc, char *argv[])
     { "cmd-help", 2, 0, 'h' },
     { "help", 0, 0, '?' },
     { "mount", 1, 0, 'm' },
+    { "no-dest-paths", 0, 0, 'D' },
     { "no-sync", 0, 0, 'n' },
     { "ro", 0, 0, 'r' },
     { "verbose", 0, 0, 'v' },
@@ -178,6 +180,10 @@ main (int argc, char *argv[])
       drvs = drv;
       break;
 
+    case 'D':
+      complete_dest_paths = 0;
+      break;
+
     case 'h':
       if (optarg)
        display_command (optarg);
@@ -694,6 +700,16 @@ free_strings (char **argv)
   free (argv);
 }
 
+int
+count_strings (char * const * const argv)
+{
+  int c;
+
+  for (c = 0; argv[c]; ++c)
+    ;
+  return c;
+}
+
 void
 print_strings (char * const * const argv)
 {
index 0dd1fc2..028deec 100644 (file)
@@ -38,6 +38,7 @@ extern void pod2text (const char *heading, const char *body);
 extern void list_builtin_commands (void);
 extern void display_builtin_command (const char *cmd);
 extern void free_strings (char **argv);
+extern int count_strings (char * const * const argv);
 extern void print_strings (char * const * const argv);
 extern void print_table (char * const * const argv);
 extern int launch (guestfs_h *);
@@ -52,6 +53,10 @@ extern int run_action (const char *cmd, int argc, char *argv[]);
 /* in completion.c (auto-generated) */
 extern char **do_completion (const char *text, int start, int end);
 
+/* in destpaths.c */
+extern int complete_dest_paths;
+extern char *complete_dest_paths_generator (const char *text, int state);
+
 /* in alloc.c */
 extern int do_alloc (const char *cmd, int argc, char *argv[]);
 
index 28daa0a..19c9d43 100644 (file)
@@ -107,6 +107,13 @@ This changes the C<-m> option so that mounts are done read-only
 Enable very verbose messages.  This is particularly useful if you find
 a bug.
 
+=item B<-D> | B<--no-dest-paths>
+
+Don't tab-complete paths on the guest filesystem.  It is useful to be
+able to hit the tab key to complete paths on the guest filesystem, but
+this causes extra "hidden" guestfs calls to be made, so this option is
+here to allow this feature to be disabled.
+
 =back
 
 =head1 COMMANDS ON COMMAND LINE
index 0a0f9b1..d8abfd6 100755 (executable)
@@ -4803,6 +4803,8 @@ generator (const char *text, int state)
     len = strlen (text);
   }
 
+  rl_attempted_completion_over = 1;
+
   while ((name = commands[index]) != NULL) {
     index++;
     if (strncasecmp (name, text, len) == 0)
@@ -4819,8 +4821,12 @@ char **do_completion (const char *text, int start, int end)
   char **matches = NULL;
 
 #ifdef HAVE_LIBREADLINE
+  rl_completion_append_character = ' ';
+
   if (start == 0)
     matches = rl_completion_matches (text, generator);
+  else if (complete_dest_paths)
+    matches = rl_completion_matches (text, complete_dest_paths_generator);
 #endif
 
   return matches;