Changed the way keys are processed.
authorDaniel Carl <danielcarl@gmx.de>
Wed, 14 Aug 2013 18:15:06 +0000 (20:15 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Thu, 19 Sep 2013 22:58:07 +0000 (00:58 +0200)
Until today vimb mapped two-part keybindings to commands. This patch changed
this paradigm into a more vi like way. The commands are separated into normal
mode commands that mainly consists of a single char and ex commands that can
by typed into the inputbox like ':open'. This change allows us to adapt also
the way keypresses and mapping are handled. Now every keypress is converted
into a unsigned char and collected into a typeahead queue. The mappings are
applied on the queue. So now we can use also long keymaps and run multiple
commands for different modes with them like ':nmap abcdef :set
scripts!<CR>:open search query<CR>50G'.

25 files changed:
src/command.c
src/command.h
src/completion.c
src/default.h
src/dom.c
src/ex.c [new file with mode: 0644]
src/ex.h [new file with mode: 0644]
src/hints.c
src/hints.h
src/hints.js
src/history.c
src/input.c [new file with mode: 0644]
src/input.h [new file with mode: 0644]
src/keybind.c [deleted file]
src/keybind.h [deleted file]
src/main.c
src/main.h
src/map.c [new file with mode: 0644]
src/map.h [new file with mode: 0644]
src/mode.c [new file with mode: 0644]
src/mode.h [new file with mode: 0644]
src/normal.c [new file with mode: 0644]
src/normal.h [new file with mode: 0644]
src/pass.c [new file with mode: 0644]
src/pass.h [new file with mode: 0644]

index cc70721..723e53f 100644 (file)
  * along with this program. If not, see http://www.gnu.org/licenses/.
  */
 
+/**
+ * This file contains functions that are used by normal mode and command mode
+ * together.
+ */
 #include "config.h"
 #include "main.h"
 #include "command.h"
-#include "keybind.h"
-#include "setting.h"
-#include "completion.h"
-#include "hints.h"
-#include "util.h"
-#include "shortcut.h"
 #include "history.h"
 #include "bookmark.h"
-#include "dom.h"
-
-typedef struct {
-    char    *file;
-    Element *element;
-} OpenEditorData;
-
-typedef struct {
-    const char *name;
-    const char *alias;
-    Command    function;
-    const Arg  arg;       /* arguments to call the command with */
-} CommandInfo;
 
 extern VbCore vb;
-extern const unsigned int INPUT_LENGTH;
-
-static GHashTable *commands;
-static GHashTable *short_commands;
-
-static CommandInfo cmd_list[] = {
-    /* command                    alias    function                      arg */
-    {"open",                      "o",     command_open,                 {VB_TARGET_CURRENT, ""}},
-    {"tabopen",                   "t",     command_open,                 {VB_TARGET_NEW, ""}},
-    {"open-closed",               NULL,    command_open_closed,          {VB_TARGET_CURRENT}},
-    {"tabopen-closed",            NULL,    command_open_closed,          {VB_TARGET_NEW}},
-    {"open-clipboard",            "oc",    command_paste,                {VB_CLIPBOARD_PRIMARY | VB_CLIPBOARD_SECONDARY | VB_TARGET_CURRENT}},
-    {"tabopen-clipboard",         "toc",   command_paste,                {VB_CLIPBOARD_PRIMARY | VB_CLIPBOARD_SECONDARY | VB_TARGET_NEW}},
-    {"input",                     "in",    command_input,                {0, ":"}},
-    {"inputuri",                  NULL,    command_input,                {VB_INPUT_CURRENT_URI, ":"}},
-    {"quit",                      "q",     command_close,                {0}},
-    {"source",                    NULL,    command_view_source,          {0}},
-    {"back",                      "ba",    command_navigate,             {VB_NAVIG_BACK}},
-    {"forward",                   "fo",    command_navigate,             {VB_NAVIG_FORWARD}},
-    {"reload",                    "re",    command_navigate,             {VB_NAVIG_RELOAD}},
-    {"reload!",                   "re!",   command_navigate,             {VB_NAVIG_RELOAD_FORCE}},
-    {"stop",                      "st",    command_navigate,             {VB_NAVIG_STOP_LOADING}},
-    {"jumpleft",                  NULL,    command_scroll,               {VB_SCROLL_TYPE_JUMP | VB_SCROLL_DIRECTION_LEFT}},
-    {"jumpright",                 NULL,    command_scroll,               {VB_SCROLL_TYPE_JUMP | VB_SCROLL_DIRECTION_RIGHT}},
-    {"jumptop",                   NULL,    command_scroll,               {VB_SCROLL_TYPE_JUMP | VB_SCROLL_DIRECTION_TOP}},
-    {"jumpbottom",                NULL,    command_scroll,               {VB_SCROLL_TYPE_JUMP | VB_SCROLL_DIRECTION_DOWN}},
-    {"pageup",                    NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_TOP | VB_SCROLL_UNIT_PAGE}},
-    {"pagedown",                  NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_DOWN | VB_SCROLL_UNIT_PAGE}},
-    {"halfpageup",                NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_TOP | VB_SCROLL_UNIT_HALFPAGE}},
-    {"halfpagedown",              NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_DOWN | VB_SCROLL_UNIT_HALFPAGE}},
-    {"scrollleft",                NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_LEFT | VB_SCROLL_UNIT_LINE}},
-    {"scrollright",               NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_RIGHT | VB_SCROLL_UNIT_LINE}},
-    {"scrollup",                  NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_TOP | VB_SCROLL_UNIT_LINE}},
-    {"scrolldown",                NULL,    command_scroll,               {VB_SCROLL_TYPE_SCROLL | VB_SCROLL_DIRECTION_DOWN | VB_SCROLL_UNIT_LINE}},
-    {"nmap",                      NULL,    command_map,                  {VB_MODE_NORMAL}},
-    {"imap",                      NULL,    command_map,                  {VB_MODE_INPUT}},
-    {"cmap",                      NULL,    command_map,                  {VB_MODE_COMMAND}},
-    {"nunmap",                    NULL,    command_unmap,                {VB_MODE_NORMAL}},
-    {"iunmap",                    NULL,    command_unmap,                {VB_MODE_INPUT}},
-    {"cunmap",                    NULL,    command_unmap,                {VB_MODE_COMMAND}},
-    {"set",                       NULL,    command_set,                  {0}},
-    {"inspect",                   NULL,    command_inspect,              {0}},
-    {"hint-link",                 NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_OPEN}},
-    {"hint-link-new",             NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_OPEN | HINTS_OPEN_NEW}},
-    {"hint-input-open",           NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_INPUT}},
-    {"hint-input-tabopen",        NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_INPUT | HINTS_OPEN_NEW}},
-    {"hint-yank",                 NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_YANK}},
-    {"hint-image-open",           NULL,    command_hints,                {HINTS_TYPE_IMAGE | HINTS_PROCESS_OPEN}},
-    {"hint-image-tabopen",        NULL,    command_hints,                {HINTS_TYPE_IMAGE | HINTS_PROCESS_OPEN | HINTS_OPEN_NEW}},
-    {"hint-editor",               NULL,    command_hints,                {HINTS_TYPE_EDITABLE}},
-    {"hint-save",                 NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_SAVE}},
-#ifdef FEATURE_QUEUE
-    {"hint-queue-push",           NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_PUSH}},
-    {"hint-queue-unshift",        NULL,    command_hints,                {HINTS_TYPE_LINK | HINTS_PROCESS_UNSHIFT}},
-#endif
-    {"yank-uri",                  "yu",    command_yank,                 {VB_CLIPBOARD_PRIMARY | VB_CLIPBOARD_SECONDARY | COMMAND_YANK_URI}},
-    {"yank-selection",            "ys",    command_yank,                 {VB_CLIPBOARD_PRIMARY | VB_CLIPBOARD_SECONDARY | COMMAND_YANK_SELECTION}},
-    {"search-forward",            NULL,    command_search,               {VB_SEARCH_FORWARD}},
-    {"search-backward",           NULL,    command_search,               {VB_SEARCH_BACKWARD}},
-    {"search-selection-forward",  NULL,    command_selsearch,            {VB_SEARCH_FORWARD}},
-    {"search-selection-backward", NULL,    command_selsearch,            {VB_SEARCH_BACKWARD}},
-    {"shortcut-add",              NULL,    command_shortcut,             {1}},
-    {"shortcut-remove",           NULL,    command_shortcut,             {0}},
-    {"shortcut-default",          NULL,    command_shortcut_default,     {0}},
-    {"zoomin",                    "zi",    command_zoom,                 {COMMAND_ZOOM_IN}},
-    {"zoomout",                   "zo",    command_zoom,                 {COMMAND_ZOOM_OUT}},
-    {"zoominfull",                "zif",   command_zoom,                 {COMMAND_ZOOM_IN | COMMAND_ZOOM_FULL}},
-    {"zoomoutfull",               "zof",   command_zoom,                 {COMMAND_ZOOM_OUT | COMMAND_ZOOM_FULL}},
-    {"zoomreset",                 "zr",    command_zoom,                 {COMMAND_ZOOM_RESET}},
-    {"hist-next",                 NULL,    command_history,              {0}},
-    {"hist-prev",                 NULL,    command_history,              {1}},
-    {"run",                       NULL,    command_run_multi,            {0}},
-    {"bookmark-add",              "bma",   command_bookmark,             {1}},
-    {"bookmark-remove",           "bmr",   command_bookmark,             {0}},
-    {"eval",                      "e",     command_eval,                 {0}},
-    {"editor",                    NULL,    command_editor,               {0}},
-    {"next",                      "n",     command_nextprev,             {0}},
-    {"prev",                      "p",     command_nextprev,             {1}},
-    {"descent",                   NULL,    command_descent,              {0}},
-    {"descent!",                  NULL,    command_descent,              {1}},
-    {"save",                      NULL,    command_save,                 {COMMAND_SAVE_CURRENT}},
-    {"shellcmd",                  NULL,    command_shellcmd,             {0}},
-#ifdef FEATURE_QUEUE
-    {"queue-push",                NULL,    command_queue,                {COMMAND_QUEUE_PUSH}},
-    {"queue-unshift",             NULL,    command_queue,                {COMMAND_QUEUE_UNSHIFT}},
-    {"queue-pop",                 NULL,    command_queue,                {COMMAND_QUEUE_POP}},
-    {"queue-clear",               NULL,    command_queue,                {COMMAND_QUEUE_CLEAR}},
-#endif
-    {"pass-through",              NULL,    command_mode,                 {VB_MODE_PASSTHROUGH}},
-    {"focus-input",               NULL,    command_focusinput,           {0}},
-};
-
-static void editor_resume(GPid pid, int status, OpenEditorData *data);
-static CommandInfo *command_lookup(const char* name);
-static char *expand_string(const char *str);
-
-
-void command_init(void)
-{
-    guint i;
-    commands       = g_hash_table_new(g_str_hash, g_str_equal);
-    short_commands = g_hash_table_new(g_str_hash, g_str_equal);
-
-    for (i = 0; i < LENGTH(cmd_list); i++) {
-        g_hash_table_insert(commands, (gpointer)cmd_list[i].name, &cmd_list[i]);
-        /* save the commands by their alias in extra hash table */
-        if (cmd_list[i].alias) {
-            g_hash_table_insert(short_commands, (gpointer)cmd_list[i].alias, &cmd_list[i]);
-        }
-    }
-}
-
-void command_cleanup(void)
-{
-    if (commands) {
-        g_hash_table_destroy(commands);
-    }
-    if (short_commands) {
-        g_hash_table_destroy(short_commands);
-    }
-}
-
-/**
- * Parses given string and put corresponding command arg and command count in
- * also given pointers.
- * Returns true if parsing was successful.
- * Dont forget to g_free arg->s.
- */
-gboolean command_parse_from_string(const char *input, Command *func, Arg *arg, guint *count)
-{
-    char *command, *name, *str, **token;
-    CommandInfo *info;
-
-    if (!input || *input == '\0') {
-        return false;
-    }
-
-    str = g_strdup(input);
-    /* remove leading whitespace */
-    g_strchug(str);
-
-    /* get a possible command count */
-    *count = g_ascii_strtoll(str, &command, 10);
-
-    /* split the input string into command and parameter part */
-    token = g_strsplit(command, " ", 2);
-    g_free(str);
-
-    if (!token[0]) {
-        g_strfreev(token);
-        return false;
-    }
-
-    name = token[0];
-    info = command_lookup(name);
-    if (!info) {
-        vb_echo(VB_MSG_ERROR, true, "Command '%s' not found", name);
-        g_strfreev(token);
-        return false;
-    }
-
-    /* assigne the data to given pointers */
-    arg->i = info->arg.i;
-    arg->s = g_strdup(token[1] ? token[1] : info->arg.s);
-    *func = info->function;
-
-    g_strfreev(token);
-
-    return true;
-}
-
-/**
- * Runs a single command form string containing the command and possible
- * parameters.
- */
-gboolean command_run_string(const char *input)
-{
-    gboolean success;
-    Command command = NULL;
-    Arg arg = {0};
-    if (!command_parse_from_string(input, &command, &arg, &vb.state.count)) {
-        vb_set_mode(VB_MODE_NORMAL, false);
-        return false;
-    }
-
-    success = command(&arg);
-    g_free(arg.s);
-
-    return success;
-}
-
-/**
- * Runs multiple commands that are seperated by |.
- */
-gboolean command_run_multi(const Arg *arg)
-{
-    gboolean result = true;
-    char **commands;
-    unsigned int len, i;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-    if (!arg->s || *(arg->s) == '\0') {
-        return false;
-    }
-
-    /* splits the commands */
-    commands = g_strsplit(arg->s, "|", 0);
-
-    len = g_strv_length(commands);
-    if (!len) {
-        g_strfreev(commands);
-        return false;
-    }
-
-    for (i = 0; i < len; i++) {
-        /* run the single commands */
-        result = (result && command_run_string(commands[i]));
-    }
-    g_strfreev(commands);
-
-    return result;
-}
-
-gboolean command_fill_completion(GtkListStore *store, const char *input)
-{
-    gboolean found = false;
-    GtkTreeIter iter;
-    /* according to vim we return only the long commands here */
-    GList *src = g_hash_table_get_keys(commands);
-
-    if (!input || input == '\0') {
-        for (GList *l = src; l; l = l->next) {
-            gtk_list_store_append(store, &iter);
-            gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
-            found = true;
-        }
-    } else {
-        for (GList *l = src; l; l = l->next) {
-            char *value = (char*)l->data;
-            if (g_str_has_prefix(value, input)) {
-                gtk_list_store_append(store, &iter);
-                gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
-                found = true;
-            }
-        }
-    }
-    g_list_free(src);
-
-    return found;
-}
-
-gboolean command_open(const Arg *arg)
-{
-    vb_set_mode(VB_MODE_NORMAL, false);
-    return vb_load_uri(arg);
-}
-
-/**
- * Reopens the last closed page.
- */
-gboolean command_open_closed(const Arg *arg)
-{
-    gboolean result;
-
-    Arg a = {arg->i};
-    a.s = util_get_file_contents(vb.files[FILES_CLOSED], NULL);
-    result = vb_load_uri(&a);
-    g_free(a.s);
-
-    return result;
-}
-
-gboolean command_input(const Arg *arg)
-{
-    const char *url;
-
-    vb_set_mode(VB_MODE_COMMAND, false);
-
-    /* add current url if requested */
-    if (VB_INPUT_CURRENT_URI == arg->i && (url = GET_URI())) {
-        /* append the current url to the input message */
-        char *input = g_strconcat(arg->s, url, NULL);
-        vb_echo_force(VB_MSG_NORMAL, false, "%s", input);
-        g_free(input);
-    } else {
-        vb_echo_force(VB_MSG_NORMAL, false, "%s", arg->s);
-    }
-
-    return true;
-}
-
-gboolean command_close(const Arg *arg)
-{
-    vb_quit();
-    return true;
-}
-
-gboolean command_view_source(const Arg *arg)
-{
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    gboolean mode = webkit_web_view_get_view_source_mode(vb.gui.webview);
-    webkit_web_view_set_view_source_mode(vb.gui.webview, !mode);
-    webkit_web_view_reload(vb.gui.webview);
-
-    return true;
-}
-
-gboolean command_navigate(const Arg *arg)
-{
-    int count = vb.state.count ? vb.state.count : 1;
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    WebKitWebView *view = vb.gui.webview;
-    if (arg->i <= VB_NAVIG_FORWARD) {
-        webkit_web_view_go_back_or_forward(
-            view, (arg->i == VB_NAVIG_BACK ? -count : count)
-        );
-    } else if (arg->i == VB_NAVIG_RELOAD) {
-        webkit_web_view_reload(view);
-    } else if (arg->i == VB_NAVIG_RELOAD_FORCE) {
-        webkit_web_view_reload_bypass_cache(view);
-    } else {
-        webkit_web_view_stop_loading(view);
-    }
-
-    return true;
-}
-
-gboolean command_scroll(const Arg *arg)
-{
-    gdouble max, new;
-    int count = vb.state.count ? vb.state.count : 1;
-    int direction = (arg->i & (1 << 2)) ? 1 : -1;
-    GtkAdjustment *adjust = (arg->i & VB_SCROLL_AXIS_H) ? vb.gui.adjust_h : vb.gui.adjust_v;
-
-    /* keep possible search mode */
-    vb_set_mode(VB_MODE_NORMAL | (vb.state.mode & VB_MODE_SEARCH), false);
-
-    max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
-    /* type scroll */
-    if (arg->i & VB_SCROLL_TYPE_SCROLL) {
-        gdouble value;
-        if (arg->i & VB_SCROLL_UNIT_LINE) {
-            value = vb.config.scrollstep;
-        } else if (arg->i & VB_SCROLL_UNIT_HALFPAGE) {
-            value = gtk_adjustment_get_page_size(adjust) / 2;
-        } else {
-            value = gtk_adjustment_get_page_size(adjust);
-        }
-        new = gtk_adjustment_get_value(adjust) + direction * value * count;
-    } else if (vb.state.count) {
-        /* jump - if count is set to count% of page */
-        new = max * vb.state.count / 100;
-    } else if (direction == 1) {
-        /* jump to top */
-        new = gtk_adjustment_get_upper(adjust);
-    } else {
-        /* jump to bottom */
-        new = gtk_adjustment_get_lower(adjust);
-    }
-    gtk_adjustment_set_value(adjust, new > max ? max : new);
-
-    return true;
-}
-
-gboolean command_map(const Arg *arg)
-{
-    char *key;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    if (!arg->s) {
-        return false;
-    }
-    if ((key = strchr(arg->s, '='))) {
-        *key = '\0';
-        return keybind_add_from_string(arg->s, key + 1, arg->i);
-    }
-    return false;
-}
-
-gboolean command_unmap(const Arg *arg)
-{
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    return keybind_remove_from_string(arg->s, arg->i);
-}
-
-gboolean command_set(const Arg *arg)
-{
-    gboolean success;
-    char *param = NULL, *line = NULL;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-    if (!arg->s || *(arg->s) == '\0') {
-        return false;
-    }
-
-    line = g_strdup(arg->s);
-    g_strstrip(line);
-
-    /* split the input string into parameter and value part */
-    if ((param = strchr(line, '='))) {
-        *param = '\0';
-        param++;
-        success = setting_run(line, param ? param : NULL);
-    } else {
-        success = setting_run(line, NULL);
-    }
-    g_free(line);
-
-    return success;
-}
-
-gboolean command_inspect(const Arg *arg)
-{
-    gboolean enabled;
-    WebKitWebSettings *settings = NULL;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    settings = webkit_web_view_get_settings(vb.gui.webview);
-    g_object_get(G_OBJECT(settings), "enable-developer-extras", &enabled, NULL);
-    if (enabled) {
-        if (vb.state.is_inspecting) {
-            webkit_web_inspector_close(vb.gui.inspector);
-        } else {
-            webkit_web_inspector_show(vb.gui.inspector);
-        }
-        return true;
-    }
-
-    vb_echo(VB_MSG_ERROR, true, "webinspector is not enabled");
-
-    return false;
-}
-
-gboolean command_hints(const Arg *arg)
-{
-    int mode = arg->i;
-    char *prefix = "";
-    /* set prefix string according to hint type */
-    switch (HINTS_GET_TYPE(mode)) {
-        case HINTS_TYPE_LINK:
-            if (mode & HINTS_PROCESS_OPEN) {
-                prefix = mode & HINTS_OPEN_NEW ? "," : ".";
-            } else if (mode & HINTS_PROCESS_INPUT) {
-                prefix = mode & HINTS_OPEN_NEW ? ";t" : ";o";
-            } else if (mode & HINTS_PROCESS_YANK) {
-                prefix = ";y";
-            } else if (mode & HINTS_PROCESS_SAVE) {
-                prefix = ";s";
-            }
-#ifdef FEATURE_QUEUE
-            else if (mode & HINTS_PROCESS_PUSH) {
-                prefix = ";p";
-            }
-#endif
-            break;
-
-        case HINTS_TYPE_IMAGE:
-            if (mode & HINTS_PROCESS_OPEN) {
-                prefix = mode & HINTS_OPEN_NEW ? ";I" : ";i";
-            }
-            break;
-
-        case HINTS_TYPE_EDITABLE:
-            prefix = ";e";
-            break;
-    }
-
-    vb_echo_force(VB_MSG_NORMAL, false, "%s%s", prefix, arg->s ? arg->s : "");
-
-    /* mode will be set in hints_create - so we don't neet to do it here */
-    hints_create(arg->s, arg->i, strlen(prefix));
-
-    return true;
-}
-
-gboolean command_yank(const Arg *arg)
-{
-    vb_set_mode(VB_MODE_NORMAL, true);
-
-    if (arg->i & COMMAND_YANK_SELECTION) {
-        char *text = NULL;
-        /* copy current selection to clipboard */
-        webkit_web_view_copy_clipboard(vb.gui.webview);
-        text = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
-        if (!text) {
-            text = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD());
-        }
-        if (text) {
-            /* TODO should we show the full ynaked content with
-             * vb_set_input_text, wich migh get very large? */
-            vb_echo_force(VB_MSG_NORMAL, false, "Yanked: %s", text);
-            g_free(text);
-
-            return true;
-        }
-
-        return false;
-    }
-    /* use current arg.s as new clipboard content */
-    Arg a = {arg->i};
-    if (arg->i & COMMAND_YANK_URI) {
-        /* yank current url */
-        a.s = g_strdup(GET_URI());
-    } else {
-        a.s = arg->s ? g_strdup(arg->s) : NULL;
-    }
-    if (a.s) {
-        vb_set_clipboard(&a);
-        vb_echo_force(VB_MSG_NORMAL, false, "Yanked: %s", a.s);
-        g_free(a.s);
-
-        return true;
-    }
-
-    return false;
-}
-
-gboolean command_paste(const Arg *arg)
-{
-    Arg a = {.i = arg->i & VB_TARGET_NEW};
-    if (arg->i & VB_CLIPBOARD_PRIMARY) {
-        a.s = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
-    }
-    if (!a.s && arg->i & VB_CLIPBOARD_SECONDARY) {
-        a.s = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD());
-    }
-
-    if (a.s) {
-        vb_load_uri(&a);
-        g_free(a.s);
-
-        return true;
-    }
-    return false;
-}
 
 gboolean command_search(const Arg *arg)
 {
@@ -593,7 +38,7 @@ gboolean command_search(const Arg *arg)
 #endif
     gboolean forward;
 
-    if (arg->i == VB_SEARCH_OFF) {
+    if (arg->i == COMMAND_SEARCH_OFF) {
 #ifdef FEATURE_SEARCH_HIGHLIGHT
         webkit_web_view_unmark_text_matches(vb.gui.webview);
         highlight = false;
@@ -616,7 +61,7 @@ gboolean command_search(const Arg *arg)
         if (!highlight) {
             /* highlight matches if the search is started new or continued
              * after switch to normal mode which calls this function with
-             * VB_SEARCH_OFF */
+             * COMMAND_SEARCH_OFF */
             webkit_web_view_mark_text_matches(vb.gui.webview, query, false, 0);
             webkit_web_view_set_highlight_text_matches(vb.gui.webview, true);
 
@@ -636,95 +81,6 @@ gboolean command_search(const Arg *arg)
 
     /* unset posibble set count */
     vb.state.count = 0;
-    vb_set_mode(VB_MODE_NORMAL | VB_MODE_SEARCH, false);
-
-    return true;
-}
-
-gboolean command_selsearch(const Arg *arg)
-{
-    char *query = NULL;
-    gboolean res;
-
-    /* don't start a new search if already in search mode */
-    if (vb.state.mode & VB_MODE_SEARCH) {
-        /* don't set a mode - keep the search mode like it is like in vim */
-        return false;
-    }
-
-    /* there is no function to get the selected text so we copy current
-     * selection to clipboard */
-    webkit_web_view_copy_clipboard(vb.gui.webview);
-    query = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
-    if (!query) {
-        vb_set_mode(VB_MODE_NORMAL, false);
-
-        return false;
-    }
-
-    res = command_search(&((Arg){arg->i, query}));
-    g_free(query);
-
-    return res;
-}
-
-gboolean command_shortcut(const Arg *arg)
-{
-    gboolean result;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    if (arg->i) {
-        char *handle;
-
-        if (arg->s && (handle = strchr(arg->s, '='))) {
-            *handle = '\0';
-            handle++;
-            result = shortcut_add(arg->s, handle);
-        } else {
-            return false;
-        }
-    } else {
-        /* remove the shortcut */
-        result = shortcut_remove(arg->s);
-    }
-
-    return result;
-}
-
-gboolean command_shortcut_default(const Arg *arg)
-{
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    return shortcut_set_default(arg->s);
-}
-
-gboolean command_zoom(const Arg *arg)
-{
-    float step, level;
-    int count = vb.state.count ? vb.state.count : 1;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    if (arg->i & COMMAND_ZOOM_RESET) {
-        webkit_web_view_set_zoom_level(vb.gui.webview, 1.0);
-
-        return true;
-    }
-
-    level = webkit_web_view_get_zoom_level(vb.gui.webview);
-
-    WebKitWebSettings *setting = webkit_web_view_get_settings(vb.gui.webview);
-    g_object_get(G_OBJECT(setting), "zoom-step", &step, NULL);
-
-    webkit_web_view_set_full_content_zoom(
-        vb.gui.webview, (arg->i & COMMAND_ZOOM_FULL) > 0
-    );
-
-    webkit_web_view_set_zoom_level(
-        vb.gui.webview,
-        level + (float)(count *step) * (arg->i & COMMAND_ZOOM_IN ? 1.0 : -1.0)
-    );
 
     return true;
 }
@@ -745,107 +101,44 @@ gboolean command_history(const Arg *arg)
     return true;
 }
 
-gboolean command_bookmark(const Arg *arg)
+gboolean command_yank(const Arg *arg)
 {
-    vb_set_mode(VB_MODE_NORMAL, false);
+    static char *tmpl = "Yanked: %s";
 
-    if (!arg->i) {
-        if (bookmark_remove(arg->s ? arg->s : GET_URI())) {
-            vb_echo_force(VB_MSG_NORMAL, false, "  Bookmark removed");
+    if (arg->i == COMMAND_YANK_SELECTION) {
+        char *text = NULL;
+        /* copy current selection to clipboard */
+        webkit_web_view_copy_clipboard(vb.gui.webview);
+        text = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
+        if (!text) {
+            text = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD());
+        }
+        if (text) {
+            vb_echo_force(VB_MSG_NORMAL, false, tmpl, text);
+            g_free(text);
 
             return true;
         }
-    } else if (bookmark_add(GET_URI(), webkit_web_view_get_title(vb.gui.webview), arg->s)) {
-        vb_echo_force(VB_MSG_NORMAL, false, "  Bookmark added");
-
-        return true;
-    }
-
-    return false;
-}
-
-gboolean command_eval(const Arg *arg)
-{
-    gboolean success;
-    char *value = NULL;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-
-    success = vb_eval_script(
-        webkit_web_view_get_main_frame(vb.gui.webview), arg->s, NULL, &value
-    );
-    if (success) {
-        vb_echo_force(VB_MSG_NORMAL, false, "%s", value);
-    } else {
-        vb_echo_force(VB_MSG_ERROR, true, "%s", value);
-    }
-    g_free(value);
-
-    return success;
-}
-
-gboolean command_nextprev(const Arg *arg)
-{
-    if (vb.state.mode & VB_MODE_HINTING) {
-        hints_focus_next(arg->i ? true : false);
-    } else {
-        /* mode will be set in completion_complete */
-        completion_complete(arg->i ? true : false);
-    }
-
-    return true;
-}
-
-gboolean command_descent(const Arg *arg)
-{
-    gboolean result;
-    int count = vb.state.count ? vb.state.count : 1;
-    const char *uri, *p = NULL, *domain = NULL;
-
-    uri = GET_URI();
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-    if (!uri || *uri == '\0') {
-        return false;
-    }
 
-    /* get domain part */
-    if (!(domain = strstr(uri, "://")) || !(domain = strchr(domain + 3, '/'))) {
         return false;
     }
 
-    if (arg->i) {
-        p = domain;
+    Arg a = {VB_CLIPBOARD_PRIMARY|VB_CLIPBOARD_SECONDARY};
+    if (arg->i == COMMAND_YANK_URI) {
+        /* yank current uri */
+        a.s = (char*)GET_URI();
     } else {
-        /* start at the end */
-        p = uri + strlen(uri);
-        /* if last char is / increment count to step over this first */
-        if (*(p - 1) == '/') {
-            count++;
-        }
-        for (int i = 0; i < count; i++) {
-            while (*(p--) != '/') {
-                if (p == uri) {
-                    /* reach the beginning */
-                    return false;
-                }
-            }
-        }
-        /* keep the last / in uri */
-        p++;
+        /* use current arg.s as new clipboard content */
+        a.s = arg->s;
     }
+    if (a.s) {
+        vb_set_clipboard(&a);
+        vb_echo_force(VB_MSG_NORMAL, false, tmpl, a.s);
 
-    /* if the url is shorter than the domain use the domain instead */
-    if (p < domain) {
-        p = domain;
+        return true;
     }
 
-    Arg a = {VB_TARGET_CURRENT};
-    a.s = g_strndup(uri, p - uri + 1);
-    result = vb_load_uri(&a);
-    g_free(a.s);
-
-    return result;
+    return false;
 }
 
 gboolean command_save(const Arg *arg)
@@ -853,7 +146,6 @@ gboolean command_save(const Arg *arg)
     WebKitDownload *download;
     const char *uri, *path = NULL;
 
-    vb_set_mode(VB_MODE_NORMAL, false);
     if (arg->i == COMMAND_SAVE_CURRENT) {
         uri = GET_URI();
         /* given string is the path to save the download to */
@@ -874,38 +166,6 @@ gboolean command_save(const Arg *arg)
     return true;
 }
 
-gboolean command_shellcmd(const Arg *arg)
-{
-    int status, argc;
-    char *cmd, *exp, *error = NULL, *out = NULL, **argv;
-
-    vb_set_mode(VB_MODE_NORMAL, false);
-    if (!arg->s || *(arg->s) == '\0') {
-        return false;
-    }
-
-    exp = expand_string(arg->s);
-    cmd = g_strdup_printf(SHELL_CMD, exp);
-    g_free(exp);
-    if (!g_shell_parse_argv(cmd, &argc, &argv, NULL)) {
-        vb_echo(VB_MSG_ERROR, true, "Could not parse command args");
-        g_free(cmd);
-
-        return false;
-    }
-    g_free(cmd);
-
-    g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &out, &error, &status, NULL);
-    g_strfreev(argv);
-    if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
-        vb_echo(VB_MSG_NORMAL, true, "%s", out);
-        return true;
-    }
-
-    vb_echo(VB_MSG_ERROR, true, "[%d] %s", WEXITSTATUS(status), error);
-    return false;
-}
-
 #ifdef FEATURE_QUEUE
 gboolean command_queue(const Arg *arg)
 {
@@ -913,9 +173,15 @@ gboolean command_queue(const Arg *arg)
     int count = 0;
     char *uri;
 
-    vb_set_mode(VB_MODE_NORMAL, false);
-
     switch (arg->i) {
+        case COMMAND_QUEUE_POP:
+            if ((uri = bookmark_queue_pop(&count))) {
+                res = vb_load_uri(&(Arg){VB_TARGET_CURRENT, uri});
+                g_free(uri);
+            }
+            vb_echo(VB_MSG_NORMAL, false, "Queue length %d", count);
+            break;
+
         case COMMAND_QUEUE_PUSH:
             res = bookmark_queue_push(arg->s ? arg->s : GET_URI());
             if (res) {
@@ -930,14 +196,6 @@ gboolean command_queue(const Arg *arg)
             }
             break;
 
-        case COMMAND_QUEUE_POP:
-            if ((uri = bookmark_queue_pop(&count))) {
-                res = vb_load_uri(&(Arg){VB_TARGET_CURRENT, uri});
-                g_free(uri);
-            }
-            vb_echo(VB_MSG_NORMAL, false, "Queue length %d", count);
-            break;
-
         case COMMAND_QUEUE_CLEAR:
             if (bookmark_queue_clear()) {
                 vb_echo(VB_MSG_NORMAL, false, "Queue cleared");
@@ -948,130 +206,3 @@ gboolean command_queue(const Arg *arg)
     return res;
 }
 #endif
-
-gboolean command_mode(const Arg *arg)
-{
-    /* if a main mode is given - set the mode like it is */
-    if (CLEAN_MODE(arg->i)) {
-        return vb_set_mode(arg->i, false);
-    }
-
-    /* else combine the current main mode with the new submode */
-    /* TODO move this logic to main.c: vb_set_mode() */
-    return vb_set_mode(arg->i|CLEAN_MODE(vb.state.mode), false);
-}
-
-gboolean command_focusinput(const Arg *arg)
-{
-    if (dom_focus_input(vb.gui.webview)) {
-        vb_set_mode(VB_MODE_INPUT, false);
-
-        return true;
-    }
-    return false;
-}
-
-gboolean command_editor(const Arg *arg)
-{
-    char *file_path = NULL;
-    const char *text;
-    char **argv;
-    int argc;
-    GPid pid;
-    gboolean success;
-
-    if (!vb.config.editor_command) {
-        vb_echo(VB_MSG_ERROR, true, "No editor-command configured");
-        return false;
-    }
-    Element* active = dom_get_active_element(vb.gui.webview);
-
-    /* check if element is suitable for editing */
-    if (!active || !dom_is_editable(active)) {
-        return false;
-    }
-
-    text = dom_editable_element_get_value(active);
-    if (!text) {
-        return false;
-    }
-
-    if (!util_create_tmp_file(text, &file_path)) {
-        return false;
-    }
-
-    /* spawn editor */
-    char* command = g_strdup_printf(vb.config.editor_command, file_path);
-    if (!g_shell_parse_argv(command, &argc, &argv, NULL)) {
-        fprintf(stderr, "Could not parse editor-command");
-        g_free(command);
-        return false;
-    }
-    g_free(command);
-
-    success = g_spawn_async(
-        NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
-        NULL, NULL, &pid, NULL
-    );
-    g_strfreev(argv);
-
-    if (!success) {
-        unlink(file_path);
-        g_free(file_path);
-        return false;
-    }
-
-    /* disable the active element */
-    dom_editable_element_set_disable(active, true);
-
-    OpenEditorData *data = g_new0(OpenEditorData, 1);
-    data->file    = file_path;
-    data->element = active;
-
-    g_child_watch_add(pid, (GChildWatchFunc)editor_resume, data);
-
-    return true;
-}
-
-static void editor_resume(GPid pid, int status, OpenEditorData *data)
-{
-    char *text;
-    if (status == 0) {
-        text = util_get_file_contents(data->file, NULL);
-        if (text) {
-            dom_editable_element_set_value(data->element, text);
-        }
-        g_free(text);
-    }
-    dom_editable_element_set_disable(data->element, false);
-
-    g_free(data->file);
-    g_free(data);
-    g_spawn_close_pid(pid);
-}
-
-static CommandInfo *command_lookup(const char* name)
-{
-    CommandInfo *c;
-    c = g_hash_table_lookup(short_commands, name);
-    if (!c) {
-        c = g_hash_table_lookup(commands, name);
-    }
-
-    return c;
-}
-
-/**
- * Expands paceholders in given string.
- * % - expanded to current uri
- * TODO allow modifiers like :p :h :e :r like in vim expand()
- *
- * Returned string must be freed.
- */
-static char *expand_string(const char *str)
-{
-    if (!str) {
-        return NULL;
-    }
-    return util_str_replace("%", GET_URI(), str);
-}
index e9020eb..b6de228 100644 (file)
 #ifndef _COMMAND_H
 #define _COMMAND_H
 
-/*
-bitmap
-1: primary cliboard
-2: secondary cliboard
-3: yank uri
-4: yank selection
-*/
-enum {
-    COMMAND_YANK_URI       = (VB_CLIPBOARD_SECONDARY<<1),
-    COMMAND_YANK_SELECTION = (VB_CLIPBOARD_SECONDARY<<2)
-};
+typedef enum {
+    COMMAND_SEARCH_OFF,
+    COMMAND_SEARCH_FORWARD  = (1<<0),
+    COMMAND_SEARCH_BACKWARD = (1<<1),
+} SearchDirection;
 
 enum {
-    COMMAND_ZOOM_OUT,
-    COMMAND_ZOOM_IN,
-    COMMAND_ZOOM_FULL  = (1<<1),
-    COMMAND_ZOOM_RESET = (1<<2)
+    COMMAND_YANK_ARG,
+    COMMAND_YANK_URI,
+    COMMAND_YANK_SELECTION
 };
 
 enum {
@@ -53,47 +46,12 @@ enum {
 };
 #endif
 
-typedef gboolean (*Command)(const Arg *arg);
-
-void command_init(void);
-void command_cleanup(void);
-gboolean command_parse_from_string(const char *input, Command *func, Arg *arg, guint *count);
-gboolean command_run_string(const char *input);
-gboolean command_run_multi(const Arg *arg);
-gboolean command_fill_completion(GtkListStore *store, const char *input);
-
-gboolean command_open(const Arg *arg);
-gboolean command_open_home(const Arg *arg);
-gboolean command_open_closed(const Arg *arg);
-gboolean command_input(const Arg *arg);
-gboolean command_close(const Arg *arg);
-gboolean command_view_source(const Arg *arg);
-gboolean command_navigate(const Arg *arg);
-gboolean command_scroll(const Arg *arg);
-gboolean command_map(const Arg *arg);
-gboolean command_unmap(const Arg *arg);
-gboolean command_set(const Arg *arg);
-gboolean command_inspect(const Arg *arg);
-gboolean command_hints(const Arg *arg);
-gboolean command_yank(const Arg *arg);
-gboolean command_paste(const Arg *arg);
 gboolean command_search(const Arg *arg);
-gboolean command_selsearch(const Arg *arg);
-gboolean command_shortcut(const Arg *arg);
-gboolean command_shortcut_default(const Arg *arg);
-gboolean command_zoom(const Arg *arg);
 gboolean command_history(const Arg *arg);
-gboolean command_bookmark(const Arg *arg);
-gboolean command_eval(const Arg *arg);
-gboolean command_editor(const Arg *arg);
-gboolean command_nextprev(const Arg *arg);
-gboolean command_descent(const Arg *arg);
+gboolean command_yank(const Arg *arg);
 gboolean command_save(const Arg *arg);
-gboolean command_shellcmd(const Arg *arg);
 #ifdef FEATURE_QUEUE
 gboolean command_queue(const Arg *arg);
 #endif
-gboolean command_mode(const Arg *arg);
-gboolean command_focusinput(const Arg *arg);
 
 #endif /* end of include guard: _COMMAND_H */
index 1f8aa14..d3e10df 100644 (file)
@@ -25,6 +25,7 @@
 #include "bookmark.h"
 #include "command.h"
 #include "setting.h"
+#include "ex.h"
 
 #define TAG_INDICATOR '!'
 
@@ -54,10 +55,11 @@ gboolean completion_complete(gboolean back)
     GtkListStore *store = NULL;
     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.state.mode & VB_MODE_COMPLETE) {
+    if (vb.mode->flags & FLAG_COMPLETION) {
         if (comp.text && !strcmp(input, comp.text)) {
             /* step through the next/prev completion item */
             move_cursor(back);
@@ -68,12 +70,6 @@ gboolean completion_complete(gboolean back)
         completion_clean();
     }
 
-    /* don't disturb other command sub modes - complete only if no sub mode
-     * is set before */
-    if (vb.state.mode != VB_MODE_COMMAND) {
-        return false;
-    }
-
     /* create the list store model */
     store = gtk_list_store_new(COMPLETION_STORE_NUM, G_TYPE_STRING, G_TYPE_STRING);
     if (type == VB_INPUT_SET) {
@@ -91,7 +87,10 @@ gboolean completion_complete(gboolean back)
         /* remove counts before command and save it to print it later in inputbox */
         comp.count = g_ascii_strtoll(suffix, &command, 10);
 
-        res = command_fill_completion(store, command);
+        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) {
@@ -107,7 +106,8 @@ gboolean completion_complete(gboolean back)
         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), COMPLETION_STORE_FIRST, GTK_SORT_ASCENDING);
     }
 
-    vb_set_mode(VB_MODE_COMMAND | VB_MODE_COMPLETE, false);
+    /* set the submode flag */
+    vb.mode->flags |= FLAG_COMPLETION;
 
     OVERWRITE_STRING(comp.prefix, prefix);
     init_completion(GTK_TREE_MODEL(store));
@@ -118,16 +118,18 @@ gboolean completion_complete(gboolean back)
 
 void completion_clean(void)
 {
-    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;
+    if (vb.mode->flags & FLAG_COMPLETION) {
+        /* remove completion flag from mode */
+        vb.mode->flags &= ~FLAG_COMPLETION;
 
-    /* remove completion flag from mode */
-    vb.state.mode &= ~VB_MODE_COMPLETE;
+        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)
index 060e34b..7be6500 100644 (file)
 #include "stdlib.h"
 
 static char *default_config[] = {
-    "nmap gf=source",
-    "nmap gF=inspect",
-    "nmap :=input",
-    "nmap /=input /",
-    "nmap ?=input ?",
-    "nmap n=search-forward",
-    "nmap N=search-backward",
-    "nmap *=search-selection-forward",
-    "nmap #=search-selection-backward",
+#if 0
     "nmap o=input :open ",
     "nmap t=input :tabopen ",
     "nmap O=inputuri :open ",
     "nmap T=inputuri :tabopen ",
     "nmap gh=open",
     "nmap gH=tabopen",
-    "nmap u=open-closed",
-    "nmap U=tabopen-closed",
-    "nmap <ctrl-q>=quit",
-    "nmap <ctrl-o>=back",
-    "nmap <ctrl-i>=forward",
-    "nmap r=reload",
-    "nmap R=reload!",
-    "nmap C=stop",
-    "nmap <ctrl-f>=pagedown",
-    "nmap <ctrl-b>=pageup",
-    "nmap <ctrl-d>=halfpagedown",
-    "nmap <ctrl-u>=halfpageup",
-    "nmap gg=jumptop",
-    "nmap G=jumpbottom",
-    "nmap 0=jumpleft",
-    "nmap $=jumpright",
-    "nmap h=scrollleft",
-    "nmap l=scrollright",
-    "nmap k=scrollup",
-    "nmap j=scrolldown",
     "nmap f=hint-link",
     "nmap F=hint-link-new",
-    "nmap ;o=hint-input-open",
-    "nmap ;t=hint-input-tabopen",
-    "nmap ;y=hint-yank",
-    "nmap ;i=hint-image-open",
-    "nmap ;I=hint-image-tabopen",
-    "nmap ;e=hint-editor",
-    "nmap ;s=hint-save",
-#ifdef FEATURE_QUEUE
-    "nmap ;p=hint-queue-push",
-    "nmap ;P=hint-queue-unshift",
     "nmap <ctrl-p>=queue-pop",
-#endif
-    "nmap y=yank-uri",
-    "nmap Y=yank-selection",
-    "nmap p=open-clipboard",
-    "nmap P=tabopen-clipboard",
     "nmap zi=zoomin",
     "nmap zI=zoominfull",
     "nmap zo=zoomout",
@@ -85,12 +42,9 @@ static char *default_config[] = {
     "nmap gU=descent!",
     "nmap <ctrl-z>=pass-through",
     "nmap gi=focus-input",
-    "cmap <tab>=next",
-    "cmap <shift-tab>=prev",
-    "cmap <up>=hist-prev",
-    "cmap <down>=hist-next",
-    "imap <ctrl-t>=editor",
+    /* XXX "imap <ctrl-t>=editor",*/
     "imap <ctrl-z>=pass-through",
+#endif
     "shortcut-add dl=https://duckduckgo.com/html/?q=$0",
     "shortcut-add dd=https://duckduckgo.com/?q=$0",
     "shortcut-default dl",
index cc5f31a..c9d7ade 100644 (file)
--- a/src/dom.c
+++ b/src/dom.c
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "main.h"
 #include "dom.h"
+#include "mode.h"
 
 extern VbCore vb;
 
@@ -194,7 +195,8 @@ static gboolean element_is_visible(WebKitDOMDOMWindow* win, WebKitDOMElement* el
 static gboolean auto_insert(Element *element)
 {
     if (dom_is_editable(element)) {
-        vb_set_mode(VB_MODE_INPUT, false);
+        mode_enter('i');
+
         return true;
     }
     return false;
@@ -205,7 +207,7 @@ static gboolean editable_focus_cb(Element *element, Event *event)
     webkit_dom_event_target_remove_event_listener(
         WEBKIT_DOM_EVENT_TARGET(element), "focus", G_CALLBACK(editable_focus_cb), false
     );
-    if (CLEAN_MODE(vb.state.mode) != VB_MODE_INPUT) {
+    if (vb.mode->id != 'i') {
         EventTarget *target = webkit_dom_event_get_target(event);
         auto_insert((void*)target);
     }
diff --git a/src/ex.c b/src/ex.c
new file mode 100644 (file)
index 0000000..3ea60e6
--- /dev/null
+++ b/src/ex.c
@@ -0,0 +1,793 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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/.
+ */
+
+/**
+ * This file contains function to handle input editing, parsing of called ex
+ * commands from inputbox and the ex commands.
+ */
+#include <ctype.h>
+#include "config.h"
+#include "main.h"
+#include "ex.h"
+#include "completion.h"
+#include "hints.h"
+#include "mode.h"
+#include "command.h"
+#include "history.h"
+#include "dom.h"
+#include "setting.h"
+#include "util.h"
+#include "bookmark.h"
+#include "shortcut.h"
+#include "map.h"
+
+typedef enum {
+    EX_BMA,
+    EX_BMR,
+    EX_EVAL,
+    EX_CMAP,
+    EX_IMAP,
+    EX_NMAP,
+    EX_CUNMAP,
+    EX_IUNMAP,
+    EX_NUNMAP,
+    EX_OPEN,
+#ifdef FEATURE_QUEUE
+    EX_QCLEAR,
+    EX_QPOP,
+    EX_QPUSH,
+    EX_QUNSHIFT,
+#endif
+    EX_QUIT,
+    EX_SAVE,
+    EX_SCA,
+    EX_SCD,
+    EX_SCR,
+    EX_SET,
+    EX_SHELLCMD,
+    EX_TABOPEN,
+} ExCode;
+
+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 */
+    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 */
+} ExArg;
+
+typedef gboolean (*ExFunc)(const ExArg *arg);
+
+typedef struct {
+    const char *name;         /* full name of the command even if called abreviated */
+    ExCode    code;           /* constant id for the command */
+    ExFunc    func;
+#define EX_FLAG_NONE   0x000  /* no flags set */
+#define EX_FLAG_BANG   0x001  /* command uses the bang ! after command name */
+#define EX_FLAG_LHS    0x002  /* command has a single word after the command name */
+#define EX_FLAG_RHS    0x004  /* command has a right hand side */
+    int        flags;
+} ExInfo;
+
+static void input_activate(void);
+static gboolean parse(const char **input, ExArg *arg);
+static gboolean parse_count(const char **input, ExArg *arg);
+static gboolean parse_command_name(const char **input, ExArg *arg);
+static gboolean parse_lhs(const char **input, ExArg *arg);
+static gboolean parse_rhs(const char **input, ExArg *arg);
+static void skip_whitespace(const char **input);
+static void free_cmdarg(ExArg *arg);
+static gboolean execute(const ExArg *arg);
+static char *expand_string(const char *str);
+
+static gboolean ex_bookmark(const ExArg *arg);
+static gboolean ex_eval(const ExArg *arg);
+static gboolean ex_map(const ExArg *arg);
+static gboolean ex_unmap(const ExArg *arg);
+static gboolean ex_open(const ExArg *arg);
+static gboolean ex_queue(const ExArg *arg);
+static gboolean ex_quit(const ExArg *arg);
+static gboolean ex_save(const ExArg *arg);
+static gboolean ex_set(const ExArg *arg);
+static gboolean ex_shellcmd(const ExArg *arg);
+static gboolean ex_shortcut(const ExArg *arg);
+
+/* 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.
+ * Also the sorting and grouping of command names matters, so we give up
+ * searching for a matching command if the next compared character did not
+ * match. */
+static ExInfo commands[] = {
+    /* command           code             func            flags */
+    {"bma",              EX_BMA,         ex_bookmark,   EX_FLAG_RHS},
+    {"bmr",              EX_BMR,         ex_bookmark,   EX_FLAG_RHS},
+#if 0
+    {"bookmark-add",     EX_BMA,         ex_bookmark,   EX_FLAG_RHS},
+    {"bookmark-remove",  EX_BMR,         ex_bookmark,   EX_FLAG_RHS},
+#endif
+    {"cmap",             EX_CMAP,        ex_map,        EX_FLAG_LHS|EX_FLAG_RHS},
+    {"cunmap",           EX_CUNMAP,      ex_unmap,      EX_FLAG_LHS},
+    {"eval",             EX_EVAL,        ex_eval,       EX_FLAG_RHS},
+    {"imap",             EX_IMAP,        ex_map,        EX_FLAG_LHS|EX_FLAG_RHS},
+    {"iunmap",           EX_IUNMAP,      ex_unmap,      EX_FLAG_LHS},
+    {"nmap",             EX_NMAP,        ex_map,        EX_FLAG_LHS|EX_FLAG_RHS},
+    {"nunmap",           EX_NUNMAP,      ex_unmap,      EX_FLAG_LHS},
+    {"open",             EX_OPEN,        ex_open,       EX_FLAG_RHS},
+    {"quit",             EX_QUIT,        ex_quit,       EX_FLAG_NONE},
+    {"qclear",           EX_QCLEAR,      ex_queue,      EX_FLAG_RHS},
+    {"qpop",             EX_QPOP,        ex_queue,      EX_FLAG_NONE},
+    {"qpush",            EX_QPUSH,       ex_queue,      EX_FLAG_RHS},
+    {"qunshift",         EX_QUNSHIFT,    ex_queue,      EX_FLAG_RHS},
+    {"save",             EX_SAVE,        ex_save,       EX_FLAG_RHS},
+#if 0
+    {"sca",              EX_SCA,         ex_shortcut,   EX_FLAG_RHS},
+    {"scd",              EX_SCD,         ex_shortcut,   EX_FLAG_RHS},
+    {"scr",              EX_SCR,         ex_shortcut,   EX_FLAG_RHS},
+#endif
+    {"set",              EX_SET,         ex_set,        EX_FLAG_RHS},
+    {"shellcmd",         EX_SHELLCMD,    ex_shellcmd,   EX_FLAG_RHS},
+    {"shortcut-add",     EX_SCA,         ex_shortcut,   EX_FLAG_RHS},
+    {"shortcut-default", EX_SCD,         ex_shortcut,   EX_FLAG_RHS},
+    {"shortcut-remove",  EX_SCR,         ex_shortcut,   EX_FLAG_RHS},
+    {"tabopen",          EX_TABOPEN,     ex_open,       EX_FLAG_RHS},
+#if 0
+    {"queue-push",       EX_QPUSH,       ex_queue,      EX_FLAG_RHS},
+    {"queue-unshift",    EX_QUNSHIFT,    ex_queue,      EX_FLAG_RHS},
+    {"queue-pop",        EX_QPOP,        ex_queue,      EX_FLAG_RHS},
+    {"queue-clear",      EX_QCLEAR,      ex_queue,      EX_FLAG_RHS},
+#endif
+};
+
+extern VbCore vb;
+
+
+/**
+ * Function called when vimb enters the command mode.
+ */
+void ex_enter(void)
+{
+    gtk_widget_grab_focus(GTK_WIDGET(vb.gui.input));
+    dom_clear_focus(vb.gui.webview);
+}
+
+/**
+ * Called when the command mode is left.
+ */
+void ex_leave(void)
+{
+    /* TODO clean those only if they where active */
+    completion_clean();
+    hints_clear();
+}
+
+/**
+ * Handles the keypress events from webview and inputbox.
+ */
+VbResult ex_keypress(unsigned int key)
+{
+    /* TODO allow to get the right prompt like ':', '/', ';o', ... */
+    char *prompt = ":";
+    GtkTextIter start, end;
+    GtkTextBuffer *buffer = vb.gui.buffer;
+    GtkTextMark *mark;
+
+    /* delegate call to the submode if hinting is active */
+    if (vb.mode->flags & FLAG_HINTING) {
+        if (RESULT_COMPLETE == hints_keypress(key)) {
+            vb.state.processed_key = true;
+            return RESULT_COMPLETE;
+        }
+    }
+
+    vb.state.processed_key = true;
+    switch (key) {
+        case CTRL('I'): /* Tab */
+            /* mode will be set in completion_complete */
+            completion_complete(false);
+            break;
+
+        case CTRL('O'): /* S-Tab */
+            completion_complete(true);
+            break;
+
+        case CTRL('['):
+        case CTRL('C'):
+            mode_enter('n');
+            vb_set_input_text("");
+            break;
+
+        case '\n':
+            input_activate();
+            break;
+
+        case CTRL('P'): /* up */
+            /* TODO don't emit input change event when stepping though history in search mode */
+            command_history(&((Arg){1}));
+            break;
+
+        case CTRL('N'): /* down */
+            command_history(&((Arg){0}));
+            break;
+
+        /* basic command line editing */
+        case CTRL('H'):
+            /* delete the last char before the cursor */
+            mark = gtk_text_buffer_get_insert(buffer);
+            gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
+            gtk_text_buffer_backspace(buffer, &start, true, true);
+            break;
+
+        case CTRL('W'):
+            /* delete word backward from cursor */
+            mark = gtk_text_buffer_get_insert(buffer);
+            gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
+
+            /* copy the iter to build start and end point for deletion */
+            start = end;
+
+            /* move the iterator to the beginning of previous word */
+            if (gtk_text_iter_backward_word_start(&start)) {
+                gtk_text_buffer_delete(buffer, &start, &end);
+            }
+            break;
+
+        case CTRL('B'):
+            /* move the cursor direct behind the prompt */
+            gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(prompt));
+            gtk_text_buffer_place_cursor(buffer, &start);
+            break;
+
+        case CTRL('E'):
+            /* move the cursor to the end of line */
+            gtk_text_buffer_get_end_iter(buffer, &start);
+            gtk_text_buffer_place_cursor(buffer, &start);
+            break;
+
+        case CTRL('U'):
+            /* remove everythings between cursor and prompt */
+            mark = gtk_text_buffer_get_insert(buffer);
+            gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
+            gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(prompt));
+            gtk_text_buffer_delete(buffer, &start, &end);
+            break;
+
+        default:
+            /* if is printable ascii char, than write it at the cursor
+             * position into input box */
+            if (key >= 0x20 && key <= 0x7e) {
+                gtk_text_buffer_insert_at_cursor(buffer, (char[2]){key, 0}, 1);
+            } else {
+                vb.state.processed_key = false;
+            }
+    }
+
+    return RESULT_COMPLETE;
+}
+
+/**
+ * Handles changes in the inputbox.
+ */
+void ex_input_changed(const char *text)
+{
+    gboolean forward = false;
+    switch (*text) {
+        case ';':
+            hints_create(text);
+            break;
+
+        case '/': forward = true; /* fall through */
+        case '?':
+            webkit_web_view_unmark_text_matches(vb.gui.webview);
+            webkit_web_view_search_text(vb.gui.webview, &text[1], false, forward, false);
+            break;
+    }
+}
+
+gboolean ex_fill_completion(GtkListStore *store, const char *input)
+{
+    GtkTreeIter iter;
+    ExInfo *cmd;
+    gboolean found = false;
+
+    if (!input || *input == '\0') {
+        for (int i = 0; i < LENGTH(commands); i++) {
+            cmd = &commands[i];
+            gtk_list_store_append(store, &iter);
+            gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1);
+            found = true;
+        }
+    } else {
+        for (int i = 0; i < LENGTH(commands); i++) {
+            cmd = &commands[i];
+            if (g_str_has_prefix(cmd->name, input)) {
+                gtk_list_store_append(store, &iter);
+                gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1);
+                found = true;
+            }
+        }
+    }
+
+    return found;
+}
+
+/**
+ * This is called if the user typed <nl> or <cr> into the inputbox.
+ */
+static void input_activate(void)
+{
+    gboolean forward = false;
+    char *text, *cmd;
+    text = vb_get_input_text();
+
+    /* skip leading promt char like ':' or '/' */
+    /* TODO should we use a flag to determine if we should record the command
+     * into the history - maybe it's not good to save commands in history that
+     * where triggered by a map like ':name \, :set scripts!<cr>' - by the way
+     * does vim also skip history recording for such mapped commands */
+    cmd = text + 1;
+    switch (*text) {
+        case '/': forward = true; /* fall throught */
+        case '?':
+            history_add(HISTORY_SEARCH, cmd, NULL);
+            mode_enter('n');
+            command_search(&((Arg){forward ? COMMAND_SEARCH_FORWARD : COMMAND_SEARCH_BACKWARD, cmd}));
+            break;
+
+        case ';':
+            hints_fire();
+            break;
+
+        case ':':
+            history_add(HISTORY_COMMAND, cmd, NULL);
+            mode_enter('n');
+            ex_run_string(cmd);
+            break;
+
+    }
+    g_free(text);
+}
+
+gboolean ex_run_string(const char *input)
+{
+    ExArg *a = g_new0(ExArg, 1);
+    a->lhs = g_string_new("");
+    a->rhs = g_string_new("");
+
+    if (!parse(&input, a)
+        || !execute(a)
+    ) {
+        free_cmdarg(a);
+
+        return false;
+    }
+    free_cmdarg(a);
+
+    return true;
+}
+
+/**
+ * Parses given input string into given ExArg pointer.
+ */
+static gboolean parse(const char **input, ExArg *arg)
+{
+    ExInfo *cmd = NULL;
+    if (!*input) {
+        return false;
+    }
+    /* remove leading whitespace */
+    skip_whitespace(input);
+    parse_count(input, arg);
+
+    skip_whitespace(input);
+    if (!parse_command_name(input, arg)) {
+        return false;
+    }
+
+    /* get the command and it's flags to decide what to parse */
+    cmd = &(commands[arg->idx]);
+
+    /* parse the lhs if this is available */
+    skip_whitespace(input);
+    if (cmd->flags & EX_FLAG_LHS) {
+        parse_lhs(input, arg);
+    }
+    /* parse the rhs if this is available */
+    skip_whitespace(input);
+    if (cmd->flags & EX_FLAG_RHS) {
+        parse_rhs(input, arg);
+    }
+
+#if 0
+    PRINT_DEBUG("CMD idx=%d, x=%d [%s][%s][%s]", arg->idx, arg->count, arg->name, arg->lhs->str, arg->rhs->str);
+#endif
+    return true;
+}
+
+/**
+ * Parses possible found count of given input into ExArg pointer.
+ */
+static gboolean parse_count(const char **input, ExArg *arg)
+{
+    if (!*input || !isdigit(**input)) {
+        arg->count = 0;
+    } else {
+        do {
+            arg->count = arg->count * 10 + (**input - '0');
+            (*input)++;
+        } while (isdigit(**input));
+    }
+    return true;
+}
+
+/**
+ * Parse the command name from given input.
+ */
+static gboolean parse_command_name(const char **input, ExArg *arg)
+{
+    int len      = 0;
+    int first    = 0;   /* number of first found command */
+    int matches  = 0;   /* number of commands that matches the input */
+    char cmd[20] = {0}; /* name of found command */
+
+    do {
+        /* copy the next char into the cmd buffer */
+        cmd[len++] = **input;
+        int i;
+        for (i = first, matches = 0; i < LENGTH(commands); i++) {
+            /* commands are grouped by their first letters, if we reached the
+             * end of the group there are no more possible matches to find */
+            if (len > 1 && strncmp(commands[i].name, cmd, len - 1)) {
+                break;
+            }
+            if (commands[i].name[len - 1] == **input) {
+                /* partial match found */
+                if (!matches) {
+                    /* if this is the first then remeber it */
+                    first = i;
+                }
+                matches++;
+            }
+        }
+        (*input)++;
+    } while (matches > 0 && **input != '\0' && **input != ' ');
+
+    if (!matches) {
+        /* TODO show readable error message */
+        return false;
+    }
+
+    arg->idx  = first;
+    arg->code = commands[first].code;
+    arg->name = commands[first].name;
+
+    return true;
+}
+
+/**
+ * Parse a single word left hand side of a command arg.
+ */
+static gboolean parse_lhs(const char **input, ExArg *arg)
+{
+    if (!*input) {
+        return false;
+    }
+    /* get the char until the next none escaped whitespace and save it into
+     * the lhs */
+    while (*input && **input != ' ') {
+        /* if we find a backslash this escapes the next whitespace */
+        if (**input == '\\') {
+            /* move pointer to the next char */
+            (*input)++;
+            if (!*input) {
+                /* if input ends here - use only the backslash */
+                g_string_append_c(arg->lhs, '\\');
+            } else if (**input == ' ') {
+                /* escaped whitespace becomes only whitespace */
+                g_string_append_c(arg->lhs, **input);
+            } else {
+                /* put escape char and next char into the result string */
+                g_string_append_c(arg->lhs, '\\');
+                g_string_append_c(arg->lhs, **input);
+            }
+        } else {
+            /* unquoted char */
+            g_string_append_c(arg->lhs, **input);
+        }
+        (*input)++;
+    }
+    return true;
+}
+
+/**
+ * Parses the right hand side of command args.
+ */
+static gboolean parse_rhs(const char **input, ExArg *arg)
+{
+    if (!*input) {
+        return false;
+    }
+    /* get char until the end of command */
+    while (*input && **input != '\n' && **input != '|') {
+        /* if we find a backslash this escapes the next whitespace */
+        if (**input == '\\') {
+            /* move pointer to the next char */
+            (*input)++;
+            if (!*input) {
+                /* if input ends here - use only the backslash */
+                g_string_append_c(arg->rhs, '\\');
+            } else if (**input == ' ') {
+                /* escaped whitespace becomes only whitespace */
+                g_string_append_c(arg->rhs, **input);
+            } else {
+                /* put escape char and next char into the result string */
+                g_string_append_c(arg->rhs, '\\');
+                g_string_append_c(arg->rhs, **input);
+            }
+        } else {
+            /* unquoted char */
+            g_string_append_c(arg->rhs, **input);
+        }
+        (*input)++;
+    }
+    return true;
+}
+
+/**
+ * Executes the command given by ExArg.
+ */
+static gboolean execute(const ExArg *arg)
+{
+    return (commands[arg->idx].func)(arg);
+}
+
+static void skip_whitespace(const char **input)
+{
+    /* TODO should \t also be skipped here? */
+    while (*input && **input == ' ') {
+        (*input)++;
+    }
+}
+
+/**
+ * Expands paceholders in given string.
+ * % - expanded to current uri
+ * TODO allow modifiers like :p :h :e :r like in vim expand()
+ *
+ * Returned string must be freed.
+ */
+static char *expand_string(const char *str)
+{
+    if (!str) {
+        return NULL;
+    }
+    return util_str_replace("%", GET_URI(), str);
+}
+
+static void free_cmdarg(ExArg *arg)
+{
+    g_string_free(arg->lhs, true);
+    g_string_free(arg->rhs, true);
+    g_free(arg);
+}
+
+static gboolean ex_bookmark(const ExArg *arg)
+{
+    if (arg->code == EX_BMR) {
+        if (bookmark_remove(*arg->rhs->str ? arg->rhs->str : GET_URI())) {
+            vb_echo_force(VB_MSG_NORMAL, false, "  Bookmark removed");
+
+            return true;
+        }
+    } else if (bookmark_add(GET_URI(), webkit_web_view_get_title(vb.gui.webview), arg->rhs->str)) {
+        vb_echo_force(VB_MSG_NORMAL, false, "  Bookmark added");
+
+        return true;
+    }
+
+    return false;
+}
+
+static gboolean ex_eval(const ExArg *arg)
+{
+    gboolean success;
+    char *value = NULL;
+
+    if (!*arg->rhs->str) {
+        return false;
+    }
+
+    success = vb_eval_script(
+        webkit_web_view_get_main_frame(vb.gui.webview), arg->rhs->str, NULL, &value
+    );
+    if (success) {
+        vb_echo(VB_MSG_NORMAL, false, "%s", value);
+    } else {
+        vb_echo(VB_MSG_ERROR, true, "%s", value);
+    }
+    g_free(value);
+
+    return success;
+}
+
+static gboolean ex_map(const ExArg *arg)
+{
+    /* TODO implement parsing of chars */
+    char *lhs = arg->lhs->str;
+    char *rhs = arg->rhs->str;
+
+    if (!*lhs || !*rhs) {
+        return false;
+    }
+
+    if (arg->code == EX_NMAP) {
+        map_insert(lhs, rhs, 'n');
+    } else if (arg->code == EX_CMAP) {
+        map_insert(lhs, rhs, 'c');
+    } else {
+        map_insert(lhs, rhs, 'i');
+    }
+    return true;;
+}
+
+static gboolean ex_unmap(const ExArg *arg)
+{
+    /* TODO implement parsing of chars */
+    char *lhs = arg->lhs->str;
+
+    if (!*lhs) {
+        return false;
+    }
+
+    if (arg->code == EX_NUNMAP) {
+        map_delete(lhs, 'n');
+    } else if (arg->code == EX_CUNMAP) {
+        map_delete(lhs, 'c');
+    } else {
+        map_delete(lhs, 'i');
+    }
+    return true;
+}
+
+static gboolean ex_open(const ExArg *arg)
+{
+    if (arg->code == EX_TABOPEN) {
+        return vb_load_uri(&((Arg){VB_TARGET_NEW, arg->rhs->str}));
+    } else {
+        return vb_load_uri(&((Arg){VB_TARGET_CURRENT, arg->rhs->str}));
+    }
+}
+
+#ifdef FEATURE_QUEUE
+static gboolean ex_queue(const ExArg *arg)
+{
+    Arg a = {.s = arg->rhs->str};
+    switch (arg->code) {
+        case EX_QPUSH:
+            a.i = COMMAND_QUEUE_PUSH;
+            break;
+
+        case EX_QUNSHIFT:
+            a.i = COMMAND_QUEUE_UNSHIFT;
+            break;
+
+        case EX_QPOP:
+            a.i = COMMAND_QUEUE_POP;
+            break;
+
+        case EX_QCLEAR:
+            a.i = COMMAND_QUEUE_CLEAR;
+            break;
+
+        default:
+            return false;
+    }
+
+    return command_queue(&a);
+}
+#endif
+
+static gboolean ex_quit(const ExArg *arg)
+{
+    vb_quit();
+    return true;
+}
+
+static gboolean ex_save(const ExArg *arg)
+{
+    return command_save(&((Arg){COMMAND_SAVE_CURRENT, arg->rhs->str}));
+}
+
+static gboolean ex_set(const ExArg *arg)
+{
+    gboolean success;
+    char *param = NULL;
+
+    if (!*arg->rhs->str) {
+        return false;
+    }
+
+    /* split the input string into parameter and value part */
+    if ((param = strchr(arg->rhs->str, '='))) {
+        *param++ = '\0';
+        success  = setting_run(arg->rhs->str, param ? param : NULL);
+    } else {
+        success = setting_run(arg->rhs->str, NULL);
+    }
+
+    return success;
+}
+
+static gboolean ex_shellcmd(const ExArg *arg)
+{
+    int status, argc;
+    char *cmd, *exp, *error = NULL, *out = NULL, **argv;
+
+    if (!*arg->rhs->str) {
+        return false;
+    }
+
+    exp = expand_string(arg->rhs->str);
+    cmd = g_strdup_printf(SHELL_CMD, exp);
+    g_free(exp);
+    if (!g_shell_parse_argv(cmd, &argc, &argv, NULL)) {
+        vb_echo(VB_MSG_ERROR, true, "Could not parse command args");
+        g_free(cmd);
+
+        return false;
+    }
+    g_free(cmd);
+
+    g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &out, &error, &status, NULL);
+    g_strfreev(argv);
+    if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+        vb_echo(VB_MSG_NORMAL, true, "%s", out);
+        return true;
+    }
+
+    vb_echo(VB_MSG_ERROR, true, "[%d] %s", WEXITSTATUS(status), error);
+    return false;
+}
+
+static gboolean ex_shortcut(const ExArg *arg)
+{
+    char *p;
+
+    /* TODO allow to set shortcust with set command like ':set
+     * shortcut[name]=http://donain.tld/?q=$0' */
+    switch (arg->code) {
+        case EX_SCA:
+            if (*arg->rhs->str && (p = strchr(arg->rhs->str, '='))) {
+                *p++ = '\0';
+                return shortcut_add(arg->rhs->str, p);
+            }
+            return false;
+
+        case EX_SCR:
+            return shortcut_remove(arg->rhs->str);
+
+        case EX_SCD:
+            return shortcut_set_default(arg->rhs->str);
+
+        default:
+            return false;
+    }
+}
diff --git a/src/ex.h b/src/ex.h
new file mode 100644 (file)
index 0000000..acb71e6
--- /dev/null
+++ b/src/ex.h
@@ -0,0 +1,33 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 _EX_H
+#define _EX_H
+
+#include "config.h"
+#include "main.h"
+
+void ex_enter(void);
+void ex_leave(void);
+VbResult ex_keypress(unsigned int key);
+void ex_input_changed(const char *text);
+gboolean ex_fill_completion(GtkListStore *store, const char *input);
+gboolean ex_run_string(const char *input);
+
+#endif /* end of include guard: _EX_H */
index 1a0cce0..b0a4cd4 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program. If not, see http://www.gnu.org/licenses/.
  */
 
+#include <ctype.h>
 #include "config.h"
 #include <gdk/gdkkeysyms.h>
 #include <gdk/gdkkeysyms-compat.h>
 #include "dom.h"
 #include "command.h"
 #include "hints.js.h"
+#include "mode.h"
+#include "input.h"
+#include "map.h"
 
 #define HINT_VAR "VbHint"
 #define HINT_FILE "hints.js"
 
 static struct {
-    gulong num;
-    guint  mode;
-    guint  prefixLength;
-    gulong change_handler;
-    gulong keypress_handler;
+    guint  num;
+    char   prompt[3];
 } hints;
 
 extern VbCore vb;
 
 static void run_script(char *js);
-static void fire();
-static void observe_input(gboolean observe);
-static void changed_cb(GtkTextBuffer *buffer);
-static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event);
+
 
 void hints_init(WebKitWebFrame *frame)
 {
@@ -51,12 +49,54 @@ void hints_init(WebKitWebFrame *frame)
     g_free(value);
 }
 
-void hints_clear()
+VbResult hints_keypress(unsigned int key)
+{
+    vb.state.processed_key = true;
+    if (key == '\n') {
+        hints_fire();
+
+        return RESULT_COMPLETE;
+    }
+    /* if there is an active filter by hint num backspace will remove the
+     * number filter first */
+    if (hints.num && key == CTRL('H')) {
+        hints.num /= 10;
+        hints_update(hints.num);
+
+        return RESULT_COMPLETE;
+    }
+    if (isdigit(key)) {
+        int num = key - '0';
+        if ((num >= 1 && num <= 9) || (num == 0 && hints.num)) {
+            /* allow a zero as non-first number */
+            hints.num = (hints.num ? hints.num * 10 : 0) + num;
+            hints_update(hints.num);
+
+            return RESULT_COMPLETE;
+        }
+    }
+    if (key == CTRL('I')) {
+        hints_focus_next(false);
+
+        return RESULT_COMPLETE;
+    }
+    if (key == CTRL('O')) {
+        hints_focus_next(true);
+
+        return RESULT_COMPLETE;
+    }
+    vb.state.processed_key = false;
+
+    return RESULT_ERROR;
+}
+
+void hints_clear(void)
 {
     char *js, *value = NULL;
 
-    observe_input(false);
-    if (vb.state.mode & VB_MODE_HINTING) {
+    if (vb.mode->flags & FLAG_HINTING) {
+        vb.mode->flags &= ~FLAG_HINTING;
+        vb_set_input_text("");
         js = g_strdup_printf("%s.clear();", HINT_VAR);
         vb_eval_script(webkit_web_view_get_main_frame(vb.gui.webview), js, HINT_FILE, &value);
         g_free(value);
@@ -66,54 +106,34 @@ void hints_clear()
     }
 }
 
-void hints_create(const char *input, guint mode, const guint prefixLength)
+void hints_create(const char *input)
 {
     char *js = NULL;
-    if (!(vb.state.mode & VB_MODE_HINTING)) {
-        vb_set_mode(VB_MODE_COMMAND | VB_MODE_HINTING, false);
-
-        hints.prefixLength = prefixLength;
-        hints.mode         = mode;
-        hints.num          = 0;
-
-        char type, usage;
-        /* convert the mode into the type chare used in the hint script */
-        if (HINTS_GET_TYPE(mode) == HINTS_TYPE_LINK) {
-            type = 'l';
-        } else if (HINTS_GET_TYPE(mode) == HINTS_TYPE_EDITABLE) {
-            type = 'e';
-        } else {
-            type = 'i';
-        }
 
-        /* images cant be opened from javascript by click event so we precess
-         * the image source in this file */
-        if (mode & HINTS_PROCESS_OPEN && type != 'i') {
-            usage = mode & HINTS_OPEN_NEW ? 'T' : 'O';
-        } else {
-            usage = 'U';
-        }
+    /* unset number filter - this is required to remove the last char from
+     * inputbox on backspace also if there was used a number filter prior */
+    hints.num = 0;
 
-        js = g_strdup_printf(
-            "%s.init('%c', '%c', %d);",
-            HINT_VAR, type, usage, MAXIMUM_HINTS
-        );
+    if (!(vb.mode->flags & FLAG_HINTING)) {
+        vb.mode->flags |= FLAG_HINTING;
 
-        observe_input(true);
+        /* save the prefix of the hinting mode for later use */
+        strncpy(hints.prompt, input, 2);
+
+        js = g_strdup_printf("%s.init('%s', %d);", HINT_VAR, hints.prompt, MAXIMUM_HINTS);
 
         run_script(js);
         g_free(js);
     }
 
-
-    js = g_strdup_printf("%s.create('%s');", HINT_VAR, input ? input : "");
+    js = g_strdup_printf("%s.create('%s');", HINT_VAR, input + 2);
     run_script(js);
     g_free(js);
 }
 
-void hints_update(const gulong num)
+void hints_update(int num)
 {
-    char *js = g_strdup_printf("%s.update(%lu);", HINT_VAR, num);
+    char *js = g_strdup_printf("%s.update(%d);", HINT_VAR, hints.num);
     run_script(js);
     g_free(js);
 }
@@ -125,10 +145,16 @@ void hints_focus_next(const gboolean back)
     g_free(js);
 }
 
+void hints_fire(void)
+{
+    char *js = g_strdup_printf("%s.fire();", HINT_VAR);
+    run_script(js);
+    g_free(js);
+}
+
 static void run_script(char *js)
 {
-    char *value = NULL;
-    int mode = hints.mode;
+    char mode, *value = NULL;
 
     gboolean success = vb_eval_script(
         webkit_web_view_get_main_frame(vb.gui.webview), js, HINT_FILE, &value
@@ -137,127 +163,67 @@ static void run_script(char *js)
         fprintf(stderr, "%s\n", value);
         g_free(value);
 
-        vb_set_mode(VB_MODE_NORMAL, false);
+        mode_enter('n');
 
         return;
     }
 
+    /* check the second char of the prompt ';X' */
+    mode = hints.prompt[1];
+
     if (!strncmp(value, "OVER:", 5)) {
         g_signal_emit_by_name(
             vb.gui.webview, "hovering-over-link", NULL, *(value + 5) == '\0' ? NULL : (value + 5)
         );
     } else if (!strncmp(value, "DONE:", 5)) {
-        vb_set_mode(VB_MODE_NORMAL, true);
+        mode_enter('n');
     } else if (!strncmp(value, "INSERT:", 7)) {
-        vb_set_mode(VB_MODE_INPUT, false);
-        if (HINTS_GET_TYPE(mode) == HINTS_TYPE_EDITABLE) {
-            command_editor(NULL);
+        mode_enter('i');
+        if (mode == 'e') {
+            input_open_editor();
         }
     } else if (!strncmp(value, "DATA:", 5)) {
-        Arg a = {0};
-        char *v = (value + 5);
-        if (mode & HINTS_PROCESS_INPUT) {
-            a.s = g_strconcat((mode & HINTS_OPEN_NEW) ? ":tabopen " : ":open ", v, NULL);
-            command_input(&a);
-            g_free(a.s);
-        } else if (mode & HINTS_PROCESS_OPEN) {
+        /* switch first to normal mode - else we would clear the inputbox on
+         * switching mode also if we want to show yanked data */
+        mode_enter('n');
+        char *v   = (value + 5);
+        Arg a     = {0};
+        switch (mode) {
             /* used if images should be opened */
-            a.s = v;
-            a.i = (mode & HINTS_OPEN_NEW) ? VB_TARGET_NEW : VB_TARGET_CURRENT;
-            command_open(&a);
-        } else if (mode & HINTS_PROCESS_SAVE) {
-            a.s = v;
-            a.i = COMMAND_SAVE_URI;
-            command_save(&a);
-        }
+            case 'i':
+            case 'I':
+                a.s = v;
+                a.i = (mode == 'I') ? VB_TARGET_NEW : VB_TARGET_CURRENT;
+                vb_load_uri(&a);
+                break;
+
+            case 'O':
+            case 'T':
+                vb_echo(VB_MSG_NORMAL, false, "%s %s", (mode == 'T') ? ":tabopen" : ":open", v);
+                mode_enter('c');
+                break;
+
+            case 's':
+                a.s = v;
+                a.i = COMMAND_SAVE_URI;
+                command_save(&a);
+                break;
+
+            case 'y':
+                a.i = COMMAND_YANK_ARG;
+                a.s = v;
+                command_yank(&a);
+                break;
+
 #ifdef FEATURE_QUEUE
-        else if (mode & HINTS_PROCESS_PUSH) {
-            a.s = v;
-            a.i = COMMAND_QUEUE_PUSH;
-            command_queue(&a);
-        } else if (mode & HINTS_PROCESS_UNSHIFT) {
-            a.s = v;
-            a.i = COMMAND_QUEUE_UNSHIFT;
-            command_queue(&a);
-        }
+            case 'p':
+            case 'P':
+                a.s = v;
+                a.i = (mode == 'P') ? COMMAND_QUEUE_UNSHIFT : COMMAND_QUEUE_PUSH;
+                command_queue(&a);
+                break;
 #endif
-        else {
-            a.i = VB_CLIPBOARD_PRIMARY | VB_CLIPBOARD_SECONDARY;
-            a.s = v;
-            command_yank(&a);
         }
     }
     g_free(value);
 }
-
-static void fire()
-{
-    char *js = g_strdup_printf("%s.fire();", HINT_VAR);
-    run_script(js);
-    g_free(js);
-}
-
-static void observe_input(gboolean observe)
-{
-    if (observe) {
-        hints.change_handler = g_signal_connect(
-            G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(vb.gui.input))),
-            "changed", G_CALLBACK(changed_cb), NULL
-        );
-
-        hints.keypress_handler = g_signal_connect(
-            G_OBJECT(vb.gui.input), "key-press-event", G_CALLBACK(keypress_cb), NULL
-        );
-    } else if (hints.change_handler && hints.keypress_handler) {
-        g_signal_handler_disconnect(
-            G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(vb.gui.input))),
-            hints.change_handler
-        );
-        g_signal_handler_disconnect(G_OBJECT(vb.gui.input), hints.keypress_handler);
-
-        hints.change_handler = hints.keypress_handler = 0;
-    }
-}
-
-/* TODO move this event handler to a more generic place in vimb */
-static void changed_cb(GtkTextBuffer *buffer)
-{
-    char *text;
-    GtkTextIter start, end;
-
-    gtk_text_buffer_get_bounds(buffer, &start, &end);
-    text = gtk_text_buffer_get_text(buffer, &start, &end, false);
-
-    /* skip hinting prefixes like '.', ',', ';y' ... */
-    hints_create(text + hints.prefixLength, hints.mode, hints.prefixLength);
-
-    g_free(text);
-}
-
-static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event)
-{
-    int numval;
-    guint keyval = event->keyval;
-    guint state  = CLEAN_STATE_WITH_SHIFT(event);
-
-    if (keyval == GDK_Return) {
-        fire();
-        return true;
-    }
-    if (keyval == GDK_BackSpace && (state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK)) {
-        hints.num /= 10;
-        hints_update(hints.num);
-
-        return true;
-    }
-    numval = g_unichar_digit_value((gunichar)gdk_keyval_to_unicode(keyval));
-    if ((numval >= 1 && numval <= 9) || (numval == 0 && hints.num)) {
-        /* allow a zero as non-first number */
-        hints.num = (hints.num ? hints.num * 10 : 0) + numval;
-        hints_update(hints.num);
-
-        return true;
-    }
-
-    return false;
-}
index ead9d13..640401b 100644 (file)
 
 #include "main.h"
 
-#define HINTS_GET_TYPE(n) ((n) & (HINTS_TYPE_LINK | HINTS_TYPE_IMAGE))
-
-enum {
-    HINTS_TYPE_LINK       = 1,
-    HINTS_TYPE_IMAGE      = 2,
-    HINTS_TYPE_EDITABLE   = 3,
-    HINTS_PROCESS_INPUT   = (1 << 2),
-    HINTS_PROCESS_YANK    = (1 << 3),
-    HINTS_PROCESS_OPEN    = (1 << 4),
-    HINTS_PROCESS_SAVE    = (1 << 5),
-    /* additional flag for HINTS_PROCESS_OPEN */
-    HINTS_OPEN_NEW        = (1 << 6),
-#ifdef FEATURE_QUEUE
-    HINTS_PROCESS_PUSH    = (1 << 7),
-    HINTS_PROCESS_UNSHIFT = (1 << 8),
-#endif
-};
-
 void hints_init(WebKitWebFrame *frame);
-void hints_create(const char *input, guint mode, const guint prefixLength);
-void hints_update(const gulong num);
-void hints_clear();
+VbResult hints_keypress(unsigned int key);
+void hints_create(const char *input);
+void hints_update(int num);
+void hints_fire(void);
+void hints_clear(void);
 void hints_focus_next(const gboolean back);
 
 #endif /* end of include guard: _HINTS_H */
index 8857c79..d183dfd 100644 (file)
@@ -441,14 +441,34 @@ var VbHint = (function(){
 
     /* the api */
     return {
-        /* mode: l - links, i - images, e - editables */
-        /* usage: O - open, T - open in new window, U - use source */
-        init: function init(mode, usage, maxHints) {
+        init: function init(prefix, maxHints) {
+            /* mode: l - links, i - images, e - editables */
+            /* usage: O - open, T - open in new window, U - use source */
+            var map = {
+                /* prompt : [mode, usage] */
+                ";e": ['e', 'U'],
+                ";i": ['i', 'U'],
+                ";I": ['i', 'U'],
+                ";o": ['l', 'O'],
+                ";O": ['l', 'U'],
+                ";p": ['l', 'U'],
+                ";P": ['l', 'U'],
+                ";s": ['l', 'U'],
+                ";t": ['l', 'T'],
+                ";T": ['l', 'U'],
+                ";y": ['l', 'U']
+            };
+            /* default config */
             config = {
-                mode:     mode,
-                usage:    usage,
+                mode:     'l',
+                usage:    'O',
                 maxHints: maxHints
             };
+            /* overwrite with mapped config if found */
+            if (map.hasOwnProperty(prefix)) {
+                config.mode  = map[prefix][0];
+                config.usage = map[prefix][1];
+            }
         },
         create: create,
         update: update,
index 058d660..e13d090 100644 (file)
@@ -72,6 +72,7 @@ void history_cleanup(void)
 
 /**
  * Write a new history entry to the end of history file.
+ * TODO identify the history by the promt char
  */
 void history_add(HistoryType type, const char *value, const char *additional)
 {
diff --git a/src/input.c b/src/input.c
new file mode 100644 (file)
index 0000000..b967cd6
--- /dev/null
@@ -0,0 +1,154 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 "config.h"
+#include "mode.h"
+#include "main.h"
+#include "input.h"
+#include "dom.h"
+#include "util.h"
+
+typedef struct {
+    char    *file;
+    Element *element;
+} EditorData;
+
+static void resume_editor(GPid pid, int status, EditorData *data);
+
+extern VbCore vb;
+
+/**
+ * Function called when vimb enters the input mode.
+ */
+void input_enter(void)
+{
+    /* switch focus first to make shure we can write to the inputbox without
+     * disturbing the user */
+    gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
+    vb_echo(VB_MSG_NORMAL, false, "-- INPUT --");
+}
+
+/**
+ * Called when the input mode is left.
+ */
+void input_leave(void)
+{
+    vb_set_input_text("");
+}
+
+/**
+ * Handles the keypress events from webview and inputbox.
+ */
+VbResult input_keypress(unsigned int key)
+{
+    switch (key) {
+        case CTRL('['): /* esc */
+            vb.state.processed_key = true;
+            mode_enter('n');
+            return RESULT_COMPLETE;
+
+        case CTRL('T'):
+            vb.state.processed_key = true;
+            return input_open_editor();
+
+        case CTRL('Z'):
+            vb.state.processed_key = true;
+            mode_enter('p');
+            return RESULT_COMPLETE;
+    }
+    return RESULT_ERROR;
+}
+
+VbResult input_open_editor(void)
+{
+    char **argv, *file_path = NULL;
+    const char *text;
+    int argc;
+    GPid pid;
+    gboolean success;
+
+    if (!vb.config.editor_command) {
+        vb_echo(VB_MSG_ERROR, true, "No editor-command configured");
+        return RESULT_ERROR;
+    }
+    Element* active = dom_get_active_element(vb.gui.webview);
+
+    /* check if element is suitable for editing */
+    if (!active || !dom_is_editable(active)) {
+        return RESULT_ERROR;
+    }
+
+    text = dom_editable_element_get_value(active);
+    if (!text) {
+        return RESULT_ERROR;
+    }
+
+    if (!util_create_tmp_file(text, &file_path)) {
+        return RESULT_ERROR;
+    }
+
+    /* spawn editor */
+    char* command = g_strdup_printf(vb.config.editor_command, file_path);
+    if (!g_shell_parse_argv(command, &argc, &argv, NULL)) {
+        fprintf(stderr, "Could not parse editor-command");
+        g_free(command);
+        return RESULT_ERROR;
+    }
+    g_free(command);
+
+    success = g_spawn_async(
+        NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+        NULL, NULL, &pid, NULL
+    );
+    g_strfreev(argv);
+
+    if (!success) {
+        unlink(file_path);
+        g_free(file_path);
+        return RESULT_ERROR;
+    }
+
+    /* disable the active element */
+    dom_editable_element_set_disable(active, true);
+
+    EditorData *data = g_new0(EditorData, 1);
+    data->file    = file_path;
+    data->element = active;
+
+    g_child_watch_add(pid, (GChildWatchFunc)resume_editor, data);
+
+    return RESULT_COMPLETE;
+}
+
+static void resume_editor(GPid pid, int status, EditorData *data)
+{
+    char *text;
+    if (status == 0) {
+        text = util_get_file_contents(data->file, NULL);
+        if (text) {
+            dom_editable_element_set_value(data->element, text);
+        }
+        g_free(text);
+    }
+    dom_editable_element_set_disable(data->element, false);
+
+    g_free(data->file);
+    g_free(data);
+    g_spawn_close_pid(pid);
+}
diff --git a/src/input.h b/src/input.h
new file mode 100644 (file)
index 0000000..fa8c908
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 _INPUT_H
+#define _INPUT_H
+
+#include "config.h"
+#include "main.h"
+
+void input_enter(void);
+void input_leave(void);
+VbResult input_keypress(unsigned int key);
+VbResult input_open_editor(void);
+
+#endif /* end of include guard: _INPUT_H */
diff --git a/src/keybind.c b/src/keybind.c
deleted file mode 100644 (file)
index 9944faa..0000000
+++ /dev/null
@@ -1,323 +0,0 @@
-/**
- * vimb - a webkit based vim like browser.
- *
- * Copyright (C) 2012-2013 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 "config.h"
-#include "main.h"
-#include "keybind.h"
-#include "command.h"
-#include "completion.h"
-
-typedef struct {
-    int     mode;        /* mode maks for allowed browser modes */
-    guint   modkey;
-    guint   modmask;     /* modemask for the kayval */
-    guint   keyval;
-    Command func;
-    Arg     arg;
-} Keybind;
-
-extern VbCore vb;
-
-static GSList  *keys;
-static GString *modkeys;
-
-static void keybind_remove(Keybind *kb);
-static void rebuild_modkeys(void);
-static GSList *find(int mode, guint modkey, guint modmask, guint keyval);
-static void string_to_keybind(char *str, Keybind *key);
-static guint string_to_modmask(const char *str);
-static guint string_to_value(const char *str);
-static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer is_input);
-static void free_keybind(Keybind *keybind);
-
-
-void keybind_init(void)
-{
-    modkeys = g_string_new("");
-    g_signal_connect(G_OBJECT(vb.gui.webview), "key-press-event", G_CALLBACK(keypress_cb), GINT_TO_POINTER(0));
-    g_signal_connect(G_OBJECT(vb.gui.input), "key-press-event", G_CALLBACK(keypress_cb), GINT_TO_POINTER(1));
-}
-
-void keybind_cleanup(void)
-{
-    if (keys) {
-        g_slist_free_full(keys, (GDestroyNotify)free_keybind);
-    }
-    if (modkeys) {
-        g_string_free(modkeys, true);
-    }
-}
-
-gboolean keybind_add_from_string(char *keystring, const char *command, const Mode mode)
-{
-    guint count;
-    if (keystring == NULL || *keystring == '\0') {
-        return false;
-    }
-
-    Keybind *kb = g_new0(Keybind, 1);
-    kb->mode    = mode;
-    if (!command_parse_from_string(command, &kb->func, &kb->arg, &count)) {
-        return false;
-    }
-
-    string_to_keybind(keystring, kb);
-
-    /* remove possible existing kbing */
-    keybind_remove(kb);
-
-    /* add the kbing to the list */
-    keys = g_slist_prepend(keys, kb);
-
-    /* save the modkey also in the modkey string if not exists already */
-    if (kb->modkey && strchr(modkeys->str, kb->modkey) == NULL) {
-        g_string_append_c(modkeys, kb->modkey);
-    }
-
-    return true;
-}
-
-gboolean keybind_remove_from_string(char *str, const Mode mode)
-{
-    Keybind keybind = {.mode = mode};
-
-    if (str == NULL || *str == '\0') {
-        return false;
-    }
-    g_strstrip(str);
-
-    /* fill the keybind with data from given string */
-    string_to_keybind(str, &keybind);
-
-    keybind_remove(&keybind);
-
-    return true;
-}
-
-static void keybind_remove(Keybind *kb)
-{
-    GSList *link = find(kb->mode, kb->modkey, kb->modmask, kb->keyval);
-    if (link) {
-        free_keybind((Keybind*)link->data);
-        keys = g_slist_delete_link(keys, link);
-        if (kb->modkey && strchr(modkeys->str, kb->modkey)) {
-            /* remove eventually no more used modkeys */
-            rebuild_modkeys();
-        }
-    }
-}
-
-/**
- * Discard all knows modkeys and walk through all keybindings to aggregate
- * them new.
- */
-static void rebuild_modkeys(void)
-{
-    GSList *link;
-    /* remove previous modkeys */
-    if (modkeys) {
-        g_string_free(modkeys, true);
-        modkeys = g_string_new("");
-    }
-
-    /* regenerate the modekeys */
-    for (link = keys; link != NULL; link = link->next) {
-        Keybind *keybind = (Keybind*)link->data;
-        /* if not not exists - add it */
-        if (keybind->modkey && strchr(modkeys->str, keybind->modkey) == NULL) {
-            g_string_append_c(modkeys, keybind->modkey);
-        }
-    }
-}
-
-static GSList *find(int mode, guint modkey, guint modmask, guint keyval)
-{
-    GSList *link;
-    for (link = keys; link != NULL; link = link->next) {
-        Keybind *keybind = (Keybind*)link->data;
-        if (keybind->keyval == keyval
-            && keybind->modmask == modmask
-            && keybind->modkey == modkey
-            && keybind->mode == mode
-        ) {
-            return link;
-        }
-    }
-
-    return NULL;
-}
-
-/**
- * Configures the given keybind by also given string.
- */
-static void string_to_keybind(char *str, Keybind *keybind)
-{
-    char **string = NULL;
-    guint len = 0;
-
-    g_strstrip(str);
-
-    /* [modkey]keyval
-     * - modkey is a single char
-     * - keval can be a single char, <tab> , <ctrl-tab>, <shift-a> */
-    keybind->modkey = 0;
-    if (strlen(str) == 1) {
-        /* only a single simple key set like "q" */
-        keybind->keyval = str[0];
-    } else if (strlen(str) == 2) {
-        /* modkey with simple key given like "gf" */
-        keybind->modkey = str[0];
-        keybind->keyval = str[1];
-    } else {
-        /* keybind has keys like "<tab>" or <ctrl-a> */
-        if (str[0] == '<') {
-            /* no modkey set */
-            string = g_strsplit_set(str, "<->", 4);
-        } else {
-            keybind->modkey = str[0];
-            string = g_strsplit_set(str + 1, "<->", 4);
-        }
-        len = g_strv_length(string);
-        if (len == 4) {
-            /* key with modmask like <ctrl-tab> */
-            keybind->modmask = string_to_modmask(string[1]);
-            keybind->keyval = string_to_value(string[2]);
-        } else if (len == 3) {
-            /* key without modmask like <tab> */
-            keybind->keyval = string_to_value(string[1]);
-        }
-        g_strfreev(string);
-    }
-
-    /* special handling for shift tab */
-    if (keybind->keyval == GDK_Tab && keybind->modmask == GDK_SHIFT_MASK) {
-        /* remove the none needed shoft mask */
-        keybind->modmask &= ~GDK_SHIFT_MASK;
-        keybind->keyval = GDK_ISO_Left_Tab;
-    }
-}
-
-static guint string_to_modmask(const char *str)
-{
-    if (!strncmp(str, "ctrl", 4)) {
-        return GDK_CONTROL_MASK;
-    }
-    if (!strncmp(str, "shift", 5)) {
-        return GDK_SHIFT_MASK;
-    }
-
-    return 0;
-}
-
-static guint string_to_value(const char *str)
-{
-    if (!strncmp(str, "tab", 3)) {
-        return GDK_Tab;
-    } else if (!strncmp(str, "up", 2)) {
-        return GDK_Up;
-    } else if (!strncmp(str, "down", 4)) {
-        return GDK_Down;
-    } else if (!strncmp(str, "left", 4)) {
-        return GDK_Left;
-    } else if (!strncmp(str, "right", 5)) {
-        return GDK_Right;
-    }
-
-    return str[0];
-}
-
-static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer is_input)
-{
-    guint keyval, state;
-    static GdkKeymap *keymap;
-    GdkModifierType irrelevant;
-
-    keymap = gdk_keymap_get_default();
-    gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
-        event->state, event->group, &keyval, NULL, NULL, &irrelevant);
-
-    /* remove modifiers that doesn't matter for the current event */
-    state = CLEAN_STATE_WITH_SHIFT(event) & ~irrelevant;
-
-    /* check for escape or modkeys or counts */
-    if (IS_ESCAPE_KEY(keyval, state)) {
-        vb.state.modkey = vb.state.count = 0;
-
-        /* remove focus from possible focused inputbox - this allows to clear
-         * the inputbox also if esc is pressed from inputbox for example after
-         * yanking some text or the result of the :shecllcmd */
-        gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
-
-        /* switch to normal mode and clear the command line */
-        vb_set_mode(VB_MODE_NORMAL, true);
-
-        return true;
-    } else if (GPOINTER_TO_INT(is_input) && keyval == GDK_Return && !(vb.state.mode & VB_MODE_HINTING)) {
-        /* simulate the gtk entries activate callback */
-        vb_input_activate();
-        return true;
-    }
-
-    /* skip further logic if we are in pass through mode */
-    if (vb.state.mode & VB_MODE_PASSTHROUGH) {
-        return false;
-    }
-
-    /* allow mode keys and counts only in normal mode and search mode */
-    if (vb.state.mode & (VB_MODE_NORMAL|VB_MODE_SEARCH)) {
-        if (vb.state.modkey == 0 && ((keyval >= GDK_1 && keyval <= GDK_9)
-                || (keyval == GDK_0 && vb.state.count))) {
-            /* append the new entered count to previous one */
-            vb.state.count = (vb.state.count ? vb.state.count * 10 : 0) + (keyval - GDK_0);
-            vb_update_statusbar();
-
-            return true;
-        }
-        /* modekeys don't have ctrl or mod modiefiers set */
-        if (!CLEAN_STATE(event)
-            && strchr(modkeys->str, keyval) && vb.state.modkey != keyval
-        ) {
-            vb.state.modkey = (char)keyval;
-            vb_update_statusbar();
-
-            return true;
-        }
-    }
-
-    /* check for keybinding */
-    GSList *link = find(CLEAN_MODE(vb.state.mode), vb.state.modkey, state, keyval);
-
-    if (link) {
-        Keybind *keybind = (Keybind*)link->data;
-        keybind->func(&keybind->arg);
-
-        vb.state.modkey = vb.state.count = 0;
-        vb_update_statusbar();
-
-        return true;
-    }
-
-    return false;
-}
-
-static void free_keybind(Keybind *keybind)
-{
-    g_free(keybind->arg.s);
-    g_free(keybind);
-}
diff --git a/src/keybind.h b/src/keybind.h
deleted file mode 100644 (file)
index 5e58d8d..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * vimb - a webkit based vim like browser.
- *
- * Copyright (C) 2012-2013 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 _KEYBIND_H
-#define _KEYBIND_H
-
-#include "command.h"
-#include <gdk/gdkkeysyms.h>
-#include <gdk/gdkkeysyms-compat.h>
-
-void keybind_init(void);
-void keybind_cleanup(void);
-gboolean keybind_add_from_string(char *keys, const char *command, const Mode mode);
-gboolean keybind_remove_from_string(char *str, const Mode mode);
-
-#endif /* end of include guard: _KEYBIND_H */
index 24d1863..6176509 100644 (file)
@@ -23,7 +23,6 @@
 #include "main.h"
 #include "util.h"
 #include "command.h"
-#include "keybind.h"
 #include "setting.h"
 #include "completion.h"
 #include "dom.h"
 #include "shortcut.h"
 #include "history.h"
 #include "session.h"
+#include "mode.h"
+#include "normal.h"
+#include "ex.h"
+#include "input.h"
+#include "map.h"
 #include "default.h"
+#include "pass.h"
+#include "bookmark.h"
 
 /* variables */
 static char **args;
 VbCore      vb;
 
 /* callbacks */
-static void input_changed_cb(GtkTextBuffer *buffer);
 static void webview_progress_cb(WebKitWebView *view, GParamSpec *pspec);
 static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec);
 static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec);
