Moved parts of completion logic to ex.c.
authorDaniel Carl <danielcarl@gmx.de>
Sat, 28 Sep 2013 21:53:05 +0000 (23:53 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Sat, 28 Sep 2013 21:53:05 +0000 (23:53 +0200)
This change makes it easier to control the found matches and it allowed to
complete also abbreviated commands like ':o !tag'.

src/completion.c
src/completion.h
src/ex.c

index ea48a6d..a0479b4 100644 (file)
 #include "config.h"
 #include "main.h"
 #include "completion.h"
-#include "util.h"
-#include "history.h"
-#include "bookmark.h"
-#include "command.h"
-#include "setting.h"
-#include "ex.h"
-
-#define TAG_INDICATOR '!'
 
 extern VbCore vb;
 
 static struct {
     GtkWidget *win;
     GtkWidget *tree;
-    int       count;   /* command count before the completed content */
-    char      *prefix; /* prefix that marks the completion ':', '/', ':open', ... */
     int       active;  /* number of the current active tree item */
-    char      *text;   /* text of the current active tree item */
+    CompletionSelectFunc selfunc;
 } comp;
 
-static void init_completion(GtkTreeModel *model, const char *prefix);
-static void show(gboolean back);
-static void move_cursor(gboolean back);
 static gboolean tree_selection_func(GtkTreeSelection *selection,
     GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data);
 
 
-gboolean completion_complete(gboolean back)
+gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc,
+    gboolean back)
 {
-    VbInputType type;
-    char *input;
-    const char *prefix, *suffix;
-    GtkListStore *store = NULL;
-    GtkTreeModel *model;
-    gboolean res = false, sort = true;
-
-    /* TODO give the type of completion to this function - because we have to
-     * handle also abreviated commands like ':op foo<tab>' */
-    input = vb_get_input_text();
-    type  = vb_get_input_parts(input, VB_INPUT_ALL, &prefix, &suffix);
-    if (vb.mode->flags & FLAG_COMPLETION) {
-        if (comp.text && !strcmp(input, comp.text)) {
-            /* step through the next/prev completion item */
-            move_cursor(back);
-            g_free(input);
-            return true;
-        }
-        /* if current input isn't the content of the completion item, stop
-         * completion and start it after that again */
-        completion_clean();
-    }
-
-    /* create the list store model */
-    store = gtk_list_store_new(COMPLETION_STORE_NUM, G_TYPE_STRING, G_TYPE_STRING);
-    if (type == VB_INPUT_SET) {
-        res = setting_fill_completion(store, suffix);
-    } else if (type == VB_INPUT_OPEN || type == VB_INPUT_TABOPEN) {
-        /* if search string begins with TAG_INDICATOR lookup the bookmarks */
-        if (suffix && *suffix == TAG_INDICATOR) {
-            res = bookmark_fill_completion(store, suffix + 1);
-        } else {
-            res = history_fill_completion(store, HISTORY_URL, suffix);
-        }
-        sort = false;
-    } else if (type == VB_INPUT_COMMAND) {
-        char *command = NULL;
-        /* remove counts before command and save it to print it later in inputbox */
-        comp.count = g_ascii_strtoll(suffix, &command, 10);
-
-        res = ex_fill_completion(store, command);
-        /* we have a special sorting of the ex commands so we don't should
-         * reorder them for the completion */
-        sort = false;
-    } else if (type == VB_INPUT_SEARCH_FORWARD || type == VB_INPUT_SEARCH_BACKWARD) {
-        res = history_fill_completion(store, HISTORY_SEARCH, suffix);
-    } else if (type == VB_INPUT_BOOKMARK_ADD) {
-        res = bookmark_fill_tag_completion(store, suffix);
-    }
-
-    if (!res) {
-        g_free(input);
-        return false;
-    }
+    GtkCellRenderer *renderer;
+    GtkTreeSelection *selection;
+    GtkTreeViewColumn *column;
+    GtkRequisition size;
+    GtkTreePath *path;
+    GtkTreeIter iter;
+    int height, width;
 
-    model = GTK_TREE_MODEL(store);
     /* if there is only one match - don't build the tree view */
     if (gtk_tree_model_iter_n_children(model, NULL) == 1) {
         char *value;
-        GtkTreePath *path = gtk_tree_path_new_from_indices(0, -1);
-        GtkTreeIter iter;
+        path = gtk_tree_path_new_from_indices(0, -1);
         if (gtk_tree_model_get_iter(model, &iter, path)) {
             gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1);
 
-            if (comp.count) {
-                vb_echo_force(VB_MSG_NORMAL, false, "%s%d%s", prefix, comp.count, value);
-            } else {
-                vb_echo_force(VB_MSG_NORMAL, false, "%s%s", prefix, value);
-            }
+            /* call the select function */
+            selfunc(value);
+
             g_free(value);
+            g_object_unref(model);
 
-            g_object_unref(G_OBJECT(store));
-            g_free(input);
             return false;
         }
     }
 
