From 84e8e88d1374291702fba050533de855a8426e32 Mon Sep 17 00:00:00 2001 From: Daniel Carl Date: Sun, 24 Aug 2014 18:47:22 +0200 Subject: [PATCH] Added basic logic for :autocmd. --- src/autocmd.c | 355 +++++++++++++++++++++++++++++++++++++++++++++++ src/autocmd.h | 45 ++++++ src/config.def.h | 2 + src/ex.c | 25 ++++ src/main.c | 27 +++- 5 files changed, 450 insertions(+), 4 deletions(-) create mode 100644 src/autocmd.c create mode 100644 src/autocmd.h diff --git a/src/autocmd.c b/src/autocmd.c new file mode 100644 index 0000000..9564a54 --- /dev/null +++ b/src/autocmd.c @@ -0,0 +1,355 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2014 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 +#include "autocmd.h" +#include "ascii.h" +#include "ex.h" + +typedef struct { + guint bits; + char *excmd; + char *pattern; +} AutoCmd; + +typedef struct { + char *name; + GSList *cmds; +} AuGroup; + +static struct { + const char *name; + guint bits; +} events[] = { + {"*", 0x001f}, + {"PageLoadProvisional", 0x0001}, + {"PageLoadCommited", 0x0002}, + {"PageLoadFirstLayout", 0x0004}, + {"PageLoadFinished", 0x0008}, + {"PageLoadFailed", 0x0010}, +}; + +static AuGroup *curgroup = NULL; +static GSList *groups = NULL; + +static guint get_event_bits(const char *name); +static AuGroup *get_group(const char *name); +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(void) +{ + curgroup = new_group("end"); + groups = g_slist_prepend(groups, curgroup); +} + +void autocmd_cleanup(void) +{ + if (groups) { + g_slist_free_full(groups, (GDestroyNotify)free_group); + } +} + +/** + * Handle the :augroup {group} ex command. + */ +gboolean autocmd_augroup(char *name, gboolean delete) +{ + AuGroup *grp; + + if (!*name) { + return false; + } + + /* check for group "end" that marks the default group */ + if (!strcmp(name, "end")) { + curgroup = (AuGroup*)groups->data; + return true; + } + + /* check if the group does already exists */ + grp = get_group(name); + if (grp) { + /* if the group is found in the known groups use it as current */ + curgroup = grp; + + return true; + } + + /* create a new group and use it as current */ + curgroup = new_group(name); + + /* append it to known groups */ + groups = g_slist_prepend(groups, 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(char *name, gboolean delete) +{ + guint bits; + char *parse, *word, *pattern, *excmd; + AuGroup *grp; + + 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 */ + grp = get_group(word); + if (grp) { + /* group is found - get the next word */ + word = get_next_word(&parse); + } else { + /* no group found - use the current one */ + grp = curgroup; + } + } + + /* parse event name - if none was matched run it for all events */ + if (word) { + bits = get_event_bits(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; + + /* 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 it pattern does not match - '*' matches everithing */ + if (strcmp(cmd->pattern, "*") && strcmp(cmd->pattern, 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); + } + + return true; + } + + /* add the new autocmd */ + if (excmd) { + AutoCmd *cmd = new_autocmd(excmd, pattern); + cmd->bits = bits; + + /* add the new autocmd to the group */ + grp->cmds = g_slist_append(grp->cmds, cmd); + } + + return true; +} + +/** + * Run named auto command. + */ +gboolean autocmd_run(const char *group, AuEvent event, const char *uri) +{ + GSList *lg, *lc; + AuGroup *grp; + AutoCmd *cmd; + guint bits = events[event].bits; + + /* loop over the groups and find matching commands */ + for (lg = 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; + } + /* TODO skip if pattern does not match */ + /* run the command */ + /* TODO shoult hte result be tested for RESULT_COMPLETE? */ + ex_run_string(cmd->excmd); + } + } + + return true; +} + +/** + * Get the augroup by it's name. + */ +static AuGroup *get_group(const char *name) +{ + GSList *lg; + AuGroup *grp; + + for (lg = groups; lg; lg = lg->next) { + grp = lg->data; + if (!strcmp(grp->name, name)) { + return grp; + } + } + + return NULL; +} + +static guint get_event_bits(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(VB_MSG_ERROR, true, "Bad autocmd event name: %s", name); + return 0; + } + } + + return result; +} + +/** + * 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..506560d --- /dev/null +++ b/src/autocmd.h @@ -0,0 +1,45 @@ +/** + * vimb - a webkit based vim like browser. + * + * Copyright (C) 2012-2014 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_PAGE_LOAD_PROVISIONAL, + AU_PAGE_LOAD_COMMITED, + AU_PAGE_LOAD_FIRST_LAYOUT, + AU_PAGE_LOAD_FINISHED, + AU_PAGE_LOAD_FAILED, +} AuEvent; + +void autocmd_init(void); +void autocmd_cleanup(void); +gboolean autocmd_augroup(char *name, gboolean delete); +gboolean autocmd_add(char *name, gboolean delete); +gboolean autocmd_run(const char *group, AuEvent event, const char *uri); + +#endif /* end of include guard: _AUTOCMD_H */ +#endif diff --git a/src/config.def.h b/src/config.def.h index dd3c2b1..c105a59 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -48,6 +48,8 @@ #define FEATURE_HSTS /* enable soup caching - size can be configure by maximum-cache-size setting */ #define FEATURE_SOUP_CACHE +/* enable the :autocmd feature */ +#define FEATURE_AUTOCMD /* time in seconds after that message will be removed from inputbox if the * message where only temporary */ diff --git a/src/ex.c b/src/ex.c index 6562f05..e965478 100644 --- a/src/ex.c +++ b/src/ex.c @@ -40,8 +40,15 @@ #include "map.h" #include "js.h" #include "normal.h" +#ifdef FEATURE_AUTOCMD +#include "autocmd.h" +#endif typedef enum { +#ifdef FEATURE_AUTOCMD + EX_AUTOCMD, + EX_AUGROUP, +#endif EX_BMA, EX_BMR, EX_EVAL, @@ -122,6 +129,8 @@ static void skip_whitespace(const char **input); static void free_cmdarg(ExArg *arg); static gboolean execute(const ExArg *arg); +static gboolean ex_augroup(const ExArg *arg); +static gboolean ex_autocmd(const ExArg *arg); static gboolean ex_bookmark(const ExArg *arg); static gboolean ex_eval(const ExArg *arg); static gboolean ex_hardcopy(const ExArg *arg); @@ -152,6 +161,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_RHS}, @@ -707,6 +720,18 @@ static void free_cmdarg(ExArg *arg) g_slice_free(ExArg, arg); } +#ifdef FEATURE_AUTOCMD +static gboolean ex_augroup(const ExArg *arg) +{ + return autocmd_augroup(arg->lhs->str, arg->bang); +} + +static gboolean ex_autocmd(const ExArg *arg) +{ + return autocmd_add(arg->rhs->str, arg->bang); +} +#endif + static gboolean ex_bookmark(const ExArg *arg) { if (arg->code == EX_BMR) { diff --git a/src/main.c b/src/main.c index a9130a3..7b0781e 100644 --- a/src/main.c +++ b/src/main.c @@ -42,6 +42,7 @@ #include "pass.h" #include "bookmark.h" #include "js.h" +#include "autocmd.h" /* variables */ static char **args; @@ -404,6 +405,9 @@ void vb_quit(gboolean force) history_cleanup(); session_cleanup(); register_cleanup(); +#ifdef FEATURE_AUTOCMD + autocmd_cleanup(); +#endif for (int i = 0; i < FILES_LAST; i++) { g_free(vb.files[i]); @@ -502,10 +506,13 @@ static void webview_download_progress_cb(WebKitWebView *view, GParamSpec *pspec) static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec) { - const char *uri; + const char *uri = webkit_web_view_get_uri(view); switch (webkit_web_view_get_load_status(view)) { case WEBKIT_LOAD_PROVISIONAL: +#ifdef FEATURE_AUTOCMD + autocmd_run(NULL, AU_PAGE_LOAD_PROVISIONAL, uri); +#endif /* update load progress in statusbar */ vb.state.progress = 0; vb_update_statusbar(); @@ -513,7 +520,9 @@ static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec) break; case WEBKIT_LOAD_COMMITTED: - uri = webkit_web_view_get_uri(view); +#ifdef FEATURE_AUTOCMD + autocmd_run(NULL, AU_PAGE_LOAD_COMMITED, uri); +#endif { WebKitWebFrame *frame = webkit_web_view_get_main_frame(view); JSContextRef ctx; @@ -548,6 +557,9 @@ static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec) break; case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT: +#ifdef FEATURE_AUTOCMD + autocmd_run(NULL, AU_PAGE_LOAD_FIRST_LAYOUT, uri); +#endif /* if we load a page from a submitted form, leave the insert mode */ if (vb.mode->id == 'i') { mode_enter('n'); @@ -555,8 +567,9 @@ static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec) break; case WEBKIT_LOAD_FINISHED: - uri = webkit_web_view_get_uri(view); - +#ifdef FEATURE_AUTOCMD + autocmd_run(NULL, AU_PAGE_LOAD_FINISHED, uri); +#endif /* update load progress in statusbar */ vb.state.progress = 100; vb_update_statusbar(); @@ -569,6 +582,9 @@ static void webview_load_status_cb(WebKitWebView *view, GParamSpec *pspec) break; case WEBKIT_LOAD_FAILED: +#ifdef FEATURE_AUTOCMD + autocmd_run(NULL, AU_PAGE_LOAD_FAILED, uri); +#endif break; } } @@ -807,6 +823,9 @@ static void init_core(void) session_init(); setting_init(); register_init(); +#ifdef FEATURE_AUTOCMD + autocmd_init(); +#endif read_config(); /* initially apply input style */ -- 2.20.1