@@ -104,8 +109,7 @@ void vb_echo(const MessageType type, gboolean hide, const char *error, ...)
  */
 void vb_set_input_text(const char *text)
 {
-    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vb.gui.input));
-    gtk_text_buffer_set_text(buffer, text, -1);
+    gtk_text_buffer_set_text(vb.gui.buffer, text, -1);
 }
 
 /**
@@ -115,64 +119,9 @@ void vb_set_input_text(const char *text)
 char *vb_get_input_text(void)
 {
     GtkTextIter start, end;
-    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vb.gui.input));
 
-    gtk_text_buffer_get_bounds(buffer, &start, &end);
-    return gtk_text_buffer_get_text(buffer, &start, &end, false);
-}
-
-/**
- * Called if the user hit the return key in the command line.
- */
-void vb_input_activate(void)
-{
-    char *command  = NULL;
-    char *text = vb_get_input_text();
-
-    Arg a;
-    command = text + 1;
-    switch (*text) {
-        case '/':
-        case '?':
-            a.i = *text == '/' ? VB_SEARCH_FORWARD : VB_SEARCH_BACKWARD;
-            a.s = command;
-            history_add(HISTORY_SEARCH, command, NULL);
-            command_search(&a);
-            break;
-
-        case ':':
-            completion_clean();
-            history_add(HISTORY_COMMAND, command, NULL);
-            command_run_string(command);
-            break;
-    }
-
-    g_free(text);
-}
-
-static void input_changed_cb(GtkTextBuffer *buffer)
-{
-    char *text;
-    GtkTextIter start, end;
-    gboolean forward;
-
-    /* if vimb isn't in command mode, all changes are considered as output of
-     * some commands that we don't want here */
-    /* TODO find a better place to only observe the change events in command
-     * mode */
-    if (!(vb.state.mode & VB_MODE_COMMAND)) {
-        return;
-    }
-
-    gtk_text_buffer_get_bounds(buffer, &start, &end);
-    text = gtk_text_buffer_get_text(buffer, &start, &end, false);
-
-    if ((forward = (*text == '/')) || *text == '?') {
-        webkit_web_view_unmark_text_matches(vb.gui.webview);
-        webkit_web_view_search_text(vb.gui.webview, &text[1], false, forward, false);
-    }
-    /* TODO start hinting also if ., or ; are the first char in inputbox */
-    g_free(text);
+    gtk_text_buffer_get_bounds(vb.gui.buffer, &start, &end);
+    return gtk_text_buffer_get_text(vb.gui.buffer, &start, &end, false);
 }
 
 gboolean vb_eval_script(WebKitWebFrame *frame, char *script, char *file, char **value)
