From: Daniel Carl Date: Wed, 14 Aug 2013 18:15:06 +0000 (+0200) Subject: Changed the way keys are processed. X-Git-Url: https://git.owens.tech/assets/static/dummy.html/assets/static/dummy.html/git?a=commitdiff_plain;h=46f8678f4600ca7c1566f483cbee143ed6294772;p=vimb.git Changed the way keys are processed. 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!:open search query50G'. --- diff --git a/src/command.c b/src/command.c index cc70721..723e53f 100644 --- a/src/command.c +++ b/src/command.c @@ -17,572 +17,17 @@ * 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); -} diff --git a/src/command.h b/src/command.h index e9020eb..b6de228 100644 --- a/src/command.h +++ b/src/command.h @@ -20,23 +20,16 @@ #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 */ diff --git a/src/completion.c b/src/completion.c index 1f8aa14..d3e10df 100644 --- a/src/completion.c +++ b/src/completion.c @@ -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' */ 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) diff --git a/src/default.h b/src/default.h index 060e34b..7be6500 100644 --- a/src/default.h +++ b/src/default.h @@ -23,59 +23,16 @@ #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 =quit", - "nmap =back", - "nmap =forward", - "nmap r=reload", - "nmap R=reload!", - "nmap C=stop", - "nmap =pagedown", - "nmap =pageup", - "nmap =halfpagedown", - "nmap =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 =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 =pass-through", "nmap gi=focus-input", - "cmap =next", - "cmap =prev", - "cmap =hist-prev", - "cmap =hist-next", - "imap =editor", + /* XXX "imap =editor",*/ "imap =pass-through", +#endif "shortcut-add dl=https://duckduckgo.com/html/?q=$0", "shortcut-add dd=https://duckduckgo.com/?q=$0", "shortcut-default dl", diff --git a/src/dom.c b/src/dom.c index cc5f31a..c9d7ade 100644 --- 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 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 +#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 or 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!' - 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 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 */ diff --git a/src/hints.c b/src/hints.c index 1a0cce0..b0a4cd4 100644 --- a/src/hints.c +++ b/src/hints.c @@ -17,6 +17,7 @@ * along with this program. If not, see http://www.gnu.org/licenses/. */ +#include #include "config.h" #include #include @@ -24,25 +25,22 @@ #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; -} diff --git a/src/hints.h b/src/hints.h index ead9d13..640401b 100644 --- a/src/hints.h +++ b/src/hints.h @@ -22,28 +22,12 @@ #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 */ diff --git a/src/hints.js b/src/hints.js index 8857c79..d183dfd 100644 --- a/src/hints.js +++ b/src/hints.js @@ -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, diff --git a/src/history.c b/src/history.c index 058d660..e13d090 100644 --- a/src/history.c +++ b/src/history.c @@ -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 index 0000000..b967cd6 --- /dev/null +++ b/src/input.c @@ -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 index 0000000..fa8c908 --- /dev/null +++ b/src/input.h @@ -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 index 9944faa..0000000 --- a/src/keybind.c +++ /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, , , */ - 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 "" or */ - 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 */ - keybind->modmask = string_to_modmask(string[1]); - keybind->keyval = string_to_value(string[2]); - } else if (len == 3) { - /* key without modmask like */ - 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 index 5e58d8d..0000000 --- a/src/keybind.h +++ /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 -#include - -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 */ diff --git a/src/main.c b/src/main.c index 24d1863..6176509 100644 --- a/src/main.c +++ b/src/main.c @@ -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" @@ -31,14 +30,20 @@ #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); diff --git a/src/main.h b/src/main.h index 3cbdce8..153f7e1 100644 --- a/src/main.h +++ b/src/main.h @@ -39,6 +39,15 @@ #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__); \ @@ -106,6 +115,7 @@ #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 */ + 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 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 +#include +#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 or -> \001 + * and -> \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 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 */ + 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 -> \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[] = { + {"", 4, "\n", 1}, + {"", 5, "\t", 1}, + {"", 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 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 index 0000000..e36e9df --- /dev/null +++ b/src/mode.c @@ -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 index 0000000..b32c870 --- /dev/null +++ b/src/mode.h @@ -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 index 0000000..b951e09 --- /dev/null +++ b/src/normal.c @@ -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 +#include +#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 index 0000000..6fb9f3f --- /dev/null +++ b/src/normal.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 _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 index 0000000..b23110f --- /dev/null +++ b/src/pass.c @@ -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 index 0000000..1e34eb0 --- /dev/null +++ b/src/pass.h @@ -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 */