1
0
mirror of https://github.com/profanity-im/profanity.git synced 2024-06-16 21:35:24 +00:00

Allow filepath autocompletion in plugins

closes #858
This commit is contained in:
James Booth 2016-10-10 22:28:23 +01:00
parent d485588a07
commit dcc2123ec4
12 changed files with 229 additions and 111 deletions

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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)
{

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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." },