Added command to open input boxes or text areas with editor (#15).
authorDaniel Carl <danielcarl@gmx.de>
Sun, 14 Apr 2013 18:01:20 +0000 (20:01 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Sun, 14 Apr 2013 19:54:21 +0000 (21:54 +0200)
doc/vimb.1.txt
src/command.c
src/command.h
src/config.h
src/dom.c
src/dom.h
src/main.h
src/searchengine.c
src/setting.c
src/util.c
src/util.h

index 4c1ed75..caf0001 100644 (file)
@@ -529,6 +529,12 @@ Search for \fIN\fPnth next search result.
 .TP
 .BI [ N ]N
 Search for \fIN\fPnth previous search result.
+.SS INSERT_MODE
+.TP
+.B ctrl-t
+If the current active form element is an inputbox or textarea, the content is
+copied to temporary file and the file openen with the configured external
+editor (setting `editor-command').
 .SH FILES
 .I $XDG_CONFIG_HOME/PROJECT/config
 .RS
index cdfb389..48cf36c 100644 (file)
 #include "bookmark.h"
 #include "dom.h"
 
+typedef struct {
+    char    *file;
+    Element *element;
+} OpenEditorData;
+
+static void editor_resume(GPid pid, int status, OpenEditorData *data);
+
 extern VbCore vb;
 extern const unsigned int INPUT_LENGTH;
 
@@ -102,6 +109,7 @@ static CommandInfo cmd_list[] = {
     {"run",                  command_run_multi,            {0}},
     {"bookmark-add",         command_bookmark,             {1}},
     {"eval",                 command_eval,                 {0}},
+    {"editor",               command_editor,               {0}},
 };
 
 
@@ -628,3 +636,82 @@ gboolean command_eval(const Arg *arg)
 
     return success;
 }
+
+gboolean command_editor(const Arg *arg)
+{
+    char *file_path = NULL;
+    const char *text;
+    char **argv;
+    int argc;
+    GPid pid;
+    gboolean success;
+
+    if (!vb.config.editor_command) {
+        vb_echo(VB_MSG_ERROR, true, "No editor-command configured");
+        return false;
+    }
+    Element* active = dom_get_active_element(vb.gui.webview);
+
+    /* check if element is suitable for editing */
+    if (!active || !dom_is_editable(active)) {
+        return false;
+    }
+
+    text = dom_editable_element_get_value(active);
+    if (!text) {
+        return false;
+    }
+
+    if (!util_create_tmp_file(text, &file_path)) {
+        return false;
+    }
+
+    /* spawn editor */
+    char* command = g_strdup_printf(vb.config.editor_command, file_path);
+    if (!g_shell_parse_argv(command, &argc, &argv, NULL)) {
+        fprintf(stderr, "Could not parse editor-command");
+        g_free(command);
+        return false;
+    }
+    g_free(command);
+
+    success = g_spawn_async(
+        NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+        NULL, NULL, &pid, NULL
+    );
+    g_strfreev(argv);
+
+    if (!success) {
+        unlink(file_path);
+        g_free(file_path);
+        return false;
+    }
+
+    /* disable the active element */
+    dom_editable_element_set_disable(active, true);
+
+    OpenEditorData *data = g_new0(OpenEditorData, 1);
+    data->file    = file_path;
+    data->element = active;
+
+    g_child_watch_add(pid, (GChildWatchFunc)editor_resume, data);
+
+    return true;
+}
+
+static void editor_resume(GPid pid, int status, OpenEditorData *data)
+{
+    char *text;
+    if (status == 0) {
+        text = util_get_file_contents(data->file, NULL);
+        if (text) {
+            dom_editable_element_set_value(data->element, text);
+        }
+        g_free(text);
+    }
+    dom_editable_element_set_disable(data->element, false);
+
+    g_free(data->file);
+    g_free(data);
+    g_spawn_close_pid(pid);
+}
index 8b81d17..a526e03 100644 (file)
@@ -75,5 +75,6 @@ gboolean command_zoom(const Arg *arg);
 gboolean command_history(const Arg *arg);
 gboolean command_bookmark(const Arg *arg);
 gboolean command_eval(const Arg *arg);
+gboolean command_editor(const Arg *arg);
 
 #endif /* end of include guard: _COMMAND_H */
index d6e0e16..325cf2c 100644 (file)
@@ -92,6 +92,7 @@ const char *default_config[] = {
     "cmap <down>=hist-next",
     "hmap <tab>=hint-focus-next",
     "hmap <shift-tab>=hint-focus-prev",
+    "imap <ctrl-t>=editor",
     "searchengine-add dl=https://duckduckgo.com/lite/?q=%s",
     "searchengine-add dd=https://duckduckgo.com/?q=%s",
     "searchengine-default dl",
@@ -148,6 +149,7 @@ const char *default_config[] = {
     "set home-page=https://github.com/fanglingsu/vimb",
     "set download-path=/tmp/vimb",
     "set history-max-items=2000",
+    "set editor-command=x-terminal-emulator -e vi %s",
     NULL
 };
 
index 9ded553..3c49b30 100644 (file)
--- a/src/dom.c
+++ b/src/dom.c
@@ -93,6 +93,39 @@ Element *dom_get_active_element(WebKitWebView *view)
     return get_active_element(webkit_web_view_get_dom_document(view));
 }
 
+const char *dom_editable_element_get_value(Element *element)
+{
+    const char *value = NULL;
+
+    if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT((HtmlInputElement*)element)) {
+        value = webkit_dom_html_input_element_get_value((HtmlInputElement*)element);
+    } else {
+        /* we should check WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT, but this
+         * seems to return alway false */
+        value = webkit_dom_html_text_area_element_get_value((HtmlTextareaElement*)element);
+    }
+
+    return value;
+}
+
+void dom_editable_element_set_value(Element *element, const char *value)
+{
+    if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(element)) {
+        webkit_dom_html_input_element_set_value((HtmlInputElement*)element, value);
+    } else {
+        webkit_dom_html_text_area_element_set_value((HtmlTextareaElement*)element, value);
+    }
+}
+
+void dom_editable_element_set_disable(Element *element, gboolean value)
+{
+    if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(element)) {
+        webkit_dom_html_input_element_set_disabled((HtmlInputElement*)element, value);
+    } else {
+        webkit_dom_html_text_area_element_set_disabled((HtmlTextareaElement*)element, value);
+    }
+}
+
 static gboolean auto_insert(Element *element)
 {
     if (dom_is_editable(element)) {
index 18f9388..0c99b45 100644 (file)
--- a/src/dom.h
+++ b/src/dom.h
 #include <webkit/webkit.h>
 
 // Types
-#define Document       WebKitDOMDocument
-#define HtmlElement    WebKitDOMHTMLElement
-#define Element        WebKitDOMElement
-#define Event          WebKitDOMEvent
-#define EventTarget    WebKitDOMEventTarget
+#define Document            WebKitDOMDocument
+#define HtmlElement         WebKitDOMHTMLElement
+#define Element             WebKitDOMElement
+#define Node                WebKitDOMNode
+#define Event               WebKitDOMEvent
+#define EventTarget         WebKitDOMEventTarget
+#define HtmlInputElement    WebKitDOMHTMLInputElement
+#define HtmlTextareaElement WebKitDOMHTMLTextAreaElement
 
 // style 
 #define style_compare_property(style, name, value)    (!strcmp(webkit_dom_css_style_declaration_get_property_value(style, name), value))
@@ -43,5 +46,8 @@ void dom_check_auto_insert(WebKitWebView *view);
 void dom_clear_focus(WebKitWebView *view);
 gboolean dom_is_editable(Element *element);
 Element *dom_get_active_element(WebKitWebView *view);
+const char *dom_editable_element_get_value(Element *element);
+void dom_editable_element_set_value(Element *element, const char *value);
+void dom_editable_element_set_disable(Element *element, gboolean value);
 
 #endif /* end of include guard: _DOM_H */
index 97f400e..7a644f1 100644 (file)
@@ -257,6 +257,7 @@ typedef struct {
     char   *home_page;
     char   *download_dir;
     guint  history_max;
+    char   *editor_command;
 } Config;
 
 typedef struct {
index e693df6..0724dd6 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "main.h"
 #include "searchengine.h"
+#include "util.h"
 
 extern VbCore vb;
 
@@ -31,7 +32,6 @@ static GSList *searchengines;
 static char   *default_handle = NULL;
 
 static GSList *find(const char *handle);
-static gboolean is_valid_uri(const char *uri);
 static void free_searchengine(Searchengine *se);
 
 
@@ -45,7 +45,7 @@ void searchengine_cleanup(void)
 gboolean searchengine_add(const char *handle, const char *uri)
 {
     /* validate if the uri contains only one %s sequence */
-    if (!is_valid_uri(uri)) {
+    if (!util_valid_format_string(uri, 's', 1)) {
         return false;
     }
     Searchengine *s = g_new0(Searchengine, 1);
@@ -128,22 +128,6 @@ static GSList *find(const char *handle)
     return NULL;
 }
 
-static gboolean is_valid_uri(const char *uri)
-{
-    int count = 0;
-
-    for (; *uri; uri++) {
-        if (*uri == '%') {
-            uri++;
-            if (*uri == 's') {
-                count++;
-            }
-        }
-    }
-
-    return count == 1;
-}
-
 static void free_searchengine(Searchengine *se)
 {
     g_free(se->uri);
index 8ed1886..671470d 100644 (file)
@@ -42,6 +42,7 @@ static gboolean download_path(const Setting *s, const SettingType type);
 static gboolean proxy(const Setting *s, const SettingType type);
 static gboolean user_style(const Setting *s, const SettingType type);
 static gboolean history_max_items(const Setting *s, const SettingType type);
+static gboolean editor_command(const Setting *s, const SettingType type);
 
 static Setting default_settings[] = {
     /* webkit settings */
@@ -103,6 +104,7 @@ static Setting default_settings[] = {
     {NULL, "home-page", TYPE_CHAR, home_page, {0}},
     {NULL, "download-path", TYPE_CHAR, download_path, {0}},
     {NULL, "history-max-items", TYPE_INTEGER, history_max_items, {0}},
+    {NULL, "editor-command", TYPE_CHAR, editor_command, {0}},
 };
 
 void setting_init(void)
@@ -691,3 +693,19 @@ static gboolean history_max_items(const Setting *s, const SettingType type)
 
     return true;
 }
+
+static gboolean editor_command(const Setting *s, const SettingType type)
+{
+    if (type == SETTING_GET) {
+        print_value(s, vb.config.editor_command);
+
+        return true;
+    }
+
+    if (!util_valid_format_string(s->arg.s, 's', 1)) {
+        return false;
+    }
+    OVERWRITE_STRING(vb.config.editor_command, s->arg.s);
+
+    return true;
+}
index ca09c9a..ba5af22 100644 (file)
@@ -119,3 +119,58 @@ next:
     }
     return NULL;
 }
+
+/**
+ * Checks if given printf style format string is valid. That means that it
+ * contains only one defined placeholder given as char.
+ */
+gboolean util_valid_format_string(const char *format, char type, unsigned int count)
+{
+    unsigned int c;
+    for (c = 0; *format; format++) {
+        if (*format == '%') {
+            format++;
+            if (*format == type) {
+                c++;
+            }
+        }
+    }
+
+    return c == count;
+}
+
+/**
+ * Creates a temporary file with given content.
+ *
+ * Upon success, and if file is non-NULL, the actual file path used is
+ * returned in file. This string should be freed with g_free() when not
+ * needed any longer.
+ */
+gboolean util_create_tmp_file(const char *content, char **file)
+{
+    int fp;
+    ssize_t bytes, len;
+
+    fp = g_file_open_tmp(PROJECT "-XXXXXX", file, NULL);
+    if (fp == -1) {
+        fprintf(stderr, "Could not create temporary file %s", *file);
+        g_free(*file);
+        return false;
+    }
+
+    len = strlen(content);
+
+    /* write content into temporary file */
+    bytes = write(fp, content, len);
+    if (bytes < len) {
+        close(fp);
+        unlink(*file);
+        fprintf(stderr, "Could not write temporary file %s", *file);
+        g_free(*file);
+
+        return false;
+    }
+    close(fp);
+
+    return true;
+}
index 5a52031..8255b30 100644 (file)
@@ -30,5 +30,7 @@ void util_create_file_if_not_exists(const char* filename);
 char* util_get_file_contents(const char* filename, gsize* length);
 char** util_get_lines(const char* filename);
 char* util_strcasestr(const char* haystack, const char* needle);
+gboolean util_valid_format_string(const char *format, char type, unsigned int count);
+gboolean util_create_tmp_file(const char *content, char **file);
 
 #endif /* end of include guard: _UTIL_H */