Added completion of commands.
authorDaniel Carl <danielcarl@gmx.de>
Sat, 3 Nov 2012 02:16:50 +0000 (03:16 +0100)
committerDaniel Carl <danielcarl@gmx.de>
Sat, 10 Nov 2012 13:43:04 +0000 (14:43 +0100)
config.mk
src/completion.c [new file with mode: 0644]
src/completion.h [new file with mode: 0644]
src/keybind.c
src/main.c
src/main.h

index dd4f505..75175a1 100644 (file)
--- a/config.mk
+++ b/config.mk
@@ -7,7 +7,7 @@ BINDIR ?= $(PREFIX)/bin/
 MANDIR ?= $(PREFIX)/share/man/
 
 #----------------compile options---------------------
-LIBS = gtk+-2.0 webkit-1.0
+LIBS = gtk+-2.0 webkit-1.0 libsoup-2.4
 
 CFLAGS += $(shell pkg-config --cflags $(LIBS))
 CFLAGS += -Wall
diff --git a/src/completion.c b/src/completion.c
new file mode 100644 (file)
index 0000000..8f610b0
--- /dev/null
@@ -0,0 +1,273 @@
+/**
+ * vimp - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012 Daniel Carl
+ *
+ * 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 3 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, see http://www.gnu.org/licenses/.
+ */
+
+#include "completion.h"
+
+typedef struct {
+    GtkWidget* label;
+    GtkWidget* event;
+} Completion;
+
+static GList* completion_init_completion(GList* target, GList* source);
+static GList* completion_update(GList* completion, GList* active, gboolean back);
+static void completion_show(gboolean back);
+static void completion_set_color(Completion* completion, gchar* fg, gchar* bg);
+static void completion_set_entry_text(Completion* completion);
+static Completion* completion_get_new(const gchar* label);
+
+gboolean completion_complete(const CompletionType type, gboolean back)
+{
+    GList* source = NULL;
+
+    if (vp.comps.completions
+        && vp.comps.active
+        && (vp.state.mode & VP_MODE_COMPLETE)
+    ) {
+        /* updatecompletions */
+        vp.comps.active = completion_update(vp.comps.completions, vp.comps.active, back);
+
+        return TRUE;
+    }
+    /* create new completion */
+    switch (type) {
+        case COMPLETE_COMMAND:
+            source = g_hash_table_get_keys(vp.behave.commands);
+            source = g_list_sort(source, (GCompareFunc)g_strcmp0);
+            break;
+    }
+
+#if _HAS_GTK3
+    vp.gui.compbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+    gtk_box_set_homogeneous(GTK_BOX(vp.gui.compbox), TRUE);
+#else
+    vp.gui.compbox = gtk_vbox_new(TRUE, 0);
+#endif
+    gtk_box_pack_start(GTK_BOX(vp.gui.box), vp.gui.compbox, FALSE, FALSE, 0);
+
+    vp.comps.completions = completion_init_completion(vp.comps.completions, source);
+
+    if (!vp.comps.completions) {
+        return FALSE;
+    }
+    /* set mode flag for complation */
+    vp.state.mode |= VP_MODE_COMPLETE;
+    completion_show(back);
+
+    return TRUE;
+}
+
+void completion_clean(void)
+{
+    for (GList *l = vp.comps.completions; l; l = l->next) {
+        g_free(l->data);
+    }
+    g_list_free(vp.comps.completions);
+
+    if (vp.gui.compbox) {
+        gtk_widget_destroy(vp.gui.compbox);
+    }
+    vp.comps.completions = NULL;
+    vp.comps.active = NULL;
+
+    /* remove completion flag from mode */
+    vp.state.mode &= ~VP_MODE_COMPLETE;
+}
+
+static GList* completion_init_completion(GList* target, GList* source)
+{
+    const gchar* input = gtk_entry_get_text(GTK_ENTRY(vp.gui.inputbox));
+    gchar* data = NULL;
+    gboolean match;
+    gchar **token = NULL;
+
+    if (input && input[0] == ':') {
+        input++;
+    }
+    token = g_strsplit(input, " ", -1);
+
+    for (GList* l = source; l; l = l->next) {
+        data = l->data;
+        match = FALSE;
+        if (*input == 0) {
+            match = TRUE;
+        } else {
+            for (gint i = 0; token[i]; i++) {
+                if (g_str_has_prefix(data, token[i])) {
+                    match = TRUE;
+                } else {
+                    match = FALSE;
+                    break;
+                }
+            }
+        }
+        if (match) {
+            Completion* c = completion_get_new(data);
+            gtk_box_pack_start(GTK_BOX(vp.gui.compbox), c->event, FALSE, FALSE, 0);
+            target = g_list_append(target, c);
+        }
+    }
+    g_strfreev(token);
+
+    return target;
+}
+
+static GList* completion_update(GList* completion, GList* active, gboolean back)
+{
+    GList *old, *new;
+    Completion *c;
+
+    int length = g_list_length(completion);
+    int max = 15;
+    int items = MAX(length, max);
+    int r = (max) % 2;
+    int offset = max / 2 - 1 + r;
+
+    old = active;
+    int position = g_list_position(completion, active) + 1;
+    if (back) {
+        if (!(new = old->prev)) {
+            new = g_list_last(completion);
+        }
+        if (position - 1 > offset && position < items - offset + r) {
+            c = g_list_nth(completion, position - offset - 2)->data;
+            gtk_widget_show_all(c->event);
+            c = g_list_nth(completion, position + offset - r)->data;
+            gtk_widget_hide(c->event);
+        } else if (position == 1) {
+            int i = 0;
+            for (GList *l = g_list_first(completion); l && i<max; l=l->next, i++) {
+                gtk_widget_hide(((Completion*)l->data)->event);
+            }
+            gtk_widget_show(vp.gui.compbox);
+            i = 0;
+            for (GList *l = g_list_last(completion); l && i<max ;l=l->prev, i++) {
+                c = l->data;
+                gtk_widget_show_all(c->event);
+            }
+        }
+    } else {
+        if (!(new = old->next)) {
+            new = g_list_first(completion);
+        }
+        if (position > offset && position < items - offset - 1 + r) {
+            c = g_list_nth(completion, position - offset - 1)->data;
+            gtk_widget_hide(c->event);
+            c = g_list_nth(completion, position + offset + 1 - r)->data;
+            gtk_widget_show_all(c->event);
+        } else if (position == items || position  == 1) {
+            int i = 0;
+            for (GList *l = g_list_last(completion); l && i<max; l=l->prev) {
+                gtk_widget_hide(((Completion*)l->data)->event);
+            }
+            gtk_widget_show(vp.gui.compbox);
+            i = 0;
+            for (GList *l = g_list_first(completion); l && i<max ;l=l->next, i++) {
+                gtk_widget_show_all(((Completion*)l->data)->event);
+            }
+        }
+    }
+
+    gchar* fg = "#77ff77";
+    gchar* bg = "#333333";
+    completion_set_color(old->data, fg, bg);
+    completion_set_color(new->data, fg, bg);
+
+    active = new;
+    completion_set_entry_text(active->data);
+    return active;
+}
+
+/* allow to chenge the direction of display */
+static void completion_show(gboolean back)
+{
+    /* TODO make this configurable */
+    const gint max_items = 15;
+    gint i = 0;
+    if (back) {
+        vp.comps.active = g_list_last(vp.comps.completions);
+        for (GList *l = vp.comps.active; l && i < max_items; l = l->prev, i++) {
+            gtk_widget_show_all(((Completion*)l->data)->event);
+        }
+    } else {
+        vp.comps.active = g_list_first(vp.comps.completions);
+        for (GList *l = vp.comps.active; l && i < max_items; l = l->next, i++) {
+            gtk_widget_show_all(((Completion*)l->data)->event);
+        }
+    }
+    if (vp.comps.active != NULL) {
+        gchar* fg = "#77ff77";
+        gchar* bg = "#333333";
+        completion_set_color(vp.comps.active->data, fg, bg);
+        completion_set_entry_text(vp.comps.active->data);
+        gtk_widget_show(vp.gui.compbox);
+    }
+}
+
+static void completion_set_color(Completion* completion, gchar* fg, gchar* bg)
+{
+    GdkColor color;
+    gdk_color_parse(fg, &color);
+    gtk_widget_modify_fg(completion->label, GTK_STATE_NORMAL, &color);
+    gdk_color_parse(bg, &color);
+    gtk_widget_modify_bg(completion->label, GTK_STATE_NORMAL, &color);
+}
+
+static void completion_set_entry_text(Completion* completion)
+{
+    const gchar* text;
+    const gchar* pre = ":";
+    gint len = strlen(pre);
+
+    text = gtk_label_get_text(GTK_LABEL(completion->label));
+    gtk_entry_set_text(GTK_ENTRY(vp.gui.inputbox), pre);
+    gtk_editable_insert_text(GTK_EDITABLE(vp.gui.inputbox), text, -1, &len);
+    gtk_editable_set_position(GTK_EDITABLE(vp.gui.inputbox), -1);
+}
+
+static Completion* completion_get_new(const gchar* label)
+{
+    /* TODO make this configurable */
+    const gint padding = 2;
+    Completion* c = g_new0(Completion, 1);
+
+    c->label = gtk_label_new(label);
+    c->event = gtk_event_box_new();
+
+#if _HAS_GTK3
+    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+    gtk_box_set_homogeneous(GTK_BOX(hbox), TRUE);
+#else
+    GtkWidget *hbox = gtk_hbox_new(TRUE, 0);
+#endif
+
+    gtk_box_pack_start(GTK_BOX(hbox), c->label, TRUE, TRUE, 5);
+    gtk_label_set_ellipsize(GTK_LABEL(c->label), PANGO_ELLIPSIZE_MIDDLE);
+    gtk_misc_set_alignment(GTK_MISC(c->label), 0.0, 0.5);
+
+    gchar* fg = "#77ff77";
+    gchar* bg = "#333333";
+    completion_set_color(c, fg, bg);
+
+    GtkWidget *alignment = gtk_alignment_new(0.5, 0.5, 1, 1);
+    gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), padding, padding, padding, padding);
+    gtk_container_add(GTK_CONTAINER(alignment), hbox);
+    gtk_container_add(GTK_CONTAINER(c->event), alignment);
+
+    return c;
+}
diff --git a/src/completion.h b/src/completion.h
new file mode 100644 (file)
index 0000000..29d1692
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * vimp - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012 Daniel Carl
+ *
+ * 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 3 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, see http://www.gnu.org/licenses/.
+ */
+
+#ifndef COMPLETION_H
+#define COMPLETION_H
+
+#include "main.h"
+
+typedef enum {
+    COMPLETE_COMMAND
+} CompletionType;
+
+void completion_clean(void);
+gboolean completion_complete(const CompletionType type, gboolean back);
+
+#endif /* end of include guard: COMPLETION_H */
index 5cdac86..0824429 100644 (file)
@@ -20,6 +20,7 @@
 #include "main.h"
 #include "keybind.h"
 #include "command.h"
