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 }