-    /* apply the default sorting to the first tree model comlumn */
-    if (sort) {
-        gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), COMPLETION_STORE_FIRST, GTK_SORT_ASCENDING);
-    }
-
-    init_completion(GTK_TREE_MODEL(store), prefix);
-    show(back);
-    g_free(input);
-
-    return true;
-}
-
-void completion_clean(void)
-{
-    if (vb.mode->flags & FLAG_COMPLETION) {
-        /* remove completion flag from mode */
-        vb.mode->flags &= ~FLAG_COMPLETION;
-
-        if (comp.win) {
-            gtk_widget_destroy(comp.win);
-            comp.win = comp.tree = NULL;
-        }
-        OVERWRITE_STRING(comp.prefix, NULL);
-        OVERWRITE_STRING(comp.text, NULL);
-        comp.count = 0;
-    }
-}
-
-static void init_completion(GtkTreeModel *model, const char *prefix)
-{
-    GtkCellRenderer *renderer;
-    GtkTreeSelection *selection;
-    GtkTreeViewColumn *column;
-    GtkRequisition size;
-    int height, width;
-
-    /* set the submode flag */
-    vb.mode->flags |= FLAG_COMPLETION;
-
-    OVERWRITE_STRING(comp.prefix, prefix);
+    comp.selfunc = selfunc;
 
     /* prepare the tree view */
     comp.win  = gtk_scrolled_window_new(NULL, NULL);
@@ -241,10 +138,7 @@ static void init_completion(GtkTreeModel *model, const char *prefix)
         gtk_widget_set_size_request(comp.win, -1, height);
     }
 #endif
-}
 
-static void show(gboolean back)
-{
     /* this prevents the first item to be placed out of view if the completion
      * is shown */
     gtk_widget_show_all(comp.win);
@@ -254,10 +148,12 @@ static void show(gboolean back)
 
     /* set to -1 to have the cursor on first or last item set in move_cursor */
     comp.active = -1;
-    move_cursor(back);
+    completion_next(back);
+
+    return true;
 }
 
-static void move_cursor(gboolean back)
+void completion_next(gboolean back)
 {
     int rows;
     GtkTreePath *path;
@@ -282,6 +178,14 @@ static void move_cursor(gboolean back)
     gtk_tree_path_free(path);
 }
 
