Added basic logic for :autocmd.
authorDaniel Carl <danielcarl@gmx.de>
Sun, 24 Aug 2014 16:47:22 +0000 (18:47 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Sat, 30 Aug 2014 22:28:56 +0000 (00:28 +0200)
src/autocmd.c [new file with mode: 0644]
src/autocmd.h [new file with mode: 0644]
src/config.def.h
src/ex.c
src/main.c

diff --git a/src/autocmd.c b/src/autocmd.c
new file mode 100644 (file)
index 0000000..9564a54
--- /dev/null
@@ -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 (file)
index 0000000..506560d
--- /dev/null
@@ -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
index dd3c2b1..c105a59 100644 (file)
@@ -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 */
index 6562f05..e965478 100644 (file)
--- a/src/ex.c
+++ b/src/ex.c
 #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) {
index a9130a3..7b0781e 100644 (file)
@@ -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 */