summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Carl <danielcarl@gmx.de>2017-02-28 00:09:19 +0100
committerDaniel Carl <danielcarl@gmx.de>2017-02-28 00:10:33 +0100
commitca8dfe7dee5f4c7474f0723ec65cf9a3978f083e (patch)
tree8cc03496ca6e49e6c7f1f5d6cd1b07d9c10f018a
parentfdb7d1fda3e34590c86579a635da1deafed32307 (diff)
Allow to manage bookmarks and queue.
-rw-r--r--README.md12
-rw-r--r--src/bookmark.c329
-rw-r--r--src/bookmark.h37
-rw-r--r--src/command.c45
-rw-r--r--src/config.def.h2
-rw-r--r--src/ex.c59
-rw-r--r--src/normal.c4
-rw-r--r--src/util.c101
-rw-r--r--src/util.h3
9 files changed, 578 insertions, 14 deletions
diff --git a/README.md b/README.md
index ca56071..2eee22e 100644
--- a/README.md
+++ b/README.md
@@ -104,13 +104,21 @@ project directory.
- [ ] hinting
- [x] searching and matching of search results
- [x] navigation j, k, h, l, ...
- - [ ] history and history lookup
+ - [x] history and history lookup
- [ ] completion
+ - [ ] augroup
+ - [ ] autocmd
+ - [x] bookmarks
+ - [ ] file paths for :source and :save
+ - [x] search phrases
+ - [x] settings
+ - [ ] url handler
+ - [x] url history
- [ ] HSTS
- [ ] auto-response-header
- [x] cookies support
- [x] register support and `:register` command
- - [ ] read it later queue
+ - [x] read it later queue
- [ ] show scroll indicator in statusbar as top, x%, bottom or all
how can we get this information from webview easily?
- [x] find a way to disable the scrollbars on the main frame
diff --git a/src/bookmark.c b/src/bookmark.c
new file mode 100644
index 0000000..110d72d
--- /dev/null
+++ b/src/bookmark.c
@@ -0,0 +1,329 @@
+/**
+ * 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"
+#include "main.h"
+#include "bookmark.h"
+#include "util.h"
+#include "completion.h"
+
+typedef struct {
+ char *uri;
+ char *title;
+ char *tags;
+} Bookmark;
+
+extern struct Vimb vb;
+
+static GList *load(const char *file);
+static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query,
+ unsigned int qlen);
+static Bookmark *line_to_bookmark(const char *uri, const char *data);
+static void free_bookmark(Bookmark *bm);
+
+/**
+ * Write a new bookmark entry to the end of bookmark file.
+ */
+gboolean bookmark_add(const char *uri, const char *title, const char *tags)
+{
+ const char *file = vb.files[FILES_BOOKMARK];
+ if (tags) {
+ return util_file_append(file, "%s\t%s\t%s\n", uri, title ? title : "", tags);
+ }
+ if (title) {
+ return util_file_append(file, "%s\t%s\n", uri, title);
+ }
+ return util_file_append(file, "%s\n", uri);
+}
+
+gboolean bookmark_remove(const char *uri)
+{
+ char **lines, *line, *p;
+ int len, i;
+ GString *new;
+ gboolean removed = false;
+
+ if (!uri) {
+ return false;
+ }
+
+ lines = util_get_lines(vb.files[FILES_BOOKMARK]);
+ if (lines) {
+ new = g_string_new(NULL);
+ len = g_strv_length(lines) - 1;
+ for (i = 0; i < len; i++) {
+ line = lines[i];
+ g_strstrip(line);
+ /* ignore the title or bookmark tags and test only the uri */
+ if ((p = strchr(line, '\t'))) {
+ *p = '\0';
+ if (!strcmp(uri, line)) {
+ removed = true;
+ continue;
+ } else {
+ /* reappend the tags */
+ *p = '\t';
+ }
+ }
+ if (!strcmp(uri, line)) {
+ removed = true;
+ continue;
+ }
+ g_string_append_printf(new, "%s\n", line);
+ }
+ g_strfreev(lines);
+ g_file_set_contents(vb.files[FILES_BOOKMARK], new->str, -1, NULL);
+ g_string_free(new, true);
+ }
+
+ return removed;
+}
+
+gboolean bookmark_fill_completion(GtkListStore *store, const char *input)
+{
+ gboolean found = false;
+ char **parts;
+ unsigned int len;
+ GtkTreeIter iter;
+ GList *src = NULL;
+ Bookmark *bm;
+
+ src = load(vb.files[FILES_BOOKMARK]);
+ src = g_list_reverse(src);
+ if (!input || !*input) {
+ /* without any tags return all bookmarked items */
+ for (GList *l = src; l; l = l->next) {
+ bm = (Bookmark*)l->data;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(
+ store, &iter,
+ COMPLETION_STORE_FIRST, bm->uri,
+#ifdef FEATURE_TITLE_IN_COMPLETION
+ COMPLETION_STORE_SECOND, bm->title,
+#endif
+ -1
+ );
+ found = true;
+ }
+ } else {
+ parts = g_strsplit(input, " ", 0);
+ len = g_strv_length(parts);
+
+ for (GList *l = src; l; l = l->next) {
+ bm = (Bookmark*)l->data;
+ if (bookmark_contains_all_tags(bm, parts, len)) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(
+ store, &iter,
+ COMPLETION_STORE_FIRST, bm->uri,
+#ifdef FEATURE_TITLE_IN_COMPLETION
+ COMPLETION_STORE_SECOND, bm->title,
+#endif
+ -1
+ );
+ found = true;
+ }
+ }
+ g_strfreev(parts);
+ }
+
+ g_list_free_full(src, (GDestroyNotify)free_bookmark);
+
+ return found;
+}
+
+gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input)
+{
+ gboolean found;
+ unsigned int len, i;
+ char **tags, *tag;
+ GList *src = NULL, *taglist = NULL;
+ Bookmark *bm;
+
+ /* get all distinct tags from bookmark file */
+ src = load(vb.files[FILES_BOOKMARK]);
+ for (GList *l = src; l; l = l->next) {
+ bm = (Bookmark*)l->data;
+ /* if bookmark contains no tags we can go to the next bookmark */
+ if (!bm->tags) {
+ continue;
+ }
+
+ tags = g_strsplit(bm->tags, " ", -1);
+ len = g_strv_length(tags);
+ for (i = 0; i < len; i++) {
+ tag = tags[i];
+ /* add tag only if it isn't already in the list */
+ if (!g_list_find_custom(taglist, tag, (GCompareFunc)strcmp)) {
+ taglist = g_list_prepend(taglist, g_strdup(tag));
+ }
+ }
+ g_strfreev(tags);
+ }
+
+ /* generate the completion with the found tags */
+ found = util_fill_completion(store, input, taglist);
+
+ g_list_free_full(src, (GDestroyNotify)free_bookmark);
+ g_list_free_full(taglist, (GDestroyNotify)g_free);
+
+ return found;
+}
+
+#ifdef FEATURE_QUEUE
+/**
+ * Push a uri to the end of the queue.
+ *
+ * @uri: URI to put into the queue
+ */
+gboolean bookmark_queue_push(const char *uri)
+{
+ return util_file_append(vb.files[FILES_QUEUE], "%s\n", uri);
+}
+
+/**
+ * Push a uri to the beginning of the queue.
+ *
+ * @uri: URI to put into the queue
+ */
+gboolean bookmark_queue_unshift(const char *uri)
+{
+ return util_file_prepend(vb.files[FILES_QUEUE], "%s\n", uri);
+}
+
+/**
+ * Retrieves the oldest entry from queue.
+ *
+ * @item_count: will be filled with the number of remaining items in queue.
+ * Returned uri must be freed with g_free.
+ */
+char *bookmark_queue_pop(int *item_count)
+{
+ return util_file_pop_line(vb.files[FILES_QUEUE], item_count);
+}
+
+/**
+ * Removes all contents from the queue file.
+ */
+gboolean bookmark_queue_clear(void)
+{
+ FILE *f;
+ if ((f = fopen(vb.files[FILES_QUEUE], "w"))) {
+ fclose(f);
+ return true;
+ }
+ return false;
+}
+#endif /* FEATURE_QUEUE */
+
+static GList *load(const char *file)
+{
+ return util_file_to_unique_list(file, (Util_Content_Func)line_to_bookmark, 0);
+}
+
+/**
+ * Checks if the given bookmark matches all given query strings as prefix. If
+ * the bookmark has no tags, the matching is done on the '/' splited URL.
+ *
+ * @bm: bookmark to test if it matches
+ * @query: char array with tags to search for
+ * @qlen: length of given query char array
+ *
+ * Return: true if the bookmark contained all tags
+ */
+static gboolean bookmark_contains_all_tags(Bookmark *bm, char **query,
+ unsigned int qlen)
+{
+ const char *separators;
+ char *cursor;
+ unsigned int i;
+ gboolean found;
+
+ if (!qlen) {
+ return true;
+ }
+
+ if (bm->tags) {
+ /* If there are tags - use them for matching. */
+ separators = " ";
+ cursor = bm->tags;
+ } else {
+ /* No tags available - matching is based on the path parts of the URL. */
+ separators = "./";
+ cursor = bm->uri;
+ }
+
+ /* iterate over all query parts */
+ for (i = 0; i < qlen; i++) {
+ found = false;
+
+ /* we want to do a prefix match on all bookmark tags - so we check for
+ * a match on string begin - if this fails we move the cursor to the
+ * next space and do the test again */
+ while (cursor && *cursor) {
+ /* match was not found at current cursor position */
+ if (g_str_has_prefix(cursor, query[i])) {
+ found = true;
+ break;
+ }
+ /* If match was not found at the cursor position - move cursor
+ * behind the next separator char. */
+ if ((cursor = strpbrk(cursor, separators))) {
+ cursor++;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static Bookmark *line_to_bookmark(const char *uri, const char *data)
+{
+ char *p;
+ Bookmark *bm;
+
+ /* data part may consist of title or title<tab>tags */
+ bm = g_slice_new(Bookmark);
+ bm->uri = g_strdup(uri);
+ if (data && (p = strchr(data, '\t'))) {
+ *p = '\0';
+ bm->title = g_strdup(data);
+ bm->tags = g_strdup(p + 1);
+ } else {
+ bm->title = g_strdup(data);
+ bm->tags = NULL;
+ }
+
+ return bm;
+}
+
+static void free_bookmark(Bookmark *bm)
+{
+ g_free(bm->uri);
+ g_free(bm->title);
+ g_free(bm->tags);
+ g_slice_free(Bookmark, bm);
+}
+
diff --git a/src/bookmark.h b/src/bookmark.h
new file mode 100644
index 0000000..dea4556
--- /dev/null
+++ b/src/bookmark.h
@@ -0,0 +1,37 @@
+/**
+ * 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/.
+ */
+
+#ifndef _BOOKMARK_H
+#define _BOOKMARK_H
+
+#include "main.h"
+
+gboolean bookmark_add(const char *uri, const char *title, const char *tags);
+gboolean bookmark_remove(const char *uri);
+gboolean bookmark_fill_completion(GtkListStore *store, const char *input);
+gboolean bookmark_fill_tag_completion(GtkListStore *store, const char *input);
+#ifdef FEATURE_QUEUE
+gboolean bookmark_queue_push(const char *uri);
+gboolean bookmark_queue_unshift(const char *uri);
+char *bookmark_queue_pop(int *item_count);
+gboolean bookmark_queue_clear(void);
+#endif
+
+#endif /* end of include guard: _BOOKMARK_H */
+
diff --git a/src/command.c b/src/command.c
index 700bd2c..7fbc878 100644
--- a/src/command.c
+++ b/src/command.c
@@ -21,11 +21,15 @@
* This file contains functions that are used by normal mode and command mode
* together.
*/
-#include "command.h"
+#include <stdlib.h>
+
#include "config.h"
+#ifdef FEATURE_QUEUE
+#include "bookmark.h"
+#endif
+#include "command.h"
#include "history.h"
#include "main.h"
-#include <stdlib.h>
extern struct Vimb vb;
@@ -164,7 +168,40 @@ gboolean command_save(Client *c, const Arg *arg)
#ifdef FEATURE_QUEUE
gboolean command_queue(Client *c, const Arg *arg)
{
- /* TODO no implemented yet */
- return TRUE;
+ gboolean res = FALSE;
+ int count = 0;
+ char *uri;
+
+ switch (arg->i) {
+ case COMMAND_QUEUE_POP:
+ if ((uri = bookmark_queue_pop(&count))) {
+ res = vb_load_uri(c, &(Arg){TARGET_CURRENT, uri});
+ g_free(uri);
+ }
+ vb_echo(c, MSG_NORMAL, FALSE, "Queue length %d", count);
+ break;
+
+ case COMMAND_QUEUE_PUSH:
+ res = bookmark_queue_push(arg->s ? arg->s : c->state.uri);
+ if (res) {
+ vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue");
+ }
+ break;
+
+ case COMMAND_QUEUE_UNSHIFT:
+ res = bookmark_queue_unshift(arg->s ? arg->s : c->state.uri);
+ if (res) {
+ vb_echo(c, MSG_NORMAL, FALSE, "Pushed to queue");
+ }
+ break;
+
+ case COMMAND_QUEUE_CLEAR:
+ if (bookmark_queue_clear()) {
+ vb_echo(c, MSG_NORMAL, FALSE, "Queue cleared");
+ }
+ break;
+ }
+
+ return res;
}
#endif
diff --git a/src/config.def.h b/src/config.def.h
index 8b2d27b..37874b7 100644
--- a/src/config.def.h
+++ b/src/config.def.h
@@ -28,6 +28,8 @@
#define FEATURE_TITLE_IN_COMPLETION
/* support gui style settings compatible with vimb2 */
#define FEATURE_GUI_STYLE_VIMB2_COMPAT
+/* enable the read it later queue */
+#define FEATURE_QUEUE
#ifdef FEATURE_WGET_PROGRESS_BAR
/* chars to use for the progressbar */
diff --git a/src/ex.c b/src/ex.c
index c3acc0a..4ca9153 100644
--- a/src/ex.c
+++ b/src/ex.c
@@ -27,6 +27,7 @@
#include <sys/wait.h>
#include "ascii.h"
+#include "bookmark.h"
#include "command.h"
#include "completion.h"
#include "config.h"
@@ -745,8 +746,19 @@ static void free_cmdarg(ExArg *arg)
static VbCmdResult ex_bookmark(Client *c, const ExArg *arg)
{
- /* TODO no implemented yet */
- return CMD_SUCCESS;
+ if (arg->code == EX_BMR) {
+ if (bookmark_remove(*arg->rhs->str ? arg->rhs->str : c->state.uri)) {
+ vb_echo_force(c, MSG_NORMAL, TRUE, " Bookmark removed");
+
+ return CMD_SUCCESS | CMD_KEEPINPUT;
+ }
+ } else if (bookmark_add(c->state.uri, c->state.title, arg->rhs->str)) {
+ vb_echo_force(c, MSG_NORMAL, TRUE, " Bookmark added");
+
+ return CMD_SUCCESS | CMD_KEEPINPUT;
+ }
+
+ return CMD_ERROR;
}
static VbCmdResult ex_eval(Client *c, const ExArg *arg)
@@ -855,8 +867,38 @@ static VbCmdResult ex_open(Client *c, const ExArg *arg)
#ifdef FEATURE_QUEUE
static VbCmdResult ex_queue(Client *c, const ExArg *arg)
{
- /* TODO no implemented yet */
- return CMD_SUCCESS;
+ Arg a = {0};
+
+ switch (arg->code) {
+ case EX_QPUSH:
+ a.i = COMMAND_QUEUE_PUSH;
+ break;
+
+ case EX_QUNSHIFT:
+ a.i = COMMAND_QUEUE_UNSHIFT;
+ break;
+
+ case EX_QPOP:
+ a.i = COMMAND_QUEUE_POP;
+ break;
+
+ case EX_QCLEAR:
+ a.i = COMMAND_QUEUE_CLEAR;
+ break;
+
+ default:
+ return CMD_ERROR;
+ }
+
+ /* if no argument is found in rhs, keep the uri in arg null to force
+ * command_queue() to use current URI */
+ if (arg->rhs->len) {
+ a.s = arg->rhs->str;
+ }
+
+ return command_queue(c, &a)
+ ? CMD_SUCCESS | CMD_KEEPINPUT
+ : CMD_ERROR | CMD_KEEPINPUT;
}
#endif
@@ -1092,9 +1134,12 @@ static gboolean complete(Client *c, short direction)
switch (arg->code) {
case EX_OPEN:
case EX_TABOPEN:
- /* TODO add bookmark completion if *token == '!' */
sort = FALSE;
- found = history_fill_completion(store, HISTORY_URL, token);
+ if (*token == '!') {
+ found = bookmark_fill_completion(store, token + 1);
+ } else {
+ found = history_fill_completion(store, HISTORY_URL, token);
+ }
break;
case EX_SET:
@@ -1102,7 +1147,7 @@ static gboolean complete(Client *c, short direction)
break;
case EX_BMA:
- /* TODO fill bookmark completion */
+ found = bookmark_fill_tag_completion(store, token);
break;
case EX_SCR:
diff --git a/src/normal.c b/src/normal.c
index e92de75..faa5733 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -633,7 +633,9 @@ static VbResult normal_prevnext(Client *c, const NormalCmdInfo *info)
static VbResult normal_queue(Client *c, const NormalCmdInfo *info)
{
- /* TODO run next uri from queu and remove it from */
+#ifdef FEATURE_QUEUE
+ command_queue(c, &((Arg){COMMAND_QUEUE_POP}));
+#endif
return RESULT_COMPLETE;
}
diff --git a/src/util.c b/src/util.c
index be48e71..29557f2 100644
--- a/src/util.c
+++ b/src/util.c
@@ -105,6 +105,79 @@ gboolean util_file_append(const char *file, const char *format, ...)
}
/**
+ * Prepend new data to file.
+ *
+ * @file: File to prepend the data
+ * @format: Format string used to process va_list
+ */
+gboolean util_file_prepend(const char *file, const char *format, ...)
+{
+ gboolean res = false;
+ va_list args;
+ char *content;
+ FILE *f;
+
+ content = util_get_file_contents(file, NULL);
+ if ((f = fopen(file, "w"))) {
+ flock(fileno(f), LOCK_EX);
+
+ va_start(args, format);
+ /* write new content to the file */
+ vfprintf(f, format, args);
+ va_end(args);
+
+ /* append previous file content */
+ fputs(content, f);
+
+ flock(fileno(f), LOCK_UN);
+ fclose(f);
+
+ res = true;
+ }
+ g_free(content);
+
+ return res;
+}
+
+
+/**
+ * Retrieves the first line from file and delete it from file.
+ *
+ * @file: file to read from
+ * @item_count: will be filled with the number of remaining lines in file if it
+ * is not NULL.
+ *
+ * Returned string must be freed with g_free.
+ */
+char *util_file_pop_line(const char *file, int *item_count)
+{
+ char **lines = util_get_lines(file);
+ char *new,
+ *line = NULL;
+ int len,
+ count = 0;
+
+ if (lines) {
+ len = g_strv_length(lines);
+ if (len) {
+ line = g_strdup(lines[0]);
+ /* minus one for last empty item and one for popped item */
+ count = len - 2;
+ new = g_strjoinv("\n", lines + 1);
+ g_file_set_contents(file, new, -1, NULL);
+ g_free(new);
+ }
+ g_strfreev(lines);
+ }
+
+ if (item_count) {
+ *item_count = count;
+ }
+
+ return line;
+}
+
+/**
* Retrieves the config directory path.
* Returnes string must be freed.
*/
@@ -260,6 +333,34 @@ GList *util_file_to_unique_list(const char *filename, Util_Content_Func func,
}
/**
+ * Fills the given list store by matching data of also given src list.
+ */
+gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src)
+{
+ gboolean found = false;
+ GtkTreeIter iter;
+
+ if (!input || !*input) {
+ for (GList *l = src; l; l = l->next) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
+ found = true;
+ }
+ } else {
+ for (GList *l = src; l; l = l->next) {
+ char *value = (char*)l->data;
+ if (g_str_has_prefix(value, input)) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COMPLETION_STORE_FIRST, l->data, -1);
+ found = true;
+ }
+ }
+ }
+
+ return found;
+}
+
+/**
* Fills file path completion entries into given list store for also given
* input.
*/
diff --git a/src/util.h b/src/util.h
index 94a60ca..ca9ba68 100644
--- a/src/util.h
+++ b/src/util.h
@@ -33,12 +33,15 @@ typedef void *(*Util_Content_Func)(const char*, const char*);
void util_cleanup(void);
char *util_expand(Client *c, const char *src, int expflags);
gboolean util_file_append(const char *file, const char *format, ...);
+gboolean util_file_prepend(const char *file, const char *format, ...);
+char *util_file_pop_line(const char *file, int *item_count);
char *util_get_config_dir(void);
char *util_get_file_contents(const char *filename, gsize *length);
char *util_get_filepath(const char *dir, const char *filename, gboolean create);
char **util_get_lines(const char *filename);
GList *util_file_to_unique_list(const char *filename, Util_Content_Func func,
guint max_items);
+gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src);
gboolean util_filename_fill_completion(Client *c, GtkListStore *store, const char *input);
gboolean util_parse_expansion(Client *c, const char **input, GString *str,
int flags, const char *quoteable);