@@ -200,11 +149,13 @@ gboolean vb_eval_script(WebKitWebFrame *frame, char *script, char *file, char **
 
 gboolean vb_load_uri(const Arg *arg)
 {
-    char *uri = NULL, *rp, *path = arg->s ? arg->s : "";
+    char *uri = NULL, *rp, *path = NULL;
     struct stat st;
 
-    g_strstrip(path);
-    if (*path == '\0') {
+    if (arg->s) {
+        path = g_strstrip(arg->s);
+    }
+    if (!path || *path == '\0') {
         path = vb.config.home_page;
     }
 
@@ -271,15 +222,19 @@ gboolean vb_set_clipboard(const Arg *arg)
     return result;
 }
 
-gboolean vb_set_mode(Mode mode, gboolean clean)
+gboolean vb_set_mode(VimbMode mode, gboolean clean)
 {
+    if (mode == VB_MODE_INPUT) {
+        mode_enter('i');
+        return true;
+    }
     /* process only if mode has changed */
     if (vb.state.mode != mode) {
         /* leaf the old mode */
         if ((vb.state.mode & VB_MODE_COMPLETE) && !(mode & VB_MODE_COMPLETE)) {
             completion_clean();
         } else if ((vb.state.mode & VB_MODE_SEARCH) && !(mode & VB_MODE_SEARCH)) {
-            command_search(&((Arg){VB_SEARCH_OFF}));
+            command_search(&((Arg){COMMAND_SEARCH_OFF}));
         } else if ((vb.state.mode & VB_MODE_HINTING) && !(mode & VB_MODE_HINTING)) {
             hints_clear();
         } else if (CLEAN_MODE(vb.state.mode) == VB_MODE_INPUT && !(mode & VB_MODE_INPUT)) {
@@ -386,6 +341,9 @@ void vb_update_status_style(void)
     vb_set_widget_font(
         vb.gui.statusbar.right, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type]
     );
+    vb_set_widget_font(
+        vb.gui.statusbar.cmd, &vb.style.status_fg[type], &vb.style.status_bg[type], vb.style.status_font[type]
+    );
 }
 
 void vb_update_input_style(void)
@@ -454,9 +412,9 @@ void vb_quit(void)
 
     webkit_web_view_stop_loading(vb.gui.webview);
 
-    command_cleanup();
+    map_cleanup();
+    mode_cleanup();
     setting_cleanup();
-    keybind_cleanup();
     shortcut_cleanup();
     history_cleanup();
 
@@ -545,8 +503,8 @@ static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec)
             vb.state.progress = 100;
             vb_update_statusbar();
 
-            dom_check_auto_insert(view);
             if (strncmp(uri, "about:", 6)) {
+                dom_check_auto_insert(view);
                 history_add(HISTORY_URL, uri, webkit_web_view_get_title(view));
             }
             break;
@@ -722,7 +680,9 @@ static void init_core(void)
 #endif
 
     /* Prepare the command line */
-    gui->input = gtk_text_view_new();
+    gui->input  = gtk_text_view_new();
+    gui->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gui->input));
+
 #ifdef HAS_GTK3
     gui->pane            = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
     gui->box             = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