+#include "completion.h"
 
 static GSList* keys = NULL;
 static GString* modkeys = NULL;
@@ -180,6 +181,7 @@ static gboolean keybind_keypress_callback(WebKitWebView* webview, GdkEventKey* e
     /* check for escape or modkeys or counts */
     if ((CLEAN(event->state) & ~irrelevant) == 0) {
         if (IS_ESCAPE(event)) {
+            completion_clean();
             /* switch to normal mode and clear the input box */
             Arg a = {VP_MODE_NORMAL, ""};
             vp_set_mode(&a);
@@ -205,6 +207,12 @@ static gboolean keybind_keypress_callback(WebKitWebView* webview, GdkEventKey* e
         }
     }
 
+    /* TODO should we use a command for that too? */
+    if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab) {
+        completion_complete(COMPLETE_COMMAND, event->keyval == GDK_ISO_Left_Tab);
+        return TRUE;
+    }
+
     /* check for keybinding */
     GSList* link = keybind_find(vp.state.mode, vp.state.modkey,
             (CLEAN(event->state) & ~irrelevant), keyval);
index 437568a..272ae0c 100644 (file)
@@ -33,7 +33,6 @@ static void vp_webview_load_commited_cb(WebKitWebView *webview, WebKitWebFrame*
 static void vp_destroy_window_cb(GtkWidget* widget, GtkWidget* window, gpointer user_data);
 static gboolean vp_frame_scrollbar_policy_changed_cb(void);
 static void vp_inputbox_activate_cb(GtkEntry* entry, gpointer user_data);
-static gboolean vp_inputbox_keypress_cb(GtkEntry* entry, GdkEventKey* event);
 static gboolean vp_inputbox_keyrelease_cb(GtkEntry* entry, GdkEventKey* event);
 #ifdef FEATURE_COOKIE
 static void vp_new_request_cb(SoupSession* session, SoupMessage *message, gpointer data);
@@ -118,12 +117,7 @@ static void vp_inputbox_activate_cb(GtkEntry *entry, gpointer user_data)
     }
 }
 
