preliminary autocomplete, for commands only for now

This commit is contained in:
Florian Stinglmayr 2019-08-03 10:53:09 +02:00
parent 24e3cb62bc
commit c5c647b054
9 changed files with 376 additions and 0 deletions

View File

@ -7,6 +7,7 @@ PKG_CHECK_MODULES(CONFUSE REQUIRED libconfuse)
SET(TARGET "ncdc")
SET(SOURCES
"include/ncdc/autocomplete.h"
"include/ncdc/cmds.h"
"include/ncdc/config.h"
"include/ncdc/input.h"
@ -15,6 +16,7 @@ SET(SOURCES
"include/ncdc/textview.h"
"include/ncdc/treeview.h"
"src/ack.c"
"src/autocomplete.c"
"src/cmds.c"
"src/config.c"
"src/close.c"

View File

@ -0,0 +1,40 @@
/*
* Part of ncdc - a discord client for the console
* Copyright (C) 2019 Florian Stinglmayr <fstinglmayr@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef NCDC_AUTOCOMPLETE_H
#define NCDC_AUTOCOMPLETE_H
#include <ncdc/ncdc.h>
struct ncdc_autocomplete_;
typedef struct ncdc_autocomplete_ *ncdc_autocomplete_t;
ncdc_autocomplete_t ncdc_autocomplete_new(void);
bool ncdc_autocomplete_prepare(ncdc_autocomplete_t a,
wchar_t const *s, ssize_t sz,
size_t pos);
void ncdc_autocomplete_completions(ncdc_autocomplete_t a,
wchar_t **words, ssize_t num);
bool ncdc_autocomplete_complete(ncdc_autocomplete_t a, int *newpos);
void ncdc_autocomplete_reset(ncdc_autocomplete_t a);
int ncdc_autocomplete_word_index(ncdc_autocomplete_t a);
wchar_t const *ncdc_autocomplete_completed(ncdc_autocomplete_t a);
#endif

View File

@ -47,6 +47,8 @@ bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s);
* sub commands. for example usage see the friends command
*/
ncdc_commands_t *ncdc_find_cmd(ncdc_commands_t *cmds, wchar_t const *name);
wchar_t **ncdc_cmd_names(void);
size_t ncdc_cmd_names_size(void);
bool ncdc_cmd_ack(ncdc_mainwindow_t n, size_t ac, wchar_t **av, wchar_t const *f);
bool ncdc_cmd_close(ncdc_mainwindow_t n, size_t ac, wchar_t **av, wchar_t const *f);

View File

@ -21,6 +21,7 @@
#include <ncdc/ncdc.h>
#include <ncdc/keycodes.h>
#include <ncdc/autocomplete.h>
struct ncdc_input_;
typedef struct ncdc_input_ *ncdc_input_t;

View File

@ -89,9 +89,12 @@ char *read_char(FILE *stream);
int aswprintf(wchar_t **buffer, wchar_t const *fmt, ...);
char *w_convert(wchar_t const *w);
wchar_t* wcsndup(const wchar_t* string, size_t maxlen);
size_t w_strlenv(wchar_t **s);
void w_strfreev(wchar_t **s);
wchar_t **w_strdupv(wchar_t **s, ssize_t sz);
wchar_t *w_joinv(wchar_t const **v, size_t len);
wchar_t **w_tokenise(wchar_t const *w);
wchar_t *w_next_tok(wchar_t const *w);
wchar_t const *w_next_word(wchar_t const *w, ssize_t len);

215
ncdc/src/autocomplete.c Normal file
View File

@ -0,0 +1,215 @@
/*
* Part of ncdc - a discord client for the console
* Copyright (C) 2019 Florian Stinglmayr <fstinglmayr@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <ncdc/autocomplete.h>
struct ncdc_autocomplete_
{
dc_refable_t ref;
wchar_t *buffer;
wchar_t *word;
wchar_t *completed;
wchar_t **completions;
size_t completions_size;
size_t search_pos;
size_t buffer_size;
size_t pos;
size_t word_pos;
size_t start;
size_t end;
};
static void ncdc_autocomplete_free(ncdc_autocomplete_t a)
{
return_if_true(a == NULL,);
free(a->buffer);
a->buffer = NULL;
free(a->completed);
a->completed = NULL;
free(a->word);
a->word = NULL;
w_strfreev(a->completions);
a->completions = NULL;
free(a);
}
ncdc_autocomplete_t ncdc_autocomplete_new(void)
{
ncdc_autocomplete_t c = calloc(1, sizeof(struct ncdc_autocomplete_));
return_if_true(c == NULL, NULL);
c->ref.cleanup = (dc_cleanup_t)ncdc_autocomplete_free;
return dc_ref(c);
}
void ncdc_autocomplete_reset(ncdc_autocomplete_t a)
{
return_if_true(a == NULL,);
free(a->buffer);
a->buffer = NULL;
free(a->word);
a->word = NULL;
free(a->completed);
a->completed = NULL;
w_strfreev(a->completions);
a->completions = NULL;
a->completions_size = 0;
a->buffer_size = a->search_pos = a->pos = a->word_pos = a->start = a->end = 0;
}
bool ncdc_autocomplete_prepare(ncdc_autocomplete_t a,
wchar_t const *s, ssize_t sz,
size_t pos)
{
return_if_true(a == NULL || s == NULL, false);
size_t start = pos, end = pos;
ssize_t i = 0, words = 0;
bool inword = false;
return_if_true(a->buffer != NULL && a->word != NULL, true);
if (sz < 0) {
sz = wcslen(s);
}
/* figure out the current word for autocompletion
*/
for (start = pos; !iswspace(s[start]) && start > 0; start--)
;
for (end = pos; !iswspace(s[end]) && pos < sz; end++)
;
/* count words backwards, to figure out what position our
* current word has in the string. this is important for
* others because they might have positional arguments
*/
if (start > 0) {
for (i = start; i >= 0; i--) {
if (iswspace(s[i])) {
inword = false;
} else if (!inword) {
inword = true;
++words;
}
}
}
free(a->buffer);
a->buffer = wcsndup(s, sz);
a->buffer_size = sz;
a->start = start;
a->end = end;
a->pos = pos;
a->word_pos = words;
a->word = wcsndup(s + start, end);
return true;
}
void ncdc_autocomplete_completions(ncdc_autocomplete_t a,
wchar_t **words, ssize_t num)
{
return_if_true(a == NULL,);
w_strfreev(a->completions);
a->completions = NULL;
a->completions_size = 0;
if (words != NULL) {
a->completions = w_strdupv(words, num);
a->completions_size = w_strlenv(words);
}
}
int ncdc_autocomplete_word_index(ncdc_autocomplete_t a)
{
return_if_true(a == NULL, -1);
return a->word_pos;
}
bool ncdc_autocomplete_complete(ncdc_autocomplete_t a, int *newpos)
{
bool found = false;
size_t wordlen = 0, foundlen = 0;
wchar_t const *item = NULL;
return_if_true(a == NULL, false);
wordlen = wcslen(a->word);
for (; a->search_pos < a->completions_size; a->search_pos++) {
item = a->completions[a->search_pos];
if (wcsncmp(a->word, item, wordlen) == 0) {
found = true;
break;
}
}
++a->search_pos;
if (a->search_pos >= a->completions_size) {
a->search_pos = 0; /* wrap around search */
}
if (!found) {
return false;
}
free(a->completed);
a->completed = NULL;
foundlen = wcslen(item);
a->completed = calloc(wcslen(a->buffer) + foundlen + 1, sizeof(wchar_t));
return_if_true(a->completed == NULL, false);
wcsncat(a->completed, a->buffer, a->start);
wcscat(a->completed, item);
wcscat(a->completed, a->buffer + a->end);
if (newpos != NULL) {
*newpos = a->start + foundlen;
}
return true;
}
wchar_t const *ncdc_autocomplete_completed(ncdc_autocomplete_t a)
{
return_if_true(a == NULL, NULL);
return a->completed;
}

View File

@ -44,6 +44,9 @@ static pthread_t thr;
static pthread_mutex_t mtx;
static pthread_cond_t cnd;
static wchar_t **names;
static size_t names_size;
typedef struct {
ncdc_commands_t *cmd;
wchar_t *f;
@ -93,6 +96,8 @@ bool ncdc_dispatch_init(void)
{
return_if_true(queue != NULL, true);
size_t i = 0;
queue = g_queue_new();
return_if_true(queue == NULL, false);
@ -101,6 +106,16 @@ bool ncdc_dispatch_init(void)
pthread_create(&thr, NULL, async_dispatcher, NULL);
/* build a table of names for the autocompletion, note that
* the cmds array is already one size too large (to hold a NULL)
*/
names_size = (sizeof(cmds) / sizeof(ncdc_commands_t)) - 1;
names = calloc(names_size + 1, sizeof(wchar_t*));
for (i = 0; cmds[i].name != NULL; i++) {
names[i] = wcsdup(cmds[i].name);
}
return true;
}
@ -121,6 +136,10 @@ bool ncdc_dispatch_deinit(void)
g_queue_free(queue);
queue = NULL;
w_strfreev(names);
names = NULL;
names_size = 0;
return true;
}
@ -137,6 +156,16 @@ ncdc_commands_t *ncdc_find_cmd(ncdc_commands_t *cmds, wchar_t const *name)
return NULL;
}
wchar_t **ncdc_cmd_names(void)
{
return names;
}
size_t ncdc_cmd_names_size(void)
{
return names_size;
}
bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s)
{
wchar_t **tokens = NULL;

View File

@ -19,6 +19,7 @@
#include <ncdc/input.h>
#include <ncdc/ncdc.h>
#include <ncdc/keycodes.h>
#include <ncdc/cmds.h>
struct ncdc_input_
{
@ -30,6 +31,8 @@ struct ncdc_input_
ncdc_input_callback_t callback;
void *callback_arg;
ncdc_autocomplete_t autocomplete;
};
static void ncdc_input_enter(ncdc_input_t p);
@ -38,6 +41,8 @@ static void ncdc_input_free(ncdc_input_t p)
{
g_array_unref(p->buffer);
dc_unref(p->autocomplete);
free(p);
}
@ -53,9 +58,60 @@ ncdc_input_t ncdc_input_new(void)
p->keys = keys_emacs;
p->autocomplete = ncdc_autocomplete_new();
return p;
}
static void ncdc_input_autocomplete(ncdc_input_t i)
{
bool complete = false;
bool ret = false;
wchar_t const *completed = NULL;
int newpos = 0;
ret = ncdc_autocomplete_prepare(i->autocomplete,
(wchar_t const*)i->buffer->data, -1,
i->cursor
);
if (!ret) {
return;
}
if (ncdc_autocomplete_word_index(i->autocomplete) == 0) {
/* check if we have a command, and if so, autocomplete for
* commands.
*/
if (g_array_index(i->buffer, wchar_t, 0) == L'/') {
ncdc_autocomplete_completions(i->autocomplete,
ncdc_cmd_names(),
ncdc_cmd_names_size()
);
complete = true;
}
}
if (complete) {
ret = ncdc_autocomplete_complete(i->autocomplete, &newpos);
if (ret) {
completed = ncdc_autocomplete_completed(i->autocomplete);
/* exchange our buffer, for the autocompleted buffer
*/
g_array_remove_range(i->buffer, 0, i->buffer->len);
g_array_append_vals(i->buffer, completed, wcslen(completed));
i->cursor = newpos;
if (i->cursor >= i->buffer->len) {
wchar_t space = L' ';
g_array_append_val(i->buffer, space);
++i->cursor;
}
}
} else {
ncdc_autocomplete_reset(i->autocomplete);
}
}
void ncdc_input_feed(ncdc_input_t input, wchar_t const *c, size_t sz)
{
return_if_true(input == NULL,);
@ -63,11 +119,16 @@ void ncdc_input_feed(ncdc_input_t input, wchar_t const *c, size_t sz)
if (c[0] == '\r') {
ncdc_input_enter(input);
ncdc_autocomplete_reset(input->autocomplete);
} else if (c[0] == '\t') {
ncdc_input_autocomplete(input);
} else if ((bind = ncdc_find_keybinding(input->keys, c, sz)) != NULL) {
bind->handler(input);
ncdc_autocomplete_reset(input->autocomplete);
} else if (iswprint(c[0])) {
g_array_insert_vals(input->buffer, input->cursor, &c[0], 1);
input->cursor += wcswidth(&c[0], 1);
ncdc_autocomplete_reset(input->autocomplete);
}
}

View File

@ -113,6 +113,29 @@ void w_strfreev(wchar_t **s)
free(s);
}
wchar_t **w_strdupv(wchar_t **s, ssize_t sz)
{
wchar_t **copy = NULL;
size_t i = 0;
if (sz < 0) {
sz = w_strlenv(s);
}
copy = calloc(sz + 1, sizeof(wchar_t*));
return_if_true(copy == NULL, NULL);
for (i = 0; i < sz; i++) {
copy[i] = wcsdup(s[i]);
if (copy[i] == NULL) {
w_strfreev(copy);
return NULL;
}
}
return copy;
}
wchar_t **w_tokenise(wchar_t const *str)
{
wchar_t const *p = NULL, *start_of_word = NULL;