@@ -734,6 +694,7 @@ static void init_core(void)
 #endif
     gui->statusbar.left  = gtk_label_new(NULL);
     gui->statusbar.right = gtk_label_new(NULL);
+    gui->statusbar.cmd   = gtk_label_new(NULL);
 
     /* Prepare the event box */
     gui->eventbox = gtk_event_box_new();
@@ -747,7 +708,9 @@ static void init_core(void)
     gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->pane));
     gtk_misc_set_alignment(GTK_MISC(gui->statusbar.left), 0.0, 0.0);
     gtk_misc_set_alignment(GTK_MISC(gui->statusbar.right), 1.0, 0.0);
+    gtk_misc_set_alignment(GTK_MISC(gui->statusbar.cmd), 1.0, 0.0);
     gtk_box_pack_start(gui->statusbar.box, gui->statusbar.left, true, true, 2);
+    gtk_box_pack_start(gui->statusbar.box, gui->statusbar.cmd, false, false, 0);
     gtk_box_pack_start(gui->statusbar.box, gui->statusbar.right, false, false, 2);
 
     gtk_box_pack_start(gui->box, gui->scroll, true, true, 0);
@@ -760,11 +723,17 @@ static void init_core(void)
      * and keyboard events */
     gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
 
+    /* initialize the modes */
+    mode_init();
+    mode_add('n', normal_enter, normal_leave, normal_keypress, normal_input_changed);
+    mode_add('c', ex_enter, ex_leave, ex_keypress, ex_input_changed);
+    mode_add('i', input_enter, input_leave, input_keypress, NULL);
+    mode_add('p', pass_enter, pass_leave, pass_keypress, NULL);
+    mode_enter('n');
+
     init_files();
     session_init();
     setting_init();