+void completion_clean(void)
+{
+    if (comp.win) {
+        gtk_widget_destroy(comp.win);
+        comp.win = comp.tree = NULL;
+    }
+}
+
 static gboolean tree_selection_func(GtkTreeSelection *selection,
     GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data)
 {
@@ -292,18 +196,9 @@ static gboolean tree_selection_func(GtkTreeSelection *selection,
      * interested in */
     if (!selected && gtk_tree_model_get_iter(model, &iter, path)) {
         gtk_tree_model_get(model, &iter, COMPLETION_STORE_FIRST, &value, -1);
-        /* save the content of the selected item so wen can access it easy */
-        if (comp.text) {
-            g_free(comp.text);
-            comp.text = NULL;
-        }
-        if (comp.count) {
-            comp.text = g_strdup_printf("%s%d%s", comp.prefix, comp.count, value);
-        } else {
-            comp.text = g_strconcat(comp.prefix, value, NULL);
-        }
-        /* print the text also into inputbox */
-        vb_set_input_text(comp.text);
+
+        comp.selfunc(value);
+
         g_free(value);
     }
 
index e628b39..ac8ec25 100644 (file)
@@ -30,7 +30,11 @@ enum {
     COMPLETION_STORE_NUM
 };
 
+typedef void (*CompletionSelectFunc) (char *match);
+
+gboolean completion_create(GtkTreeModel *model, CompletionSelectFunc selfunc,
+    gboolean back);
 void completion_clean(void);
-gboolean completion_complete(gboolean back);
+void completion_next(gboolean back);
 
 #endif /* end of include guard: _COMPLETION_H */
index 8872de4..c207533 100644 (file)
--- a/src/ex.c
+++ b/src/ex.c
@@ -68,7 +68,7 @@ typedef struct {
     int        count;    /* commands count */
     int        idx;      /* index in commands array */
     const char *name;    /* name of the command */
-    ExCode    code;      /* id of the command */
+    ExCode     code;     /* id of the command */
     gboolean   bang;     /* if the command was called with a bang ! */
     GString    *lhs;     /* left hand side of the command - single word */
     GString    *rhs;     /* right hand side of the command - multiple words */
@@ -110,6 +110,9 @@ static gboolean ex_set(const ExArg *arg);
 static gboolean ex_shellcmd(const ExArg *arg);
 static gboolean ex_shortcut(const ExArg *arg);
 
+static gboolean ex_complete(short direction);
+static void ex_completion_select(char *match);
+
 /* The order of following command names is significant. If there exists
  * ambiguous commands matching to the users input, the first defined will be
  * the prefered match.
@@ -157,6 +160,12 @@ static ExInfo commands[] = {
 #endif
 };
 
+static struct {
+    guint count;
+    char  *prefix;  /* completion prefix like :, ? and / */
+    char  *current; /* holds the current written input box content */
+} excomp;
+
 extern VbCore vb;
 
 
@@ -174,8 +183,10 @@ void ex_enter(void)
  */
 void ex_leave(void)
 {
-    /* TODO clean those only if they where active */
-    completion_clean();
+    if (vb.mode->flags & FLAG_COMPLETION) {
+        completion_clean();
+        vb.mode->flags &= ~FLAG_COMPLETION;
+    }
     hints_clear();
 }
 
@@ -197,12 +208,11 @@ VbResult ex_keypress(unsigned int key)
 
     switch (key) {
         case CTRL('I'): /* Tab */
-            /* mode will be set in completion_complete */
-            completion_complete(false);
+            ex_complete(1);
             break;
 
         case CTRL('O'): /* S-Tab */
-            completion_complete(true);
+            ex_complete(-1);
             break;
 
         case CTRL('['):
@@ -817,3 +827,145 @@ static gboolean ex_shortcut(const ExArg *arg)
             return false;
     }
 }
+
+/**
+ * Manage the generation and stepping through completions.
+ * This function prepared some prefix and suffix string that are required to
+ * put hte matched data back to inputbox, and prepares the tree list store
+ * model containing matched values.
+ */
+static gboolean ex_complete(short direction)
+{
+    char prefix[2] = {0};
+    char *input;            /* input read from inputbox */
+    const char *in;         /* pointer to input that we move */
+    gboolean found = false;
+    gboolean sort  = false;
+    GtkListStore *store;
+
+    /* if direction is 0 stop the completion */
+    if (!direction) {
+        completion_clean();
+        vb.mode->flags &= ~FLAG_COMPLETION;
+
+        return true;
+    }
+
+    input = vb_get_input_text();
+    /* if completion was already started move to the next/prev item */
+    if (vb.mode->flags & FLAG_COMPLETION) {
+        if (excomp.current && !strcmp(input, excomp.current)) {
+            /* step through the next/prev completion item */
+            completion_next(direction < 0);
+            g_free(input);
+
+            return true;
+        }
+
+        /* if current input isn't the content of the completion item, stop
+         * completion and start it after that again */
+        completion_clean();
+        vb.mode->flags &= ~FLAG_COMPLETION;
+    }
+
+    store = gtk_list_store_new(COMPLETION_STORE_NUM, G_TYPE_STRING, G_TYPE_STRING);
+
+    in = (const char*)input;
+    if (*in == ':') {
+        const char *before_cmdname;
+        /* skipt the first : */
+        in++;
+
+        ExArg *arg = g_new0(ExArg, 1);
+
+        skip_whitespace(&in);
+        parse_count(&in, arg);
+
+        /* packup the current pointer so that we can restore the input pointer
+         * if tha command name parsing fails */
+        before_cmdname = in;
+
+        if (parse_command_name(&in, arg) && *in == ' ') {
+            skip_whitespace(&in);
+            if (arg->code == EX_SET) {
+                if (setting_fill_completion(store, in)) {
+                    /* TODO calculate the prefix automatically */
+                    OVERWRITE_STRING(excomp.prefix, ":set ");
+                    sort = true;
+                    found = true;
+                }
+            } else if (arg->code == EX_OPEN || arg->code == EX_TABOPEN) {
+                OVERWRITE_STRING(excomp.prefix, arg->code == EX_OPEN ? ":open " : ":tabopen ");
+                if (*in == '!') {
+                    if (bookmark_fill_completion(store, in + 1)) {
+                        found = true;
+                    }
+                } else {
+                    if (history_fill_completion(store, HISTORY_URL, in)) {
+                        found = true;
+                    }
+                }
+            } else if (arg->code == EX_BMA) {
+                if (bookmark_fill_tag_completion(store, in)) {
+                    OVERWRITE_STRING(excomp.prefix, ":bma ");
+                    sort  = true;
+                    found = true;
+                }
+            }
+        } else { /* complete command names */
+            /* restore the 'in' pointer after try to parse command name */
+            in = before_cmdname;
+
+            /* backup the parsed data so we can access them in
+             * ex_completion_select function */
+            excomp.count = arg->count;
+
+            if (ex_fill_completion(store, in)) {
+                OVERWRITE_STRING(excomp.prefix, ":");
+                found = true;
+            }
+        }
+        free_cmdarg(arg);
+    } else if (*in == '/' || *in == '?') {
+        prefix[0] = *in;
+
+        if (history_fill_completion(store, HISTORY_SEARCH, in + 1)) {
+            OVERWRITE_STRING(excomp.prefix, prefix);
+            sort  = true;
+            found = true;
+        }
+    }
+
+    /* if the input could be parsed and the tree view could be filled */
+    if (sort) {
+        gtk_tree_sortable_set_sort_column_id(
+            GTK_TREE_SORTABLE(store), COMPLETION_STORE_FIRST, GTK_SORT_ASCENDING
+        );
+    }
+    if (found
+        && completion_create(GTK_TREE_MODEL(store), ex_completion_select, direction < 0)
+    ) {
+        /* set the submode flag */
+        vb.mode->flags |= FLAG_COMPLETION;
+    }
+
+    g_free(input);
+    return true;
+}
+
+/**
+ * Callback called from the completion if a item is selected to write the
+ * matche item accordings with previously saved prefix and command name to the
+ * inputbox.
+ */
+static void ex_completion_select(char *match)
+{
+    OVERWRITE_STRING(excomp.current, NULL);
+
+    if (excomp.count) {
+        excomp.current = g_strdup_printf("%s%d%s", excomp.prefix, excomp.count, match);
+    } else {
+        excomp.current = g_strconcat(excomp.prefix, match, NULL);
+    }
+    vb_set_input_text(excomp.current);
+}