/** * vimb - a webkit based vim like browser. * * Copyright (C) 2012-2018 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[] = { {"*", 0x01ff}, {"LoadStarting", 0x0001}, {"LoadStarted", 0x0002}, {"LoadCommitted", 0x0004}, /*{"LoadFirstLayout", 0x0008},*/ {"LoadFinished", 0x0010}, /*{"LoadFailed", 0x0020},*/ {"DownloadStarted", 0x0040}, {"DownloadFinished", 0x0080}, {"DownloadFailed", 0x0100}, }; 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 becomes the ex command to run */ if (parse && !*parse) { parse = NULL; } excmd = parse; /* delete the autocmd if bang was given */ if (delete) { GSList *lc, *next; AutoCmd *cmd; gboolean removed = false; /* check if the group does already exists */ for (lc = grp->cmds; lc; lc = next) { /* Save the next element in case this element is removed */ next = 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; } /* 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); } } /* 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, *lcc; AuGroup *grp; AutoCmd *cmd, *new; 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; } /* make a deep copy of grp->cmds since it can be modified by ex_run_string() below */ lcc = NULL; for (lc = grp->cmds; lc; lc = lc->next) { cmd = lc->data; new = new_autocmd(cmd->excmd, cmd->pattern); new->bits = cmd->bits; lcc = g_slist_prepend(lcc, new); // use prepend+reverse instead of append for efficiency } lcc = g_slist_reverse(lcc); /* test each command in group */ for (lc = lcc; lc; lc = lc->next, free_autocmd(cmd)) { 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; } } g_slist_free(lcc); } 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