-    command_init();
-    keybind_init();
     shortcut_init();
     read_config();
 
@@ -780,7 +749,7 @@ static void read_config(void)
 
     /* load default config */
     for (guint i = 0; default_config[i] != NULL; i++) {
-        if (!command_run_string(default_config[i])) {
+        if (!ex_run_string(default_config[i])) {
             fprintf(stderr, "Invalid default config: %s\n", default_config[i]);
         }
     }
@@ -797,7 +766,7 @@ static void read_config(void)
             if (!g_ascii_isalpha(line[0])) {
                 continue;
             }
-            if (!command_run_string(line)) {
+            if (!ex_run_string(line)) {
                 fprintf(stderr, "Invalid config: %s\n", line);
             }
         }
@@ -831,9 +800,12 @@ static void setup_signals()
     g_signal_connect(G_OBJECT(frame), "scrollbars-policy-changed", G_CALLBACK(gtk_true), NULL);
 #endif
 
+    g_signal_connect(
+        G_OBJECT(vb.gui.window), "key-press-event", G_CALLBACK(map_keypress), NULL
+    );
     g_object_connect(
-        G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(vb.gui.input))),
-        "signal::changed", G_CALLBACK(input_changed_cb), NULL,
+        G_OBJECT(vb.gui.buffer),
+        "signal::changed", G_CALLBACK(mode_input_changed), NULL,
         NULL
     );
 
