From: Daniel Carl Date: Sat, 3 Nov 2012 02:16:50 +0000 (+0100) Subject: Added completion of commands. X-Git-Url: https://git.owens.tech///git?a=commitdiff_plain;h=e734ed21996ba2a6b52e932375cd1cd04a92206e;p=vimb.git Added completion of commands. --- diff --git a/config.mk b/config.mk index dd4f505..75175a1 100644 --- 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 index 0000000..8f610b0 --- /dev/null +++ b/src/completion.c @@ -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 && inext, i++) { + gtk_widget_hide(((Completion*)l->data)->event); + } + gtk_widget_show(vp.gui.compbox); + i = 0; + for (GList *l = g_list_last(completion); l && iprev, 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 && iprev) { + gtk_widget_hide(((Completion*)l->data)->event); + } + gtk_widget_show(vp.gui.compbox); + i = 0; + for (GList *l = g_list_first(completion); l && inext, 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 index 0000000..29d1692 --- /dev/null +++ b/src/completion.h @@ -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 */ diff --git a/src/keybind.c b/src/keybind.c index 5cdac86..0824429 100644 --- a/src/keybind.c +++ b/src/keybind.c @@ -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); diff --git a/src/main.c b/src/main.c index 437568a..272ae0c 100644 --- a/src/main.c +++ b/src/main.c @@ -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 ); diff --git a/src/main.h b/src/main.h index 8f896ee..99674a3 100644 --- a/src/main.h +++ b/src/main.h @@ -45,10 +45,12 @@ /* 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;