hirc

IRC client
Log | Files | Refs

commit 72898803cfc5b2670d25558aaeb6e8a342e64800
parent cce60d0b72958d5c374928f128c806abb66019e1
Author: hhvn <dev@hhvn.uk>
Date:   Sun, 20 Mar 2022 13:04:54 +0000

Tab completion

Diffstat:
Msrc/config.c | 6++++++
Msrc/hirc.h | 1+
Msrc/ui.c | 257++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 263 insertions(+), 1 deletion(-)

diff --git a/src/config.c b/src/config.c @@ -187,6 +187,12 @@ struct Config config[] = { .strhandle = NULL, .description = { "Message to send on /kill", NULL}}, + {"completion.hchar", 1, Val_string, + .str = ",", + .strhandle = NULL, + .description = { + "Character to place after hilightning a nick", + "(eg, \",\" -> \"hhvn, hi!\"", NULL}}, {"divider.toggle", 1, Val_bool, .num = 1, .numhandle = config_redrawl, diff --git a/src/hirc.h b/src/hirc.h @@ -139,6 +139,7 @@ void handle(struct Server *server, char *msg); void ui_init(void); #define ui_deinit() endwin() void ui_read(void); +void ui_complete(wchar_t *str, size_t size); int ui_input_insert(char c, int counter); int ui_input_delete(int num, int counter); void ui_redraw(void); diff --git a/src/ui.c b/src/ui.c @@ -476,6 +476,9 @@ ui_read(void) { if (input.string[input.counter]) input.counter++; break; + case '\t': + ui_complete(input.string, sizeof(input.string)); + break; case KEY_ENTER: case '\r': if (*input.string != L'\0') { @@ -496,12 +499,264 @@ ui_read(void) { input.string + input.counter, (wcslen(input.string + input.counter) + 1) * sizeof(wchar_t)); input.string[input.counter++] = (wchar_t)key; - input.string[input.counter] = 0; + } break; } } } +static void +ui_complete_stitch(wchar_t *dest, size_t dsize, unsigned *counter, unsigned coff, + wchar_t **stoks, size_t slen, + wchar_t *str, + wchar_t **etoks, size_t elen, + int fullcomplete) { + wchar_t **wp; + size_t i, dc = 0; + + for (wp = stoks, i = 0; i < slen; i++, wp++) + dc += swprintf(dest + dc, dsize - dc, L"%ls%s", *wp, (i != slen - 1 || str || elen) ? " " : ""); + + if (str) + dc += swprintf(dest + dc, dsize - dc, L"%ls%s", str, (fullcomplete || elen) ? " " : ""); + + if (!fullcomplete && elen) + coff -= 1; + *counter = dc + coff; + + for (wp = etoks, i = 0; i < elen; i++, wp++) + dc += swprintf(dest + dc, dsize - dc, L"%ls%s", *wp, i != elen - 1 ? " " : ""); +} + +static void +ui_complete_get_cmds(char *str, size_t len, char **ret, int *fullcomplete) { + int i, j; + + for (i = 0; commands[i].name; i++) { + if (strncmp(commands[i].name, str, len) == 0) { + if ((*ret)) { + (*fullcomplete) = 0; + for (j = 0; (*ret)[j] && commands[i].name[j]; j++) { + if ((*ret)[j] != commands[i].name[j]) { + (*ret)[j] = '\0'; + break; + } + } + } else (*ret) = estrdup(commands[i].name); + } + } +} + +static void +ui_complete_get_settings(char *str, size_t len, char **ret, int *fullcomplete) { + int i, j; + + for (i = 0; config[i].name; i++) { + if (strncmp(config[i].name, str, len) == 0) { + if ((*ret)) { + (*fullcomplete) = 0; + for (j = 0; (*ret)[j] && config[i].name[j]; j++) { + if ((*ret)[j] != config[i].name[j]) { + (*ret)[j] = '\0'; + break; + } + } + } else (*ret) = estrdup(config[i].name); + } + } +} + +static void +ui_complete_get_nicks(struct Channel *chan, char *str, size_t len, char **ret, int *fullcomplete) { + struct Nick *np; + int i, j; + + for (np = chan->nicks; np; np = np->next) { + if (!np->self && strncmp(np->nick, str, len) == 0) { + if ((*ret)) { + (*fullcomplete) = 0; + for (j = 0; (*ret)[j] && np->nick[j]; j++) { + if ((*ret)[j] != np->nick[j]) { + (*ret)[j] = '\0'; + break; + } + } + } else (*ret) = estrdup(np->nick); + } + } +} + +void +ui_complete(wchar_t *str, size_t size) { + wchar_t *wstem = NULL; + char *stem = NULL; + static int pctok, prcnt; + wchar_t **_toks; + wchar_t **toks; + wchar_t *cmd; + size_t tokn, i, j, len; + wchar_t *wp, *dup, *save; + char *found = NULL, *p; + int ctok = -1, rcnt = -1; /* toks[ctok] + rcnt == char before cursor */ + unsigned coff = 0; /* str + coff == input.string */ + int fullcomplete = 1; + int type; + + /* start at 1: 'a b c' -> 2 spaces, but 3 tokens */ + for (wp = str, tokn = 1, i = j = 0; wp && *wp; wp++, i++, j++) { + if (i == input.counter || (*(wp+1) == '\0' && rcnt == -1)) { + if (*wp == ' ' && *(wp+1) == '\0' && i + 1 == input.counter) { + ctok = tokn; + rcnt = 0; + } else { + ctok = tokn - 1; + rcnt = j; + if (i != input.counter) + rcnt++; + } + } + if (*wp == L' ') { + j = -1; + tokn++; + } + } + + _toks = toks = emalloc(tokn * sizeof(wchar_t *)); + dup = ewcsdup(str); + wp = NULL; + i = 0; + memset(toks, 0, tokn * sizeof(wchar_t *)); + while ((wp = wcstok(!wp ? dup : NULL, L" ", &save)) && i < tokn) + *(_toks + i++) = wp; + +getcmd: + if (*str == L'/' && tokn) + cmd = toks[0] + 1; + else + cmd = NULL; + + /* /server network /comman<cursor --> /comman<cursor> */ + if (cmd && (wcscmp(cmd, L"server") == 0 || + wcscmp(cmd, L"alias") == 0 || + wcscmp(cmd, L"bind") == 0) && tokn >= 2) { + i = wcslen(toks[0]) + wcslen(toks[1]) + (tokn > 2 ? 2 : 1); + str += i; + coff += i; + size -= i; + + toks += 2; + tokn -= 2; + ctok -= 2; + goto getcmd; + } + + /* complete commands */ + if (cmd && ctok == 0) { + wstem = toks[0] + 1; + + stem = wctos(wstem); + len = strlen(stem); + + ui_complete_get_cmds(stem, len, &found, &fullcomplete); + free(stem); + + if (found) { + len = strlen(found) + 2; + p = emalloc(len); + snprintf(p, len, "/%s", found); + free(found); + found = p; + + wp = stowc(found); + free(found); + ui_complete_stitch(str, size, &input.counter, coff, + NULL, 0, + wp, + toks + 1, tokn - 1, + fullcomplete); + free(wp); + goto end; + } + free(found); + found = NULL; + } + + /* complete commands/variables as arguments */ + type = 0; + if (cmd) { + if (wcscmp(cmd, L"help") == 0) + type = 1; + else if (wcscmp(cmd, L"set") == 0) + type = 2; + else if (wcscmp(cmd, L"format") == 0) + type = 3; + if (type && ctok == 1) { + wstem = toks[1]; + + p = wctos(wstem); + if (type == 3) { + len = strlen(p) + 8; /* format.\0 */ + stem = emalloc(len); + snprintf(stem, len, "format.%s", p); + } else stem = p; + len = strlen(stem); + + if (type == 1) + ui_complete_get_cmds(stem, len, &found, &fullcomplete); + ui_complete_get_settings(stem, len, &found, &fullcomplete); + free(stem); + + if (found) { + if (type == 3) + p = found + 7; /* format. */ + else + p = found; + wp = stowc(p); + ui_complete_stitch(str, size, &input.counter, coff, + toks, 1, + wp, + toks + 2, tokn - 2, + fullcomplete); + free(wp); + free(found); + goto end; + } + free(found); + found = NULL; + } + } + + /* complete nicks */ + if (selected.channel && ctok > -1 && toks[ctok] && *toks[ctok]) { + wstem = toks[ctok]; + stem = wctos(wstem); + len = strlen(stem); + + ui_complete_get_nicks(selected.channel, stem, len, &found, &fullcomplete); + free(stem); + + if (found) { + wp = stowc(found); + ui_complete_stitch(str, size, &input.counter, coff, + toks, ctok, + wp, + toks + ctok + 1, tokn - ctok - 1, + fullcomplete); + free(wp); + free(found); + goto end; + } + free(found); + found = NULL; + } + +end: + free(_toks); + /* elements in _toks are pointers to dup */ + free(dup); + return; +} + void ui_redraw(void) { struct History *p;