@@ -903,16 +875,17 @@ static gboolean button_relase_cb(WebKitWebView *webview, GdkEventButton *event)
 {
     gboolean propagate = false;
     WebKitHitTestResultContext context;
-    Mode mode = CLEAN_MODE(vb.state.mode);
 
     WebKitHitTestResult *result = webkit_web_view_get_hit_test_result(webview, event);
 
     g_object_get(result, "context", &context, NULL);
-    if (mode == VB_MODE_NORMAL && context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
+    /* TODO move this to normal.c */
+    if (vb.mode->id == 'n' && context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
         vb_set_mode(VB_MODE_INPUT, false);
 
         propagate = true;
     }
+    /* TODO move this to normal.c */
     /* middle mouse click onto link */
     if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK && event->button == 2) {
         Arg a = {VB_TARGET_NEW};
@@ -1055,13 +1028,16 @@ static void download_progress_cp(WebKitDownload *download, GParamSpec *pspec)
         return;
     }
 
-    char *file = g_path_get_basename(webkit_download_get_destination_uri(download));
+    const char *file = webkit_download_get_destination_uri(download);
+    /* skip the file protocoll for the display */
+    if (!strncmp(file, "file://", 7)) {
+        file += 7;
+    }
     if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
         vb_echo(VB_MSG_ERROR, false, "Error downloading %s", file);
     } else {
         vb_echo(VB_MSG_NORMAL, false, "Download %s finished", file);
     }
-    g_free(file);
 
     /* remove the donwload from the list */
     vb.state.downloads = g_list_remove(vb.state.downloads, download);
index 3cbdce8..153f7e1 100644 (file)
 
 #define LENGTH(x) (sizeof x / sizeof x[0])
 
+/* this macro converts a non-'g' ascii command into a 'g' command by setting
+ * the 8th bit for the char */
+/* TODO maybe these macros are only used in keybind.c or mode.c */
+#define G_CMD(x)     ((x) | 0x80)
+#define UNG_CMD(x)   ((x) & ~0x80)
+#define CTRL(x)      ((x) ^ 0x40)
+/* check if the char x is a char with CTRL like ^C */
+#define IS_CTRL(x)   (((unsigned char)x) <= 32)
+
 #ifdef DEBUG
 #define PRINT_DEBUG(...) { \
     fprintf(stderr, "\n\033[31;1mDEBUG:\033[0m %s:%d:%s()\t", __FILE__, __LINE__, __func__); \
 #endif
 
 /* enums */
