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 @@ -93,6 +93,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 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 @@ -178,6 +178,12 @@ api_completer_clear(const char *const plugin_name, const char *key) autocompleters_clear(plugin_name, 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) { 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 #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 @@ -142,6 +142,17 @@ c_api_completer_clear(const char *filename, const char *key) free(plugin_name); } +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) { @@ -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 @@ -339,6 +339,30 @@ python_api_completer_clear(PyObject *self, PyObject *args) Py_RETURN_NONE; } +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) { @@ -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." },