implements external editor (closes #347)
authorRobert Timm <mail@rtti.de>
Thu, 11 May 2017 21:15:45 +0000 (23:15 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Thu, 11 May 2017 22:17:54 +0000 (00:17 +0200)
src/input.c
src/util.c
src/util.h

index 3e7943c..f0ece14 100644 (file)
 #include "util.h"
 #include "ext-proxy.h"
 
+typedef struct {
+    Client *c;
+    char   *file;
+} EditorData;
+
+static void resume_editor(GPid pid, int status, EditorData *data);
 
 /**
  * Function called when vimb enters the input mode.
@@ -100,7 +106,147 @@ VbResult input_keypress(Client *c, int key)
 
 VbResult input_open_editor(Client *c)
 {
-    /* TODO should the editor be opened by the webextension or by the
-     * application? */
+    char **argv, *file_path = NULL;
+    const char *text = NULL, *editor_command;
+    int argc;
+    GPid pid;
+    gboolean success;
+    GVariant *jsreturn;
+
+    g_assert(c);
+
+    /* get the editor command */
+    editor_command = GET_CHAR(c, "editor-command");
+    if (!editor_command || !*editor_command) {
+        vb_echo(c, MSG_ERROR, true, "No editor-command configured");
+        return RESULT_ERROR;
+    }
+
+    /* get the selected input element */
+    jsreturn = ext_proxy_eval_script_sync(c, "vimb_input_mode_element.value");
+    g_variant_get(jsreturn, "(bs)", &success, &text);
+
+    if (!success || !text) {
+        return RESULT_ERROR;
+    }
+
+    /* create a temp file to pass text to and from editor */
+    if (!util_create_tmp_file(text, &file_path)) {
+        return RESULT_ERROR;
+    }
+
+    /* spawn editor */
+    char* command = g_strdup_printf(editor_command, file_path);
+    if (!g_shell_parse_argv(command, &argc, &argv, NULL)) {
+        g_critical("Could not parse editor-command '%s'", command);
+        g_free(command);
+        return RESULT_ERROR;
+    }
+    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);
+        g_warning("Could not spawn editor-command");
+        return RESULT_ERROR;
+    }
+
+    /* disable the active element */
+    ext_proxy_eval_script(c, "vimb_input_mode_element.disabled=true", NULL);
+
+    /* watch the editor process */
+    EditorData *data = g_slice_new0(EditorData);
+    data->file = file_path;
+    data->c    = c;
+    g_child_watch_add(pid, (GChildWatchFunc)resume_editor, data);
+
     return RESULT_COMPLETE;
 }
+
+static void resume_editor(GPid pid, int status, EditorData *data)
+{
+    char *text, *tmp;
+    char *jscode;
+
+    g_assert(pid);
+    g_assert(data);
+    g_assert(data->c);
+    g_assert(data->file);
+
+    if (status == 0) {
+        /* get the text the editor stored */
+        text = util_get_file_contents(data->file, NULL);
+
+        if (text) {
+            /* escape the text to form a valid JS string */
+            /* TODO: could go into util.c maybe */
+            struct search_replace {
+                const char* search;
+                const char* replace;
+            } escapes[] = {
+                {"\x01", ""},
+                {"\x02", ""},
+                {"\x03", ""},
+                {"\x04", ""},
+                {"\x05", ""},
+                {"\x06", ""},
+                {"\a", ""},
+                {"\b", ""},
+                {"\t", "\\t"},
+                {"\n", "\\n"},
+                {"\v", ""},
+                {"\f", ""},
+                {"\r", ""},
+                {"\x0E", ""},
+                {"\x0F", ""},
+                {"\x10", ""},
+                {"\x11", ""},
+                {"\x12", ""},
+                {"\x13", ""},
+                {"\x14", ""},
+                {"\x15", ""},
+                {"\x16", ""},
+                {"\x17", ""},
+                {"\x18", ""},
+                {"\x19", ""},
+                {"\x1A", ""},
+                {"\x1B", ""},
+                {"\x1C", ""},
+                {"\x1D", ""},
+                {"\x1E", ""},
+                {"\x1F", ""},
+                {"\"", "\\\""},
+                {"\x7F", ""},
+                {NULL, NULL},
+            };
+
+            for(struct search_replace *i = escapes; i->search; i++) {
+                tmp = util_str_replace(i->search, i->replace, text);
+                g_free(text);
+                text = tmp;
+            }
+
+            /* put the text back into the element */
+            jscode = g_strdup_printf("vimb_input_mode_element.value=\"%s\"", text);
+            ext_proxy_eval_script(data->c, jscode, NULL);
+
+            g_free(jscode);
+            g_free(text);
+        }
+    }
+
+    ext_proxy_eval_script(data->c,
+            "vimb_input_mode_element.disabled=false;"
+            "vimb_input_mode_element.focus()", NULL);
+
+    g_unlink(data->file);
+    g_free(data->file);
+    g_slice_free(EditorData, data);
+    g_spawn_close_pid(pid);
+}
index f91dd89..54bc416 100644 (file)
@@ -110,6 +110,42 @@ gboolean util_create_dir_if_not_exists(const char *dirpath)
     return TRUE;
 }
 
+/**
+ * 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) {
+        g_critical("Could not create temp 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);
+        g_critical("Could not write temp file %s", *file);
+        g_free(*file);
+
+        return false;
+    }
+    close(fp);
+
+    return true;
+}
+
 /**
  * Expand ~user, ~/, $ENV and ${ENV} for given string into new allocated
  * string.
@@ -717,7 +753,7 @@ next:
  * Replaces appearances of search in string by given replace.
  * Returns a new allocated string if search was found.
  */
-char *util_str_replace(const char* search, const char* replace, const char* string)
+char *util_str_replace(const char *search, const char *replace, const char *string)
 {
     if (!string) {
         return NULL;
index 801654d..9043eb2 100644 (file)
@@ -33,6 +33,7 @@ typedef void *(*Util_Content_Func)(const char*, const char*);
 char *util_build_path(Client *c, const char *path, const char *dir);
 void util_cleanup(void);
 gboolean util_create_dir_if_not_exists(const char *dirpath);
+gboolean util_create_tmp_file(const char *content, char **file);
 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, ...);