+/* TODO remove this when the modes are implemented in own files */
 typedef enum _vb_mode {
     /* main modes */
     VB_MODE_NORMAL        = 1<<0,
@@ -116,7 +126,13 @@ typedef enum _vb_mode {
     VB_MODE_SEARCH        = 1<<4, /* normal mode */
     VB_MODE_COMPLETE      = 1<<5, /* command mode */
     VB_MODE_HINTING       = 1<<6, /* command mode */
-} Mode;
+} VimbMode;
+
+typedef enum {
+    RESULT_COMPLETE,
+    RESULT_MORE,
+    RESULT_ERROR
+} VbResult;
 
 typedef enum {
     VB_INPUT_UNKNOWN,
@@ -171,12 +187,6 @@ enum {
     VB_SCROLL_UNIT_HALFPAGE = (1 << 4)
 };
 
-typedef enum {
-    VB_SEARCH_OFF,
-    VB_SEARCH_FORWARD  = (1<<0),
-    VB_SEARCH_BACKWARD = (1<<1),
-} SearchDirection;
-
 typedef enum {
     VB_MSG_NORMAL,
     VB_MSG_ERROR,
@@ -234,11 +244,28 @@ typedef struct {
     char *s;
 } Arg;
 
+typedef void (*ModeTransitionFunc) (void);
+typedef VbResult (*ModeKeyFunc) (unsigned int);
+typedef void (*ModeInputChangedFunc) (const char*);
+typedef struct {
+    char                 id;
+    ModeTransitionFunc   enter;         /* is called if the mode is entered */
+    ModeTransitionFunc   leave;         /* is called if the mode is left */
+    ModeKeyFunc          keypress;      /* receives key to process */
+    ModeInputChangedFunc input_changed; /* is triggered if input textbuffer is changed */
+#define FLAG_NOMAP       0x0001  /* disables mapping for key strokes */
+#define FLAG_HINTING     0x0002  /* marks active hinting submode */
+#define FLAG_COMPLETION  0x0004  /* marks active completion submode */
+#define FLAG_PASSTHROUGH 0x0008  /* don't handle any other keybind than <esc> */
+    unsigned int         flags;
+} Mode;
+
 /* statusbar */
 typedef struct {
     GtkBox    *box;
     GtkWidget *left;
     GtkWidget *right;
+    GtkWidget *cmd;
 } StatusBar;
 
 /* gui */
@@ -250,6 +277,7 @@ typedef struct {
     GtkBox             *box;
     GtkWidget          *eventbox;
     GtkWidget          *input;
+    GtkTextBuffer      *buffer; /* text buffer associated with the input for fast access */
     GtkWidget          *pane;
     StatusBar          statusbar;
     GtkScrollbar       *sb_h;
@@ -260,7 +288,7 @@ typedef struct {
 
 /* state */
 typedef struct {
-    Mode            mode;
+    VimbMode        mode;
     char            modkey;
     guint           count;
     guint           progress;
@@ -268,6 +296,7 @@ typedef struct {
     MessageType     input_type;
     gboolean        is_inspecting;
     GList           *downloads;
+    gboolean        processed_key;
 } State;
 
 typedef struct {
@@ -298,6 +327,7 @@ typedef struct {
     State           state;
 
     char            *files[FILES_LAST];
+    Mode            *mode;
     Config          config;
     Style           style;
     SoupSession     *session;
@@ -321,7 +351,7 @@ void vb_input_activate(void);
 gboolean vb_eval_script(WebKitWebFrame *frame, char *script, char *file, char **value);
 gboolean vb_load_uri(const Arg *arg);
 gboolean vb_set_clipboard(const Arg *arg);
-gboolean vb_set_mode(Mode mode, gboolean clean);
+gboolean vb_set_mode(VimbMode mode, gboolean clean);
 void vb_set_widget_font(GtkWidget *widget, const VbColor *fg, const VbColor *bg, PangoFontDescription *font);
 void vb_update_statusbar(void);
 void vb_update_status_style(void);
diff --git a/src/map.c b/src/map.c
new file mode 100644 (file)
index 0000000..a016c98
--- /dev/null
+++ b/src/map.c
@@ -0,0 +1,428 @@
+/**
+ * Copyright (C) 2012-2013 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 <gdk/gdkkeysyms.h>
+#include <gdk/gdkkeysyms-compat.h>
+#include "config.h"
+#include "main.h"
+#include "map.h"
+#include "mode.h"
+
+extern VbCore vb;
+
+typedef struct {
+    char *in;         /* input keys */
+    int  inlen;       /* length of the input keys */
+    char *mapped;     /* mapped keys */
+    int  mappedlen;   /* length of the mapped keys string */
+    char mode;        /* mode for which the map is available */
+} Map;
+
+static struct {
+    GSList *list;
+    char   queue[MAP_QUEUE_SIZE];   /* queue holding typed keys */
+    int    qlen;                    /* pointer to last char in queue */
+    int    resolved;                /* number of resolved keys (no mapping required) */
+    char   showbuf[12];             /* buffer used to show ambiguous keys to the user */
+    int    slen;                    /* pointer to last char in showbuf */
+    guint  timout_id;               /* source id of the timeout function */
+} map;
+
+static char *map_convert_keys(char *in, int inlen, int *len);
+static char *map_convert_keylabel(char *in, int inlen, int *len);
+static gboolean map_timeout(gpointer data);
+static void showcmd(char *keys, int keylen, gboolean append);
+static void free_map(Map *map);
+
+
+void map_cleanup(void)
+{
+    if (map.list) {
+        g_slist_free_full(map.list, (GDestroyNotify)free_map);
+    }
+}
+
+/**
+ * Handle all key events, convert the key event into the internal used ASCII
+ * representation and put this into the key queue to be mapped.
+ */
+gboolean map_keypress(GtkWidget *widget, GdkEventKey* event, gpointer data)
+{
+    unsigned int key = 0;
+    if ((event->state & GDK_CONTROL_MASK) == 0
+        && event->keyval > 0
+        && event->keyval < 0xff
+    ) {
+        key = event->keyval;
+    } else {
+        /* convert chars A-Za-z with ctrl flag <ctrl-a> or <ctrl-A> -> \001
+         * and <ctrl-z> -> \032 like vi */
+        if (event->keyval == GDK_Escape) {
+            key = CTRL('[');
+        } else if (event->keyval == GDK_Tab) {
+            key = CTRL('I');
+        } else if (event->keyval == GDK_ISO_Left_Tab) {
+            key = CTRL('O');
+        } else if (event->keyval == GDK_Return) {
+            key = '\n';
+        } else if (event->keyval == GDK_BackSpace) {
+            /* FIXME how to handle <C-S-Del> to remove selected Numbers in
+             * hint mode */
+            key = CTRL('H'); /* 0x08 */
+        } else if (event->keyval == GDK_Up) {
+            key = CTRL('P'); /* or ^Ok instead? */
+        } else if (event->keyval == GDK_Down) {
+            key = CTRL('N');
+        } else if (event->keyval >= 0x41 && event->keyval <= 0x5d) {/* chars A-] */
+            key = event->keyval - 0x40;
+        } else if (event->keyval >= 0x61 && event->keyval <= 0x7a) {/* chars a-z */
+            key = event->keyval - 0x60;
+        }
+    }
+
+    /* set initial value for the flag that should be changed in the modes key
+     * handler functions */
+    vb.state.processed_key = false;
+    if (key) {
+        map_handle_keys((char*)(&key), 1);
+    }
+    return vb.state.processed_key;
+}
+
+
+/**
+ * Added the given key sequence ot the key queue and precesses the mapping of
+ * chars. The key sequence do not need to be NUL terminated.
+ * Keylen of 0 signalized a key timeout.
+ */
+MapState map_handle_keys(const char *keys, int keylen)
+{
+    int ambiguous;
+    Map *match = NULL;
+    gboolean timeout = (keylen == 0); /* keylen 0 signalized timeout */
+
+    /* don't set the timeout function if a timeout is handled */
+    if (!timeout) {
+        /* if a previous timeout function was set remove this to start the
+         * timeout new */
+        if (map.timout_id) {
+            g_source_remove(map.timout_id);
+        }
+        map.timout_id = g_timeout_add(1000, (GSourceFunc)map_timeout, NULL);
+    }
+
+    /* copy the keys onto the end of queue */
+    while (map.qlen < LENGTH(map.queue) && keylen > 0) {
+        map.queue[map.qlen++] = *keys++;
+        keylen--;
+    }
+
+    /* try to resolve keys against the map */
+    while (true) {
+        /* send any resolved key to the parser */
+        while (map.resolved > 0) {
+            /* pop the next char from queue */
+            map.resolved--;
+            map.qlen--;
+
+            /* get first char of queue */
+            char qk = map.queue[0];
+            /* move all other queue entries one step to the left */
+            for (int i = 0; i < map.qlen; i++) {
+                map.queue[i] = map.queue[i + 1];
+            }
+
+            /* remove the nomap flag */
+            vb.mode->flags &= ~FLAG_NOMAP;
+
+            /* send the key to the parser */
+            if (RESULT_MORE != mode_handle_key((unsigned int)qk)) {
+                showcmd(NULL, 0, false);
+            }
+        }
+
+        /* if all keys where processed return MAP_DONE */
+        if (map.qlen == 0) {
+            map.resolved = 0;
+            return match ? MAP_DONE : MAP_NOMATCH;
+        }
+
+        /* try to find matching maps */
+        match     = NULL;
+        ambiguous = 0;
+        if (!(vb.mode->flags & FLAG_NOMAP)) {
+            for (GSList *l = map.list; l != NULL; l = l->next) {
+                Map *m = (Map*)l->data;
+                /* ignore maps for other modes */
+                if (m->mode != vb.mode->id) {
+                    continue;
+                }
+
+                /* find ambiguous matches */
+                if (!timeout && m->inlen > map.qlen && !strncmp(m->in, map.queue, map.qlen)) {
+                    ambiguous++;
+                    showcmd(map.queue, map.qlen, false);
+                }
+                /* complete match or better/longer match than previous found */
+                if (m->inlen <= map.qlen
+                    && !strncmp(m->in, map.queue, m->inlen)
+                    && (!match || match->inlen < m->inlen)
+                ) {
+                    /* backup this found possible match */
+                    match = m;
+                }
+            }
+        }
+
+        /* if there are ambiguous matches return MAP_KEY and flush queue
+         * after a timeout if the user do not type more keys */
+        if (ambiguous) {
+            return MAP_AMBIGUOUS;
+        }
+
+        /* replace the matched chars from queue by the cooked string that
+         * is the result of the mapping */
+        if (match) {
+            int i, j;
+            if (match->inlen < match->mappedlen) {
+                /* make some space within the queue */
+                for (i = map.qlen + match->mappedlen - match->inlen, j = map.qlen; j > match->inlen; ) {
+                    map.queue[--i] = map.queue[--j];
+                }
+            } else if (match->inlen > match->mappedlen) {
+                /* delete some keys */
+                for (i = match->mappedlen, j = match->inlen; i < map.qlen; ) {
+                    map.queue[i++] = map.queue[j++];
+                }
+            }
+
+            /* copy the mapped string into the queue */
+            strncpy(map.queue, match->mapped, match->mappedlen);
+            map.qlen += match->mappedlen - match->inlen;
+            if (match->inlen <= match->mappedlen) {
+                map.resolved = match->inlen;
+            } else {
+                map.resolved = match->mappedlen;
+            }
+        } else {
+            /* first char is not mapped but resolved */
+            map.resolved = 1;
+            showcmd(map.queue, map.resolved, true);
+        }
+    }
+
+    /* should never be reached */
+    return MAP_DONE;
+}
+
+void map_insert(char *in, char *mapped, char mode)
+{
+    int inlen, mappedlen;
+    char *lhs = map_convert_keys(in, strlen(in), &inlen);
+    char *rhs = map_convert_keys(mapped, strlen(mapped), &mappedlen);
+
+    /* TODO replace keysymbols in 'in' and 'mapped' string */
+    Map *new = g_new(Map, 1);
+    new->in        = lhs;
+    new->inlen     = inlen;
+    new->mapped    = rhs;
+    new->mappedlen = mappedlen;
+    new->mode      = mode;
+
+    map.list = g_slist_prepend(map.list, new);
+}
+
+gboolean map_delete(char *in, char mode)
+{
+    int len;
+    char *lhs = map_convert_keys(in, strlen(in), &len);
+
+    for (GSList *l = map.list; l != NULL; l = l->next) {
+        Map *m = (Map*)l->data;
+
+        /* remove only if the map's lhs matches the given key sequence */
+        if (m->mode == mode && m->inlen == len && !strcmp(m->in, lhs)) {
+            /* remove the found list item */
+            map.list = g_slist_delete_link(map.list, l);
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Converts a keysequence into a internal raw keysequence.
+ * Returned keyseqence must be freed if not used anymore.
+ */
+static char *map_convert_keys(char *in, int inlen, int *len)
+{
+    int symlen, rawlen;
+    char *p, *dest, *raw;
+    char ch[1];
+    GString *str = g_string_new("");
+
+    *len = 0;
+    for (p = in; p < &in[inlen]; p++) {
+        /* if it starts not with < we can add it literally */
+        if (*p != '<') {
+            g_string_append_len(str, p, 1);
+            *len += 1;
+            continue;
+        }
+
+        /* search matching > of symbolic name */
+        symlen = 1;
+        do {
+            if (&p[symlen] == &in[inlen]
+                || p[symlen] == '<'
+                || p[symlen] == ' '
+            ) {
+                break;
+            }
+        } while (p[symlen++] != '>');
+
+        raw    = NULL;
+        rawlen = 0;
+        /* check if we found a real keylabel */
+        if (p[symlen - 1] == '>') {
+            if (symlen == 5 && p[2] == '-') {
+                /* is it a <C-X> */
+                if (p[1] == 'C') {
+                    /* TODO add macro to check if the char is a upper or lower
+                     * with these ranges */
+                    if (p[3] >= 0x41 && p[3] <= 0x5d) {
+                        ch[0]  = p[3] - 0x40;
+                        raw    = ch;
+                        rawlen = 1;
+                    } else if (p[3] >= 0x61 && p[3] <= 0x7a) {
+                        ch[0]  = p[3] - 0x60;
+                        raw    = ch;
+                        rawlen = 1;
+                    }
+                }
+            }
+
+            if (!raw) {
+                raw = map_convert_keylabel(p, symlen, &rawlen);
+            }
+        }
+
+        /* we found no known keylabel - so use the chars literally */
+        if (!rawlen) {
+            rawlen = symlen;
+            raw    = p;
+        }
+
+        /* write the converted keylabel into the buffer */
+        g_string_append_len(str, raw, rawlen);
+
+        /* move p after the keylabel */
+        p += symlen - 1;
+
+        *len += rawlen;
+    }
+    dest = str->str;
+
+    /* don't free the character data of the GString */
+    g_string_free(str, false);
+
+    return dest;
+}
+
+/**
+ * Translate given key string into a internal representation <cr> -> \n.
+ * The len of the translated key sequence is put into given *len pointer.
+ */
+static char *map_convert_keylabel(char *in, int inlen, int *len)
+{
+    static struct {
+        char *label;
+        int  len;
+        char *ch;
+        int  chlen;
+    } keys[] = {
+        {"<CR>",  4, "\n",   1},
+        {"<Tab>", 5, "\t",   1},
+        {"<Esc>", 5, "\x1b", 1}
+    };
+
+    for (int i = 0; i < LENGTH(keys); i++) {
+        if (inlen == keys[i].len && !strncmp(keys[i].label, in, inlen)) {
+            *len = keys[i].chlen;
+            return keys[i].ch;
+        }
+    }
+    *len = 0;
+
+    return NULL;
+}
+
+/**
+ * Timeout function to signalize a key timeout to the map.
+ */
+static gboolean map_timeout(gpointer data)
+{
+    /* signalize the timeout to the key handler */
+    map_handle_keys("", 0);
+
+    /* call only once */
+    return false;
+}
+
+/**
+ * Add given keys to the show command queue to show them to the user.
+ * If the keylen of 0 is given, the show command queue is cleared.
+ */
+static void showcmd(char *keys, int keylen, gboolean append)
+{
+    /* make sure we keep in buffer range also for ^X chars */
+    int max = LENGTH(map.showbuf) - 2;
+
+    if (!append) {
+        map.slen = 0;
+    }
+
+    /* truncate the buffer */
+    if (!keylen) {
+        map.showbuf[(map.slen = 0)] = '\0';
+    } else {
+        /* TODO if not all keys would fit into the buffer use the last significat
+         * chars instead */
+        while (keylen-- && map.slen < max) {
+            char key = *keys++;
+            if (IS_CTRL(key)) {
+                map.showbuf[map.slen++] = '^';
+                map.showbuf[map.slen++] = CTRL(key);
+            } else {
+                map.showbuf[map.slen++] = key;
+            }
+        }
+        map.showbuf[map.slen] = '\0';
+    }
+
+    /* show the typed keys */
+    gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.cmd), map.showbuf);
+}
+
+static void free_map(Map *map)
+{
+    g_free(map->in);
+    g_free(map->mapped);
+    g_free(map);
+}
diff --git a/src/map.h b/src/map.h
new file mode 100644 (file)
index 0000000..26d66df
--- /dev/null
+++ b/src/map.h
@@ -0,0 +1,41 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 _MAP_H
+#define _MAP_H
+
+/* size of the typeahead buffer that will altered during mapping an can
+ * therefor become larger than expected for examlpe by
+ * ':nmap gh :open LONG_URI TO HOME PAGE' where two keys expand to a large
+ * string */
+#define MAP_QUEUE_SIZE 500
+
+typedef enum {
+    MAP_DONE,
+    MAP_AMBIGUOUS,
+    MAP_NOMATCH
+} MapState;
+
+void map_cleanup(void);
+gboolean map_keypress(GtkWidget *widget, GdkEventKey* event, gpointer data);
+MapState map_handle_keys(const char *keys, int keylen);
+void map_insert(char *in, char *mapped, char mode);
+gboolean map_delete(char *in, char mode);
+
+#endif /* end of include guard: _MAP_H */
diff --git a/src/mode.c b/src/mode.c
new file mode 100644 (file)
index 0000000..e36e9df
--- /dev/null
@@ -0,0 +1,130 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 "config.h"
+#include "main.h"
+#include "mode.h"
+
+static GHashTable *modes = NULL;
+extern VbCore vb;
+
+void mode_init(void)
+{
+    modes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+}
+
+void mode_cleanup(void)
+{
+    if (modes) {
+        g_hash_table_destroy(modes);
+        vb.mode = NULL;
+    }
+}
+
+/**
+ * Creates a new mode with given callback functions.
+ */
+void mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave,
+    ModeKeyFunc keypress, ModeInputChangedFunc input_changed)
+{
+    Mode *new = g_new(Mode, 1);
+    new->id            = id;
+    new->enter         = enter;
+    new->leave         = leave;
+    new->keypress      = keypress;
+    new->input_changed = input_changed;
+    new->flags         = 0;
+
+    g_hash_table_insert(modes, GINT_TO_POINTER(id), new);
+}
+
+/**
+ * Enter into the new given mode and leave possible active current mode.
+ */
+void mode_enter(char id)
+{
+    Mode *new = g_hash_table_lookup(modes, GINT_TO_POINTER(id));
+    if (!new) {
+        PRINT_DEBUG("!mode %c not found", id);
+        return;
+    }
+
+    if (vb.mode) {
+        /* don't do anything if the mode isn't a new one */
+        if (vb.mode == new) {
+            return;
+        }
+
+        /* if there is a active mode, leave this first */
+        if (vb.mode->leave) {
+            PRINT_DEBUG("leave %c", vb.mode->id);
+            vb.mode->leave();
+        }
+    }
+
+    /* reset the flags of the new entered mode */
+    new->flags = 0;
+
+    /* set the new mode so that it is available also in enter function */
+    vb.mode = new;
+    /* call enter only if the new mode isn't the current mode */
+    if (new->enter) {
+        PRINT_DEBUG("enter %c", new->id);
+        new->enter();
+    }
+
+    vb_update_statusbar();
+}
+
+VbResult mode_handle_key(unsigned int key)
+{
+    VbResult res;
+    if (vb.mode && vb.mode->keypress) {
+        int flags = vb.mode->flags;
+        int id    = vb.mode->id;
+        res = vb.mode->keypress(key);
+        if (vb.mode) {
+            PRINT_DEBUG(
+                "%c: key[0x%x %c] flags[%d] >> %c: flags[%d]",
+                id, key, (key >= 0x20 && key <= 0x7e) ? key : ' ',
+                flags, vb.mode->id, vb.mode->flags
+            );
+        }
+        return res;
+    }
+    return RESULT_ERROR;
+}
+
+/**
+ * Process input changed event on current active mode.
+ */
+void mode_input_changed(GtkTextBuffer* buffer, gpointer data)
+{
+    char *text;
+    GtkTextIter start, end;
+    /* don't process changes not typed by the user */
+    if (gtk_widget_is_focus(vb.gui.input) && vb.mode && vb.mode->input_changed) {
+        gtk_text_buffer_get_bounds(buffer, &start, &end);
+        text = gtk_text_buffer_get_text(buffer, &start, &end, false);
+
+        vb.mode->input_changed(text);
+
+        g_free(text);
+    }
+}
diff --git a/src/mode.h b/src/mode.h
new file mode 100644 (file)
index 0000000..b32c870
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 _MODE_H
+#define _MODE_H
+
+#include "config.h"
+#include "main.h"
+
+void mode_init(void);
+void mode_cleanup(void);
+void mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave,
+    ModeKeyFunc keypress, ModeInputChangedFunc input_changed);
+void mode_enter(char id);
+VbResult mode_handle_key(unsigned int key);
+void mode_input_changed(GtkTextBuffer* buffer, gpointer data);
+
+#endif /* end of include guard: _MODE_H */
diff --git a/src/normal.c b/src/normal.c
new file mode 100644 (file)
index 0000000..b951e09
--- /dev/null
@@ -0,0 +1,813 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 <gdk/gdkkeysyms.h>
+#include <ctype.h>
+#include "config.h"
+#include "mode.h"
+#include "main.h"
+#include "normal.h"
+#include "command.h"
+#include "hints.h"
+#include "dom.h"
+#include "history.h"
+#include "util.h"
+
+typedef enum {
+    PHASE_START,
+    PHASE_AFTERG,
+    PHASE_KEY2,
+    PHASE_COMPLETE,
+} Phase;
+
+struct NormalCmdInfo_s {
+    int count;          /* count used for the command */
+    unsigned char cmd;  /* command key */
+    unsigned char arg;  /* argument key if this is used */
+    Phase phase;        /* current parsing phase */
+} info = {0, '\0', '\0', PHASE_START};
+
+typedef VbResult (*NormalCommand)(const NormalCmdInfo *info);
+
+static VbResult normal_clear_input(const NormalCmdInfo *info);
+static VbResult normal_descent(const NormalCmdInfo *info);
+static VbResult normal_ex(const NormalCmdInfo *info);
+static VbResult normal_focus_input(const NormalCmdInfo *info);
+static VbResult normal_hint(const NormalCmdInfo *info);
+static VbResult normal_navigate(const NormalCmdInfo *info);
+static VbResult normal_open_clipboard(const NormalCmdInfo *info);
+static VbResult normal_open(const NormalCmdInfo *info);
+static VbResult normal_pass(const NormalCmdInfo *info);
+static VbResult normal_queue(const NormalCmdInfo *info);
+static VbResult normal_quit(const NormalCmdInfo *info);
+static VbResult normal_scroll(const NormalCmdInfo *info);
+static VbResult normal_search(const NormalCmdInfo *info);
+static VbResult normal_search_selection(const NormalCmdInfo *info);
+static VbResult normal_view_inspector(const NormalCmdInfo *info);
+static VbResult normal_view_source(const NormalCmdInfo *info);
+static VbResult normal_yank(const NormalCmdInfo *info);
+static VbResult normal_zoom(const NormalCmdInfo *info);
+
+static struct {
+    NormalCommand func;
+} commands[] = {
+/* NUL  */ {NULL},
+/* ^A   */ {NULL},
+/* ^B   */ {normal_scroll},
+/* ^C   */ {NULL},
+/* ^D   */ {normal_scroll},
+/* ^E   */ {NULL},
+/* ^F   */ {normal_scroll},
+/* ^G   */ {NULL},
+/* ^H   */ {NULL},
+/* ^I   */ {normal_navigate},
+/* ^J   */ {NULL},
+/* ^K   */ {NULL},
+/* ^L   */ {NULL},
+/* ^M   */ {NULL},
+/* ^N   */ {NULL},
+/* ^O   */ {normal_navigate},
+/* ^P   */ {normal_queue},
+/* ^Q   */ {normal_quit},
+/* ^R   */ {NULL},
+/* ^S   */ {NULL},
+/* ^T   */ {NULL},
+/* ^U   */ {normal_scroll},
+/* ^V   */ {NULL},
+/* ^W   */ {NULL},
+/* ^X   */ {NULL},
+/* ^Y   */ {NULL},
+/* ^Z   */ {normal_pass},
+/* ^[   */ {normal_clear_input},
+/* ^\   */ {NULL},
+/* ^]   */ {NULL},
+/* ^^   */ {NULL},
+/* ^_   */ {NULL},
+/* SPC  */ {NULL},
+/* !    */ {NULL},
+/* "    */ {NULL},
+/* #    */ {normal_search_selection},
+/* $    */ {normal_scroll},
+/* %    */ {NULL},
+/* &    */ {NULL},
+/* '    */ {NULL},
+/* (    */ {NULL},
+/* )    */ {NULL},
+/* *    */ {normal_search_selection},
+/* +    */ {NULL},
+/* ,    */ {NULL},
+/* -    */ {NULL},
+/* .    */ {NULL},
+/* /    */ {normal_ex},
+/* 0    */ {normal_scroll},
+/* 1    */ {NULL},
+/* 2    */ {NULL},
+/* 3    */ {NULL},
+/* 4    */ {NULL},
+/* 5    */ {NULL},
+/* 6    */ {NULL},
+/* 7    */ {NULL},
+/* 8    */ {NULL},
+/* 9    */ {NULL},
+/* :    */ {normal_ex},
+/* ;    */ {normal_hint},
+/* <    */ {NULL},
+/* =    */ {NULL},
+/* >    */ {NULL},
+/* ?    */ {normal_ex},
+/* @    */ {NULL},
+/* A    */ {NULL},
+/* B    */ {NULL},
+/* C    */ {NULL},
+/* D    */ {NULL},
+/* E    */ {NULL},
+/* F    */ {normal_ex},
+/* G    */ {normal_scroll},
+/* H    */ {NULL},
+/* I    */ {NULL},
+/* J    */ {NULL},
+/* K    */ {NULL},
+/* L    */ {NULL},
+/* M    */ {NULL},
+/* N    */ {normal_search},
+/* O    */ {NULL},
+/* P    */ {normal_open_clipboard},
+/* Q    */ {NULL},
+/* R    */ {normal_navigate},
+/* S    */ {NULL},
+/* T    */ {NULL},
+/* U    */ {normal_open},
+/* V    */ {NULL},
+/* W    */ {NULL},
+/* X    */ {NULL},
+/* Y    */ {normal_yank},
+/* Z    */ {NULL},
+/* [    */ {NULL},
+/* \    */ {NULL},
+/* ]    */ {NULL},
+/* ^    */ {NULL},
+/* _    */ {NULL},
+/* `    */ {NULL},
+/* a    */ {NULL},
+/* b    */ {NULL},
+/* c    */ {NULL},
+/* d    */ {NULL},
+/* e    */ {NULL},
+/* f    */ {normal_ex},
+/* g    */ {NULL},
+/* h    */ {normal_scroll},
+/* i    */ {NULL},
+/* j    */ {normal_scroll},
+/* k    */ {normal_scroll},
+/* l    */ {normal_scroll},
+/* m    */ {NULL},
+/* n    */ {normal_search},
+/* o    */ {normal_ex},
+/* p    */ {normal_open_clipboard},
+/* q    */ {NULL},
+/* r    */ {normal_navigate},
+/* s    */ {NULL},
+/* t    */ {normal_ex},
+/* u    */ {normal_open},
+/* v    */ {NULL},
+/* w    */ {NULL},
+/* x    */ {NULL},
+/* y    */ {normal_yank},
+/* z    */ {normal_zoom}, /* arg chars are handled in normal_keypress */
+/* {    */ {NULL},
+/* |    */ {NULL},
+/* }    */ {NULL},
+/* ~    */ {NULL},
+/* DEL  */ {NULL},
+/***************/
+/* gNUL */ {NULL},
+/* g^A  */ {NULL},
+/* g^B  */ {NULL},
+/* g^C  */ {NULL},
+/* g^D  */ {NULL},
+/* g^E  */ {NULL},
+/* g^F  */ {NULL},
+/* g^G  */ {NULL},
+/* g^H  */ {NULL},
+/* g^I  */ {NULL},
+/* g^J  */ {NULL},
+/* g^K  */ {NULL},
+/* g^L  */ {NULL},
+/* g^M  */ {NULL},
+/* g^N  */ {NULL},
+/* g^O  */ {NULL},
+/* g^P  */ {NULL},
+/* g^Q  */ {NULL},
+/* g^R  */ {NULL},
+/* g^S  */ {NULL},
+/* g^T  */ {NULL},
+/* g^U  */ {NULL},
+/* g^V  */ {NULL},
+/* g^W  */ {NULL},
+/* g^X  */ {NULL},
+/* g^Y  */ {NULL},
+/* g^Z  */ {NULL},
+/* gESC */ {NULL},
+/* g^\  */ {NULL},
+/* g^]  */ {NULL},
+/* g^^  */ {NULL},
+/* g^_  */ {NULL},
+/* gSPC */ {NULL},
+/* g!   */ {NULL},
+/* g"   */ {NULL},
+/* g#   */ {NULL},
+/* g$   */ {NULL},
+/* g%   */ {NULL},
+/* g&   */ {NULL},
+/* g'   */ {NULL},
+/* g(   */ {NULL},
+/* g)   */ {NULL},
+/* g*   */ {NULL},
+/* g+   */ {NULL},
+/* g,   */ {NULL},
+/* g-   */ {NULL},
+/* g.   */ {NULL},
+/* g/   */ {NULL},
+/* g0   */ {NULL},
+/* g1   */ {NULL},
+/* g2   */ {NULL},
+/* g3   */ {NULL},
+/* g4   */ {NULL},
+/* g5   */ {NULL},
+/* g6   */ {NULL},
+/* g7   */ {NULL},
+/* g8   */ {NULL},
+/* g9   */ {NULL},
+/* g:   */ {NULL},
+/* g;   */ {NULL},
+/* g<   */ {NULL},
+/* g=   */ {NULL},
+/* g>   */ {NULL},
+/* g?   */ {NULL},
+/* g@   */ {NULL},
+/* gA   */ {NULL},
+/* gB   */ {NULL},
+/* gC   */ {NULL},
+/* gD   */ {NULL},
+/* gE   */ {NULL},
+/* gF   */ {normal_view_inspector},
+/* gG   */ {NULL},
+/* gH   */ {normal_open},
+/* gI   */ {NULL},
+/* gJ   */ {NULL},
+/* gK   */ {NULL},
+/* gL   */ {NULL},
+/* gM   */ {NULL},
+/* gN   */ {NULL},
+/* gO   */ {NULL},
+/* gP   */ {NULL},
+/* gQ   */ {NULL},
+/* gR   */ {NULL},
+/* gS   */ {NULL},
+/* gT   */ {NULL},
+/* gU   */ {normal_descent},
+/* gV   */ {NULL},
+/* gW   */ {NULL},
+/* gX   */ {NULL},
+/* gY   */ {NULL},
+/* gZ   */ {NULL},
+/* g[   */ {NULL},
+/* g\   */ {NULL},
+/* g]   */ {NULL},
+/* g^   */ {NULL},
+/* g_   */ {NULL},
+/* g`   */ {NULL},
+/* ga   */ {NULL},
+/* gb   */ {NULL},
+/* gc   */ {NULL},
+/* gd   */ {NULL},
+/* ge   */ {NULL},
+/* gf   */ {normal_view_source},
+/* gg   */ {normal_scroll},
+/* gh   */ {normal_open},
+/* gi   */ {normal_focus_input},
+/* gj   */ {NULL},
+/* gk   */ {NULL},
+/* gl   */ {NULL},
+/* gm   */ {NULL},
+/* gn   */ {NULL},
+/* go   */ {NULL},
+/* gp   */ {NULL},
+/* gq   */ {NULL},
+/* gr   */ {NULL},
+/* gs   */ {NULL},
+/* gt   */ {NULL},
+/* gu   */ {normal_descent},
+/* gv   */ {NULL},
+/* gw   */ {NULL},
+/* gx   */ {NULL},
+/* gy   */ {NULL},
+/* gz   */ {NULL},
+/* g{   */ {NULL},
+/* g|   */ {NULL},
+/* g}   */ {NULL},
+/* g~   */ {NULL},
+/* gDEL */ {NULL}
+};
+
+extern VbCore vb;
+
+
+/**
+ * Function called when vimb enters the normal mode.
+ */
+void normal_enter(void)
+{
+    dom_clear_focus(vb.gui.webview);
+    gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
+    history_rewind();
+    hints_clear();
+}
+
+/**
+ * Called when the normal mode is left.
+ */
+void normal_leave(void)
+{
+    /* TODO clean those only if they where active */
+    command_search(&((Arg){COMMAND_SEARCH_OFF}));
+}
+
+/**
+ * Handles the keypress events from webview and inputbox.
+ */
+VbResult normal_keypress(unsigned int key)
+{
+    State *s = &vb.state;
+    s->processed_key = true;
+    VbResult res;
+    if (info.phase == PHASE_START && key == 'g') {
+        info.phase = PHASE_AFTERG;
+        vb.mode->flags |= FLAG_NOMAP;
+
+        return RESULT_MORE;
+    }
+
+    if (info.phase == PHASE_AFTERG) {
+        key        = G_CMD(key);
+        info.phase = PHASE_START;
+    }
+
+    if (info.phase == PHASE_START && info.count == 0 && key == '0') {
+        info.cmd   = key;
+        info.phase = PHASE_COMPLETE;
+    } else if (info.phase == PHASE_KEY2) {
+        info.arg = key;
+        info.phase = PHASE_COMPLETE;
+    } else if (info.phase == PHASE_START && isdigit(key)) {
+        info.count = info.count * 10 + key - '0';
+    } else if (strchr(";z", (char)key)) {
+        /* handle commands that needs additional char */
+        info.phase = PHASE_KEY2;
+        info.cmd   = key;
+        vb.mode->flags |= FLAG_NOMAP;
+    } else {
+        info.cmd   = key & 0xff;
+        info.phase = PHASE_COMPLETE;
+    }
+
+    if (info.phase == PHASE_COMPLETE) {
+        if (commands[info.cmd].func) {
+            res = commands[info.cmd].func(&info);
+        } else {
+            /* let gtk handle the keyevent if we have no command attached to
+             * it */
+            s->processed_key = false;
+            res = RESULT_COMPLETE;
+        }
+    } else {
+        res = RESULT_MORE;
+    }
+
+    if (res == RESULT_COMPLETE) {
+        /* unset the info */
+        info.cmd = info.arg = info.count = 0;
+        info.phase = PHASE_START;
+    }
+    return res;
+}
+
+/**
+ * Handles changes in the inputbox. If there are usergenerated changes, we
+ * switch to command mode.
+ */
+void normal_input_changed(const char *text)
+{
+    /*mode_enter('i');*/
+}
+
+static VbResult normal_clear_input(const NormalCmdInfo *info)
+{
+    vb_set_input_text("");
+    gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
+    /* TODO implement the search new - so we can remove the command.c file */
+    command_search(&((Arg){COMMAND_SEARCH_OFF}));
+    /* check flags on the current state to only clear hints if they where
+     * enabled before */
+    /*hints_clear();*/
+
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_descent(const NormalCmdInfo *info)
+{
+    int count = info->count ? info->count : 1;
+    const char *uri, *p = NULL, *domain = NULL;
+
+    uri = GET_URI();
+
+    /* get domain part */
+    if (!uri || *uri == '\0'
+        || !(domain = strstr(uri, "://"))
+        || !(domain = strchr(domain + 3, '/'))
+    ) {
+        return RESULT_ERROR;
+    }
+
+    switch (UNG_CMD(info->cmd)) {
+        case 'U':
+            p = domain;
+            break;
+
+        case 'u':
+            /* start at the end */
+            p = uri + strlen(uri);
+            /* if last char is / increment count to step over this first */
+            if (*(p - 1) == '/') {
+                count++;
+            }
+            for (int i = 0; i < count; i++) {
+                while (*(p--) != '/') {
+                    if (p == uri) {
+                        /* reach the beginning */
+                        return RESULT_ERROR;
+                    }
+                }
+            }
+            /* keep the last / in uri */
+            p++;
+            break;
+    }
+
+    /* if the url is shorter than the domain use the domain instead */
+    if (p < domain) {
+        p = domain;
+    }
+
+    Arg a = {VB_TARGET_CURRENT};
+    a.s   = g_strndup(uri, p - uri + 1);
+
+    vb_load_uri(&a);
+    g_free(a.s);
+
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_ex(const NormalCmdInfo *info)
+{
+    char prompt[2] = {0};
+
+    /* avoid to print the char that called this function into the input box */
+    vb.state.processed_key = true;
+
+    /* Handle some hardwired mapping here instead of using map_insert. This
+     * makes the binding imutable and we can simply use f, F, o and t in
+     * mapped keys too */
+    switch (info->cmd) {
+        case 'o':
+            vb_set_input_text(":open ");
+            /* switch mode after setting the input text to not trigger the
+             * commands modes input change handler */
+            mode_enter('c');
+            break;
+
+        case 't':
+            vb_set_input_text(":tabopen ");
+            mode_enter('c');
+            break;
+
+        case 'f':
+            /* switch the mode first so that the input change handler of
+             * command mode will recognize the ; to switch to hinting submode */
+            mode_enter('c');
+            vb_set_input_text(";o");
+            break;
+
+        case 'F':
+            mode_enter('c');
+            vb_set_input_text(";t");
+            break;
+        
+        default:
+            prompt[0] = info->cmd;
+            vb_set_input_text(prompt);
+            mode_enter('c');
+    }
+
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_focus_input(const NormalCmdInfo *info)
+{
+    if (dom_focus_input(vb.gui.webview)) {
+        mode_enter('i');
+
+        return RESULT_COMPLETE;
+    }
+    return RESULT_ERROR;
+}
+
+static VbResult normal_hint(const NormalCmdInfo *info)
+{
+    char prompt[3] = {info->cmd, info->arg, 0};
+#ifdef FEATURE_QUEUE
+    const char *allowed = "eiIoOpPstTy";
+#else
+    const char *allowed = "eiIoOstTy";
+#endif
+
+    /* check if this is a valid hint mode */
+    if (!info->arg || !strchr(allowed, info->arg)) {
+        vb.state.processed_key = true;
+
+        return RESULT_ERROR;
+    }
+
+    mode_enter('c');
+    vb_set_input_text(prompt);
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_navigate(const NormalCmdInfo *info)
+{
+    int count;
+
+    WebKitWebView *view = vb.gui.webview;
+    switch (info->cmd) {
+        case CTRL('I'): /* fall through */
+        case CTRL('O'):
+            count = info->count ? info->count : 1;
+            if (info->cmd == CTRL('O')) {
+                count *= -1;
+            }
+            webkit_web_view_go_back_or_forward(view, count);
+            break;
+
+        case 'r':
+            webkit_web_view_reload(view);
+            break;
+
+        case 'R':
+            webkit_web_view_reload_bypass_cache(view);
+            break;
+
+        case 'C': /* TODO shouldn't we use ^C instead? */
+            webkit_web_view_stop_loading(view);
+            break;
+    }
+
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_open_clipboard(const NormalCmdInfo *info)
+{
+    Arg a = {info->cmd == 'P' ? VB_TARGET_NEW : VB_TARGET_CURRENT};
+
+    a.s = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
+    if (!a.s) {
+        a.s = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD());
+    }
+
+    if (a.s) {
+        vb_load_uri(&a);
+        g_free(a.s);
+
+        return RESULT_COMPLETE;
+    }
+
+    return RESULT_ERROR;
+}
+
+static VbResult normal_open(const NormalCmdInfo *info)
+{
+    Arg a;
+    if (strchr("uU", info->cmd)) { /* open last closed */
+        a.i = info->cmd == 'U' ? VB_TARGET_NEW : VB_TARGET_CURRENT;
+        a.s = util_get_file_contents(vb.files[FILES_CLOSED], NULL);
+        vb_load_uri(&a);
+        g_free(a.s);
+    } else { /* open home page */
+        a.i = UNG_CMD(info->cmd) == 'H' ? VB_TARGET_NEW : VB_TARGET_CURRENT;
+        a.s = NULL;
+        vb_load_uri(&a);
+    }
+
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_pass(const NormalCmdInfo *info)
+{
+    mode_enter('p');
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_queue(const NormalCmdInfo *info)
+{
+    command_queue(&((Arg){COMMAND_QUEUE_POP}));
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_quit(const NormalCmdInfo *info)
+{
+    vb_quit();
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_scroll(const NormalCmdInfo *info)
+{
+    GtkAdjustment *adjust;
+    gdouble value, max, new;
+    int count = info->count ? info->count : 1;
+
+    /* TODO is this required? */
+    /*vb_set_mode(VB_MODE_NORMAL | (vb.state.mode & VB_MODE_SEARCH), false);*/
+
+    /* TODO split this into more functions - reduce similar code */
+    switch (info->cmd) {
+        case 'h':
+            adjust = vb.gui.adjust_h;
+            value  = vb.config.scrollstep;
+            new    = gtk_adjustment_get_value(adjust) - value * count;
+            break;
+        case 'j':
+            adjust = vb.gui.adjust_v;
+            value  = vb.config.scrollstep;
+            new    = gtk_adjustment_get_value(adjust) + value * count;
+            break;
+        case 'k':
+            adjust = vb.gui.adjust_v;
+            value  = vb.config.scrollstep;
+            new    = gtk_adjustment_get_value(adjust) - value * count;
+            break;
+        case 'l':
+            adjust = vb.gui.adjust_h;
+            value  = vb.config.scrollstep;
+            new    = gtk_adjustment_get_value(adjust) + value * count;
+            break;
+        case CTRL('D'):
+            adjust = vb.gui.adjust_v;
+            value  = gtk_adjustment_get_page_size(adjust) / 2;
+            new    = gtk_adjustment_get_value(adjust) + value * count;
+            break;
+        case CTRL('U'):
+            adjust = vb.gui.adjust_v;
+            value  = gtk_adjustment_get_page_size(adjust) / 2;
+            new    = gtk_adjustment_get_value(adjust) - value * count;
+            break;
+        case CTRL('F'):
+            adjust = vb.gui.adjust_v;
+            value  = gtk_adjustment_get_page_size(adjust);
+            new    = gtk_adjustment_get_value(adjust) + value * count;
+            break;
+        case CTRL('B'):
+            adjust = vb.gui.adjust_v;
+            value  = gtk_adjustment_get_page_size(adjust);
+            new    = gtk_adjustment_get_value(adjust) - value * count;
+            break;
+        case 'G':
+            adjust = vb.gui.adjust_v;
+            max    = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
+            new    = info->count ? (max * info->count / 100) : gtk_adjustment_get_upper(adjust);
+            break;
+        case G_CMD('g'):
+            adjust = vb.gui.adjust_v;
+            max    = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
+            new    = info->count ? (max * info->count / 100) : gtk_adjustment_get_lower(adjust);
+            break;
+        case '0':
+            adjust = vb.gui.adjust_h;
+            new    = gtk_adjustment_get_lower(adjust);
+            break;
+        case '$':
+            adjust = vb.gui.adjust_h;
+            new    = gtk_adjustment_get_upper(adjust);
+            break;
+
+        default:
+            return RESULT_ERROR;
+    }
+    max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
+    gtk_adjustment_set_value(adjust, new > max ? max : new);
+
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_search(const NormalCmdInfo *info)
+{
+    command_search(&((Arg){info->cmd == 'n' ? COMMAND_SEARCH_FORWARD : COMMAND_SEARCH_BACKWARD}));
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_search_selection(const NormalCmdInfo *info)
+{
+    /* there is no function to get the selected text so we copy current
+     * selection to clipboard */
+    webkit_web_view_copy_clipboard(vb.gui.webview);
+    char *query = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
+    if (!query) {
+        return RESULT_ERROR;
+    }
+
+    command_search(&((Arg){info->cmd == '*' ? COMMAND_SEARCH_FORWARD : COMMAND_SEARCH_BACKWARD, query}));
+    g_free(query);
+
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_view_inspector(const NormalCmdInfo *info)
+{
+    gboolean enabled;
+    WebKitWebSettings *settings = webkit_web_view_get_settings(vb.gui.webview);
+
+    g_object_get(G_OBJECT(settings), "enable-developer-extras", &enabled, NULL);
+    if (enabled) {
+        if (vb.state.is_inspecting) {
+            webkit_web_inspector_close(vb.gui.inspector);
+        } else {
+            webkit_web_inspector_show(vb.gui.inspector);
+        }
+        return RESULT_COMPLETE;
+    }
+
+    vb_echo(VB_MSG_ERROR, true, "webinspector is not enabled");
+    return RESULT_ERROR;
+}
+
+static VbResult normal_view_source(const NormalCmdInfo *info)
+{
+    gboolean mode = webkit_web_view_get_view_source_mode(vb.gui.webview);
+    webkit_web_view_set_view_source_mode(vb.gui.webview, !mode);
+    webkit_web_view_reload(vb.gui.webview);
+    return RESULT_COMPLETE;
+}
+
+static VbResult normal_yank(const NormalCmdInfo *info)
+{
+    Arg a = {info->cmd == 'Y' ? COMMAND_YANK_SELECTION : COMMAND_YANK_URI};
+
+    return command_yank(&a) ? RESULT_COMPLETE : RESULT_ERROR;
+}
+
+static VbResult normal_zoom(const NormalCmdInfo *info)
+{
+    float step, level, count;
+    WebKitWebSettings *setting;
+    WebKitWebView *view = vb.gui.webview;
+
+    count = info->count ? (float)info->count : 1.0;
+
+    if (info->arg == 'z') { /* zz reset zoom */
+        webkit_web_view_set_zoom_level(view, 1.0);
+
+        return RESULT_COMPLETE;
+    }
+
+    level   = webkit_web_view_get_zoom_level(view);
+    setting = webkit_web_view_get_settings(view);
+    g_object_get(G_OBJECT(setting), "zoom-step", &step, NULL);
+
+    webkit_web_view_set_full_content_zoom(view, isupper(info->arg));
+
+    /* calculate the new zoom level */
+    if (info->arg == 'i' || info->arg == 'I') {
+        level += ((float)count * step);
+    } else {
+        level -= ((float)count * step);
+    }
+
+    /* apply the new zoom level */
+    webkit_web_view_set_zoom_level(view, level);
+
+    return RESULT_COMPLETE;
+}
diff --git a/src/normal.h b/src/normal.h
new file mode 100644 (file)
index 0000000..6fb9f3f
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 _NORMAL_H
+#define _NORMAL_H
+
+#include "config.h"
+#include "main.h"
+
+typedef struct NormalCmdInfo_s NormalCmdInfo;
+
+void normal_enter(void);
+void normal_leave(void);
+VbResult normal_keypress(unsigned int key);
+void normal_input_changed(const char *text);
+
+#endif /* end of include guard: _NORMAL_H */
diff --git a/src/pass.c b/src/pass.c
new file mode 100644 (file)
index 0000000..b23110f
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 "config.h"
+#include "main.h"
+#include "pass.h"
+#include "mode.h"
+#include "dom.h"
+
+extern VbCore vb;
+
+
+/**
+ * Function called when vimb enters the passthrough mode.
+ */
+void pass_enter(void)
+{
+    /* switch focus first to make shure we can write to the inputbox without
+     * disturbing the user */
+    gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
+    vb_echo(VB_MSG_NORMAL, false, "-- PASS THROUGH --");
+}
+
+/**
+ * Called when passthrough mode is left.
+ */
+void pass_leave(void)
+{
+    vb_set_input_text("");
+}
+
+VbResult pass_keypress(unsigned int key)
+{
+    if (key == CTRL('[')) { /* esc */
+        vb.state.processed_key = true;
+        mode_enter('n');
+    }
+    return RESULT_COMPLETE;
+}
diff --git a/src/pass.h b/src/pass.h
new file mode 100644 (file)
index 0000000..1e34eb0
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 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 _PASS_H
+#define _PASS_H
+
+#include "config.h"
+#include "main.h"
+
+void pass_enter(void);
+void pass_leave(void);
+VbResult pass_keypress(unsigned int key);
+
+#endif /* end of include guard: _PASS_H */