about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJames Booth <boothj5@gmail.com>2016-10-10 22:28:23 +0100
committerJames Booth <boothj5@gmail.com>2016-10-10 22:28:23 +0100
commitdcc2123ec4d2210c8631b181cae9d817504e8b48 (patch)
treea8bb9c746aa6b2739e0ef6af9ce01f9966bff1bd
parentd485588a07f9d4a477a7615fee926fcfee44b29e (diff)
downloadprofani-tty-dcc2123ec4d2210c8631b181cae9d817504e8b48.tar.gz
Allow filepath autocompletion in plugins
closes #858
-rw-r--r--apidocs/c/profapi.h7
-rw-r--r--apidocs/python/src/prof.py14
-rw-r--r--src/command/cmd_ac.c219
-rw-r--r--src/command/cmd_ac.h2
-rw-r--r--src/plugins/api.c6
-rw-r--r--src/plugins/api.h1
-rw-r--r--src/plugins/autocompleters.c50
-rw-r--r--src/plugins/autocompleters.h1
-rw-r--r--src/plugins/c_api.c12
-rw-r--r--src/plugins/profapi.c1
-rw-r--r--src/plugins/profapi.h2
-rw-r--r--src/plugins/python_api.c25
12 files changed, 229 insertions, 111 deletions
diff --git a/apidocs/c/profapi.h b/apidocs/c/profapi.h
index 6e625f2c..5a8a11cb 100644
--- a/apidocs/c/profapi.h
+++ b/apidocs/c/profapi.h
@@ -94,6 +94,13 @@ Remove all values from autocompletion for a command, or command argument.
 void prof_completer_clear(const char *key);
 
 /**
+Add filepath autocompletion for a command, or command argument.
+
+@param prefix the prefix from which filepath autocompletion will be triggered
+*/
+void prof_filepath_completer_add(const char *prefix);
+
+/**
 Send a desktop notification.
 @param message the message to display in the notification
 @param timeout_ms the length of time before the notification disappears in milliseconds
diff --git a/apidocs/python/src/prof.py b/apidocs/python/src/prof.py
index 789e1f3e..69d5aaea 100644
--- a/apidocs/python/src/prof.py
+++ b/apidocs/python/src/prof.py
@@ -182,6 +182,20 @@ def completer_clear(key):
     pass
 
 
+def filepath_completer_add(prefix):
+    """Add filepath autocompletion for a command, or command argument.
+
+    :param prefix: the prefix from which filepath autocompletion will be triggered
+
+    Examples:
+    ::
+        prof.filepath_completer_add("/filecmd")
+
+        prof.filepath_completer_add("/mycommand open")
+    """
+    pass
+
+
 def send_line(line): 
     """Send a line of input to Profanity to execute.
 
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index 47655114..6d87a5d8 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -57,8 +57,6 @@
 #include "pgp/gpg.h"
 #endif
 
-static char* _complete_filepath(const char *const input, char *const startstr);
-
 static char* _sub_autocomplete(ProfWin *window, const char *const input);
 static char* _notify_autocomplete(ProfWin *window, const char *const input);
 static char* _theme_autocomplete(ProfWin *window, const char *const input);
@@ -1140,6 +1138,113 @@ cmd_ac_uninit(void)
     autocomplete_free(winpos_ac);
 }
 
+char*
+cmd_ac_complete_filepath(const char *const input, char *const startstr)
+{
+    static char* last_directory = NULL;
+
+    unsigned int output_off = 0;
+
+    char *result = NULL;
+    char *tmp;
+
+    // strip command
+    char *inpcp = (char*)input + strlen(startstr);
+    while (*inpcp == ' ') {
+        inpcp++;
+    }
+
+    inpcp = strdup(inpcp);
+
+    // strip quotes
+    if (*inpcp == '"') {
+        tmp = strchr(inpcp+1, '"');
+        if (tmp) {
+            *tmp = '\0';
+        }
+        tmp = strdup(inpcp+1);
+        free(inpcp);
+        inpcp = tmp;
+    }
+
+    // expand ~ to $HOME
+    if (inpcp[0] == '~' && inpcp[1] == '/') {
+        if (asprintf(&tmp, "%s/%sfoo", getenv("HOME"), inpcp+2) == -1) {
+            return NULL;
+        }
+        output_off = strlen(getenv("HOME"))+1;
+    } else {
+        if (asprintf(&tmp, "%sfoo", inpcp) == -1) {
+            return NULL;
+        }
+    }
+    free(inpcp);
+    inpcp = tmp;
+
+    char* inpcp2 = strdup(inpcp);
+    char* foofile = strdup(basename(inpcp2));
+    char* directory = strdup(dirname(inpcp));
+    free(inpcp);
+    free(inpcp2);
+
+    if (!last_directory || strcmp(last_directory, directory) != 0) {
+        free(last_directory);
+        last_directory = directory;
+        autocomplete_reset(filepath_ac);
+
+        struct dirent *dir;
+
+        DIR *d = opendir(directory);
+        if (d) {
+            while ((dir = readdir(d)) != NULL) {
+                if (strcmp(dir->d_name, ".") == 0) {
+                    continue;
+                } else if (strcmp(dir->d_name, "..") == 0) {
+                    continue;
+                } else if (*(dir->d_name) == '.' && *foofile != '.') {
+                    // only show hidden files on explicit request
+                    continue;
+                }
+                char * acstring;
+                if (output_off) {
+                    if (asprintf(&tmp, "%s/%s", directory, dir->d_name) == -1) {
+                        free(foofile);
+                        return NULL;
+                    }
+                    if (asprintf(&acstring, "~/%s", tmp+output_off) == -1) {
+                        free(foofile);
+                        return NULL;
+                    }
+                    free(tmp);
+                } else if (strcmp(directory, "/") == 0) {
+                    if (asprintf(&acstring, "/%s", dir->d_name) == -1) {
+                        free(foofile);
+                        return NULL;
+                    }
+                } else {
+                    if (asprintf(&acstring, "%s/%s", directory, dir->d_name) == -1) {
+                        free(foofile);
+                        return NULL;
+                    }
+                }
+                autocomplete_add(filepath_ac, acstring);
+                free(acstring);
+            }
+            closedir(d);
+        }
+    } else {
+        free(directory);
+    }
+    free(foofile);
+
+    result = autocomplete_param_with_ac(input, startstr, filepath_ac, TRUE);
+    if (result) {
+        return result;
+    }
+
+    return NULL;
+}
+
 static char*
 _cmd_ac_complete_params(ProfWin *window, const char *const input)
 {
@@ -1918,7 +2023,7 @@ _plugins_autocomplete(ProfWin *window, const char *const input)
     char *result = NULL;
 
     if (strncmp(input, "/plugins install ", 17) == 0) {
-        return _complete_filepath(input, "/plugins install");
+        return cmd_ac_complete_filepath(input, "/plugins install");
     }
 
     if (strncmp(input, "/plugins load ", 14) == 0) {
@@ -2736,7 +2841,7 @@ _close_autocomplete(ProfWin *window, const char *const input)
 static char*
 _sendfile_autocomplete(ProfWin *window, const char *const input)
 {
-    return _complete_filepath(input, "/sendfile");
+    return cmd_ac_complete_filepath(input, "/sendfile");
 }
 
 static char*
@@ -2945,109 +3050,3 @@ _presence_autocomplete(ProfWin *window, const char *const input)
     return NULL;
 }
 
-static char*
-_complete_filepath(const char *const input, char *const startstr)
-{
-    static char* last_directory = NULL;
-
-    unsigned int output_off = 0;
-
-    char *result = NULL;
-    char *tmp;
-
-    // strip command
-    char *inpcp = (char*)input + strlen(startstr);
-    while (*inpcp == ' ') {
-        inpcp++;
-    }
-
-    inpcp = strdup(inpcp);
-
-    // strip quotes
-    if (*inpcp == '"') {
-        tmp = strchr(inpcp+1, '"');
-        if (tmp) {
-            *tmp = '\0';
-        }
-        tmp = strdup(inpcp+1);
-        free(inpcp);
-        inpcp = tmp;
-    }
-
-    // expand ~ to $HOME
-    if (inpcp[0] == '~' && inpcp[1] == '/') {
-        if (asprintf(&tmp, "%s/%sfoo", getenv("HOME"), inpcp+2) == -1) {
-            return NULL;
-        }
-        output_off = strlen(getenv("HOME"))+1;
-    } else {
-        if (asprintf(&tmp, "%sfoo", inpcp) == -1) {
-            return NULL;
-        }
-    }
-    free(inpcp);
-    inpcp = tmp;
-
-    char* inpcp2 = strdup(inpcp);
-    char* foofile = strdup(basename(inpcp2));
-    char* directory = strdup(dirname(inpcp));
-    free(inpcp);
-    free(inpcp2);
-
-    if (!last_directory || strcmp(last_directory, directory) != 0) {
-        free(last_directory);
-        last_directory = directory;
-        autocomplete_reset(filepath_ac);
-
-        struct dirent *dir;
-
-        DIR *d = opendir(directory);
-        if (d) {
-            while ((dir = readdir(d)) != NULL) {
-                if (strcmp(dir->d_name, ".") == 0) {
-                    continue;
-                } else if (strcmp(dir->d_name, "..") == 0) {
-                    continue;
-                } else if (*(dir->d_name) == '.' && *foofile != '.') {
-                    // only show hidden files on explicit request
-                    continue;
-                }
-                char * acstring;
-                if (output_off) {
-                    if (asprintf(&tmp, "%s/%s", directory, dir->d_name) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
-                    if (asprintf(&acstring, "~/%s", tmp+output_off) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
-                    free(tmp);
-                } else if (strcmp(directory, "/") == 0) {
-                    if (asprintf(&acstring, "/%s", dir->d_name) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
-                } else {
-                    if (asprintf(&acstring, "%s/%s", directory, dir->d_name) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
-                }
-                autocomplete_add(filepath_ac, acstring);
-                free(acstring);
-            }
-            closedir(d);
-        }
-    } else {
-        free(directory);
-    }
-    free(foofile);
-
-    result = autocomplete_param_with_ac(input, startstr, filepath_ac, TRUE);
-    if (result) {
-        return result;
-    }
-
-    return NULL;
-}
diff --git a/src/command/cmd_ac.h b/src/command/cmd_ac.h
index 3d1069bc..78aec927 100644
--- a/src/command/cmd_ac.h
+++ b/src/command/cmd_ac.h
@@ -57,4 +57,6 @@ void cmd_ac_remove_alias_value(char *value);
 void cmd_ac_add_form_fields(DataForm *form);
 void cmd_ac_remove_form_fields(DataForm *form);
 
+char* cmd_ac_complete_filepath(const char *const input, char *const startstr);
+
 #endif
diff --git a/src/plugins/api.c b/src/plugins/api.c
index ac419ebe..b504237b 100644
--- a/src/plugins/api.c
+++ b/src/plugins/api.c
@@ -179,6 +179,12 @@ api_completer_clear(const char *const plugin_name, const char *key)
 }
 
 void
+api_filepath_completer_add(const char *const plugin_name, const char *prefix)
+{
+    autocompleters_filepath_add(plugin_name, prefix);
+}
+
+void
 api_notify(const char *message, const char *category, int timeout_ms)
 {
     notify(message, timeout_ms, category);
diff --git a/src/plugins/api.h b/src/plugins/api.h
index 98728eb6..94788615 100644
--- a/src/plugins/api.h
+++ b/src/plugins/api.h
@@ -59,6 +59,7 @@ void api_register_timed(const char *const plugin_name, void *callback, int inter
 void api_completer_add(const char *const plugin_name, const char *key, char **items);
 void api_completer_remove(const char *const plugin_name, const char *key, char **items);
 void api_completer_clear(const char *const plugin_name, const char *key);
+void api_filepath_completer_add(const char *const plugin_name, const char *prefix);
 
 void api_log_debug(const char *message);
 void api_log_info(const char *message);
diff --git a/src/plugins/autocompleters.c b/src/plugins/autocompleters.c
index a4dd80e7..496913e6 100644
--- a/src/plugins/autocompleters.c
+++ b/src/plugins/autocompleters.c
@@ -37,8 +37,10 @@
 #include <glib.h>
 
 #include "tools/autocomplete.h"
+#include "command/cmd_ac.h"
 
 static GHashTable *plugin_to_acs;
+static GHashTable *plugin_to_filepath_acs;
 
 static void
 _free_autocompleters(GHashTable *key_to_ac)
@@ -46,10 +48,17 @@ _free_autocompleters(GHashTable *key_to_ac)
     g_hash_table_destroy(key_to_ac);
 }
 
+static void
+_free_filepath_autocompleters(GHashTable *prefixes)
+{
+    g_hash_table_destroy(prefixes);
+}
+
 void
 autocompleters_init(void)
 {
     plugin_to_acs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_free_autocompleters);
+    plugin_to_filepath_acs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_free_filepath_autocompleters);
 }
 
 void
@@ -106,7 +115,20 @@ autocompleters_clear(const char *const plugin_name, const char *key)
     autocomplete_clear(ac);
 }
 
-char *
+void
+autocompleters_filepath_add(const char *const plugin_name, const char *prefix)
+{
+    GHashTable *prefixes = g_hash_table_lookup(plugin_to_filepath_acs, plugin_name);
+    if (prefixes) {
+        g_hash_table_add(prefixes, strdup(prefix));
+    } else {
+        prefixes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+        g_hash_table_add(prefixes, strdup(prefix));
+        g_hash_table_insert(plugin_to_filepath_acs, strdup(plugin_name), prefixes);
+    }
+}
+
+char*
 autocompleters_complete(const char * const input)
 {
     char *result = NULL;
@@ -121,6 +143,7 @@ autocompleters_complete(const char * const input)
         while (curr) {
             result = autocomplete_param_with_ac(input, curr->data, g_hash_table_lookup(key_to_ac, curr->data), TRUE);
             if (result) {
+                g_list_free(ac_hashes);
                 g_list_free(keys);
                 return result;
             }
@@ -132,6 +155,31 @@ autocompleters_complete(const char * const input)
     }
     g_list_free(ac_hashes);
 
+    GList *filepath_hashes = g_hash_table_get_values(plugin_to_filepath_acs);
+    curr_hash = filepath_hashes;
+    while (curr_hash) {
+        GHashTable *prefixes_hash = curr_hash->data;
+        GList *prefixes = g_hash_table_get_keys(prefixes_hash);
+        GList *curr_prefix = prefixes;
+        while (curr_prefix) {
+            char *prefix = curr_prefix->data;
+            if (g_str_has_prefix(input, prefix)) {
+                result = cmd_ac_complete_filepath(input, prefix);
+                if (result) {
+                    g_list_free(filepath_hashes);
+                    g_list_free(prefixes);
+                    return result;
+                }
+            }
+
+            curr_prefix = g_list_next(curr_prefix);
+        }
+        g_list_free(prefixes);
+
+        curr_hash = g_list_next(curr_hash);
+    }
+    g_list_free(filepath_hashes);
+
     return NULL;
 }
 
diff --git a/src/plugins/autocompleters.h b/src/plugins/autocompleters.h
index 15580514..22ec2ca4 100644
--- a/src/plugins/autocompleters.h
+++ b/src/plugins/autocompleters.h
@@ -41,6 +41,7 @@ void autocompleters_init(void);
 void autocompleters_add(const char *const plugin_name, const char *key, char **items);
 void autocompleters_remove(const char *const plugin_name, const char *key, char **items);
 void autocompleters_clear(const char *const plugin_name, const char *key);
+void autocompleters_filepath_add(const char *const plugin_name, const char *prefix);
 char* autocompleters_complete(const char * const input);
 void autocompleters_reset(void);
 void autocompleters_destroy(void);
diff --git a/src/plugins/c_api.c b/src/plugins/c_api.c
index 43428140..0b323268 100644
--- a/src/plugins/c_api.c
+++ b/src/plugins/c_api.c
@@ -143,6 +143,17 @@ c_api_completer_clear(const char *filename, const char *key)
 }
 
 static void
+c_api_filepath_completer_add(const char *filename, const char *prefix)
+{
+    char *plugin_name = _c_plugin_name(filename);
+    log_debug("Filepath autocomplete added '%s' for %s", prefix, plugin_name);
+
+    api_filepath_completer_add(plugin_name, prefix);
+
+    free(plugin_name);
+}
+
+static void
 c_api_notify(const char *message, int timeout_ms, const char *category)
 {
     api_notify(message, category, timeout_ms);
@@ -360,6 +371,7 @@ c_api_init(void)
     _prof_completer_add = c_api_completer_add;
     _prof_completer_remove = c_api_completer_remove;
     _prof_completer_clear = c_api_completer_clear;
+    _prof_filepath_completer_add = c_api_filepath_completer_add;
     _prof_win_create = c_api_win_create;
     prof_notify = c_api_notify;
     prof_send_line = c_api_send_line;
diff --git a/src/plugins/profapi.c b/src/plugins/profapi.c
index b2130a01..4601d403 100644
--- a/src/plugins/profapi.c
+++ b/src/plugins/profapi.c
@@ -51,6 +51,7 @@ void (*_prof_register_timed)(const char *filename, TIMED_CB callback, int interv
 void (*_prof_completer_add)(const char *filename, const char *key, char **items) = NULL;
 void (*_prof_completer_remove)(const char *filename, const char *key, char **items) = NULL;
 void (*_prof_completer_clear)(const char *filename, const char *key) = NULL;
+void (*_prof_filepath_completer_add)(const char *filename, const char *prefix) = NULL;
 
 void (*prof_notify)(const char *message, int timeout_ms, const char *category) = NULL;
 
diff --git a/src/plugins/profapi.h b/src/plugins/profapi.h
index e7119a2b..7bc2a9e9 100644
--- a/src/plugins/profapi.h
+++ b/src/plugins/profapi.h
@@ -40,6 +40,7 @@
 #define prof_completer_add(key, items) _prof_completer_add(__FILE__, key, items)
 #define prof_completer_remove(key, items) _prof_completer_remove(__FILE__, key, items)
 #define prof_completer_clear(key) _prof_completer_clear(__FILE__, key)
+#define prof_filepath_completer_add(prefix) _prof_filepath_completer_add(__FILE__, prefix)
 #define prof_win_create(win, input_handler) _prof_win_create(__FILE__, win, input_handler)
 #define prof_disco_add_feature(feature) _prof_disco_add_feature(__FILE__, feature)
 
@@ -62,6 +63,7 @@ void (*_prof_register_timed)(const char *filename, TIMED_CB callback, int interv
 void (*_prof_completer_add)(const char *filename, const char *key, char **items);
 void (*_prof_completer_remove)(const char *filename, const char *key, char **items);
 void (*_prof_completer_clear)(const char *filename, const char *key);
+void (*_prof_filepath_completer_add)(const char *filename, const char *prefix);
 
 void (*prof_notify)(const char *message, int timeout_ms, const char *category);
 
diff --git a/src/plugins/python_api.c b/src/plugins/python_api.c
index f2bce2e3..0b78b055 100644
--- a/src/plugins/python_api.c
+++ b/src/plugins/python_api.c
@@ -340,6 +340,30 @@ python_api_completer_clear(PyObject *self, PyObject *args)
 }
 
 static PyObject*
+python_api_filepath_completer_add(PyObject *self, PyObject *args)
+{
+    PyObject *prefix = NULL;
+
+    if (!PyArg_ParseTuple(args, "O", &prefix)) {
+        Py_RETURN_NONE;
+    }
+
+    char *prefix_str = python_str_or_unicode_to_string(prefix);
+
+    char *plugin_name = _python_plugin_name();
+    log_debug("Filepath autocomplete added '%s' for %s", prefix_str, plugin_name);
+
+    allow_python_threads();
+    api_filepath_completer_add(plugin_name, prefix_str);
+    free(prefix_str);
+    disable_python_threads();
+
+    free(plugin_name);
+
+    Py_RETURN_NONE;
+}
+
+static PyObject*
 python_api_notify(PyObject *self, PyObject *args)
 {
     PyObject *message = NULL;
@@ -1063,6 +1087,7 @@ static PyMethodDef apiMethods[] = {
     { "completer_add", python_api_completer_add, METH_VARARGS, "Add items to an autocompleter." },
     { "completer_remove", python_api_completer_remove, METH_VARARGS, "Remove items from an autocompleter." },
     { "completer_clear", python_api_completer_clear, METH_VARARGS, "Remove all items from an autocompleter." },
+    { "filepath_completer_add", python_api_filepath_completer_add, METH_VARARGS, "Add filepath autocompleter" },
     { "send_line", python_api_send_line, METH_VARARGS, "Send a line of input." },
     { "notify", python_api_notify, METH_VARARGS, "Send desktop notification." },
     { "get_current_recipient", python_api_get_current_recipient, METH_VARARGS, "Return the jid of the recipient of the current window." },