commit 0a6bec51259e91ee20b8e4b755096d2deca6e739
parent 98094718f072978b3a1273a19c2116bd27bddbf9
Author: hhvn <dev@hhvn.uk>
Date: Fri, 8 Apr 2022 17:22:56 +0100
Server completion
Diffstat:
M | Makefile | | | 2 | +- |
A | src/complete.c | | | 365 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/hirc.h | | | 3 | +++ |
M | src/ui.c | | | 286 | +------------------------------------------------------------------------------ |
4 files changed, 370 insertions(+), 286 deletions(-)
diff --git a/Makefile b/Makefile
@@ -20,7 +20,7 @@ MANDIR = $(PREFIX)/share/man
BIN = hirc
SRC = src/main.c src/mem.c src/handle.c src/hist.c \
src/nick.c src/chan.c src/serv.c src/ui.c \
- src/commands.c src/config.c
+ src/complete.c src/commands.c src/config.c
OBJ = $(SRC:.c=.o)
MAN = hirc.1
COMMIT = $(shell git log HEAD...HEAD~1 --pretty=format:%h)
diff --git a/src/complete.c b/src/complete.c
@@ -0,0 +1,365 @@
+/*
+ * src/complete.c from hirc
+ *
+ * Copyright (c) 2022 hhvn <dev@hhvn.uk>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include <string.h>
+#include <libgen.h>
+#include <dirent.h>
+#include "hirc.h"
+
+void 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);
+void complete_add(char **ret, char *str, int *fullcomplete);
+void complete_cmds(char *str, size_t len, char **ret, int *fullcomplete);
+void complete_settings(char *str, size_t len, char **ret, int *fullcomplete);
+void complete_nicks(struct Channel *chan, char *str, size_t len, char **ret, int *fullcomplete);
+void complete_servers(struct Server **head, char *str, size_t len, char **ret, int *fullcomplete);
+void complete_files(char *str, size_t len, char **ret, int *fullcomplete);
+
+void
+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 ? " " : "");
+}
+
+void
+complete_add(char **ret, char *str, int *fullcomplete) {
+ int i;
+
+ if ((*ret)) {
+ (*fullcomplete) = 0;
+ for (i = 0; (*ret)[i] && str[i] && (*ret)[i] == str[i]; i++);
+ (*ret)[i] = '\0';
+ } else (*ret) = estrdup(str);
+}
+
+void
+complete_cmds(char *str, size_t len, char **ret, int *fullcomplete) {
+ int i;
+ for (i = 0; commands[i].name; i++)
+ if (strncmp(commands[i].name, str, len) == 0)
+ complete_add(ret, commands[i].name, fullcomplete);
+}
+
+void
+complete_settings(char *str, size_t len, char **ret, int *fullcomplete) {
+ int i;
+ for (i = 0; config[i].name; i++)
+ if (strncmp(config[i].name, str, len) == 0)
+ complete_add(ret, config[i].name, fullcomplete);
+}
+
+void
+complete_nicks(struct Channel *chan, char *str, size_t len, char **ret, int *fullcomplete) {
+ struct Nick *np;
+ for (np = chan->nicks; np; np = np->next)
+ if (!np->self && strncmp(np->nick, str, len) == 0)
+ complete_add(ret, np->nick, fullcomplete);
+}
+
+void
+complete_servers(struct Server **head, char *str, size_t len, char **ret, int *fullcomplete) {
+ struct Server *sp;
+ for (sp = *head; sp; sp = sp->next)
+ if (strncmp(sp->name, str, len) == 0)
+ complete_add(ret, sp->name, fullcomplete);
+}
+
+void
+complete_files(char *str, size_t len, char **ret, int *fullcomplete) {
+ char *cpy[2], *dir, *file;
+ struct dirent **dirent;
+ int dirs, i;
+
+ cpy[0] = estrdup(str);
+ cpy[1] = estrdup(str);
+ dir = dirname(cpy[0]);
+ file = basename(cpy[1]);
+
+ if ((dirs = scandir(dir, &dirent, NULL, alphasort)) >= 0) {
+ for (i = 0; i < dirs; i++) {
+ if (strncmp(dirent[i]->d_name, str, len) == 0)
+ complete_add(ret, dirent[i]->d_name, fullcomplete);
+ pfree(&dirent[i]);
+ }
+ pfree(&dirent);
+ }
+
+ pfree(&cpy[0]);
+ pfree(&cpy[1]);
+}
+
+void
+complete(wchar_t *str, size_t size, unsigned *counter) {
+ 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, *hchar;
+ int ctok = -1, rcnt = -1; /* toks[ctok] + rcnt == char before cursor */
+ unsigned coff = 0; /* str + coff == *counter */
+ 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 == *counter || (*(wp+1) == '\0' && rcnt == -1)) {
+ if (*wp == ' ' && *(wp+1) == '\0' && i + 1 == *counter) {
+ ctok = tokn;
+ rcnt = 0;
+ } else {
+ ctok = tokn - 1;
+ rcnt = j;
+ if (i != *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>
+ * /server [-auto] network<cursor> --> ... nah */
+ if (cmd && ((wcscmp(cmd, L"server") == 0 && ctok != 1 && (tokn <= 2 || wcscmp(toks[1], L"-auto") != 0 || ctok != 2)) ||
+ wcscmp(cmd, L"alias") == 0 ||
+ wcscmp(cmd, L"bind") == 0) && tokn >= 2) {
+ if (tokn > 2 && wcscmp(toks[1], L"-auto") == 0)
+ i = 3;
+ else if (tokn > 2)
+ i = 2;
+ else
+ i = 1;
+ j = 1 + wcslen(toks[0]);
+ if (i >= 2)
+ j += 1 + wcslen(toks[1]);
+ if (i >= 3)
+ j += 1 + wcslen(toks[2]);
+ str += j;
+ coff += j;
+ size -= j;
+
+ toks += i;
+ tokn -= i;
+ ctok -= i;
+ goto getcmd;
+ }
+
+ /* complete commands */
+ if (cmd && ctok == 0) {
+ wstem = toks[0] + 1;
+
+ stem = wctos(wstem);
+ len = strlen(stem);
+
+ complete_cmds(stem, len, &found, &fullcomplete);
+ pfree(&stem);
+
+ if (found) {
+ len = strlen(found) + 2;
+ p = emalloc(len);
+ snprintf(p, len, "/%s", found);
+ pfree(&found);
+ found = p;
+
+ wp = stowc(found);
+ pfree(&found);
+ complete_stitch(str, size, counter, coff,
+ NULL, 0,
+ wp,
+ toks + 1, tokn - 1,
+ fullcomplete);
+ pfree(&wp);
+ goto end;
+ }
+ pfree(&found);
+ }
+
+ /* 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 && toks[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)
+ complete_cmds(stem, len, &found, &fullcomplete);
+ complete_settings(stem, len, &found, &fullcomplete);
+ pfree(&stem);
+
+ if (found) {
+ if (type == 3)
+ p = found + 7; /* format. */
+ else
+ p = found;
+ wp = stowc(p);
+ complete_stitch(str, size, counter, coff,
+ toks, 1,
+ wp,
+ toks + 2, tokn - 2,
+ fullcomplete);
+ pfree(&wp);
+ pfree(&found);
+ goto end;
+ }
+ pfree(&found);
+ }
+ }
+
+ /* complete nicks */
+ if (selected.channel && ctok > -1 && toks[ctok] && *toks[ctok]) {
+ wstem = toks[ctok];
+ stem = wctos(wstem);
+ len = strlen(stem);
+
+ complete_nicks(selected.channel, stem, len, &found, &fullcomplete);
+ pfree(&stem);
+
+ if (found) {
+ if (ctok == 0 && fullcomplete) {
+ hchar = config_gets("completion.hchar");
+ len = strlen(found) + strlen(hchar) + 1;
+ p = emalloc(len);
+ snprintf(p, len, "%s%s", found, hchar);
+ pfree(&found);
+ found = p;
+ }
+ wp = stowc(found);
+ complete_stitch(str, size, counter, coff,
+ toks, ctok,
+ wp,
+ toks + ctok + 1, tokn - ctok - 1,
+ fullcomplete);
+ pfree(&wp);
+ pfree(&found);
+ goto end;
+ }
+ pfree(&found);
+ }
+
+ /* complete filenames with /source and /dump */
+ if (cmd && (wcscmp(cmd, L"source") == 0 || wcscmp(cmd, L"dump") == 0) && ctok > 0 &&
+ toks[ctok] && *toks[ctok] && *toks[ctok] != L'-' && ctok == tokn - 1) {
+ wstem = toks[ctok];
+ stem = wctos(wstem);
+ len = strlen(stem);
+
+ complete_files(stem, len, &found, &fullcomplete);
+ pfree(&stem);
+
+ if (found) {
+ wp = stowc(found);
+ complete_stitch(str, size, counter, coff,
+ toks, tokn - 1,
+ wp,
+ NULL, 0,
+ fullcomplete);
+ pfree(&wp);
+ pfree(&found);
+ goto end;
+ }
+ pfree(&found);
+ }
+
+ /* complete servers with /server */
+ if (cmd && wcscmp(cmd, L"server") == 0) {
+ if (toks[1] && wcscmp(toks[1], L"-auto") == 0)
+ i = 2;
+ else
+ i = 1;
+ if (i == ctok && toks[ctok] && *toks[ctok] != L'-' && *toks[ctok]) {
+ wstem = toks[ctok];
+ stem = wctos(wstem);
+ len = strlen(stem);
+
+ complete_servers(&servers, stem, len, &found, &fullcomplete);
+ pfree(&stem);
+
+ if (found) {
+ wp = stowc(found);
+ complete_stitch(str, size, counter, coff,
+ toks, ctok,
+ wp,
+ toks + ctok + 1, tokn - ctok - 1,
+ fullcomplete);
+ pfree(&wp);
+ pfree(&found);
+ goto end;
+ }
+ pfree(&found);
+ }
+ }
+
+end:
+ pfree(&_toks);
+ /* elements in _toks are pointers to dup */
+ pfree(&dup);
+ return;
+}
diff --git a/src/hirc.h b/src/hirc.h
@@ -193,6 +193,9 @@ void config_sets(char *name, char *str);
void config_setr(char *name, long a, long b);
void config_read(char *filename);
+/* complete.c */
+void complete(wchar_t *str, size_t size, unsigned *counter);
+
/* main.c */
extern struct Server *servers;
extern struct HistInfo *main_buf;
diff --git a/src/ui.c b/src/ui.c
@@ -25,8 +25,6 @@
#include <string.h>
#include <stdlib.h>
#include <locale.h>
-#include <dirent.h>
-#include <libgen.h>
#include <ncurses.h>
#ifdef TLS
#include <tls.h>
@@ -484,7 +482,7 @@ ui_read(void) {
input.counter++;
break;
case '\t':
- ui_complete(input.string, sizeof(input.string));
+ complete(input.string, sizeof(input.string), &input.counter);
break;
case KEY_ENTER:
case '\r':
@@ -512,288 +510,6 @@ ui_read(void) {
}
}
-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_add(char **ret, char *str, int *fullcomplete) {
- int i;
-
- if ((*ret)) {
- (*fullcomplete) = 0;
- for (i = 0; (*ret)[i] && str[i] && (*ret)[i] == str[i]; i++);
- (*ret)[i] = '\0';
- } else (*ret) = estrdup(str);
-}
-
-static void
-ui_complete_cmds(char *str, size_t len, char **ret, int *fullcomplete) {
- int i;
- for (i = 0; commands[i].name; i++)
- if (strncmp(commands[i].name, str, len) == 0)
- ui_complete_add(ret, commands[i].name, fullcomplete);
-}
-
-static void
-ui_complete_settings(char *str, size_t len, char **ret, int *fullcomplete) {
- int i;
- for (i = 0; config[i].name; i++)
- if (strncmp(config[i].name, str, len) == 0)
- ui_complete_add(ret, config[i].name, fullcomplete);
-}
-
-static void
-ui_complete_nicks(struct Channel *chan, char *str, size_t len, char **ret, int *fullcomplete) {
- struct Nick *np;
- for (np = chan->nicks; np; np = np->next)
- if (!np->self && strncmp(np->nick, str, len) == 0)
- ui_complete_add(ret, np->nick, fullcomplete);
-}
-
-static void
-ui_complete_files(char *str, size_t len, char **ret, int *fullcomplete) {
- char *cpy[2], *dir, *file;
- struct dirent **dirent;
- int dirs, i;
-
- cpy[0] = estrdup(str);
- cpy[1] = estrdup(str);
- dir = dirname(cpy[0]);
- file = basename(cpy[1]);
-
- if ((dirs = scandir(dir, &dirent, NULL, alphasort)) >= 0) {
- for (i = 0; i < dirs; i++) {
- if (strncmp(dirent[i]->d_name, str, len) == 0)
- ui_complete_add(ret, dirent[i]->d_name, fullcomplete);
- free(dirent[i]);
- }
- free(dirent);
- }
-
- free(cpy[0]);
- free(cpy[1]);
-}
-
-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, *hchar;
- 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_cmds(stem, len, &found, &fullcomplete);
- pfree(&stem);
-
- if (found) {
- len = strlen(found) + 2;
- p = emalloc(len);
- snprintf(p, len, "/%s", found);
- pfree(&found);
- found = p;
-
- wp = stowc(found);
- pfree(&found);
- ui_complete_stitch(str, size, &input.counter, coff,
- NULL, 0,
- wp,
- toks + 1, tokn - 1,
- fullcomplete);
- pfree(&wp);
- goto end;
- }
- pfree(&found);
- }
-
- /* 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 && toks[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_cmds(stem, len, &found, &fullcomplete);
- ui_complete_settings(stem, len, &found, &fullcomplete);
- pfree(&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);
- pfree(&wp);
- pfree(&found);
- goto end;
- }
- pfree(&found);
- }
- }
-
- /* complete nicks */
- if (selected.channel && ctok > -1 && toks[ctok] && *toks[ctok]) {
- wstem = toks[ctok];
- stem = wctos(wstem);
- len = strlen(stem);
-
- ui_complete_nicks(selected.channel, stem, len, &found, &fullcomplete);
- pfree(&stem);
-
- if (found) {
- if (ctok == 0 && fullcomplete) {
- hchar = config_gets("completion.hchar");
- len = strlen(found) + strlen(hchar) + 1;
- p = emalloc(len);
- snprintf(p, len, "%s%s", found, hchar);
- pfree(&found);
- found = p;
- }
- wp = stowc(found);
- ui_complete_stitch(str, size, &input.counter, coff,
- toks, ctok,
- wp,
- toks + ctok + 1, tokn - ctok - 1,
- fullcomplete);
- pfree(&wp);
- pfree(&found);
- goto end;
- }
- pfree(&found);
- }
-
- /* complete filenames with /source and /dump */
- if (cmd && (wcscmp(cmd, L"source") == 0 || wcscmp(cmd, L"dump") == 0) && ctok > 0 &&
- toks[ctok] && *toks[ctok] && *toks[ctok] != L'-' && ctok == tokn - 1) {
- wstem = toks[ctok];
- stem = wctos(wstem);
- len = strlen(stem);
-
- ui_complete_files(stem, len, &found, &fullcomplete);
- pfree(&stem);
-
- if (found) {
- wp = stowc(found);
- ui_complete_stitch(str, size, &input.counter, coff,
- toks, tokn - 1,
- wp,
- NULL, 0,
- fullcomplete);
- pfree(&wp);
- pfree(&found);
- goto end;
- }
- pfree(&found);
- }
-
-end:
- pfree(&_toks);
- /* elements in _toks are pointers to dup */
- pfree(&dup);
- return;
-}
-
void
ui_redraw(void) {
struct History *p;