bookmarks without loading the page first.
* Refresh hints after scrolling the page or resizing the window which makes
extended hint mode more comfortable.
+* Reintroduce the automatic commands from vimb2. An automatic command is
+ executed automatically in response to some event, such as a URI being opened.
### Changed
.TP
.B :qc[lear]
Removes all entries from queue.
+.SS Automatic commands
+An autocommand is a command that is executed automatically in response to some
+event, such as a URI being opened.
+Autocommands are very powerful.
+Use them with care and they will help you avoid typing many commands.
+.PP
+Autocommands are built with following properties.
+.TP
+.I group
+When the [\fIgroup\fP] argument is not given, Vimb uses the current group as
+defined with ':augroup', otherwise, Vimb uses the group defined with
+[\fIgroup\fP].
+Groups are useful to remove multiple grouped autocommands.
+.TP
+.I event
+You can specify a comma separated list of event names.
+No white space can be used in this list.
+.RS
+.PP
+.PD 0
+Events:
+.TP
+.B LoadStarted
+Fired if a new page is going to be opened.
+No data has been received yet, the load may still fail for transport issues.
+.TP
+.B LoadCommited
+Fired if first data chunk has arrived, meaning that the necessary transport
+requirements are established, and the load is being performed.
+This is the right event to toggle content related setting
+like 'scripts', 'plugins' and such things.
+.TP
+.B LoadFinished
+Fires when everything that was required to display on the page has been loaded.
+.TP
+.B DownloadStarted
+Fired right after a download is started.
+.TP
+.B DownloadFinished
+Fired if a Vimb managed download is finished.
+For external download this event is not available.
+.TP
+.B DownloadFailed
+Fired if a Vimb managed download failed.
+For external download this event is not available.
+.PD
+.RE
+.TP
+.I pat
+Comma separated list of patterns, matches in order to check if a autocommand
+applies to the URI associated to an event.
+To use ',' within the single patterns this must be escaped as '\e,'.
+.RS
+.PP
+.PD 0
+Patterns:
+.IP "\fB*\fP"
+Matches any sequence of characters.
+This includes also '/' in contrast to shell patterns.
+.IP "\fB?\fP"
+Matches any single character except of '/'.
+.IP "\fB{one,two}\fP"
+Matches 'one' or 'two'.
+Any '{', ',' and '}' within this pattern must be escaped by a '\\'.
+\&'*' and '?' have no special meaning within the curly braces.
+.IP "\fB\e\fP"
+Use backslash to escape the special meaning of '?*{},' in the pattern or
+pattern list.
+.PD
+.RE
+.TP
+.I cmd
+Any `ex` command vimb understands.
+The leading ':' is not required.
+Multiple commands can be separated by '|'.
+.TP
+.BI ":au[tocmd] [" group "] {" event "} {" pat "} {" cmd "}"
+Add \fIcmd\fP to the list of commands that Vimb will execute automatically on
+\fIevent\fP for a URI matching \fIpat\fP autocmd-patterns.
+Vimb always adds the \fIcmd\fP after existing autocommands, so that the
+autocommands are executed in the order in which they were given.
+.TP
+.BI ":au[tocmd]! [" group "] {" event "} {" pat "} {" cmd "}"
+Remove all autocommands associated with \fIevent\fP and which pattern match
+\fIpat\fP, and add the command \fIcmd\fP.
+Note that the pattern is not matches literally to find autocommands
+to remove, like Vim does.
+Vimb matches the autocommand pattern with \fIpat\fP.
+If [\fIgroup\fP] is not given, deletes autocommands in current group,
+as noted above.
+.TP
+.BI ":au[tocmd]! [" group "] {" event "} {" pat "}"
+Remove all autocommands associated with \fIevent\fP and which pattern matches
+\fIpat\fP in given group (current group by default).
+.TP
+.BI ":au[tocmd]! [" group "] * {" pat "}"
+Remove all autocommands with patterns matching \fIpat\fP for all events
+in given group (current group by default).
+.TP
+.BI ":au[tocmd]! [" group "] {" event "}"
+Remove all autocommands for \fIevent\fP in given group (current group
+by default).
+.TP
+.BI ":au[tocmd]! [" group "]"
+Remove all autocommands in given group (current group by default).
+.TP
+.BI ":aug[roup] {" name "}"
+Define the autocmd group \fIname\fP for the following ":autocmd" commands.
+The name "end" selects the default group.
+.TP
+.BI ":aug[roup]! {" name "}"
+Delete the autocmd group \fIname\fP.
+.PP
+Example:
+.EX
+:aug github
+: au LoadCommited * set scripts=off|set cookie-accept=never
+: au LoadCommited http{s,}://github.com/* set scripts=on
+:aug end
+.EE
.SS Misc
.TP
.B :cl[earcache]
--- /dev/null
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2017 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 <string.h>
+
+#include "config.h"
+#ifdef FEATURE_AUTOCMD
+#include "autocmd.h"
+#include "ascii.h"
+#include "ex.h"
+#include "util.h"
+#include "completion.h"
+
+typedef struct {
+ guint bits; /* the bits identify the events the command applies to */
+ char *excmd; /* ex command string to be run on matches event */
+ char *pattern; /* list of patterns the uri is matched agains */
+} AutoCmd;
+
+struct AuGroup {
+ char *name;
+ GSList *cmds;
+};
+
+typedef struct AuGroup AuGroup;
+
+static struct {
+ const char *name;
+ guint bits;
+} events[] = {
+ {"*", 0x00ff},
+ {"LoadStarted", 0x0001},
+ {"LoadCommitted", 0x0002},
+ /*{"LoadFirstLayout", 0x0004},*/
+ {"LoadFinished", 0x0008},
+ /*{"LoadFailed", 0x0010},*/
+ {"DownloadStarted", 0x0020},
+ {"DownloadFinished", 0x0040},
+ {"DownloadFailed", 0x0080},
+};
+
+static GSList *get_group(Client *c, const char *name);
+static guint get_event_bits(Client *c, const char *name);
+static void rebuild_used_bits(Client *c);
+static char *get_next_word(char **line);
+static AuGroup *new_group(const char *name);
+static void free_group(AuGroup *group);
+static AutoCmd *new_autocmd(const char *excmd, const char *pattern);
+static void free_autocmd(AutoCmd *cmd);
+
+
+void autocmd_init(Client *c)
+{
+ c->autocmd.curgroup = new_group("end");
+ c->autocmd.groups = g_slist_prepend(c->autocmd.groups, c->autocmd.curgroup);
+ c->autocmd.usedbits = 0;
+}
+
+void autocmd_cleanup(Client *c)
+{
+ if (c->autocmd.groups) {
+ g_slist_free_full(c->autocmd.groups, (GDestroyNotify)free_group);
+ }
+}
+
+/**
+ * Handle the :augroup {group} ex command.
+ */
+gboolean autocmd_augroup(Client *c, char *name, gboolean delete)
+{
+ GSList *item;
+
+ if (!*name) {
+ return false;
+ }
+
+ /* check for group "end" that marks the default group */
+ if (!strcmp(name, "end")) {
+ c->autocmd.curgroup = (AuGroup*)c->autocmd.groups->data;
+ return true;
+ }
+
+ item = get_group(c, name);
+
+ /* check if the group is going to be removed */
+ if (delete) {
+ /* group does not exist - so do nothing */
+ if (!item) {
+ return true;
+ }
+ if (c->autocmd.curgroup == (AuGroup*)item->data) {
+ /* if the group to delete is the current - switch the the default
+ * group after removing it */
+ c->autocmd.curgroup = (AuGroup*)c->autocmd.groups->data;
+ }
+
+ /* now remove the group */
+ free_group((AuGroup*)item->data);
+ c->autocmd.groups = g_slist_delete_link(c->autocmd.groups, item);
+
+ /* there where autocmds remove - so recreate the usedbits */
+ rebuild_used_bits(c);
+
+ return true;
+ }
+
+ /* check if the group does already exists */
+ if (item) {
+ /* if the group is found in the known groups use it as current */
+ c->autocmd.curgroup = (AuGroup*)item->data;
+
+ return true;
+ }
+
+ /* create a new group and use it as current */
+ c->autocmd.curgroup = new_group(name);
+
+ /* append it to known groups */
+ c->autocmd.groups = g_slist_prepend(c->autocmd.groups, c->autocmd.curgroup);
+
+ return true;
+}
+
+/**
+ * Add or delete auto commands.
+ *
+ * :au[tocmd]! [group] {event} {pat} [nested] {cmd}
+ * group and nested flag are not supported at the moment.
+ */
+gboolean autocmd_add(Client *c, char *name, gboolean delete)
+{
+ guint bits;
+ char *parse, *word, *pattern, *excmd;
+ GSList *item;
+ AuGroup *grp = NULL;
+
+ parse = name;
+
+ /* parse group name if it's there */
+ word = get_next_word(&parse);
+ if (word) {
+ /* check if the word is a known group name */
+ item = get_group(c, word);
+ if (item) {
+ grp = (AuGroup*)item->data;
+
+ /* group is found - get the next word */
+ word = get_next_word(&parse);
+ }
+ }
+ if (!grp) {
+ /* no group found - use the current one */
+ grp = c->autocmd.curgroup;
+ }
+
+ /* parse event name - if none was matched run it for all events */
+ if (word) {
+ bits = get_event_bits(c, word);
+ if (!bits) {
+ return false;
+ }
+ word = get_next_word(&parse);
+ } else {
+ bits = events[AU_ALL].bits;
+ }
+
+ /* last word is the pattern - if not found use '*' */
+ pattern = word ? word : "*";
+
+ /* the rest of the line becoes the ex command to run */
+ if (parse && !*parse) {
+ parse = NULL;
+ }
+ excmd = parse;
+
+ /* delete the autocmd if bang was given */
+ if (delete) {
+ GSList *lc;
+ AutoCmd *cmd;
+ gboolean removed = false;
+
+ /* check if the group does already exists */
+ for (lc = grp->cmds; lc; lc = lc->next) {
+ cmd = (AutoCmd*)lc->data;
+ /* if not bits match - skip the command */
+ if (!(cmd->bits & bits)) {
+ continue;
+ }
+ /* skip if pattern does not match - we check the pattern against
+ * another pattern */
+ if (!util_wildmatch(pattern, cmd->pattern)) {
+ continue;
+ }
+ /* remove the bits from the item */
+ cmd->bits &= ~bits;
+
+ /* if the command has no matching events - remove it */
+ grp->cmds = g_slist_delete_link(grp->cmds, lc);
+ free_autocmd(cmd);
+
+ removed = true;
+ }
+
+ /* if ther was at least one command removed - rebuilt the used bits */
+ if (removed) {
+ rebuild_used_bits(c);
+ }
+
+ return true;
+ }
+
+ /* add the new autocmd */
+ if (excmd && grp) {
+ AutoCmd *cmd = new_autocmd(excmd, pattern);
+ cmd->bits = bits;
+
+ /* add the new autocmd to the group */
+ grp->cmds = g_slist_append(grp->cmds, cmd);
+
+ /* merge the autocmd bits into the used bits */
+ c->autocmd.usedbits |= cmd->bits;
+ }
+
+ return true;
+}
+
+/**
+ * Run named auto command.
+ */
+gboolean autocmd_run(Client *c, AuEvent event, const char *uri, const char *group)
+{
+ GSList *lg, *lc;
+ AuGroup *grp;
+ AutoCmd *cmd;
+ guint bits = events[event].bits;
+
+ /* if there is no autocmd for this event - skip here */
+ if (!(c->autocmd.usedbits & bits)) {
+ return true;
+ }
+
+ /* loop over the groups and find matching commands */
+ for (lg = c->autocmd.groups; lg; lg = lg->next) {
+ grp = lg->data;
+ /* if a group was given - skip all none matching groupes */
+ if (group && strcmp(group, grp->name)) {
+ continue;
+ }
+ /* test each command in group */
+ for (lc = grp->cmds; lc; lc = lc->next) {
+ cmd = lc->data;
+ /* skip if this dos not match the event bits */
+ if (!(bits & cmd->bits)) {
+ continue;
+ }
+ /* check pattern only if uri was given */
+ /* skip if pattern does not match */
+ if (uri && !util_wildmatch(cmd->pattern, uri)) {
+ continue;
+ }
+ /* run the command */
+ /* TODO shoult the result be tested for RESULT_COMPLETE? */
+ /* run command and make sure it's not writte to command history */
+ ex_run_string(c, cmd->excmd, false);
+ }
+ }
+
+ return true;
+}
+
+gboolean autocmd_fill_group_completion(Client *c, GtkListStore *store, const char *input)
+{
+ GSList *lg;
+ gboolean found = false;
+ GtkTreeIter iter;
+
+ if (!input || !*input) {
+ for (lg = c->autocmd.groups; lg; lg = lg->next) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, ((AuGroup*)lg->data)->name, -1);
+ found = true;
+ }
+ } else {
+ for (lg = c->autocmd.groups; lg; lg = lg->next) {
+ char *value = ((AuGroup*)lg->data)->name;
+ if (g_str_has_prefix(value, input)) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1);
+ found = true;
+ }
+ }
+ }
+
+ return found;
+}
+
+gboolean autocmd_fill_event_completion(Client *c, GtkListStore *store, const char *input)
+{
+ int i;
+ const char *value;
+ gboolean found = false;
+ GtkTreeIter iter;
+
+ if (!input || !*input) {
+ for (i = 0; i < LENGTH(events); i++) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, events[i].name, -1);
+ found = true;
+ }
+ } else {
+ for (i = 0; i < LENGTH(events); i++) {
+ value = events[i].name;
+ if (g_str_has_prefix(value, input)) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, value, -1);
+ found = true;
+ }
+ }
+ }
+
+ return found;
+}
+
+/**
+ * Get the augroup by it's name.
+ */
+static GSList *get_group(Client *c, const char *name)
+{
+ GSList *lg;
+ AuGroup *grp;
+
+ for (lg = c->autocmd.groups; lg; lg = lg->next) {
+ grp = lg->data;
+ if (!strcmp(grp->name, name)) {
+ return lg;
+ }
+ }
+
+ return NULL;
+}
+
+static guint get_event_bits(Client *c, const char *name)
+{
+ int result = 0;
+
+ while (*name) {
+ int i, len;
+ for (i = 0; i < LENGTH(events); i++) {
+ /* we count the chars that given name has in common with the
+ * current processed event */
+ len = 0;
+ while (name[len] && events[i].name[len] && name[len] == events[i].name[len]) {
+ len++;
+ }
+
+ /* check if the matching chars built a full word match */
+ if (events[i].name[len] == '\0'
+ && (name[len] == '\0' || name[len] == ',')
+ ) {
+ /* add the bits to the result */
+ result |= events[i].bits;
+
+ /* move pointer after the match and skip possible ',' */
+ name += len;
+ if (*name == ',') {
+ name++;
+ }
+ break;
+ }
+ }
+
+ /* check if the end was reached without a match */
+ if (i >= LENGTH(events)) {
+ /* is this the right place to write the error */
+ vb_echo(c, MSG_ERROR, TRUE, "Bad autocmd event name: %s", name);
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Rebuild the usedbits from scratch.
+ * Save all used autocmd event bits in the bitmap.
+ */
+static void rebuild_used_bits(Client *c)
+{
+ GSList *lc, *lg;
+ AuGroup *grp;
+
+ /* clear the current set bits */
+ c->autocmd.usedbits = 0;
+ /* loop over the groups */
+ for (lg = c->autocmd.groups; lg; lg = lg->next) {
+ grp = (AuGroup*)lg->data;
+
+ /* merge the used event bints into the bitmap */
+ for (lc = grp->cmds; lc; lc = lc->next) {
+ c->autocmd.usedbits |= ((AutoCmd*)lc->data)->bits;
+ }
+ }
+}
+
+/**
+ * Get the next word from given line.
+ * Given line pointer is set past the word and and a 0-byte is added there.
+ */
+static char *get_next_word(char **line)
+{
+ char *word;
+
+ if (!*line || !**line) {
+ return NULL;
+ }
+
+ /* remember where the word starts */
+ word = *line;
+
+ /* move pointer to the end of the word or of the line */
+ while (**line && !VB_IS_SPACE(**line)) {
+ (*line)++;
+ }
+
+ /* end the word */
+ if (**line) {
+ *(*line)++ = '\0';
+ }
+
+ /* skip trailing whitespace */
+ while (VB_IS_SPACE(**line)) {
+ (*line)++;
+ }
+
+ return word;
+}
+
+static AuGroup *new_group(const char *name)
+{
+ AuGroup *new = g_slice_new(AuGroup);
+ new->name = g_strdup(name);
+ new->cmds = NULL;
+
+ return new;
+}
+
+static void free_group(AuGroup *group)
+{
+ g_free(group->name);
+ if (group->cmds) {
+ g_slist_free_full(group->cmds, (GDestroyNotify)free_autocmd);
+ }
+ g_slice_free(AuGroup, group);
+}
+
+static AutoCmd *new_autocmd(const char *excmd, const char *pattern)
+{
+ AutoCmd *new = g_slice_new(AutoCmd);
+ new->excmd = g_strdup(excmd);
+ new->pattern = g_strdup(pattern);
+ return new;
+}
+
+static void free_autocmd(AutoCmd *cmd)
+{
+ g_free(cmd->excmd);
+ g_free(cmd->pattern);
+ g_slice_free(AutoCmd, cmd);
+}
+
+#endif
--- /dev/null
+/**
+ * vimb - a webkit based vim like browser.
+ *
+ * Copyright (C) 2012-2017 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"
+#ifdef FEATURE_AUTOCMD
+
+#ifndef _AUTOCMD_H
+#define _AUTOCMD_H
+
+#include "main.h"
+
+/* this values correspond to indices in events[] array in autocmd.c */
+typedef enum {
+ AU_ALL,
+ AU_LOAD_STARTED,
+ AU_LOAD_COMMITTED,
+ /*AU_LOAD_FIRST_LAYOUT,*/
+ AU_LOAD_FINISHED,
+ /*AU_LOAD_FAILED,*/
+ AU_DOWNLOAD_STARTED,
+ AU_DOWNLOAD_FINISHED,
+ AU_DOWNLOAD_FAILED,
+} AuEvent;
+
+void autocmd_init(Client *c);
+void autocmd_cleanup(Client *c);
+gboolean autocmd_augroup(Client *c, char *name, gboolean delete);
+gboolean autocmd_add(Client *c, char *name, gboolean delete);
+gboolean autocmd_run(Client *c, AuEvent event, const char *uri, const char *group);
+gboolean autocmd_fill_group_completion(Client *c, GtkListStore *store, const char *input);
+gboolean autocmd_fill_event_completion(Client *c, GtkListStore *store, const char *input);
+
+#endif /* end of include guard: _AUTOCMD_H */
+#endif
#define PROGRESS_BAR_LEN 20
#endif
+#define FEATURE_AUTOCMD
+
/* time in seconds after that message will be removed from inputbox if the
* message where only temporary */
#define MESSAGE_TIMEOUT 5
#include "shortcut.h"
#include "util.h"
#include "ext-proxy.h"
+#include "autocmd.h"
typedef enum {
- /* TODO add feature autocmd */
+#ifdef FEATURE_AUTOCMD
+ EX_AUTOCMD,
+ EX_AUGROUP,
+#endif
EX_BMA,
EX_BMR,
EX_EVAL,
static void free_cmdarg(ExArg *arg);
static VbCmdResult execute(Client *c, const ExArg *arg);
+#ifdef FEATURE_AUTOCMD
+static VbCmdResult ex_augroup(Client *c, const ExArg *arg);
+static VbCmdResult ex_autocmd(Client *c, const ExArg *arg);
+#endif
static VbCmdResult ex_bookmark(Client *c, const ExArg *arg);
static VbCmdResult ex_eval(Client *c, const ExArg *arg);
static void on_eval_script_finished(GDBusProxy *proxy, GAsyncResult *result, Client *c);
* match. */
static ExInfo commands[] = {
/* command code func flags */
+#ifdef FEATURE_AUTOCMD
+ {"autocmd", EX_AUTOCMD, ex_autocmd, EX_FLAG_CMD|EX_FLAG_BANG},
+ {"augroup", EX_AUGROUP, ex_augroup, EX_FLAG_LHS|EX_FLAG_BANG},
+#endif
{"bma", EX_BMA, ex_bookmark, EX_FLAG_RHS},
{"bmr", EX_BMR, ex_bookmark, EX_FLAG_RHS},
{"cmap", EX_CMAP, ex_map, EX_FLAG_LHS|EX_FLAG_CMD},
g_slice_free(ExArg, arg);
}
+#ifdef FEATURE_AUTOCMD
+static VbCmdResult ex_augroup(Client *c, const ExArg *arg)
+{
+ return autocmd_augroup(c, arg->lhs->str, arg->bang) ? CMD_SUCCESS : CMD_ERROR;
+}
+
+static VbCmdResult ex_autocmd(Client *c, const ExArg *arg)
+{
+ return autocmd_add(c, arg->rhs->str, arg->bang) ? CMD_SUCCESS : CMD_ERROR;
+}
+#endif
+
static VbCmdResult ex_bookmark(Client *c, const ExArg *arg)
{
if (arg->code == EX_BMR) {
found = util_filename_fill_completion(c, store, token);
break;
+#ifdef FEATURE_AUTOCMD
+ case EX_AUTOCMD:
+ found = autocmd_fill_event_completion(c, store, token);
+ break;
+
+ case EX_AUGROUP:
+ found = autocmd_fill_group_completion(c, store, token);
+ break;
+#endif
+
default:
break;
}
#include "setting.h"
#include "shortcut.h"
#include "util.h"
+#include "autocmd.h"
static void client_destroy(Client *c);
static Client *client_new(WebKitWebView *webview);
register_cleanup(c);
setting_cleanup(c);
handler_cleanup(c);
+#ifdef FEATURE_AUTOCMD
+ autocmd_cleanup(c);
+#endif
g_slice_free(Client, c);
completion_init(c);
map_init(c);
handler_init(c);
+#ifdef FEATURE_AUTOCMD
+ autocmd_init(c);
+#endif
/* webview */
c->webview = webview_new(c, webview);
static void on_webctx_download_started(WebKitWebContext *webctx,
WebKitDownload *download, Client *c)
{
+#ifdef FEATURE_AUTOCMD
+ const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
+ autocmd_run(c, AU_DOWNLOAD_STARTED, uri, NULL);
+#endif
+
g_signal_connect(download, "decide-destination", G_CALLBACK(on_webdownload_decide_destination), c);
g_signal_connect(download, "failed", G_CALLBACK(on_webdownload_failed), c);
g_signal_connect(download, "finished", G_CALLBACK(on_webdownload_finished), c);
g_assert(error);
g_assert(c);
+#ifdef FEATURE_AUTOCMD
+ const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
+ autocmd_run(c, AU_DOWNLOAD_FAILED, uri, NULL);
+#endif
+
/* get the failed download's destination uri */
g_object_get(download, "destination", &destination, NULL);
g_assert(destination);
g_assert(download);
g_assert(c);
+#ifdef FEATURE_AUTOCMD
+ const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
+ autocmd_run(c, AU_DOWNLOAD_FINISHED, uri, NULL);
+#endif
+
c->state.downloads = g_list_remove(c->state.downloads, download);
/* to reflect the correct download count */
switch (event) {
case WEBKIT_LOAD_STARTED:
+ uri = webkit_web_view_get_uri(webview);
+#ifdef FEATURE_AUTOCMD
+ autocmd_run(c, AU_LOAD_STARTED, uri, NULL);
+#endif
/* update load progress in statusbar */
c->state.progress = 0;
vb_statusbar_update(c);
- set_title(c, webkit_web_view_get_uri(webview));
+ set_title(c, uri);
/* Make sure hinting is cleared before the new page is loaded.
* Without that vimb would still be in hinting mode after hinting
* was started and some links was clicked my mouse. Even if there
case WEBKIT_LOAD_COMMITTED:
uri = webkit_web_view_get_uri(webview);
+#ifdef FEATURE_AUTOCMD
+ autocmd_run(c, AU_LOAD_COMMITTED, uri, NULL);
+#endif
/* save the current URI in register % */
vb_register_add(c, '%', uri);
/* check if tls is on and the page is trusted */
case WEBKIT_LOAD_FINISHED:
uri = webkit_web_view_get_uri(webview);
+#ifdef FEATURE_AUTOCMD
+ autocmd_run(c, AU_LOAD_FINISHED, uri, NULL);
+#endif
c->state.progress = 100;
if (strncmp(uri, "about:", 6)) {
history_add(c, HISTORY_URL, uri, webkit_web_view_get_title(webview));
GtkWidget *mode, *left, *right, *cmd;
};
+struct AuGroup;
+
struct Client {
struct Client *next;
struct State state;
struct {
GHashTable *table; /* holds the protocol handlers */
} handlers;
+ struct {
+ struct AuGroup *curgroup;
+ GSList *groups;
+ guint usedbits; /* holds all used event bits */
+ } autocmd;
};
struct Vimb {
extern struct Vimb vb;
static void create_dir_if_not_exists(const char *dirpath);
+static gboolean match(const char *pattern, int patlen, const char *subject);
+static gboolean match_list(const char *pattern, int patlen, const char *subject);
/**
* Build the absolute file path of given path and possible given directory.
g_mkdir_with_parents(dirpath, 0755);
}
}
+
+/**
+ * Compares given string against also given list of patterns.
+ *
+ * * Matches any sequence of characters.
+ * ? Matches any single character except of '/'.
+ * {foo,bar} Matches foo or bar - '{', ',' and '}' within this pattern must be
+ * escaped by '\'. '*' and '?' have no special meaning within the
+ * curly braces.
+ * *?{} these chars must always be escaped by '\' to match them literally
+ */
+gboolean util_wildmatch(const char *pattern, const char *subject)
+{
+ const char *end;
+ int braces, patlen, count;
+
+ /* loop through all pattens */
+ for (count = 0; *pattern; pattern = (*end == ',' ? end + 1 : end), count++) {
+ /* find end of the pattern - but be careful with comma in curly braces */
+ braces = 0;
+ for (end = pattern; *end && (*end != ',' || braces || *(end - 1) == '\\'); ++end) {
+ if (*end == '{') {
+ braces++;
+ } else if (*end == '}') {
+ braces--;
+ }
+ }
+ /* ignore single comma */
+ if (*pattern == *end) {
+ continue;
+ }
+ /* calculate the length of the pattern */
+ patlen = end - pattern;
+
+ /* if this pattern matches - return */
+ if (match(pattern, patlen, subject)) {
+ return true;
+ }
+ }
+
+ if (!count) {
+ /* empty pattern matches only on empty subject */
+ return !*subject;
+ }
+ /* there where one or more patterns but none of them matched */
+ return false;
+}
+
+
+/**
+ * Compares given subject string against the given pattern.
+ * The pattern needs not to bee NUL terminated.
+ */
+static gboolean match(const char *pattern, int patlen, const char *subject)
+{
+ int i;
+ char sl, pl;
+
+ while (patlen > 0) {
+ switch (*pattern) {
+ case '?':
+ /* '?' matches a single char except of / and subject end */
+ if (*subject == '/' || !*subject) {
+ return false;
+ }
+ break;
+
+ case '*':
+ /* easiest case - the '*' ist the last char in pattern - this
+ * will always match */
+ if (patlen == 1) {
+ return true;
+ }
+ /* Try to match as much as possible. Try to match the complete
+ * uri, if that fails move forward in uri and check for a
+ * match. */
+ i = strlen(subject);
+ while (i >= 0 && !match(pattern + 1, patlen - 1, subject + i)) {
+ i--;
+ }
+ return i >= 0;
+
+ case '}':
+ /* spurious '}' in pattern */
+ return false;
+
+ case '{':
+ /* possible {foo,bar} pattern */
+ return match_list(pattern, patlen, subject);
+
+ case '\\':
+ /* '\' escapes next special char */
+ if (strchr("*?{}", pattern[1])) {
+ pattern++;
+ patlen--;
+ if (*pattern != *subject) {
+ return false;
+ }
+ }
+ break;
+
+ default:
+ /* compare case insensitive */
+ sl = *subject;
+ if (VB_IS_UPPER(sl)) {
+ sl += 'a' - 'A';
+ }
+ pl = *pattern;
+ if (VB_IS_UPPER(pl)) {
+ pl += 'a' - 'A';
+ }
+ if (sl != pl) {
+ return false;
+ }
+ break;
+ }
+ /* do another loop run with next pattern and subject char */
+ pattern++;
+ patlen--;
+ subject++;
+ }
+
+ /* on end of pattern only a also ended subject is a match */
+ return !*subject;
+}
+
+/**
+ * Matches pattern starting with '{'.
+ * This function can process also on none null terminated pattern.
+ */
+static gboolean match_list(const char *pattern, int patlen, const char *subject)
+{
+ int endlen;
+ const char *end, *s;
+
+ /* finde the next none escaped '}' */
+ for (end = pattern, endlen = patlen; endlen > 0 && *end != '}'; end++, endlen--) {
+ /* if escape char - move pointer one additional step */
+ if (*end == '\\') {
+ end++;
+ endlen--;
+ }
+ }
+
+ if (!*end) {
+ /* unterminated '{' in pattern */
+ return false;
+ }
+
+ s = subject;
+ end++; /* skip over } */
+ endlen--;
+ pattern++; /* skip over { */
+ patlen--;
+ while (true) {
+ switch (*pattern) {
+ case ',':
+ if (match(end, endlen, s)) {
+ return true;
+ }
+ s = subject;
+ pattern++;
+ patlen--;
+ break;
+
+ case '}':
+ return match(end, endlen, s);
+
+ case '\\':
+ if (pattern[1] == ',' || pattern[1] == '}' || pattern[1] == '{') {
+ pattern++;
+ patlen--;
+ }
+ /* fall through */
+
+ default:
+ if (*pattern == *s) {
+ pattern++;
+ patlen--;
+ s++;
+ } else {
+ /* this item of the list does not match - move forward to
+ * the next none escaped ',' or '}' */
+ s = subject;
+ for (s = subject; *pattern != ',' && *pattern != '}'; pattern++, patlen--) {
+ /* if escape char is found - skip next char */
+ if (*pattern == '\\') {
+ pattern++;
+ patlen--;
+ }
+ }
+ /* found ',' skip over it to check the next list item */
+ if (*pattern == ',') {
+ pattern++;
+ patlen--;
+ }
+ }
+ }
+ }
+}
+
char *util_sanitize_filename(char *filename);
char *util_strcasestr(const char *haystack, const char *needle);
char *util_str_replace(const char* search, const char* replace, const char* string);
+gboolean util_wildmatch(const char *pattern, const char *subject);
#endif /* end of include guard: _UTIL_H */