.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 <Esc> 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
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;
}
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;
}
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);
}
switch (*text) {
- case ';':
+ case ';': /* fall through - the modes are handled by hints_create */
+ case 'g':
hints_create(text);
break;
* 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;
#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;
{
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');
}
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);
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);
}
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
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':
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
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);
tag = e.nodeName.toLowerCase(),
type = e.type || "";
- clear();
-
if (tag === "input" || tag === "textarea" || tag === "select") {
if (type === "radio" || type === "checkbox") {
e.focus();
/* 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",
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;
}
return show();
},
update: function update(n) {
- filterNum = n > 0 ? n : 0;
+ filterNum = n;
return show();
},
clear: clear,
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 {
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};
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);
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;
}
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);
return RESULT_ERROR;
}
- switch (info->ncmd) {
+ switch (info->key2) {
case 'U':
p = domain;
break;
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);
}
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);
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;
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;
}
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 */
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);
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) {
{
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);
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;
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;
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);
{
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;
}
}
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;
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;
}
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;
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);