hirc

[archived] IRC client
git clone https://hhvn.uk/hirc
git clone git://hhvn.uk/hirc
Log | Files | Refs

complete.c (9772B)


      1 /*
      2  * src/complete.c from hirc
      3  *
      4  * Copyright (c) 2022 hhvn <dev@hhvn.uk>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  */
     19 
     20 #include <string.h>
     21 #include <libgen.h>
     22 #include <limits.h>
     23 #include <dirent.h>
     24 #include <sys/stat.h>
     25 #include "hirc.h"
     26 
     27 void complete_stitch(wchar_t *dest, size_t dsize, unsigned *counter, unsigned coff,
     28 		wchar_t **stoks, size_t slen,
     29 		wchar_t *str,
     30 		wchar_t **etoks, size_t elen,
     31 		int fullcomplete);
     32 void complete_add(char **ret, char *str, int *fullcomplete);
     33 void complete_cmds(char *str, size_t len, char **ret, int *fullcomplete);
     34 void complete_settings(char *str, size_t len, char **ret, int *fullcomplete);
     35 void complete_nicks(struct Channel *chan, char *str, size_t len, char **ret, int *fullcomplete);
     36 void complete_servers(struct Server **head, char *str, size_t len, char **ret, int *fullcomplete);
     37 void complete_files(char *str, char **ret, int *fullcomplete);
     38 
     39 void
     40 complete_stitch(wchar_t *dest, size_t dsize, unsigned *counter, unsigned coff,
     41 		wchar_t **stoks, size_t slen,
     42 		wchar_t *str,
     43 		wchar_t **etoks, size_t elen,
     44 		int fullcomplete) {
     45 	wchar_t **wp;
     46 	size_t i, dc = 0;
     47 
     48 	for (wp = stoks, i = 0; i < slen; i++, wp++)
     49 		dc += swprintf(dest + dc, dsize - dc, L"%ls%s", *wp, (i != slen - 1 || str || elen) ? " " : "");
     50 
     51 	if (str)
     52 		dc += swprintf(dest + dc, dsize - dc, L"%ls%s", str, (fullcomplete || elen) ? " " : "");
     53 
     54 	if (!fullcomplete && elen)
     55 		coff -= 1;
     56 	*counter = dc + coff;
     57 
     58 	for (wp = etoks, i = 0; i < elen; i++, wp++)
     59 		dc += swprintf(dest + dc, dsize - dc, L"%ls%s", *wp, i != elen - 1 ? " " : "");
     60 }
     61 
     62 void
     63 complete_add(char **ret, char *str, int *fullcomplete) {
     64 	int i;
     65 
     66 	if ((*ret)) {
     67 		(*fullcomplete) = 0;
     68 		for (i = 0; (*ret)[i] && str[i] && (*ret)[i] == str[i]; i++);
     69 		(*ret)[i] = '\0';
     70 	} else (*ret) = estrdup(str);
     71 }
     72 
     73 void
     74 complete_cmds(char *str, size_t len, char **ret, int *fullcomplete) {
     75 	struct Alias *p;
     76 	char *tmp;
     77 	int i;
     78 	for (i = 0; commands[i].name; i++)
     79 		if (strncmp(commands[i].name, str, len) == 0)
     80 			complete_add(ret, commands[i].name, fullcomplete);
     81 	tmp = smprintf(len + 2, "/%s", str);
     82 	for (p = aliases; p; p = p->next)
     83 		if (strncmp(p->alias, tmp, len + 1) == 0)
     84 			complete_add(ret, p->alias + 1, fullcomplete);
     85 	pfree(&tmp);
     86 }
     87 
     88 void
     89 complete_settings(char *str, size_t len, char **ret, int *fullcomplete) {
     90 	int i;
     91 	for (i = 0; config[i].name; i++)
     92 		if (strncmp(config[i].name, str, len) == 0)
     93 			complete_add(ret, config[i].name, fullcomplete);
     94 }
     95 
     96 void
     97 complete_nicks(struct Channel *chan, char *str, size_t len, char **ret, int *fullcomplete) {
     98 	struct Nick *np;
     99 	for (np = chan->nicks; np; np = np->next)
    100 		if (!np->self && strncmp(np->nick, str, len) == 0)
    101 			complete_add(ret, np->nick, fullcomplete);
    102 }
    103 
    104 void
    105 complete_servers(struct Server **head, char *str, size_t len, char **ret, int *fullcomplete) {
    106 	struct Server *sp;
    107 	for (sp = *head; sp; sp = sp->next)
    108 		if (strncmp(sp->name, str, len) == 0)
    109 			complete_add(ret, sp->name, fullcomplete);
    110 }
    111 
    112 void
    113 complete_files(char *str, char **ret, int *fullcomplete) {
    114 	char *cpy[2], *dir, *base;
    115 	char path[PATH_MAX], *p;
    116 	struct dirent **dirent;
    117 	struct stat st;
    118 	int dirs, i;
    119 	size_t len;
    120 
    121 	cpy[0] = estrdup(homepath(str));
    122 	cpy[1] = estrdup(cpy[0]);
    123 	dir = estrdup(dirname(cpy[0]));
    124 	base = basename(cpy[1]);
    125 	len = strlen(base);
    126 
    127 	if ((dirs = scandir(dir, &dirent, NULL, alphasort)) >= 0) {
    128 		if (*(p = dir + strlen(dir) - 1) == '/')
    129 			*p = '\0';
    130 		for (i = 0; i < dirs; i++) {
    131 			if (strncmp(dirent[i]->d_name, base, len) == 0) {
    132 				snprintf(path, sizeof(path), "%s/%s", dir, dirent[i]->d_name);
    133 				complete_add(ret, path, fullcomplete);
    134 			}
    135 			pfree(&dirent[i]);
    136 		}
    137 		pfree(&dirent);
    138 	}
    139 
    140 	/* A directory isn't a full completion */
    141 	if (*fullcomplete && stat(*ret, &st) == 0 && S_ISDIR(st.st_mode)) {
    142 		snprintf(path, sizeof(path), "%s/", *ret);
    143 		pfree(ret); /* already a pointer to the pointer */
    144 		*ret = estrdup(path);
    145 		*fullcomplete = 0;
    146 	}
    147 
    148 	pfree(&cpy[0]);
    149 	pfree(&cpy[1]);
    150 	pfree(&dir);
    151 }
    152 
    153 void
    154 complete(wchar_t *str, size_t size, unsigned *counter) {
    155 	wchar_t *wstem = NULL;
    156 	char *stem = NULL;
    157 	wchar_t **_toks;
    158 	wchar_t **toks;
    159 	wchar_t *cmd;
    160 	size_t tokn, i, j, len;
    161 	wchar_t *wp, *dup, *save;
    162 	char *found = NULL, *p, *hchar;
    163 	int ctok = -1, rcnt = -1; /* toks[ctok] + rcnt == char before cursor */
    164 	unsigned coff = 0; /* str + coff == *counter */
    165 	int fullcomplete = 1;
    166 	int type;
    167 
    168 	/* start at 1: 'a b c' -> 2 spaces, but 3 tokens */
    169 	for (wp = str, tokn = 1, i = j = 0; wp && *wp; wp++, i++, j++) {
    170 		if (i == *counter || (*(wp+1) == '\0' && rcnt == -1)) {
    171 			if (*wp == ' ' && *(wp+1) == '\0' && i + 1 == *counter) {
    172 				ctok = tokn;
    173 				rcnt = 0;
    174 			} else {
    175 				ctok = tokn - 1;
    176 				rcnt = j;
    177 				if (i != *counter)
    178 					rcnt++;
    179 			}
    180 		}
    181 		if (*wp == L' ') {
    182 			j = -1;
    183 			tokn++;
    184 		}
    185 	}
    186 
    187 	_toks = toks = emalloc(tokn * sizeof(wchar_t *));
    188 	dup = ewcsdup(str);
    189 	wp = NULL;
    190 	i = 0;
    191 	memset(toks, 0, tokn * sizeof(wchar_t *));
    192 	while ((wp = wcstok(!wp ? dup : NULL, L" ", &save)) && i < tokn)
    193 		*(_toks + i++) = wp;
    194 
    195 getcmd:
    196 	if (*str == L'/' && tokn)
    197 		cmd = toks[0] + 1;
    198 	else
    199 		cmd = NULL;
    200 
    201 	/* /server network /comman<cursor --> /comman<cursor>
    202 	 * /server [-auto] network<cursor> --> ... nah */
    203 	if (cmd && ((wcscmp(cmd, L"server") == 0 && ctok != 1 && (tokn <= 2 || wcscmp(toks[1], L"-auto") != 0 || ctok != 2)) ||
    204 			wcscmp(cmd, L"alias") == 0 ||
    205 			wcscmp(cmd, L"bind") == 0) && tokn >= 2) {
    206 		if (tokn > 2 && wcscmp(toks[1], L"-auto") == 0)
    207 			i = 3;
    208 		else if (tokn > 2)
    209 			i = 2;
    210 		else
    211 			i = 1;
    212 		j = 1 + wcslen(toks[0]);
    213 		if (i >= 2)
    214 			j += 1 + wcslen(toks[1]);
    215 		if (i >= 3)
    216 			j += 1 + wcslen(toks[2]);
    217 		str += j;
    218 		coff += j;
    219 		size -= j;
    220 
    221 		toks += i;
    222 		tokn -= i;
    223 		ctok -= i;
    224 		goto getcmd;
    225 	}
    226 
    227 	/* complete commands */
    228 	if (cmd && ctok == 0) {
    229 		wstem = toks[0] + 1;
    230 
    231 		stem = wctos(wstem);
    232 		len = strlen(stem);
    233 
    234 		complete_cmds(stem, len, &found, &fullcomplete);
    235 		pfree(&stem);
    236 
    237 		if (found) {
    238 			p = smprintf(strlen(found) + 2, "/%s", found);
    239 			pfree(&found);
    240 			found = p;
    241 
    242 			wp = stowc(found);
    243 			pfree(&found);
    244 			complete_stitch(str, size, counter, coff,
    245 					NULL, 0,
    246 					wp,
    247 					toks + 1, tokn - 1,
    248 					fullcomplete);
    249 			pfree(&wp);
    250 			goto end;
    251 		}
    252 		pfree(&found);
    253 	}
    254 
    255 	/* complete commands/variables as arguments */
    256 	type = 0;
    257 	if (cmd) {
    258 		if (wcscmp(cmd, L"help") == 0)
    259 			type = 1;
    260 		else if (wcscmp(cmd, L"set") == 0 || wcscmp(cmd, L"toggle") == 0)
    261 			type = 2;
    262 		else if (wcscmp(cmd, L"format") == 0)
    263 			type = 3;
    264 		if (type && ctok == 1 && toks[1]) {
    265 			wstem = toks[1];
    266 
    267 			p = wctos(wstem);
    268 			if (type == 3)
    269 				stem = smprintf(strlen(p) + CONSTLEN("format.") + 1, "format.%s", p);
    270 			else
    271 				stem = p;
    272 			len = strlen(stem);
    273 
    274 			if (type == 1)
    275 				complete_cmds(stem, len, &found, &fullcomplete);
    276 			complete_settings(stem, len, &found, &fullcomplete);
    277 			pfree(&stem);
    278 
    279 			if (found) {
    280 				if (type == 3)
    281 					p = found + CONSTLEN("format.");
    282 				else
    283 					p = found;
    284 				wp = stowc(p);
    285 				complete_stitch(str, size, counter, coff,
    286 						toks, 1,
    287 						wp,
    288 						toks + 2, tokn - 2,
    289 						fullcomplete);
    290 				pfree(&wp);
    291 				pfree(&found);
    292 				goto end;
    293 			}
    294 			pfree(&found);
    295 		}
    296 	}
    297 
    298 	/* complete nicks */
    299 	if (selected.channel && ctok > -1 && toks[ctok] && *toks[ctok]) {
    300 		wstem = toks[ctok];
    301 		stem = wctos(wstem);
    302 		len = strlen(stem);
    303 
    304 		complete_nicks(selected.channel, stem, len, &found, &fullcomplete);
    305 		pfree(&stem);
    306 
    307 		if (found) {
    308 			if (ctok == 0 && fullcomplete) {
    309 				hchar = config_gets("completion.hchar");
    310 				p = smprintf(strlen(found) + strlen(hchar) + 1, "%s%s", found, hchar);
    311 				pfree(&found);
    312 				found = p;
    313 			}
    314 			wp = stowc(found);
    315 			complete_stitch(str, size, counter, coff,
    316 					toks, ctok,
    317 					wp,
    318 					toks + ctok + 1, tokn - ctok - 1,
    319 					fullcomplete);
    320 			pfree(&wp);
    321 			pfree(&found);
    322 			goto end;
    323 		}
    324 		pfree(&found);
    325 	}
    326 
    327 	/* complete filenames with /source and /dump */
    328 	if (cmd && (wcscmp(cmd, L"source") == 0 || wcscmp(cmd, L"dump") == 0) && ctok > 0 &&
    329 			toks[ctok] && *toks[ctok] && *toks[ctok] != L'-' && ctok == tokn - 1) {
    330 		wstem = toks[ctok];
    331 		stem = wctos(wstem);
    332 		len = strlen(stem);
    333 
    334 		complete_files(stem, &found, &fullcomplete);
    335 		pfree(&stem);
    336 
    337 		if (found) {
    338 			wp = stowc(found);
    339 			complete_stitch(str, size, counter, coff,
    340 					toks, tokn - 1,
    341 					wp,
    342 					NULL, 0,
    343 					fullcomplete);
    344 			pfree(&wp);
    345 			pfree(&found);
    346 			goto end;
    347 		}
    348 		pfree(&found);
    349 	}
    350 
    351 	/* complete servers with /server */
    352 	if (cmd && wcscmp(cmd, L"server") == 0) {
    353 		if (toks[1] && wcscmp(toks[1], L"-auto") == 0)
    354 			i = 2;
    355 		else
    356 			i = 1;
    357 		if (i == ctok && toks[ctok] && *toks[ctok] != L'-' && *toks[ctok]) {
    358 			wstem = toks[ctok];
    359 			stem = wctos(wstem);
    360 			len = strlen(stem);
    361 
    362 			complete_servers(&servers, stem, len, &found, &fullcomplete);
    363 			pfree(&stem);
    364 
    365 			if (found) {
    366 				wp = stowc(found);
    367 				complete_stitch(str, size, counter, coff,
    368 						toks, ctok,
    369 						wp,
    370 						toks + ctok + 1, tokn - ctok - 1,
    371 						fullcomplete);
    372 				pfree(&wp);
    373 				pfree(&found);
    374 				goto end;
    375 			}
    376 			pfree(&found);
    377 		}
    378 	}
    379 
    380 end:
    381 	pfree(&_toks);
    382 	/* elements in _toks are pointers to dup */
    383 	pfree(&dup);
    384 	return;
    385 }