-static gboolean vp_inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event)
-{
-    return FALSE;
-}
-
-static gboolean vp_inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event)
+static gboolean vp_inputbox_keyrelease_cb(GtkEntry* entry, GdkEventKey* event)
 {
     return FALSE;
 }
@@ -267,6 +261,10 @@ static gboolean vp_hide_message(void)
     return FALSE;
 }
 
+/**
+ * Set the base modes. All other mode flags like completion can be set directly
+ * to vp.state.mode.
+ */
 gboolean vp_set_mode(const Arg* arg)
 {
     vp.state.mode = arg->i;
@@ -540,7 +538,6 @@ static void vp_setup_signals(void)
     g_object_connect(
         G_OBJECT(gui->inputbox),
         "signal::activate",          G_CALLBACK(vp_inputbox_activate_cb),   NULL,
-        "signal::key-press-event",   G_CALLBACK(vp_inputbox_keypress_cb),   NULL,
         "signal::key-release-event", G_CALLBACK(vp_inputbox_keyrelease_cb), NULL,
         NULL
     );
index 8f896ee..99674a3 100644 (file)
 
 /* enums */
 typedef enum _vp_mode {
-    VP_MODE_NORMAL,
-    VP_MODE_COMMAND,
-    VP_MODE_PATH_THROUGH,
-    VP_MODE_INSERT,
+    VP_MODE_NORMAL        = 1<<0,
+    VP_MODE_COMMAND       = 1<<1,
+    VP_MODE_PATH_THROUGH  = 1<<2,
+    VP_MODE_INSERT        = 1<<3,
+    VP_MODE_COMPLETE      = 1<<4,
+    VP_MODE_SEARCH        = 1<<5,
 } Mode;
 
 enum {
@@ -127,6 +129,7 @@ typedef struct {
     GtkBox*        box;
     GtkWidget*     eventbox;
     GtkWidget*     inputbox;
+    GtkWidget*     compbox;
     StatusBar      statusbar;
     GtkScrollbar*  sb_h;
     GtkScrollbar*  sb_v;
@@ -161,6 +164,11 @@ typedef struct {
     gchar* status_font;
 } Config;
 
+typedef struct {
+    GList* completions;
+    GList* active;
+} Completions;
+
 /* core struct */
 typedef struct {
     Gui           gui;
@@ -171,6 +179,7 @@ typedef struct {
     Network       net;
 #endif
     Config        config;
+    Completions   comps;
 #if 0
     Ssl           ssl;
     Communication comm;