From 063e7fb4f67a2fccc543cca62fb7344aabd085e3 Mon Sep 17 00:00:00 2001 From: Yoann Blein Date: Sun, 1 Oct 2017 15:39:09 +0200 Subject: [PATCH] Reintroduce autocmd and augroups. Fix #356 --- CHANGELOG.md | 2 + doc/vimb.1 | 120 ++++++++++++ src/autocmd.c | 487 +++++++++++++++++++++++++++++++++++++++++++++++ src/autocmd.h | 50 +++++ src/config.def.h | 2 + src/ex.c | 36 +++- src/main.c | 34 +++- src/main.h | 7 + src/util.c | 203 ++++++++++++++++++++ src/util.h | 1 + 10 files changed, 940 insertions(+), 2 deletions(-) create mode 100644 src/autocmd.c create mode 100644 src/autocmd.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ccdceb..90a7cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. 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 diff --git a/doc/vimb.1 b/doc/vimb.1 index 0599232..31a0ae7 100644 --- a/doc/vimb.1 +++ b/doc/vimb.1 @@ -632,6 +632,126 @@ queue. .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] diff --git a/src/autocmd.c b/src/autocmd.c new file mode 100644 index 0000000..c021f33 --- /dev/null +++ b/src/autocmd.c @@ -0,0 +1,487 @@ +/** + * 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 + +#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 diff --git a/src/autocmd.h b/src/autocmd.h new file mode 100644 index 0000000..736bd88 --- /dev/null +++ b/src/autocmd.h @@ -0,0 +1,50 @@ +/** + * 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 diff --git a/src/config.def.h b/src/config.def.h index 48f3392..12c22a0 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -35,6 +35,8 @@ #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 diff --git a/src/ex.c b/src/ex.c index d6a49de..8c41026 100644 --- a/src/ex.c +++ b/src/ex.c @@ -41,9 +41,13 @@ #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, @@ -127,6 +131,10 @@ static void skip_whitespace(const char **input); 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); @@ -161,6 +169,10 @@ static void history_rewind(void); * 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}, @@ -778,6 +790,18 @@ static void free_cmdarg(ExArg *arg) 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) { @@ -1224,6 +1248,16 @@ static gboolean complete(Client *c, short direction) 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; } diff --git a/src/main.c b/src/main.c index 131c149..c440902 100644 --- a/src/main.c +++ b/src/main.c @@ -43,6 +43,7 @@ #include "setting.h" #include "shortcut.h" #include "util.h" +#include "autocmd.h" static void client_destroy(Client *c); static Client *client_new(WebKitWebView *webview); @@ -647,6 +648,9 @@ static void client_destroy(Client *c) register_cleanup(c); setting_cleanup(c); handler_cleanup(c); +#ifdef FEATURE_AUTOCMD + autocmd_cleanup(c); +#endif g_slice_free(Client, c); @@ -677,6 +681,9 @@ static Client *client_new(WebKitWebView *webview) completion_init(c); map_init(c); handler_init(c); +#ifdef FEATURE_AUTOCMD + autocmd_init(c); +#endif /* webview */ c->webview = webview_new(c, webview); @@ -964,6 +971,11 @@ static void spawn_new_instance(const char *uri) 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); @@ -1019,6 +1031,11 @@ static void on_webdownload_failed(WebKitDownload *download, 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); @@ -1054,6 +1071,11 @@ static void on_webdownload_finished(WebKitDownload *download, Client *c) 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 */ @@ -1216,10 +1238,14 @@ static void on_webview_load_changed(WebKitWebView *webview, 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 @@ -1234,6 +1260,9 @@ static void on_webview_load_changed(WebKitWebView *webview, 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 */ @@ -1258,6 +1287,9 @@ static void on_webview_load_changed(WebKitWebView *webview, 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)); diff --git a/src/main.h b/src/main.h index 7eb1141..de66fc5 100644 --- a/src/main.h +++ b/src/main.h @@ -207,6 +207,8 @@ struct Statusbar { GtkWidget *mode, *left, *right, *cmd; }; +struct AuGroup; + struct Client { struct Client *next; struct State state; @@ -250,6 +252,11 @@ struct Client { struct { GHashTable *table; /* holds the protocol handlers */ } handlers; + struct { + struct AuGroup *curgroup; + GSList *groups; + guint usedbits; /* holds all used event bits */ + } autocmd; }; struct Vimb { diff --git a/src/util.c b/src/util.c index 8008533..fac1157 100644 --- a/src/util.c +++ b/src/util.c @@ -38,6 +38,8 @@ static struct { 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. @@ -776,3 +778,204 @@ static void create_dir_if_not_exists(const char *dirpath) 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--; + } + } + } + } +} + diff --git a/src/util.h b/src/util.h index 7e802e4..b0b73b4 100644 --- a/src/util.h +++ b/src/util.h @@ -55,5 +55,6 @@ gboolean util_parse_expansion(Client *c, const char **input, GString *str, 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 */ -- 2.20.1