* 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)
{
#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;
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);
/* 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;
}
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)
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 */
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)
{
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) {
}
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");
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);
-}
#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 {
};
#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 */
#include "bookmark.h"
#include "command.h"
#include "setting.h"
+#include "ex.h"
#define TAG_INDICATOR '!'
GtkListStore *store = NULL;
gboolean res = false, sort = true;
+ /* TODO give the type of completion to this function - because we have to
+ * handle also abreviated commands like ':op foo<tab>' */
input = vb_get_input_text();
type = vb_get_input_parts(input, VB_INPUT_ALL, &prefix, &suffix);
-
- if (vb.state.mode & VB_MODE_COMPLETE) {
+ if (vb.mode->flags & FLAG_COMPLETION) {
if (comp.text && !strcmp(input, comp.text)) {
/* step through the next/prev completion item */
move_cursor(back);
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) {
/* 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) {
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));
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)
#include "stdlib.h"
static char *default_config[] = {
- "nmap gf=source",
- "nmap gF=inspect",
- "nmap :=input",
- "nmap /=input /",
- "nmap ?=input ?",
- "nmap n=search-forward",
- "nmap N=search-backward",
- "nmap *=search-selection-forward",
- "nmap #=search-selection-backward",
+#if 0
"nmap o=input :open ",
"nmap t=input :tabopen ",
"nmap O=inputuri :open ",
"nmap T=inputuri :tabopen ",
"nmap gh=open",
"nmap gH=tabopen",
- "nmap u=open-closed",
- "nmap U=tabopen-closed",
- "nmap <ctrl-q>=quit",
- "nmap <ctrl-o>=back",
- "nmap <ctrl-i>=forward",
- "nmap r=reload",
- "nmap R=reload!",
- "nmap C=stop",
- "nmap <ctrl-f>=pagedown",
- "nmap <ctrl-b>=pageup",
- "nmap <ctrl-d>=halfpagedown",
- "nmap <ctrl-u>=halfpageup",
- "nmap gg=jumptop",
- "nmap G=jumpbottom",
- "nmap 0=jumpleft",
- "nmap $=jumpright",
- "nmap h=scrollleft",
- "nmap l=scrollright",
- "nmap k=scrollup",
- "nmap j=scrolldown",
"nmap f=hint-link",
"nmap F=hint-link-new",
- "nmap ;o=hint-input-open",
- "nmap ;t=hint-input-tabopen",
- "nmap ;y=hint-yank",
- "nmap ;i=hint-image-open",
- "nmap ;I=hint-image-tabopen",
- "nmap ;e=hint-editor",
- "nmap ;s=hint-save",
-#ifdef FEATURE_QUEUE
- "nmap ;p=hint-queue-push",
- "nmap ;P=hint-queue-unshift",
"nmap <ctrl-p>=queue-pop",
-#endif
- "nmap y=yank-uri",
- "nmap Y=yank-selection",
- "nmap p=open-clipboard",
- "nmap P=tabopen-clipboard",
"nmap zi=zoomin",
"nmap zI=zoominfull",
"nmap zo=zoomout",
"nmap gU=descent!",
"nmap <ctrl-z>=pass-through",
"nmap gi=focus-input",
- "cmap <tab>=next",
- "cmap <shift-tab>=prev",
- "cmap <up>=hist-prev",
- "cmap <down>=hist-next",
- "imap <ctrl-t>=editor",
+ /* XXX "imap <ctrl-t>=editor",*/
"imap <ctrl-z>=pass-through",
+#endif
"shortcut-add dl=https://duckduckgo.com/html/?q=$0",
"shortcut-add dd=https://duckduckgo.com/?q=$0",
"shortcut-default dl",
#include "config.h"
#include "main.h"
#include "dom.h"
+#include "mode.h"
extern VbCore vb;
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;
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);
}
--- /dev/null
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 Daniel Carl
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+
+/**
+ * This file contains function to handle input editing, parsing of called ex
+ * commands from inputbox and the ex commands.
+ */
+#include <ctype.h>
+#include "config.h"
+#include "main.h"
+#include "ex.h"
+#include "completion.h"
+#include "hints.h"
+#include "mode.h"
+#include "command.h"
+#include "history.h"
+#include "dom.h"
+#include "setting.h"
+#include "util.h"
+#include "bookmark.h"
+#include "shortcut.h"
+#include "map.h"
+
+typedef enum {
+ EX_BMA,
+ EX_BMR,
+ EX_EVAL,
+ EX_CMAP,
+ EX_IMAP,
+ EX_NMAP,
+ EX_CUNMAP,
+ EX_IUNMAP,
+ EX_NUNMAP,
+ EX_OPEN,
+#ifdef FEATURE_QUEUE
+ EX_QCLEAR,
+ EX_QPOP,
+ EX_QPUSH,
+ EX_QUNSHIFT,
+#endif
+ EX_QUIT,
+ EX_SAVE,
+ EX_SCA,
+ EX_SCD,
+ EX_SCR,
+ EX_SET,
+ EX_SHELLCMD,
+ EX_TABOPEN,
+} ExCode;
+
+typedef struct {
+ int count; /* commands count */
+ int idx; /* index in commands array */
+ const char *name; /* name of the command */
+ ExCode code; /* id of the command */
+ gboolean bang; /* if the command was called with a bang ! */
+ GString *lhs; /* left hand side of the command - single word */
+ GString *rhs; /* right hand side of the command - multiple words */
+} ExArg;
+
+typedef gboolean (*ExFunc)(const ExArg *arg);
+
+typedef struct {
+ const char *name; /* full name of the command even if called abreviated */
+ ExCode code; /* constant id for the command */
+ ExFunc func;
+#define EX_FLAG_NONE 0x000 /* no flags set */
+#define EX_FLAG_BANG 0x001 /* command uses the bang ! after command name */
+#define EX_FLAG_LHS 0x002 /* command has a single word after the command name */
+#define EX_FLAG_RHS 0x004 /* command has a right hand side */
+ int flags;
+} ExInfo;
+
+static void input_activate(void);
+static gboolean parse(const char **input, ExArg *arg);
+static gboolean parse_count(const char **input, ExArg *arg);
+static gboolean parse_command_name(const char **input, ExArg *arg);
+static gboolean parse_lhs(const char **input, ExArg *arg);
+static gboolean parse_rhs(const char **input, ExArg *arg);
+static void skip_whitespace(const char **input);
+static void free_cmdarg(ExArg *arg);
+static gboolean execute(const ExArg *arg);
+static char *expand_string(const char *str);
+
+static gboolean ex_bookmark(const ExArg *arg);
+static gboolean ex_eval(const ExArg *arg);
+static gboolean ex_map(const ExArg *arg);
+static gboolean ex_unmap(const ExArg *arg);
+static gboolean ex_open(const ExArg *arg);
+static gboolean ex_queue(const ExArg *arg);
+static gboolean ex_quit(const ExArg *arg);
+static gboolean ex_save(const ExArg *arg);
+static gboolean ex_set(const ExArg *arg);
+static gboolean ex_shellcmd(const ExArg *arg);
+static gboolean ex_shortcut(const ExArg *arg);
+
+/* The order of following command names is significant. If there exists
+ * ambiguous commands matching to the users input, the first defined will be
+ * the prefered match.
+ * Also the sorting and grouping of command names matters, so we give up
+ * searching for a matching command if the next compared character did not
+ * match. */
+static ExInfo commands[] = {
+ /* command code func flags */
+ {"bma", EX_BMA, ex_bookmark, EX_FLAG_RHS},
+ {"bmr", EX_BMR, ex_bookmark, EX_FLAG_RHS},
+#if 0
+ {"bookmark-add", EX_BMA, ex_bookmark, EX_FLAG_RHS},
+ {"bookmark-remove", EX_BMR, ex_bookmark, EX_FLAG_RHS},
+#endif
+ {"cmap", EX_CMAP, ex_map, EX_FLAG_LHS|EX_FLAG_RHS},
+ {"cunmap", EX_CUNMAP, ex_unmap, EX_FLAG_LHS},
+ {"eval", EX_EVAL, ex_eval, EX_FLAG_RHS},
+ {"imap", EX_IMAP, ex_map, EX_FLAG_LHS|EX_FLAG_RHS},
+ {"iunmap", EX_IUNMAP, ex_unmap, EX_FLAG_LHS},
+ {"nmap", EX_NMAP, ex_map, EX_FLAG_LHS|EX_FLAG_RHS},
+ {"nunmap", EX_NUNMAP, ex_unmap, EX_FLAG_LHS},
+ {"open", EX_OPEN, ex_open, EX_FLAG_RHS},
+ {"quit", EX_QUIT, ex_quit, EX_FLAG_NONE},
+ {"qclear", EX_QCLEAR, ex_queue, EX_FLAG_RHS},
+ {"qpop", EX_QPOP, ex_queue, EX_FLAG_NONE},
+ {"qpush", EX_QPUSH, ex_queue, EX_FLAG_RHS},
+ {"qunshift", EX_QUNSHIFT, ex_queue, EX_FLAG_RHS},
+ {"save", EX_SAVE, ex_save, EX_FLAG_RHS},
+#if 0
+ {"sca", EX_SCA, ex_shortcut, EX_FLAG_RHS},
+ {"scd", EX_SCD, ex_shortcut, EX_FLAG_RHS},
+ {"scr", EX_SCR, ex_shortcut, EX_FLAG_RHS},
+#endif
+ {"set", EX_SET, ex_set, EX_FLAG_RHS},
+ {"shellcmd", EX_SHELLCMD, ex_shellcmd, EX_FLAG_RHS},
+ {"shortcut-add", EX_SCA, ex_shortcut, EX_FLAG_RHS},
+ {"shortcut-default", EX_SCD, ex_shortcut, EX_FLAG_RHS},
+ {"shortcut-remove", EX_SCR, ex_shortcut, EX_FLAG_RHS},
+ {"tabopen", EX_TABOPEN, ex_open, EX_FLAG_RHS},
+#if 0
+ {"queue-push", EX_QPUSH, ex_queue, EX_FLAG_RHS},
+ {"queue-unshift", EX_QUNSHIFT, ex_queue, EX_FLAG_RHS},
+ {"queue-pop", EX_QPOP, ex_queue, EX_FLAG_RHS},
+ {"queue-clear", EX_QCLEAR, ex_queue, EX_FLAG_RHS},
+#endif
+};
+
+extern VbCore vb;
+
+
+/**
+ * Function called when vimb enters the command mode.
+ */
+void ex_enter(void)
+{
+ gtk_widget_grab_focus(GTK_WIDGET(vb.gui.input));
+ dom_clear_focus(vb.gui.webview);
+}
+
+/**
+ * Called when the command mode is left.
+ */
+void ex_leave(void)
+{
+ /* TODO clean those only if they where active */
+ completion_clean();
+ hints_clear();
+}
+
+/**
+ * Handles the keypress events from webview and inputbox.
+ */
+VbResult ex_keypress(unsigned int key)
+{
+ /* TODO allow to get the right prompt like ':', '/', ';o', ... */
+ char *prompt = ":";
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer = vb.gui.buffer;
+ GtkTextMark *mark;
+
+ /* delegate call to the submode if hinting is active */
+ if (vb.mode->flags & FLAG_HINTING) {
+ if (RESULT_COMPLETE == hints_keypress(key)) {
+ vb.state.processed_key = true;
+ return RESULT_COMPLETE;
+ }
+ }
+
+ vb.state.processed_key = true;
+ switch (key) {
+ case CTRL('I'): /* Tab */
+ /* mode will be set in completion_complete */
+ completion_complete(false);
+ break;
+
+ case CTRL('O'): /* S-Tab */
+ completion_complete(true);
+ break;
+
+ case CTRL('['):
+ case CTRL('C'):
+ mode_enter('n');
+ vb_set_input_text("");
+ break;
+
+ case '\n':
+ input_activate();
+ break;
+
+ case CTRL('P'): /* up */
+ /* TODO don't emit input change event when stepping though history in search mode */
+ command_history(&((Arg){1}));
+ break;
+
+ case CTRL('N'): /* down */
+ command_history(&((Arg){0}));
+ break;
+
+ /* basic command line editing */
+ case CTRL('H'):
+ /* delete the last char before the cursor */
+ mark = gtk_text_buffer_get_insert(buffer);
+ gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
+ gtk_text_buffer_backspace(buffer, &start, true, true);
+ break;
+
+ case CTRL('W'):
+ /* delete word backward from cursor */
+ mark = gtk_text_buffer_get_insert(buffer);
+ gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
+
+ /* copy the iter to build start and end point for deletion */
+ start = end;
+
+ /* move the iterator to the beginning of previous word */
+ if (gtk_text_iter_backward_word_start(&start)) {
+ gtk_text_buffer_delete(buffer, &start, &end);
+ }
+ break;
+
+ case CTRL('B'):
+ /* move the cursor direct behind the prompt */
+ gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(prompt));
+ gtk_text_buffer_place_cursor(buffer, &start);
+ break;
+
+ case CTRL('E'):
+ /* move the cursor to the end of line */
+ gtk_text_buffer_get_end_iter(buffer, &start);
+ gtk_text_buffer_place_cursor(buffer, &start);
+ break;
+
+ case CTRL('U'):
+ /* remove everythings between cursor and prompt */
+ mark = gtk_text_buffer_get_insert(buffer);
+ gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
+ gtk_text_buffer_get_iter_at_offset(buffer, &start, strlen(prompt));
+ gtk_text_buffer_delete(buffer, &start, &end);
+ break;
+
+ default:
+ /* if is printable ascii char, than write it at the cursor
+ * position into input box */
+ if (key >= 0x20 && key <= 0x7e) {
+ gtk_text_buffer_insert_at_cursor(buffer, (char[2]){key, 0}, 1);
+ } else {
+ vb.state.processed_key = false;
+ }
+ }
+
+ return RESULT_COMPLETE;
+}
+
+/**
+ * Handles changes in the inputbox.
+ */
+void ex_input_changed(const char *text)
+{
+ gboolean forward = false;
+ switch (*text) {
+ case ';':
+ hints_create(text);
+ break;
+
+ case '/': forward = true; /* fall through */
+ case '?':
+ webkit_web_view_unmark_text_matches(vb.gui.webview);
+ webkit_web_view_search_text(vb.gui.webview, &text[1], false, forward, false);
+ break;
+ }
+}
+
+gboolean ex_fill_completion(GtkListStore *store, const char *input)
+{
+ GtkTreeIter iter;
+ ExInfo *cmd;
+ gboolean found = false;
+
+ if (!input || *input == '\0') {
+ for (int i = 0; i < LENGTH(commands); i++) {
+ cmd = &commands[i];
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1);
+ found = true;
+ }
+ } else {
+ for (int i = 0; i < LENGTH(commands); i++) {
+ cmd = &commands[i];
+ if (g_str_has_prefix(cmd->name, input)) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, cmd->name, -1);
+ found = true;
+ }
+ }
+ }
+
+ return found;
+}
+
+/**
+ * This is called if the user typed <nl> or <cr> into the inputbox.
+ */
+static void input_activate(void)
+{
+ gboolean forward = false;
+ char *text, *cmd;
+ text = vb_get_input_text();
+
+ /* skip leading promt char like ':' or '/' */
+ /* TODO should we use a flag to determine if we should record the command
+ * into the history - maybe it's not good to save commands in history that
+ * where triggered by a map like ':name \, :set scripts!<cr>' - by the way
+ * does vim also skip history recording for such mapped commands */
+ cmd = text + 1;
+ switch (*text) {
+ case '/': forward = true; /* fall throught */
+ case '?':
+ history_add(HISTORY_SEARCH, cmd, NULL);
+ mode_enter('n');
+ command_search(&((Arg){forward ? COMMAND_SEARCH_FORWARD : COMMAND_SEARCH_BACKWARD, cmd}));
+ break;
+
+ case ';':
+ hints_fire();
+ break;
+
+ case ':':
+ history_add(HISTORY_COMMAND, cmd, NULL);
+ mode_enter('n');
+ ex_run_string(cmd);
+ break;
+
+ }
+ g_free(text);
+}
+
+gboolean ex_run_string(const char *input)
+{
+ ExArg *a = g_new0(ExArg, 1);
+ a->lhs = g_string_new("");
+ a->rhs = g_string_new("");
+
+ if (!parse(&input, a)
+ || !execute(a)
+ ) {
+ free_cmdarg(a);
+
+ return false;
+ }
+ free_cmdarg(a);
+
+ return true;
+}
+
+/**
+ * Parses given input string into given ExArg pointer.
+ */
+static gboolean parse(const char **input, ExArg *arg)
+{
+ ExInfo *cmd = NULL;
+ if (!*input) {
+ return false;
+ }
+ /* remove leading whitespace */
+ skip_whitespace(input);
+ parse_count(input, arg);
+
+ skip_whitespace(input);
+ if (!parse_command_name(input, arg)) {
+ return false;
+ }
+
+ /* get the command and it's flags to decide what to parse */
+ cmd = &(commands[arg->idx]);
+
+ /* parse the lhs if this is available */
+ skip_whitespace(input);
+ if (cmd->flags & EX_FLAG_LHS) {
+ parse_lhs(input, arg);
+ }
+ /* parse the rhs if this is available */
+ skip_whitespace(input);
+ if (cmd->flags & EX_FLAG_RHS) {
+ parse_rhs(input, arg);
+ }
+
+#if 0
+ PRINT_DEBUG("CMD idx=%d, x=%d [%s][%s][%s]", arg->idx, arg->count, arg->name, arg->lhs->str, arg->rhs->str);
+#endif
+ return true;
+}
+
+/**
+ * Parses possible found count of given input into ExArg pointer.
+ */
+static gboolean parse_count(const char **input, ExArg *arg)
+{
+ if (!*input || !isdigit(**input)) {
+ arg->count = 0;
+ } else {
+ do {
+ arg->count = arg->count * 10 + (**input - '0');
+ (*input)++;
+ } while (isdigit(**input));
+ }
+ return true;
+}
+
+/**
+ * Parse the command name from given input.
+ */
+static gboolean parse_command_name(const char **input, ExArg *arg)
+{
+ int len = 0;
+ int first = 0; /* number of first found command */
+ int matches = 0; /* number of commands that matches the input */
+ char cmd[20] = {0}; /* name of found command */
+
+ do {
+ /* copy the next char into the cmd buffer */
+ cmd[len++] = **input;
+ int i;
+ for (i = first, matches = 0; i < LENGTH(commands); i++) {
+ /* commands are grouped by their first letters, if we reached the
+ * end of the group there are no more possible matches to find */
+ if (len > 1 && strncmp(commands[i].name, cmd, len - 1)) {
+ break;
+ }
+ if (commands[i].name[len - 1] == **input) {
+ /* partial match found */
+ if (!matches) {
+ /* if this is the first then remeber it */
+ first = i;
+ }
+ matches++;
+ }
+ }
+ (*input)++;
+ } while (matches > 0 && **input != '\0' && **input != ' ');
+
+ if (!matches) {
+ /* TODO show readable error message */
+ return false;
+ }
+
+ arg->idx = first;
+ arg->code = commands[first].code;
+ arg->name = commands[first].name;
+
+ return true;
+}
+
+/**
+ * Parse a single word left hand side of a command arg.
+ */
+static gboolean parse_lhs(const char **input, ExArg *arg)
+{
+ if (!*input) {
+ return false;
+ }
+ /* get the char until the next none escaped whitespace and save it into
+ * the lhs */
+ while (*input && **input != ' ') {
+ /* if we find a backslash this escapes the next whitespace */
+ if (**input == '\\') {
+ /* move pointer to the next char */
+ (*input)++;
+ if (!*input) {
+ /* if input ends here - use only the backslash */
+ g_string_append_c(arg->lhs, '\\');
+ } else if (**input == ' ') {
+ /* escaped whitespace becomes only whitespace */
+ g_string_append_c(arg->lhs, **input);
+ } else {
+ /* put escape char and next char into the result string */
+ g_string_append_c(arg->lhs, '\\');
+ g_string_append_c(arg->lhs, **input);
+ }
+ } else {
+ /* unquoted char */
+ g_string_append_c(arg->lhs, **input);
+ }
+ (*input)++;
+ }
+ return true;
+}
+
+/**
+ * Parses the right hand side of command args.
+ */
+static gboolean parse_rhs(const char **input, ExArg *arg)
+{
+ if (!*input) {
+ return false;
+ }
+ /* get char until the end of command */
+ while (*input && **input != '\n' && **input != '|') {
+ /* if we find a backslash this escapes the next whitespace */
+ if (**input == '\\') {
+ /* move pointer to the next char */
+ (*input)++;
+ if (!*input) {
+ /* if input ends here - use only the backslash */
+ g_string_append_c(arg->rhs, '\\');
+ } else if (**input == ' ') {
+ /* escaped whitespace becomes only whitespace */
+ g_string_append_c(arg->rhs, **input);
+ } else {
+ /* put escape char and next char into the result string */
+ g_string_append_c(arg->rhs, '\\');
+ g_string_append_c(arg->rhs, **input);
+ }
+ } else {
+ /* unquoted char */
+ g_string_append_c(arg->rhs, **input);
+ }
+ (*input)++;
+ }
+ return true;
+}
+
+/**
+ * Executes the command given by ExArg.
+ */
+static gboolean execute(const ExArg *arg)
+{
+ return (commands[arg->idx].func)(arg);
+}
+
+static void skip_whitespace(const char **input)
+{
+ /* TODO should \t also be skipped here? */
+ while (*input && **input == ' ') {
+ (*input)++;
+ }
+}
+
+/**
+ * Expands paceholders in given string.
+ * % - expanded to current uri
+ * TODO allow modifiers like :p :h :e :r like in vim expand()
+ *
+ * Returned string must be freed.
+ */
+static char *expand_string(const char *str)
+{
+ if (!str) {
+ return NULL;
+ }
+ return util_str_replace("%", GET_URI(), str);
+}
+
+static void free_cmdarg(ExArg *arg)
+{
+ g_string_free(arg->lhs, true);
+ g_string_free(arg->rhs, true);
+ g_free(arg);
+}
+
+static gboolean ex_bookmark(const ExArg *arg)
+{
+ if (arg->code == EX_BMR) {
+ if (bookmark_remove(*arg->rhs->str ? arg->rhs->str : GET_URI())) {
+ vb_echo_force(VB_MSG_NORMAL, false, " Bookmark removed");
+
+ return true;
+ }
+ } else if (bookmark_add(GET_URI(), webkit_web_view_get_title(vb.gui.webview), arg->rhs->str)) {
+ vb_echo_force(VB_MSG_NORMAL, false, " Bookmark added");
+
+ return true;
+ }
+
+ return false;
+}
+
+static gboolean ex_eval(const ExArg *arg)
+{
+ gboolean success;
+ char *value = NULL;
+
+ if (!*arg->rhs->str) {
+ return false;
+ }
+
+ success = vb_eval_script(
+ webkit_web_view_get_main_frame(vb.gui.webview), arg->rhs->str, NULL, &value
+ );
+ if (success) {
+ vb_echo(VB_MSG_NORMAL, false, "%s", value);
+ } else {
+ vb_echo(VB_MSG_ERROR, true, "%s", value);
+ }
+ g_free(value);
+
+ return success;
+}
+
+static gboolean ex_map(const ExArg *arg)
+{
+ /* TODO implement parsing of chars */
+ char *lhs = arg->lhs->str;
+ char *rhs = arg->rhs->str;
+
+ if (!*lhs || !*rhs) {
+ return false;
+ }
+
+ if (arg->code == EX_NMAP) {
+ map_insert(lhs, rhs, 'n');
+ } else if (arg->code == EX_CMAP) {
+ map_insert(lhs, rhs, 'c');
+ } else {
+ map_insert(lhs, rhs, 'i');
+ }
+ return true;;
+}
+
+static gboolean ex_unmap(const ExArg *arg)
+{
+ /* TODO implement parsing of chars */
+ char *lhs = arg->lhs->str;
+
+ if (!*lhs) {
+ return false;
+ }
+
+ if (arg->code == EX_NUNMAP) {
+ map_delete(lhs, 'n');
+ } else if (arg->code == EX_CUNMAP) {
+ map_delete(lhs, 'c');
+ } else {
+ map_delete(lhs, 'i');
+ }
+ return true;
+}
+
+static gboolean ex_open(const ExArg *arg)
+{
+ if (arg->code == EX_TABOPEN) {
+ return vb_load_uri(&((Arg){VB_TARGET_NEW, arg->rhs->str}));
+ } else {
+ return vb_load_uri(&((Arg){VB_TARGET_CURRENT, arg->rhs->str}));
+ }
+}
+
+#ifdef FEATURE_QUEUE
+static gboolean ex_queue(const ExArg *arg)
+{
+ Arg a = {.s = arg->rhs->str};
+ switch (arg->code) {
+ case EX_QPUSH:
+ a.i = COMMAND_QUEUE_PUSH;
+ break;
+
+ case EX_QUNSHIFT:
+ a.i = COMMAND_QUEUE_UNSHIFT;
+ break;
+
+ case EX_QPOP:
+ a.i = COMMAND_QUEUE_POP;
+ break;
+
+ case EX_QCLEAR:
+ a.i = COMMAND_QUEUE_CLEAR;
+ break;
+
+ default:
+ return false;
+ }
+
+ return command_queue(&a);
+}
+#endif
+
+static gboolean ex_quit(const ExArg *arg)
+{
+ vb_quit();
+ return true;
+}
+
+static gboolean ex_save(const ExArg *arg)
+{
+ return command_save(&((Arg){COMMAND_SAVE_CURRENT, arg->rhs->str}));
+}
+
+static gboolean ex_set(const ExArg *arg)
+{
+ gboolean success;
+ char *param = NULL;
+
+ if (!*arg->rhs->str) {
+ return false;
+ }
+
+ /* split the input string into parameter and value part */
+ if ((param = strchr(arg->rhs->str, '='))) {
+ *param++ = '\0';
+ success = setting_run(arg->rhs->str, param ? param : NULL);
+ } else {
+ success = setting_run(arg->rhs->str, NULL);
+ }
+
+ return success;
+}
+
+static gboolean ex_shellcmd(const ExArg *arg)
+{
+ int status, argc;
+ char *cmd, *exp, *error = NULL, *out = NULL, **argv;
+
+ if (!*arg->rhs->str) {
+ return false;
+ }
+
+ exp = expand_string(arg->rhs->str);
+ cmd = g_strdup_printf(SHELL_CMD, exp);
+ g_free(exp);
+ if (!g_shell_parse_argv(cmd, &argc, &argv, NULL)) {
+ vb_echo(VB_MSG_ERROR, true, "Could not parse command args");
+ g_free(cmd);
+
+ return false;
+ }
+ g_free(cmd);
+
+ g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &out, &error, &status, NULL);
+ g_strfreev(argv);
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ vb_echo(VB_MSG_NORMAL, true, "%s", out);
+ return true;
+ }
+
+ vb_echo(VB_MSG_ERROR, true, "[%d] %s", WEXITSTATUS(status), error);
+ return false;
+}
+
+static gboolean ex_shortcut(const ExArg *arg)
+{
+ char *p;
+
+ /* TODO allow to set shortcust with set command like ':set
+ * shortcut[name]=http://donain.tld/?q=$0' */
+ switch (arg->code) {
+ case EX_SCA:
+ if (*arg->rhs->str && (p = strchr(arg->rhs->str, '='))) {
+ *p++ = '\0';
+ return shortcut_add(arg->rhs->str, p);
+ }
+ return false;
+
+ case EX_SCR:
+ return shortcut_remove(arg->rhs->str);
+
+ case EX_SCD:
+ return shortcut_set_default(arg->rhs->str);
+
+ default:
+ return false;
+ }
+}
--- /dev/null
+/**
+ * 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 */
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
+#include <ctype.h>
#include "config.h"
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkkeysyms-compat.h>
#include "dom.h"
#include "command.h"
#include "hints.js.h"
+#include "mode.h"
+#include "input.h"
+#include "map.h"
#define HINT_VAR "VbHint"
#define HINT_FILE "hints.js"
static struct {
- gulong num;
- guint mode;
- guint prefixLength;
- gulong change_handler;
- gulong keypress_handler;
+ guint num;
+ char prompt[3];
} hints;
extern VbCore vb;
static void run_script(char *js);
-static void fire();
-static void observe_input(gboolean observe);
-static void changed_cb(GtkTextBuffer *buffer);
-static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event);
+
void hints_init(WebKitWebFrame *frame)
{
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);
}
}
-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);
}
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
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;
-}
#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 */
/* 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,
/**
* 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)
{
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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 */
+++ /dev/null
-/**
- * vimb - a webkit based vim like browser.
- *
- * Copyright (C) 2012-2013 Daniel Carl
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see http://www.gnu.org/licenses/.
- */
-
-#include "config.h"
-#include "main.h"
-#include "keybind.h"
-#include "command.h"
-#include "completion.h"
-
-typedef struct {
- int mode; /* mode maks for allowed browser modes */
- guint modkey;
- guint modmask; /* modemask for the kayval */
- guint keyval;
- Command func;
- Arg arg;
-} Keybind;
-
-extern VbCore vb;
-
-static GSList *keys;
-static GString *modkeys;
-
-static void keybind_remove(Keybind *kb);
-static void rebuild_modkeys(void);
-static GSList *find(int mode, guint modkey, guint modmask, guint keyval);
-static void string_to_keybind(char *str, Keybind *key);
-static guint string_to_modmask(const char *str);
-static guint string_to_value(const char *str);
-static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer is_input);
-static void free_keybind(Keybind *keybind);
-
-
-void keybind_init(void)
-{
- modkeys = g_string_new("");
- g_signal_connect(G_OBJECT(vb.gui.webview), "key-press-event", G_CALLBACK(keypress_cb), GINT_TO_POINTER(0));
- g_signal_connect(G_OBJECT(vb.gui.input), "key-press-event", G_CALLBACK(keypress_cb), GINT_TO_POINTER(1));
-}
-
-void keybind_cleanup(void)
-{
- if (keys) {
- g_slist_free_full(keys, (GDestroyNotify)free_keybind);
- }
- if (modkeys) {
- g_string_free(modkeys, true);
- }
-}
-
-gboolean keybind_add_from_string(char *keystring, const char *command, const Mode mode)
-{
- guint count;
- if (keystring == NULL || *keystring == '\0') {
- return false;
- }
-
- Keybind *kb = g_new0(Keybind, 1);
- kb->mode = mode;
- if (!command_parse_from_string(command, &kb->func, &kb->arg, &count)) {
- return false;
- }
-
- string_to_keybind(keystring, kb);
-
- /* remove possible existing kbing */
- keybind_remove(kb);
-
- /* add the kbing to the list */
- keys = g_slist_prepend(keys, kb);
-
- /* save the modkey also in the modkey string if not exists already */
- if (kb->modkey && strchr(modkeys->str, kb->modkey) == NULL) {
- g_string_append_c(modkeys, kb->modkey);
- }
-
- return true;
-}
-
-gboolean keybind_remove_from_string(char *str, const Mode mode)
-{
- Keybind keybind = {.mode = mode};
-
- if (str == NULL || *str == '\0') {
- return false;
- }
- g_strstrip(str);
-
- /* fill the keybind with data from given string */
- string_to_keybind(str, &keybind);
-
- keybind_remove(&keybind);
-
- return true;
-}
-
-static void keybind_remove(Keybind *kb)
-{
- GSList *link = find(kb->mode, kb->modkey, kb->modmask, kb->keyval);
- if (link) {
- free_keybind((Keybind*)link->data);
- keys = g_slist_delete_link(keys, link);
- if (kb->modkey && strchr(modkeys->str, kb->modkey)) {
- /* remove eventually no more used modkeys */
- rebuild_modkeys();
- }
- }
-}
-
-/**
- * Discard all knows modkeys and walk through all keybindings to aggregate
- * them new.
- */
-static void rebuild_modkeys(void)
-{
- GSList *link;
- /* remove previous modkeys */
- if (modkeys) {
- g_string_free(modkeys, true);
- modkeys = g_string_new("");
- }
-
- /* regenerate the modekeys */
- for (link = keys; link != NULL; link = link->next) {
- Keybind *keybind = (Keybind*)link->data;
- /* if not not exists - add it */
- if (keybind->modkey && strchr(modkeys->str, keybind->modkey) == NULL) {
- g_string_append_c(modkeys, keybind->modkey);
- }
- }
-}
-
-static GSList *find(int mode, guint modkey, guint modmask, guint keyval)
-{
- GSList *link;
- for (link = keys; link != NULL; link = link->next) {
- Keybind *keybind = (Keybind*)link->data;
- if (keybind->keyval == keyval
- && keybind->modmask == modmask
- && keybind->modkey == modkey
- && keybind->mode == mode
- ) {
- return link;
- }
- }
-
- return NULL;
-}
-
-/**
- * Configures the given keybind by also given string.
- */
-static void string_to_keybind(char *str, Keybind *keybind)
-{
- char **string = NULL;
- guint len = 0;
-
- g_strstrip(str);
-
- /* [modkey]keyval
- * - modkey is a single char
- * - keval can be a single char, <tab> , <ctrl-tab>, <shift-a> */
- keybind->modkey = 0;
- if (strlen(str) == 1) {
- /* only a single simple key set like "q" */
- keybind->keyval = str[0];
- } else if (strlen(str) == 2) {
- /* modkey with simple key given like "gf" */
- keybind->modkey = str[0];
- keybind->keyval = str[1];
- } else {
- /* keybind has keys like "<tab>" or <ctrl-a> */
- if (str[0] == '<') {
- /* no modkey set */
- string = g_strsplit_set(str, "<->", 4);
- } else {
- keybind->modkey = str[0];
- string = g_strsplit_set(str + 1, "<->", 4);
- }
- len = g_strv_length(string);
- if (len == 4) {
- /* key with modmask like <ctrl-tab> */
- keybind->modmask = string_to_modmask(string[1]);
- keybind->keyval = string_to_value(string[2]);
- } else if (len == 3) {
- /* key without modmask like <tab> */
- keybind->keyval = string_to_value(string[1]);
- }
- g_strfreev(string);
- }
-
- /* special handling for shift tab */
- if (keybind->keyval == GDK_Tab && keybind->modmask == GDK_SHIFT_MASK) {
- /* remove the none needed shoft mask */
- keybind->modmask &= ~GDK_SHIFT_MASK;
- keybind->keyval = GDK_ISO_Left_Tab;
- }
-}
-
-static guint string_to_modmask(const char *str)
-{
- if (!strncmp(str, "ctrl", 4)) {
- return GDK_CONTROL_MASK;
- }
- if (!strncmp(str, "shift", 5)) {
- return GDK_SHIFT_MASK;
- }
-
- return 0;
-}
-
-static guint string_to_value(const char *str)
-{
- if (!strncmp(str, "tab", 3)) {
- return GDK_Tab;
- } else if (!strncmp(str, "up", 2)) {
- return GDK_Up;
- } else if (!strncmp(str, "down", 4)) {
- return GDK_Down;
- } else if (!strncmp(str, "left", 4)) {
- return GDK_Left;
- } else if (!strncmp(str, "right", 5)) {
- return GDK_Right;
- }
-
- return str[0];
-}
-
-static gboolean keypress_cb(GtkWidget *widget, GdkEventKey *event, gpointer is_input)
-{
- guint keyval, state;
- static GdkKeymap *keymap;
- GdkModifierType irrelevant;
-
- keymap = gdk_keymap_get_default();
- gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
- event->state, event->group, &keyval, NULL, NULL, &irrelevant);
-
- /* remove modifiers that doesn't matter for the current event */
- state = CLEAN_STATE_WITH_SHIFT(event) & ~irrelevant;
-
- /* check for escape or modkeys or counts */
- if (IS_ESCAPE_KEY(keyval, state)) {
- vb.state.modkey = vb.state.count = 0;
-
- /* remove focus from possible focused inputbox - this allows to clear
- * the inputbox also if esc is pressed from inputbox for example after
- * yanking some text or the result of the :shecllcmd */
- gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
-
- /* switch to normal mode and clear the command line */
- vb_set_mode(VB_MODE_NORMAL, true);
-
- return true;
- } else if (GPOINTER_TO_INT(is_input) && keyval == GDK_Return && !(vb.state.mode & VB_MODE_HINTING)) {
- /* simulate the gtk entries activate callback */
- vb_input_activate();
- return true;
- }
-
- /* skip further logic if we are in pass through mode */
- if (vb.state.mode & VB_MODE_PASSTHROUGH) {
- return false;
- }
-
- /* allow mode keys and counts only in normal mode and search mode */
- if (vb.state.mode & (VB_MODE_NORMAL|VB_MODE_SEARCH)) {
- if (vb.state.modkey == 0 && ((keyval >= GDK_1 && keyval <= GDK_9)
- || (keyval == GDK_0 && vb.state.count))) {
- /* append the new entered count to previous one */
- vb.state.count = (vb.state.count ? vb.state.count * 10 : 0) + (keyval - GDK_0);
- vb_update_statusbar();
-
- return true;
- }
- /* modekeys don't have ctrl or mod modiefiers set */
- if (!CLEAN_STATE(event)
- && strchr(modkeys->str, keyval) && vb.state.modkey != keyval
- ) {
- vb.state.modkey = (char)keyval;
- vb_update_statusbar();
-
- return true;
- }
- }
-
- /* check for keybinding */
- GSList *link = find(CLEAN_MODE(vb.state.mode), vb.state.modkey, state, keyval);
-
- if (link) {
- Keybind *keybind = (Keybind*)link->data;
- keybind->func(&keybind->arg);
-
- vb.state.modkey = vb.state.count = 0;
- vb_update_statusbar();
-
- return true;
- }
-
- return false;
-}
-
-static void free_keybind(Keybind *keybind)
-{
- g_free(keybind->arg.s);
- g_free(keybind);
-}
+++ /dev/null
-/**
- * vimb - a webkit based vim like browser.
- *
- * Copyright (C) 2012-2013 Daniel Carl
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see http://www.gnu.org/licenses/.
- */
-
-#ifndef _KEYBIND_H
-#define _KEYBIND_H
-
-#include "command.h"
-#include <gdk/gdkkeysyms.h>
-#include <gdk/gdkkeysyms-compat.h>
-
-void keybind_init(void);
-void keybind_cleanup(void);
-gboolean keybind_add_from_string(char *keys, const char *command, const Mode mode);
-gboolean keybind_remove_from_string(char *str, const Mode mode);
-
-#endif /* end of include guard: _KEYBIND_H */
#include "main.h"
#include "util.h"
#include "command.h"
-#include "keybind.h"
#include "setting.h"
#include "completion.h"
#include "dom.h"
#include "shortcut.h"
#include "history.h"
#include "session.h"
+#include "mode.h"
+#include "normal.h"
+#include "ex.h"
+#include "input.h"
+#include "map.h"
#include "default.h"
+#include "pass.h"
+#include "bookmark.h"
/* variables */
static char **args;
VbCore vb;
/* callbacks */
-static void input_changed_cb(GtkTextBuffer *buffer);
static void webview_progress_cb(WebKitWebView *view, GParamSpec *pspec);
static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec);
static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec);
*/
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);
}
/**
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)
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;
}
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)) {
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)
webkit_web_view_stop_loading(vb.gui.webview);
- command_cleanup();
+ map_cleanup();
+ mode_cleanup();
setting_cleanup();
- keybind_cleanup();
shortcut_cleanup();
history_cleanup();
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;
#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));
#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();
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);
* 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();
/* 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]);
}
}
if (!g_ascii_isalpha(line[0])) {
continue;
}
- if (!command_run_string(line)) {
+ if (!ex_run_string(line)) {
fprintf(stderr, "Invalid config: %s\n", line);
}
}
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
);
{
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};
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);
#define LENGTH(x) (sizeof x / sizeof x[0])
+/* this macro converts a non-'g' ascii command into a 'g' command by setting
+ * the 8th bit for the char */
+/* TODO maybe these macros are only used in keybind.c or mode.c */
+#define G_CMD(x) ((x) | 0x80)
+#define UNG_CMD(x) ((x) & ~0x80)
+#define CTRL(x) ((x) ^ 0x40)
+/* check if the char x is a char with CTRL like ^C */
+#define IS_CTRL(x) (((unsigned char)x) <= 32)
+
#ifdef DEBUG
#define PRINT_DEBUG(...) { \
fprintf(stderr, "\n\033[31;1mDEBUG:\033[0m %s:%d:%s()\t", __FILE__, __LINE__, __func__); \
#endif
/* enums */
+/* TODO remove this when the modes are implemented in own files */
typedef enum _vb_mode {
/* main modes */
VB_MODE_NORMAL = 1<<0,
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,
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,
char *s;
} Arg;
+typedef void (*ModeTransitionFunc) (void);
+typedef VbResult (*ModeKeyFunc) (unsigned int);
+typedef void (*ModeInputChangedFunc) (const char*);
+typedef struct {
+ char id;
+ ModeTransitionFunc enter; /* is called if the mode is entered */
+ ModeTransitionFunc leave; /* is called if the mode is left */
+ ModeKeyFunc keypress; /* receives key to process */
+ ModeInputChangedFunc input_changed; /* is triggered if input textbuffer is changed */
+#define FLAG_NOMAP 0x0001 /* disables mapping for key strokes */
+#define FLAG_HINTING 0x0002 /* marks active hinting submode */
+#define FLAG_COMPLETION 0x0004 /* marks active completion submode */
+#define FLAG_PASSTHROUGH 0x0008 /* don't handle any other keybind than <esc> */
+ unsigned int flags;
+} Mode;
+
/* statusbar */
typedef struct {
GtkBox *box;
GtkWidget *left;
GtkWidget *right;
+ GtkWidget *cmd;
} StatusBar;
/* gui */
GtkBox *box;
GtkWidget *eventbox;
GtkWidget *input;
+ GtkTextBuffer *buffer; /* text buffer associated with the input for fast access */
GtkWidget *pane;
StatusBar statusbar;
GtkScrollbar *sb_h;
/* state */
typedef struct {
- Mode mode;
+ VimbMode mode;
char modkey;
guint count;
guint progress;
MessageType input_type;
gboolean is_inspecting;
GList *downloads;
+ gboolean processed_key;
} State;
typedef struct {
State state;
char *files[FILES_LAST];
+ Mode *mode;
Config config;
Style style;
SoupSession *session;
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);
--- /dev/null
+/**
+ * Copyright (C) 2012-2013 Daniel Carl
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkkeysyms-compat.h>
+#include "config.h"
+#include "main.h"
+#include "map.h"
+#include "mode.h"
+
+extern VbCore vb;
+
+typedef struct {
+ char *in; /* input keys */
+ int inlen; /* length of the input keys */
+ char *mapped; /* mapped keys */
+ int mappedlen; /* length of the mapped keys string */
+ char mode; /* mode for which the map is available */
+} Map;
+
+static struct {
+ GSList *list;
+ char queue[MAP_QUEUE_SIZE]; /* queue holding typed keys */
+ int qlen; /* pointer to last char in queue */
+ int resolved; /* number of resolved keys (no mapping required) */
+ char showbuf[12]; /* buffer used to show ambiguous keys to the user */
+ int slen; /* pointer to last char in showbuf */
+ guint timout_id; /* source id of the timeout function */
+} map;
+
+static char *map_convert_keys(char *in, int inlen, int *len);
+static char *map_convert_keylabel(char *in, int inlen, int *len);
+static gboolean map_timeout(gpointer data);
+static void showcmd(char *keys, int keylen, gboolean append);
+static void free_map(Map *map);
+
+
+void map_cleanup(void)
+{
+ if (map.list) {
+ g_slist_free_full(map.list, (GDestroyNotify)free_map);
+ }
+}
+
+/**
+ * Handle all key events, convert the key event into the internal used ASCII
+ * representation and put this into the key queue to be mapped.
+ */
+gboolean map_keypress(GtkWidget *widget, GdkEventKey* event, gpointer data)
+{
+ unsigned int key = 0;
+ if ((event->state & GDK_CONTROL_MASK) == 0
+ && event->keyval > 0
+ && event->keyval < 0xff
+ ) {
+ key = event->keyval;
+ } else {
+ /* convert chars A-Za-z with ctrl flag <ctrl-a> or <ctrl-A> -> \001
+ * and <ctrl-z> -> \032 like vi */
+ if (event->keyval == GDK_Escape) {
+ key = CTRL('[');
+ } else if (event->keyval == GDK_Tab) {
+ key = CTRL('I');
+ } else if (event->keyval == GDK_ISO_Left_Tab) {
+ key = CTRL('O');
+ } else if (event->keyval == GDK_Return) {
+ key = '\n';
+ } else if (event->keyval == GDK_BackSpace) {
+ /* FIXME how to handle <C-S-Del> to remove selected Numbers in
+ * hint mode */
+ key = CTRL('H'); /* 0x08 */
+ } else if (event->keyval == GDK_Up) {
+ key = CTRL('P'); /* or ^Ok instead? */
+ } else if (event->keyval == GDK_Down) {
+ key = CTRL('N');
+ } else if (event->keyval >= 0x41 && event->keyval <= 0x5d) {/* chars A-] */
+ key = event->keyval - 0x40;
+ } else if (event->keyval >= 0x61 && event->keyval <= 0x7a) {/* chars a-z */
+ key = event->keyval - 0x60;
+ }
+ }
+
+ /* set initial value for the flag that should be changed in the modes key
+ * handler functions */
+ vb.state.processed_key = false;
+ if (key) {
+ map_handle_keys((char*)(&key), 1);
+ }
+ return vb.state.processed_key;
+}
+
+
+/**
+ * Added the given key sequence ot the key queue and precesses the mapping of
+ * chars. The key sequence do not need to be NUL terminated.
+ * Keylen of 0 signalized a key timeout.
+ */
+MapState map_handle_keys(const char *keys, int keylen)
+{
+ int ambiguous;
+ Map *match = NULL;
+ gboolean timeout = (keylen == 0); /* keylen 0 signalized timeout */
+
+ /* don't set the timeout function if a timeout is handled */
+ if (!timeout) {
+ /* if a previous timeout function was set remove this to start the
+ * timeout new */
+ if (map.timout_id) {
+ g_source_remove(map.timout_id);
+ }
+ map.timout_id = g_timeout_add(1000, (GSourceFunc)map_timeout, NULL);
+ }
+
+ /* copy the keys onto the end of queue */
+ while (map.qlen < LENGTH(map.queue) && keylen > 0) {
+ map.queue[map.qlen++] = *keys++;
+ keylen--;
+ }
+
+ /* try to resolve keys against the map */
+ while (true) {
+ /* send any resolved key to the parser */
+ while (map.resolved > 0) {
+ /* pop the next char from queue */
+ map.resolved--;
+ map.qlen--;
+
+ /* get first char of queue */
+ char qk = map.queue[0];
+ /* move all other queue entries one step to the left */
+ for (int i = 0; i < map.qlen; i++) {
+ map.queue[i] = map.queue[i + 1];
+ }
+
+ /* remove the nomap flag */
+ vb.mode->flags &= ~FLAG_NOMAP;
+
+ /* send the key to the parser */
+ if (RESULT_MORE != mode_handle_key((unsigned int)qk)) {
+ showcmd(NULL, 0, false);
+ }
+ }
+
+ /* if all keys where processed return MAP_DONE */
+ if (map.qlen == 0) {
+ map.resolved = 0;
+ return match ? MAP_DONE : MAP_NOMATCH;
+ }
+
+ /* try to find matching maps */
+ match = NULL;
+ ambiguous = 0;
+ if (!(vb.mode->flags & FLAG_NOMAP)) {
+ for (GSList *l = map.list; l != NULL; l = l->next) {
+ Map *m = (Map*)l->data;
+ /* ignore maps for other modes */
+ if (m->mode != vb.mode->id) {
+ continue;
+ }
+
+ /* find ambiguous matches */
+ if (!timeout && m->inlen > map.qlen && !strncmp(m->in, map.queue, map.qlen)) {
+ ambiguous++;
+ showcmd(map.queue, map.qlen, false);
+ }
+ /* complete match or better/longer match than previous found */
+ if (m->inlen <= map.qlen
+ && !strncmp(m->in, map.queue, m->inlen)
+ && (!match || match->inlen < m->inlen)
+ ) {
+ /* backup this found possible match */
+ match = m;
+ }
+ }
+ }
+
+ /* if there are ambiguous matches return MAP_KEY and flush queue
+ * after a timeout if the user do not type more keys */
+ if (ambiguous) {
+ return MAP_AMBIGUOUS;
+ }
+
+ /* replace the matched chars from queue by the cooked string that
+ * is the result of the mapping */
+ if (match) {
+ int i, j;
+ if (match->inlen < match->mappedlen) {
+ /* make some space within the queue */
+ for (i = map.qlen + match->mappedlen - match->inlen, j = map.qlen; j > match->inlen; ) {
+ map.queue[--i] = map.queue[--j];
+ }
+ } else if (match->inlen > match->mappedlen) {
+ /* delete some keys */
+ for (i = match->mappedlen, j = match->inlen; i < map.qlen; ) {
+ map.queue[i++] = map.queue[j++];
+ }
+ }
+
+ /* copy the mapped string into the queue */
+ strncpy(map.queue, match->mapped, match->mappedlen);
+ map.qlen += match->mappedlen - match->inlen;
+ if (match->inlen <= match->mappedlen) {
+ map.resolved = match->inlen;
+ } else {
+ map.resolved = match->mappedlen;
+ }
+ } else {
+ /* first char is not mapped but resolved */
+ map.resolved = 1;
+ showcmd(map.queue, map.resolved, true);
+ }
+ }
+
+ /* should never be reached */
+ return MAP_DONE;
+}
+
+void map_insert(char *in, char *mapped, char mode)
+{
+ int inlen, mappedlen;
+ char *lhs = map_convert_keys(in, strlen(in), &inlen);
+ char *rhs = map_convert_keys(mapped, strlen(mapped), &mappedlen);
+
+ /* TODO replace keysymbols in 'in' and 'mapped' string */
+ Map *new = g_new(Map, 1);
+ new->in = lhs;
+ new->inlen = inlen;
+ new->mapped = rhs;
+ new->mappedlen = mappedlen;
+ new->mode = mode;
+
+ map.list = g_slist_prepend(map.list, new);
+}
+
+gboolean map_delete(char *in, char mode)
+{
+ int len;
+ char *lhs = map_convert_keys(in, strlen(in), &len);
+
+ for (GSList *l = map.list; l != NULL; l = l->next) {
+ Map *m = (Map*)l->data;
+
+ /* remove only if the map's lhs matches the given key sequence */
+ if (m->mode == mode && m->inlen == len && !strcmp(m->in, lhs)) {
+ /* remove the found list item */
+ map.list = g_slist_delete_link(map.list, l);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Converts a keysequence into a internal raw keysequence.
+ * Returned keyseqence must be freed if not used anymore.
+ */
+static char *map_convert_keys(char *in, int inlen, int *len)
+{
+ int symlen, rawlen;
+ char *p, *dest, *raw;
+ char ch[1];
+ GString *str = g_string_new("");
+
+ *len = 0;
+ for (p = in; p < &in[inlen]; p++) {
+ /* if it starts not with < we can add it literally */
+ if (*p != '<') {
+ g_string_append_len(str, p, 1);
+ *len += 1;
+ continue;
+ }
+
+ /* search matching > of symbolic name */
+ symlen = 1;
+ do {
+ if (&p[symlen] == &in[inlen]
+ || p[symlen] == '<'
+ || p[symlen] == ' '
+ ) {
+ break;
+ }
+ } while (p[symlen++] != '>');
+
+ raw = NULL;
+ rawlen = 0;
+ /* check if we found a real keylabel */
+ if (p[symlen - 1] == '>') {
+ if (symlen == 5 && p[2] == '-') {
+ /* is it a <C-X> */
+ if (p[1] == 'C') {
+ /* TODO add macro to check if the char is a upper or lower
+ * with these ranges */
+ if (p[3] >= 0x41 && p[3] <= 0x5d) {
+ ch[0] = p[3] - 0x40;
+ raw = ch;
+ rawlen = 1;
+ } else if (p[3] >= 0x61 && p[3] <= 0x7a) {
+ ch[0] = p[3] - 0x60;
+ raw = ch;
+ rawlen = 1;
+ }
+ }
+ }
+
+ if (!raw) {
+ raw = map_convert_keylabel(p, symlen, &rawlen);
+ }
+ }
+
+ /* we found no known keylabel - so use the chars literally */
+ if (!rawlen) {
+ rawlen = symlen;
+ raw = p;
+ }
+
+ /* write the converted keylabel into the buffer */
+ g_string_append_len(str, raw, rawlen);
+
+ /* move p after the keylabel */
+ p += symlen - 1;
+
+ *len += rawlen;
+ }
+ dest = str->str;
+
+ /* don't free the character data of the GString */
+ g_string_free(str, false);
+
+ return dest;
+}
+
+/**
+ * Translate given key string into a internal representation <cr> -> \n.
+ * The len of the translated key sequence is put into given *len pointer.
+ */
+static char *map_convert_keylabel(char *in, int inlen, int *len)
+{
+ static struct {
+ char *label;
+ int len;
+ char *ch;
+ int chlen;
+ } keys[] = {
+ {"<CR>", 4, "\n", 1},
+ {"<Tab>", 5, "\t", 1},
+ {"<Esc>", 5, "\x1b", 1}
+ };
+
+ for (int i = 0; i < LENGTH(keys); i++) {
+ if (inlen == keys[i].len && !strncmp(keys[i].label, in, inlen)) {
+ *len = keys[i].chlen;
+ return keys[i].ch;
+ }
+ }
+ *len = 0;
+
+ return NULL;
+}
+
+/**
+ * Timeout function to signalize a key timeout to the map.
+ */
+static gboolean map_timeout(gpointer data)
+{
+ /* signalize the timeout to the key handler */
+ map_handle_keys("", 0);
+
+ /* call only once */
+ return false;
+}
+
+/**
+ * Add given keys to the show command queue to show them to the user.
+ * If the keylen of 0 is given, the show command queue is cleared.
+ */
+static void showcmd(char *keys, int keylen, gboolean append)
+{
+ /* make sure we keep in buffer range also for ^X chars */
+ int max = LENGTH(map.showbuf) - 2;
+
+ if (!append) {
+ map.slen = 0;
+ }
+
+ /* truncate the buffer */
+ if (!keylen) {
+ map.showbuf[(map.slen = 0)] = '\0';
+ } else {
+ /* TODO if not all keys would fit into the buffer use the last significat
+ * chars instead */
+ while (keylen-- && map.slen < max) {
+ char key = *keys++;
+ if (IS_CTRL(key)) {
+ map.showbuf[map.slen++] = '^';
+ map.showbuf[map.slen++] = CTRL(key);
+ } else {
+ map.showbuf[map.slen++] = key;
+ }
+ }
+ map.showbuf[map.slen] = '\0';
+ }
+
+ /* show the typed keys */
+ gtk_label_set_text(GTK_LABEL(vb.gui.statusbar.cmd), map.showbuf);
+}
+
+static void free_map(Map *map)
+{
+ g_free(map->in);
+ g_free(map->mapped);
+ g_free(map);
+}
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2013 Daniel Carl
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+
+#include <gdk/gdkkeysyms.h>
+#include <ctype.h>
+#include "config.h"
+#include "mode.h"
+#include "main.h"
+#include "normal.h"
+#include "command.h"
+#include "hints.h"
+#include "dom.h"
+#include "history.h"
+#include "util.h"
+
+typedef enum {
+ PHASE_START,
+ PHASE_AFTERG,
+ PHASE_KEY2,
+ PHASE_COMPLETE,
+} Phase;
+
+struct NormalCmdInfo_s {
+ int count; /* count used for the command */
+ unsigned char cmd; /* command key */
+ unsigned char arg; /* argument key if this is used */
+ Phase phase; /* current parsing phase */
+} info = {0, '\0', '\0', PHASE_START};
+
+typedef VbResult (*NormalCommand)(const NormalCmdInfo *info);
+
+static VbResult normal_clear_input(const NormalCmdInfo *info);
+static VbResult normal_descent(const NormalCmdInfo *info);
+static VbResult normal_ex(const NormalCmdInfo *info);
+static VbResult normal_focus_input(const NormalCmdInfo *info);
+static VbResult normal_hint(const NormalCmdInfo *info);
+static VbResult normal_navigate(const NormalCmdInfo *info);
+static VbResult normal_open_clipboard(const NormalCmdInfo *info);
+static VbResult normal_open(const NormalCmdInfo *info);
+static VbResult normal_pass(const NormalCmdInfo *info);
+static VbResult normal_queue(const NormalCmdInfo *info);
+static VbResult normal_quit(const NormalCmdInfo *info);
+static VbResult normal_scroll(const NormalCmdInfo *info);
+static VbResult normal_search(const NormalCmdInfo *info);
+static VbResult normal_search_selection(const NormalCmdInfo *info);
+static VbResult normal_view_inspector(const NormalCmdInfo *info);
+static VbResult normal_view_source(const NormalCmdInfo *info);
+static VbResult normal_yank(const NormalCmdInfo *info);
+static VbResult normal_zoom(const NormalCmdInfo *info);
+
+static struct {
+ NormalCommand func;
+} commands[] = {
+/* NUL */ {NULL},
+/* ^A */ {NULL},
+/* ^B */ {normal_scroll},
+/* ^C */ {NULL},
+/* ^D */ {normal_scroll},
+/* ^E */ {NULL},
+/* ^F */ {normal_scroll},
+/* ^G */ {NULL},
+/* ^H */ {NULL},
+/* ^I */ {normal_navigate},
+/* ^J */ {NULL},
+/* ^K */ {NULL},
+/* ^L */ {NULL},
+/* ^M */ {NULL},
+/* ^N */ {NULL},
+/* ^O */ {normal_navigate},
+/* ^P */ {normal_queue},
+/* ^Q */ {normal_quit},
+/* ^R */ {NULL},
+/* ^S */ {NULL},
+/* ^T */ {NULL},
+/* ^U */ {normal_scroll},
+/* ^V */ {NULL},
+/* ^W */ {NULL},
+/* ^X */ {NULL},
+/* ^Y */ {NULL},
+/* ^Z */ {normal_pass},
+/* ^[ */ {normal_clear_input},
+/* ^\ */ {NULL},
+/* ^] */ {NULL},
+/* ^^ */ {NULL},
+/* ^_ */ {NULL},
+/* SPC */ {NULL},
+/* ! */ {NULL},
+/* " */ {NULL},
+/* # */ {normal_search_selection},
+/* $ */ {normal_scroll},
+/* % */ {NULL},
+/* & */ {NULL},
+/* ' */ {NULL},
+/* ( */ {NULL},
+/* ) */ {NULL},
+/* * */ {normal_search_selection},
+/* + */ {NULL},
+/* , */ {NULL},
+/* - */ {NULL},
+/* . */ {NULL},
+/* / */ {normal_ex},
+/* 0 */ {normal_scroll},
+/* 1 */ {NULL},
+/* 2 */ {NULL},
+/* 3 */ {NULL},
+/* 4 */ {NULL},
+/* 5 */ {NULL},
+/* 6 */ {NULL},
+/* 7 */ {NULL},
+/* 8 */ {NULL},
+/* 9 */ {NULL},
+/* : */ {normal_ex},
+/* ; */ {normal_hint},
+/* < */ {NULL},
+/* = */ {NULL},
+/* > */ {NULL},
+/* ? */ {normal_ex},
+/* @ */ {NULL},
+/* A */ {NULL},
+/* B */ {NULL},
+/* C */ {NULL},
+/* D */ {NULL},
+/* E */ {NULL},
+/* F */ {normal_ex},
+/* G */ {normal_scroll},
+/* H */ {NULL},
+/* I */ {NULL},
+/* J */ {NULL},
+/* K */ {NULL},
+/* L */ {NULL},
+/* M */ {NULL},
+/* N */ {normal_search},
+/* O */ {NULL},
+/* P */ {normal_open_clipboard},
+/* Q */ {NULL},
+/* R */ {normal_navigate},
+/* S */ {NULL},
+/* T */ {NULL},
+/* U */ {normal_open},
+/* V */ {NULL},
+/* W */ {NULL},
+/* X */ {NULL},
+/* Y */ {normal_yank},
+/* Z */ {NULL},
+/* [ */ {NULL},
+/* \ */ {NULL},
+/* ] */ {NULL},
+/* ^ */ {NULL},
+/* _ */ {NULL},
+/* ` */ {NULL},
+/* a */ {NULL},
+/* b */ {NULL},
+/* c */ {NULL},
+/* d */ {NULL},
+/* e */ {NULL},
+/* f */ {normal_ex},
+/* g */ {NULL},
+/* h */ {normal_scroll},
+/* i */ {NULL},
+/* j */ {normal_scroll},
+/* k */ {normal_scroll},
+/* l */ {normal_scroll},
+/* m */ {NULL},
+/* n */ {normal_search},
+/* o */ {normal_ex},
+/* p */ {normal_open_clipboard},
+/* q */ {NULL},
+/* r */ {normal_navigate},
+/* s */ {NULL},
+/* t */ {normal_ex},
+/* u */ {normal_open},
+/* v */ {NULL},
+/* w */ {NULL},
+/* x */ {NULL},
+/* y */ {normal_yank},
+/* z */ {normal_zoom}, /* arg chars are handled in normal_keypress */
+/* { */ {NULL},
+/* | */ {NULL},
+/* } */ {NULL},
+/* ~ */ {NULL},
+/* DEL */ {NULL},
+/***************/
+/* gNUL */ {NULL},
+/* g^A */ {NULL},
+/* g^B */ {NULL},
+/* g^C */ {NULL},
+/* g^D */ {NULL},
+/* g^E */ {NULL},
+/* g^F */ {NULL},
+/* g^G */ {NULL},
+/* g^H */ {NULL},
+/* g^I */ {NULL},
+/* g^J */ {NULL},
+/* g^K */ {NULL},
+/* g^L */ {NULL},
+/* g^M */ {NULL},
+/* g^N */ {NULL},
+/* g^O */ {NULL},
+/* g^P */ {NULL},
+/* g^Q */ {NULL},
+/* g^R */ {NULL},
+/* g^S */ {NULL},
+/* g^T */ {NULL},
+/* g^U */ {NULL},
+/* g^V */ {NULL},
+/* g^W */ {NULL},
+/* g^X */ {NULL},
+/* g^Y */ {NULL},
+/* g^Z */ {NULL},
+/* gESC */ {NULL},
+/* g^\ */ {NULL},
+/* g^] */ {NULL},
+/* g^^ */ {NULL},
+/* g^_ */ {NULL},
+/* gSPC */ {NULL},
+/* g! */ {NULL},
+/* g" */ {NULL},
+/* g# */ {NULL},
+/* g$ */ {NULL},
+/* g% */ {NULL},
+/* g& */ {NULL},
+/* g' */ {NULL},
+/* g( */ {NULL},
+/* g) */ {NULL},
+/* g* */ {NULL},
+/* g+ */ {NULL},
+/* g, */ {NULL},
+/* g- */ {NULL},
+/* g. */ {NULL},
+/* g/ */ {NULL},
+/* g0 */ {NULL},
+/* g1 */ {NULL},
+/* g2 */ {NULL},
+/* g3 */ {NULL},
+/* g4 */ {NULL},
+/* g5 */ {NULL},
+/* g6 */ {NULL},
+/* g7 */ {NULL},
+/* g8 */ {NULL},
+/* g9 */ {NULL},
+/* g: */ {NULL},
+/* g; */ {NULL},
+/* g< */ {NULL},
+/* g= */ {NULL},
+/* g> */ {NULL},
+/* g? */ {NULL},
+/* g@ */ {NULL},
+/* gA */ {NULL},
+/* gB */ {NULL},
+/* gC */ {NULL},
+/* gD */ {NULL},
+/* gE */ {NULL},
+/* gF */ {normal_view_inspector},
+/* gG */ {NULL},
+/* gH */ {normal_open},
+/* gI */ {NULL},
+/* gJ */ {NULL},
+/* gK */ {NULL},
+/* gL */ {NULL},
+/* gM */ {NULL},
+/* gN */ {NULL},
+/* gO */ {NULL},
+/* gP */ {NULL},
+/* gQ */ {NULL},
+/* gR */ {NULL},
+/* gS */ {NULL},
+/* gT */ {NULL},
+/* gU */ {normal_descent},
+/* gV */ {NULL},
+/* gW */ {NULL},
+/* gX */ {NULL},
+/* gY */ {NULL},
+/* gZ */ {NULL},
+/* g[ */ {NULL},
+/* g\ */ {NULL},
+/* g] */ {NULL},
+/* g^ */ {NULL},
+/* g_ */ {NULL},
+/* g` */ {NULL},
+/* ga */ {NULL},
+/* gb */ {NULL},
+/* gc */ {NULL},
+/* gd */ {NULL},
+/* ge */ {NULL},
+/* gf */ {normal_view_source},
+/* gg */ {normal_scroll},
+/* gh */ {normal_open},
+/* gi */ {normal_focus_input},
+/* gj */ {NULL},
+/* gk */ {NULL},
+/* gl */ {NULL},
+/* gm */ {NULL},
+/* gn */ {NULL},
+/* go */ {NULL},
+/* gp */ {NULL},
+/* gq */ {NULL},
+/* gr */ {NULL},
+/* gs */ {NULL},
+/* gt */ {NULL},
+/* gu */ {normal_descent},
+/* gv */ {NULL},
+/* gw */ {NULL},
+/* gx */ {NULL},
+/* gy */ {NULL},
+/* gz */ {NULL},
+/* g{ */ {NULL},
+/* g| */ {NULL},
+/* g} */ {NULL},
+/* g~ */ {NULL},
+/* gDEL */ {NULL}
+};
+
+extern VbCore vb;
+
+
+/**
+ * Function called when vimb enters the normal mode.
+ */
+void normal_enter(void)
+{
+ dom_clear_focus(vb.gui.webview);
+ gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
+ history_rewind();
+ hints_clear();
+}
+
+/**
+ * Called when the normal mode is left.
+ */
+void normal_leave(void)
+{
+ /* TODO clean those only if they where active */
+ command_search(&((Arg){COMMAND_SEARCH_OFF}));
+}
+
+/**
+ * Handles the keypress events from webview and inputbox.
+ */
+VbResult normal_keypress(unsigned int key)
+{
+ State *s = &vb.state;
+ s->processed_key = true;
+ VbResult res;
+ if (info.phase == PHASE_START && key == 'g') {
+ info.phase = PHASE_AFTERG;
+ vb.mode->flags |= FLAG_NOMAP;
+
+ return RESULT_MORE;
+ }
+
+ if (info.phase == PHASE_AFTERG) {
+ key = G_CMD(key);
+ info.phase = PHASE_START;
+ }
+
+ if (info.phase == PHASE_START && info.count == 0 && key == '0') {
+ info.cmd = key;
+ info.phase = PHASE_COMPLETE;
+ } else if (info.phase == PHASE_KEY2) {
+ info.arg = key;
+ info.phase = PHASE_COMPLETE;
+ } else if (info.phase == PHASE_START && isdigit(key)) {
+ info.count = info.count * 10 + key - '0';
+ } else if (strchr(";z", (char)key)) {
+ /* handle commands that needs additional char */
+ info.phase = PHASE_KEY2;
+ info.cmd = key;
+ vb.mode->flags |= FLAG_NOMAP;
+ } else {
+ info.cmd = key & 0xff;
+ info.phase = PHASE_COMPLETE;
+ }
+
+ if (info.phase == PHASE_COMPLETE) {
+ if (commands[info.cmd].func) {
+ res = commands[info.cmd].func(&info);
+ } else {
+ /* let gtk handle the keyevent if we have no command attached to
+ * it */
+ s->processed_key = false;
+ res = RESULT_COMPLETE;
+ }
+ } else {
+ res = RESULT_MORE;
+ }
+
+ if (res == RESULT_COMPLETE) {
+ /* unset the info */
+ info.cmd = info.arg = info.count = 0;
+ info.phase = PHASE_START;
+ }
+ return res;
+}
+
+/**
+ * Handles changes in the inputbox. If there are usergenerated changes, we
+ * switch to command mode.
+ */
+void normal_input_changed(const char *text)
+{
+ /*mode_enter('i');*/
+}
+
+static VbResult normal_clear_input(const NormalCmdInfo *info)
+{
+ vb_set_input_text("");
+ gtk_widget_grab_focus(GTK_WIDGET(vb.gui.webview));
+ /* TODO implement the search new - so we can remove the command.c file */
+ command_search(&((Arg){COMMAND_SEARCH_OFF}));
+ /* check flags on the current state to only clear hints if they where
+ * enabled before */
+ /*hints_clear();*/
+
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_descent(const NormalCmdInfo *info)
+{
+ int count = info->count ? info->count : 1;
+ const char *uri, *p = NULL, *domain = NULL;
+
+ uri = GET_URI();
+
+ /* get domain part */
+ if (!uri || *uri == '\0'
+ || !(domain = strstr(uri, "://"))
+ || !(domain = strchr(domain + 3, '/'))
+ ) {
+ return RESULT_ERROR;
+ }
+
+ switch (UNG_CMD(info->cmd)) {
+ case 'U':
+ p = domain;
+ break;
+
+ case 'u':
+ /* start at the end */
+ p = uri + strlen(uri);
+ /* if last char is / increment count to step over this first */
+ if (*(p - 1) == '/') {
+ count++;
+ }
+ for (int i = 0; i < count; i++) {
+ while (*(p--) != '/') {
+ if (p == uri) {
+ /* reach the beginning */
+ return RESULT_ERROR;
+ }
+ }
+ }
+ /* keep the last / in uri */
+ p++;
+ break;
+ }
+
+ /* if the url is shorter than the domain use the domain instead */
+ if (p < domain) {
+ p = domain;
+ }
+
+ Arg a = {VB_TARGET_CURRENT};
+ a.s = g_strndup(uri, p - uri + 1);
+
+ vb_load_uri(&a);
+ g_free(a.s);
+
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_ex(const NormalCmdInfo *info)
+{
+ char prompt[2] = {0};
+
+ /* avoid to print the char that called this function into the input box */
+ vb.state.processed_key = true;
+
+ /* Handle some hardwired mapping here instead of using map_insert. This
+ * makes the binding imutable and we can simply use f, F, o and t in
+ * mapped keys too */
+ switch (info->cmd) {
+ case 'o':
+ vb_set_input_text(":open ");
+ /* switch mode after setting the input text to not trigger the
+ * commands modes input change handler */
+ mode_enter('c');
+ break;
+
+ case 't':
+ vb_set_input_text(":tabopen ");
+ mode_enter('c');
+ break;
+
+ case 'f':
+ /* switch the mode first so that the input change handler of
+ * command mode will recognize the ; to switch to hinting submode */
+ mode_enter('c');
+ vb_set_input_text(";o");
+ break;
+
+ case 'F':
+ mode_enter('c');
+ vb_set_input_text(";t");
+ break;
+
+ default:
+ prompt[0] = info->cmd;
+ vb_set_input_text(prompt);
+ mode_enter('c');
+ }
+
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_focus_input(const NormalCmdInfo *info)
+{
+ if (dom_focus_input(vb.gui.webview)) {
+ mode_enter('i');
+
+ return RESULT_COMPLETE;
+ }
+ return RESULT_ERROR;
+}
+
+static VbResult normal_hint(const NormalCmdInfo *info)
+{
+ char prompt[3] = {info->cmd, info->arg, 0};
+#ifdef FEATURE_QUEUE
+ const char *allowed = "eiIoOpPstTy";
+#else
+ const char *allowed = "eiIoOstTy";
+#endif
+
+ /* check if this is a valid hint mode */
+ if (!info->arg || !strchr(allowed, info->arg)) {
+ vb.state.processed_key = true;
+
+ return RESULT_ERROR;
+ }
+
+ mode_enter('c');
+ vb_set_input_text(prompt);
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_navigate(const NormalCmdInfo *info)
+{
+ int count;
+
+ WebKitWebView *view = vb.gui.webview;
+ switch (info->cmd) {
+ case CTRL('I'): /* fall through */
+ case CTRL('O'):
+ count = info->count ? info->count : 1;
+ if (info->cmd == CTRL('O')) {
+ count *= -1;
+ }
+ webkit_web_view_go_back_or_forward(view, count);
+ break;
+
+ case 'r':
+ webkit_web_view_reload(view);
+ break;
+
+ case 'R':
+ webkit_web_view_reload_bypass_cache(view);
+ break;
+
+ case 'C': /* TODO shouldn't we use ^C instead? */
+ webkit_web_view_stop_loading(view);
+ break;
+ }
+
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_open_clipboard(const NormalCmdInfo *info)
+{
+ Arg a = {info->cmd == 'P' ? VB_TARGET_NEW : VB_TARGET_CURRENT};
+
+ a.s = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
+ if (!a.s) {
+ a.s = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD());
+ }
+
+ if (a.s) {
+ vb_load_uri(&a);
+ g_free(a.s);
+
+ return RESULT_COMPLETE;
+ }
+
+ return RESULT_ERROR;
+}
+
+static VbResult normal_open(const NormalCmdInfo *info)
+{
+ Arg a;
+ if (strchr("uU", info->cmd)) { /* open last closed */
+ a.i = info->cmd == 'U' ? VB_TARGET_NEW : VB_TARGET_CURRENT;
+ a.s = util_get_file_contents(vb.files[FILES_CLOSED], NULL);
+ vb_load_uri(&a);
+ g_free(a.s);
+ } else { /* open home page */
+ a.i = UNG_CMD(info->cmd) == 'H' ? VB_TARGET_NEW : VB_TARGET_CURRENT;
+ a.s = NULL;
+ vb_load_uri(&a);
+ }
+
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_pass(const NormalCmdInfo *info)
+{
+ mode_enter('p');
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_queue(const NormalCmdInfo *info)
+{
+ command_queue(&((Arg){COMMAND_QUEUE_POP}));
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_quit(const NormalCmdInfo *info)
+{
+ vb_quit();
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_scroll(const NormalCmdInfo *info)
+{
+ GtkAdjustment *adjust;
+ gdouble value, max, new;
+ int count = info->count ? info->count : 1;
+
+ /* TODO is this required? */
+ /*vb_set_mode(VB_MODE_NORMAL | (vb.state.mode & VB_MODE_SEARCH), false);*/
+
+ /* TODO split this into more functions - reduce similar code */
+ switch (info->cmd) {
+ case 'h':
+ adjust = vb.gui.adjust_h;
+ value = vb.config.scrollstep;
+ new = gtk_adjustment_get_value(adjust) - value * count;
+ break;
+ case 'j':
+ adjust = vb.gui.adjust_v;
+ value = vb.config.scrollstep;
+ new = gtk_adjustment_get_value(adjust) + value * count;
+ break;
+ case 'k':
+ adjust = vb.gui.adjust_v;
+ value = vb.config.scrollstep;
+ new = gtk_adjustment_get_value(adjust) - value * count;
+ break;
+ case 'l':
+ adjust = vb.gui.adjust_h;
+ value = vb.config.scrollstep;
+ new = gtk_adjustment_get_value(adjust) + value * count;
+ break;
+ case CTRL('D'):
+ adjust = vb.gui.adjust_v;
+ value = gtk_adjustment_get_page_size(adjust) / 2;
+ new = gtk_adjustment_get_value(adjust) + value * count;
+ break;
+ case CTRL('U'):
+ adjust = vb.gui.adjust_v;
+ value = gtk_adjustment_get_page_size(adjust) / 2;
+ new = gtk_adjustment_get_value(adjust) - value * count;
+ break;
+ case CTRL('F'):
+ adjust = vb.gui.adjust_v;
+ value = gtk_adjustment_get_page_size(adjust);
+ new = gtk_adjustment_get_value(adjust) + value * count;
+ break;
+ case CTRL('B'):
+ adjust = vb.gui.adjust_v;
+ value = gtk_adjustment_get_page_size(adjust);
+ new = gtk_adjustment_get_value(adjust) - value * count;
+ break;
+ case 'G':
+ adjust = vb.gui.adjust_v;
+ max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
+ new = info->count ? (max * info->count / 100) : gtk_adjustment_get_upper(adjust);
+ break;
+ case G_CMD('g'):
+ adjust = vb.gui.adjust_v;
+ max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
+ new = info->count ? (max * info->count / 100) : gtk_adjustment_get_lower(adjust);
+ break;
+ case '0':
+ adjust = vb.gui.adjust_h;
+ new = gtk_adjustment_get_lower(adjust);
+ break;
+ case '$':
+ adjust = vb.gui.adjust_h;
+ new = gtk_adjustment_get_upper(adjust);
+ break;
+
+ default:
+ return RESULT_ERROR;
+ }
+ max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
+ gtk_adjustment_set_value(adjust, new > max ? max : new);
+
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_search(const NormalCmdInfo *info)
+{
+ command_search(&((Arg){info->cmd == 'n' ? COMMAND_SEARCH_FORWARD : COMMAND_SEARCH_BACKWARD}));
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_search_selection(const NormalCmdInfo *info)
+{
+ /* there is no function to get the selected text so we copy current
+ * selection to clipboard */
+ webkit_web_view_copy_clipboard(vb.gui.webview);
+ char *query = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD());
+ if (!query) {
+ return RESULT_ERROR;
+ }
+
+ command_search(&((Arg){info->cmd == '*' ? COMMAND_SEARCH_FORWARD : COMMAND_SEARCH_BACKWARD, query}));
+ g_free(query);
+
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_view_inspector(const NormalCmdInfo *info)
+{
+ gboolean enabled;
+ WebKitWebSettings *settings = webkit_web_view_get_settings(vb.gui.webview);
+
+ g_object_get(G_OBJECT(settings), "enable-developer-extras", &enabled, NULL);
+ if (enabled) {
+ if (vb.state.is_inspecting) {
+ webkit_web_inspector_close(vb.gui.inspector);
+ } else {
+ webkit_web_inspector_show(vb.gui.inspector);
+ }
+ return RESULT_COMPLETE;
+ }
+
+ vb_echo(VB_MSG_ERROR, true, "webinspector is not enabled");
+ return RESULT_ERROR;
+}
+
+static VbResult normal_view_source(const NormalCmdInfo *info)
+{
+ gboolean mode = webkit_web_view_get_view_source_mode(vb.gui.webview);
+ webkit_web_view_set_view_source_mode(vb.gui.webview, !mode);
+ webkit_web_view_reload(vb.gui.webview);
+ return RESULT_COMPLETE;
+}
+
+static VbResult normal_yank(const NormalCmdInfo *info)
+{
+ Arg a = {info->cmd == 'Y' ? COMMAND_YANK_SELECTION : COMMAND_YANK_URI};
+
+ return command_yank(&a) ? RESULT_COMPLETE : RESULT_ERROR;
+}
+
+static VbResult normal_zoom(const NormalCmdInfo *info)
+{
+ float step, level, count;
+ WebKitWebSettings *setting;
+ WebKitWebView *view = vb.gui.webview;
+
+ count = info->count ? (float)info->count : 1.0;
+
+ if (info->arg == 'z') { /* zz reset zoom */
+ webkit_web_view_set_zoom_level(view, 1.0);
+
+ return RESULT_COMPLETE;
+ }
+
+ level = webkit_web_view_get_zoom_level(view);
+ setting = webkit_web_view_get_settings(view);
+ g_object_get(G_OBJECT(setting), "zoom-step", &step, NULL);
+
+ webkit_web_view_set_full_content_zoom(view, isupper(info->arg));
+
+ /* calculate the new zoom level */
+ if (info->arg == 'i' || info->arg == 'I') {
+ level += ((float)count * step);
+ } else {
+ level -= ((float)count * step);
+ }
+
+ /* apply the new zoom level */
+ webkit_web_view_set_zoom_level(view, level);
+
+ return RESULT_COMPLETE;
+}
--- /dev/null
+/**
+ * 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 */
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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 */