From: Daniel Carl Date: Sat, 21 Dec 2013 17:38:32 +0000 (+0100) Subject: Added extended hint modes g;X (#53). X-Git-Url: https://git.owens.tech/assets/favicon.png/assets/favicon.png/git?a=commitdiff_plain;h=c7d799acff0be0dee950056e41f6058cd31a46cb;p=vimb.git Added extended hint modes g;X (#53). These hint modes are taken from pentadactyl and works like the other hint modes, accept that the hints are not cleared after a hint was fired using a numeric filter. --- diff --git a/doc/vimb.1 b/doc/vimb.1 index 85c325a..2f03015 100644 --- a/doc/vimb.1 +++ b/doc/vimb.1 @@ -252,6 +252,13 @@ feature. .TP .B ;\-y Yank hint's destination location into primary and secondary clipboard. +.TP +.BI Syntax: " g;{mode}{hint}" +Start an extended hints mode and stay there until is pressed. Like the +normal hinting except that after a hint is selected, hints remain visible so +that another one can be selected with the same action as the first. Note that +the extended hint mode can only be combined with the following hint modes +\fI;I ;p ;P ;s ;t ;y\fP. .SS Searching .TP .BI / QUERY ", ?" QUERY diff --git a/src/command.c b/src/command.c index 958251c..9f98ff2 100644 --- a/src/command.c +++ b/src/command.c @@ -93,7 +93,7 @@ gboolean command_yank(const Arg *arg) text = gtk_clipboard_wait_for_text(SECONDARY_CLIPBOARD()); } if (text) { - vb_echo_force(VB_MSG_NORMAL, false, tmpl, text); + vb_echo(VB_MSG_NORMAL, false, tmpl, text); g_free(text); return true; @@ -112,7 +112,7 @@ gboolean command_yank(const Arg *arg) } if (a.s) { vb_set_clipboard(&a); - vb_echo_force(VB_MSG_NORMAL, false, tmpl, a.s); + vb_echo(VB_MSG_NORMAL, false, tmpl, a.s); return true; } diff --git a/src/ex.c b/src/ex.c index 126ae5d..8a9882b 100644 --- a/src/ex.c +++ b/src/ex.c @@ -306,6 +306,7 @@ void ex_input_changed(const char *text) GtkTextIter start, end; GtkTextBuffer *buffer = vb.gui.buffer; + /* don't add line breaks if content is pasted from clipboard into inputbox */ if (gtk_text_buffer_get_line_count(buffer) > 1) { /* remove everething from the buffer, except of the first line */ gtk_text_buffer_get_iter_at_line(buffer, &start, 0); @@ -316,7 +317,8 @@ void ex_input_changed(const char *text) } switch (*text) { - case ';': + case ';': /* fall through - the modes are handled by hints_create */ + case 'g': hints_create(text); break; @@ -371,14 +373,15 @@ static void input_activate(void) * does vim also skip history recording for such mapped commands */ cmd = text + 1; switch (*text) { - case '/': count = 1; /* fall throught */ + case '/': count = 1; /* fall through */ case '?': history_add(HISTORY_SEARCH, cmd, NULL); mode_enter('n'); command_search(&((Arg){count, cmd})); break; - case ';': + case ';': /* fall through */ + case 'g': hints_fire(); break; diff --git a/src/hints.c b/src/hints.c index 0600ef6..792cee7 100644 --- a/src/hints.c +++ b/src/hints.c @@ -34,8 +34,10 @@ #define HINT_FILE "hints.js" static struct { - guint num; - char prompt[3]; + guint num; /* olds the numeric filter for hints typed by the user */ + char mode; /* mode identifying char - that last char of the hint prompt */ + int promptlen; /* lenfth of the hint prompt chars 2 or 3 */ + gboolean gmode; /* indicate if the hints g mode is used */ } hints; extern VbCore vb; @@ -116,15 +118,9 @@ void hints_create(const char *input) { char *js = NULL; - /* 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; - - /* if there is no input or the input is no valid hint mode prefix, clear - * possible previous hint mode */ - if (!input || strlen(input) < 2) { - /* stop hint mode - we switch direct to normal mode that will clear - * the hinting and additional will remove focus from input box */ + /* check if the input contains a valid hinting prompt */ + if (!hints_parse_prompt(input, &hints.mode, &hints.gmode)) { + /* if input is not valid, clear possible previous hint mode */ if (vb.mode->flags & FLAG_HINTING) { mode_enter('n'); } @@ -134,10 +130,12 @@ void hints_create(const char *input) if (!(vb.mode->flags & FLAG_HINTING)) { vb.mode->flags |= FLAG_HINTING; - /* save the prefix of the hinting mode for later use */ - strncpy(hints.prompt, input, 2); + /* 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; + hints.promptlen = hints.gmode ? 3 : 2; - js = g_strdup_printf("%s.init('%s', %d);", HINT_VAR, hints.prompt, MAXIMUM_HINTS); + js = g_strdup_printf("%s.init('%c', %d);", HINT_VAR, hints.mode, MAXIMUM_HINTS); run_script(js); g_free(js); @@ -147,7 +145,7 @@ void hints_create(const char *input) return; } - js = g_strdup_printf("%s.filter('%s');", HINT_VAR, *(input + 2) ? input + 2 : ""); + js = g_strdup_printf("%s.filter('%s');", HINT_VAR, *(input + hints.promptlen) ? input + hints.promptlen : ""); run_script(js); g_free(js); } @@ -186,9 +184,68 @@ void hints_follow_link(const gboolean back, int count) g_free(js); } +/** + * Checks if the given hint prompt belong to a known and valid hints mode and + * parses the mode and is_gmode into given pointers. + * + * The given prompt sting may also contain additional chars after the prompt. + * + * @prompt: String to be parsed as prompt. The Prompt can be followed by + * additional characters. + * @mode: Pointer to char that will be filled with mode char if prompt was + * valid and given pointer is not NULL. + * @is_gmode: Pointer to gboolean to be filled with the flag to indicate gmode + * hinting. + */ +gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode) +{ + gboolean res; + char pmode = '\0'; +#ifdef FEATURE_QUEUE + static char *modes = "eiIoOpPstTy"; + static char *g_modes = "IpPsty"; +#else + static char *modes = "eiIoOstTy"; + static char *g_modes = "Isty"; +#endif + + if (!prompt) { + return false; + } + + /* get the mode identifying char from prompt */ + if (*prompt == ';') { + pmode = prompt[1]; + } else if (*prompt == 'g' && strlen(prompt) >= 3) { + /* get mode for g;X hint modes */ + pmode = prompt[2]; + } + + /* no mode found in prompt */ + if (!pmode) { + return false; + } + + res = *prompt == 'g' + ? strchr(g_modes, pmode) != NULL + : strchr(modes, pmode) != NULL; + + /* fill pointer only if the promt was valid */ + if (res) { + if (mode != NULL) { + *mode = pmode; + } + if (is_gmode != NULL) { + *is_gmode = *prompt == 'g'; + } + } + + return res; +} + static void run_script(char *js) { - char mode, *value = NULL; + char *value = NULL; gboolean success = vb_eval_script( webkit_web_view_get_main_frame(vb.gui.webview), js, HINT_FILE, &value @@ -202,39 +259,51 @@ static void run_script(char *js) 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)) { - mode_enter('n'); + if (hints.gmode) { + /* if g mode is used reset number filter and keep in hint mode */ + hints.num = 0; + hints_update(hints.num); + } else { + mode_enter('n'); + } } else if (!strncmp(value, "INSERT:", 7)) { mode_enter('i'); - if (mode == 'e') { + if (hints.mode == 'e') { input_open_editor(); } } else if (!strncmp(value, "DATA:", 5)) { - /* 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'); + if (hints.gmode) { + /* if g mode is used reset number filter and keep in hint mode */ + hints.num = 0; + hints_update(hints.num); + } else { + /* 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) { + switch (hints.mode) { /* used if images should be opened */ case 'i': case 'I': a.s = v; - a.i = (mode == 'I') ? VB_TARGET_NEW : VB_TARGET_CURRENT; + a.i = (hints.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'); + vb_echo(VB_MSG_NORMAL, false, "%s %s", (hints.mode == 'T') ? ":tabopen" : ":open", v); + if (!hints.gmode) { + mode_enter('c'); + } break; case 's': @@ -253,7 +322,7 @@ static void run_script(char *js) case 'p': case 'P': a.s = v; - a.i = (mode == 'P') ? COMMAND_QUEUE_UNSHIFT : COMMAND_QUEUE_PUSH; + a.i = (hints.mode == 'P') ? COMMAND_QUEUE_UNSHIFT : COMMAND_QUEUE_PUSH; command_queue(&a); break; #endif diff --git a/src/hints.h b/src/hints.h index 386e7ca..b02f009 100644 --- a/src/hints.h +++ b/src/hints.h @@ -28,6 +28,7 @@ void hints_create(const char *input); void hints_update(int num); void hints_fire(void); void hints_follow_link(const gboolean back, int count); +gboolean hints_parse_prompt(const char *prompt, char *mode, gboolean *is_gmode); void hints_clear(void); void hints_focus_next(const gboolean back); diff --git a/src/hints.js b/src/hints.js index 93e6f44..58b9b1e 100644 --- a/src/hints.js +++ b/src/hints.js @@ -331,8 +331,6 @@ var VbHint = (function(){ tag = e.nodeName.toLowerCase(), type = e.type || ""; - clear(); - if (tag === "input" || tag === "textarea" || tag === "select") { if (type === "radio" || type === "checkbox") { e.focus(); @@ -470,10 +468,8 @@ var VbHint = (function(){ /* the api */ return { - init: function init(prompt, maxHints) { + init: function init(mode, maxHints) { var prop, - /* get the last mode identifying char of prompt */ - c = prompt.slice(-1), /* holds the xpaths for the different modes */ xpathmap = { ot: "//*[@href] | //*[@onclick or @tabindex or @class='lk' or @role='link' or @role='button'] | //input[not(@type='hidden' or @disabled or @readonly)] | //textarea[not(@disabled or @readonly)] | //button | //select", @@ -490,13 +486,13 @@ var VbHint = (function(){ config = {maxHints: maxHints}; for (prop in xpathmap) { - if (prop.indexOf(c) >= 0) { + if (prop.indexOf(mode) >= 0) { config["xpath"] = xpathmap[prop]; break; } } for (prop in actionmap) { - if (prop.indexOf(c) >= 0) { + if (prop.indexOf(mode) >= 0) { config["action"] = actionmap[prop]; break; } @@ -513,7 +509,7 @@ var VbHint = (function(){ return show(); }, update: function update(n) { - filterNum = n > 0 ? n : 0; + filterNum = n; return show(); }, clear: clear, diff --git a/src/main.h b/src/main.h index 0cf1d77..5649ac9 100644 --- a/src/main.h +++ b/src/main.h @@ -270,8 +270,8 @@ typedef struct { GList *downloads; gboolean processed_key; char *title; /* holds the window title */ -#define PROMPT_SIZE 3 - char prompt[PROMPT_SIZE]; /* current prompt ':', ';o', '/' */ +#define PROMPT_SIZE 4 + char prompt[PROMPT_SIZE]; /* current prompt ':', 'g;t', '/' including nul */ } State; typedef struct { diff --git a/src/normal.c b/src/normal.c index 21ec070..25b8f94 100644 --- a/src/normal.c +++ b/src/normal.c @@ -36,13 +36,15 @@ typedef enum { PHASE_START, PHASE_KEY2, + PHASE_KEY3, PHASE_COMPLETE, } Phase; struct NormalCmdInfo_s { int count; /* count used for the command */ - char cmd; /* command key */ - char ncmd; /* second command key (optional) */ + char key; /* command key */ + char key2; /* second command key (optional) */ + char key3; /* third command key only for hinting */ Phase phase; /* current parsing phase */ } info = {0, '\0', '\0', PHASE_START}; @@ -56,6 +58,7 @@ static VbResult normal_ex(const NormalCmdInfo *info); static VbResult normal_focus_input(const NormalCmdInfo *info); static VbResult normal_g_cmd(const NormalCmdInfo *info); static VbResult normal_hint(const NormalCmdInfo *info); +static VbResult normal_do_hint(const char *prompt); static VbResult normal_input_open(const NormalCmdInfo *info); static VbResult normal_navigate(const NormalCmdInfo *info); static VbResult normal_open_clipboard(const NormalCmdInfo *info); @@ -238,31 +241,40 @@ VbResult normal_keypress(int key) VbResult res; if (info.phase == PHASE_START && info.count == 0 && key == '0') { - info.cmd = key; + info.key = key; info.phase = PHASE_COMPLETE; } else if (info.phase == PHASE_KEY2) { - info.ncmd = key; + info.key2 = key; + + /* hinting g; mode requires a third key */ + if (info.key == 'g' && info.key2 == ';') { + info.phase = PHASE_KEY3; + vb.mode->flags |= FLAG_NOMAP; + } else { + info.phase = PHASE_COMPLETE; + } + } else if (info.phase == PHASE_KEY3) { + info.key3 = key; info.phase = PHASE_COMPLETE; } else if (info.phase == PHASE_START && isdigit(key)) { info.count = info.count * 10 + key - '0'; } else if (strchr(";zg[]", (char)key)) { /* handle commands that needs additional char */ - info.phase = PHASE_KEY2; - info.cmd = key; + info.phase = PHASE_KEY2; + info.key = key; vb.mode->flags |= FLAG_NOMAP; } else { - info.cmd = key; + info.key = key; info.phase = PHASE_COMPLETE; } if (info.phase == PHASE_COMPLETE) { /* TODO allow more commands - some that are looked up via command key * direct and those that are searched via binary search */ - if ((guchar)info.cmd <= LENGTH(commands) && commands[(guchar)info.cmd].func) { - res = commands[(guchar)info.cmd].func(&info); + if ((guchar)info.key <= LENGTH(commands) && commands[(guchar)info.key].func) { + res = commands[(guchar)info.key].func(&info); } else { - /* let gtk handle the keyevent if we have no command attached to - * it */ + /* let gtk handle the keyevent if we have no command attached to it */ s->processed_key = false; res = RESULT_COMPLETE; } @@ -272,7 +284,7 @@ VbResult normal_keypress(int key) if (res == RESULT_COMPLETE) { /* unset the info */ - info.cmd = info.ncmd = info.count = 0; + info.key = info.key2 = info.key3 = info.count = 0; info.phase = PHASE_START; } else if (res == RESULT_MORE) { normal_showcmd(key); @@ -352,7 +364,7 @@ static VbResult normal_descent(const NormalCmdInfo *info) return RESULT_ERROR; } - switch (info->ncmd) { + switch (info->key2) { case 'U': p = domain; break; @@ -393,12 +405,12 @@ static VbResult normal_descent(const NormalCmdInfo *info) static VbResult normal_ex(const NormalCmdInfo *info) { - if (info->cmd == 'F') { + if (info->key == 'F') { mode_enter_promt('c', ";t", true); - } else if (info->cmd == 'f') { + } else if (info->key == 'f') { mode_enter_promt('c', ";o", true); } else { - char prompt[2] = {info->cmd, '\0'}; + char prompt[2] = {info->key, '\0'}; mode_enter_promt('c', prompt, true); } @@ -418,7 +430,13 @@ static VbResult normal_focus_input(const NormalCmdInfo *info) static VbResult normal_g_cmd(const NormalCmdInfo *info) { Arg a; - switch (info->ncmd) { + switch (info->key2) { + case ';': { + const char prompt[4] = {'g', ';', info->key3, 0}; + + return normal_do_hint(prompt); + } + case 'F': return normal_view_inspector(info); @@ -430,7 +448,7 @@ static VbResult normal_g_cmd(const NormalCmdInfo *info) case 'H': case 'h': - a.i = info->ncmd == 'H' ? VB_TARGET_NEW : VB_TARGET_CURRENT; + a.i = info->key2 == 'H' ? VB_TARGET_NEW : VB_TARGET_CURRENT; a.s = NULL; vb_load_uri(&a); return RESULT_COMPLETE; @@ -448,15 +466,15 @@ static VbResult normal_g_cmd(const NormalCmdInfo *info) static VbResult normal_hint(const NormalCmdInfo *info) { - char prompt[3] = {info->cmd, info->ncmd, 0}; -#ifdef FEATURE_QUEUE - const char *allowed = "eiIoOpPstTy"; -#else - const char *allowed = "eiIoOstTy"; -#endif + const char prompt[3] = {info->key, info->key2, 0}; + + return normal_do_hint(prompt); +} +static VbResult normal_do_hint(const char *prompt) +{ /* check if this is a valid hint mode */ - if (!info->ncmd || !strchr(allowed, info->ncmd)) { + if (!hints_parse_prompt(prompt, NULL, NULL)) { return RESULT_ERROR; } @@ -466,10 +484,10 @@ static VbResult normal_hint(const NormalCmdInfo *info) static VbResult normal_input_open(const NormalCmdInfo *info) { - if (strchr("ot", info->cmd)) { - vb_set_input_text(info->cmd == 't' ? ":tabopen " : ":open "); + if (strchr("ot", info->key)) { + vb_set_input_text(info->key == 't' ? ":tabopen " : ":open "); } else { - vb_echo(VB_MSG_NORMAL, false, ":%s %s", info->cmd == 'T' ? "tabopen" : "open", GET_URI()); + vb_echo(VB_MSG_NORMAL, false, ":%s %s", info->key == 'T' ? "tabopen" : "open", GET_URI()); } /* switch mode after setting the input text to not trigger the * commands modes input change handler */ @@ -483,11 +501,11 @@ static VbResult normal_navigate(const NormalCmdInfo *info) int count; WebKitWebView *view = vb.gui.webview; - switch (info->cmd) { + switch (info->key) { case CTRL('I'): /* fall through */ case CTRL('O'): count = info->count ? info->count : 1; - if (info->cmd == CTRL('O')) { + if (info->key == CTRL('O')) { count *= -1; } webkit_web_view_go_back_or_forward(view, count); @@ -511,7 +529,7 @@ static VbResult normal_navigate(const NormalCmdInfo *info) static VbResult normal_open_clipboard(const NormalCmdInfo *info) { - Arg a = {info->cmd == 'P' ? VB_TARGET_NEW : VB_TARGET_CURRENT}; + Arg a = {info->key == 'P' ? VB_TARGET_NEW : VB_TARGET_CURRENT}; a.s = gtk_clipboard_wait_for_text(PRIMARY_CLIPBOARD()); if (!a.s) { @@ -532,7 +550,7 @@ static VbResult normal_open(const NormalCmdInfo *info) { Arg a; /* open last closed */ - a.i = info->cmd == 'U' ? VB_TARGET_NEW : VB_TARGET_CURRENT; + a.i = info->key == '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); @@ -549,9 +567,9 @@ static VbResult normal_pass(const NormalCmdInfo *info) static VbResult normal_prevnext(const NormalCmdInfo *info) { int count = info->count ? info->count : 1; - if (info->ncmd == ']') { + if (info->key2 == ']') { hints_follow_link(false, count); - } else if (info->ncmd == '[') { + } else if (info->key2 == '[') { hints_follow_link(true, count); } else { return RESULT_ERROR; @@ -578,7 +596,7 @@ static VbResult normal_scroll(const NormalCmdInfo *info) int count = info->count ? info->count : 1; /* TODO split this into more functions - reduce similar code */ - switch (info->cmd) { + switch (info->key) { case 'h': adjust = vb.gui.adjust_h; value = vb.config.scrollstep; @@ -634,7 +652,7 @@ static VbResult normal_scroll(const NormalCmdInfo *info) break; default: - if (info->ncmd == 'g') { + if (info->key2 == '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); @@ -652,7 +670,7 @@ static VbResult normal_search(const NormalCmdInfo *info) { int count = (info->count > 0) ? info->count : 1; - command_search(&((Arg){info->cmd == 'n' ? count : -count})); + command_search(&((Arg){info->key == 'n' ? count : -count})); return RESULT_COMPLETE; } @@ -670,7 +688,7 @@ static VbResult normal_search_selection(const NormalCmdInfo *info) } count = (info->count > 0) ? info->count : 1; - command_search(&((Arg){info->cmd == '*' ? count : -count, query})); + command_search(&((Arg){info->key == '*' ? count : -count, query})); g_free(query); return RESULT_COMPLETE; @@ -705,7 +723,7 @@ static VbResult normal_view_source(const NormalCmdInfo *info) static VbResult normal_yank(const NormalCmdInfo *info) { - Arg a = {info->cmd == 'Y' ? COMMAND_YANK_SELECTION : COMMAND_YANK_URI}; + Arg a = {info->key == 'Y' ? COMMAND_YANK_SELECTION : COMMAND_YANK_URI}; return command_yank(&a) ? RESULT_COMPLETE : RESULT_ERROR; } @@ -718,7 +736,7 @@ static VbResult normal_zoom(const NormalCmdInfo *info) count = info->count ? (float)info->count : 1.0; - if (info->ncmd == 'z') { /* zz reset zoom */ + if (info->key2 == 'z') { /* zz reset zoom */ webkit_web_view_set_zoom_level(view, 1.0); return RESULT_COMPLETE; @@ -728,10 +746,10 @@ static VbResult normal_zoom(const NormalCmdInfo *info) 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->ncmd)); + webkit_web_view_set_full_content_zoom(view, isupper(info->key2)); /* calculate the new zoom level */ - if (info->ncmd == 'i' || info->ncmd == 'I') { + if (info->key2 == 'i' || info->key2 == 'I') { level += ((float)count * step); } else { level -= ((float)count * step);