Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/extern/isocline/src/completers.c
2727 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2021, Daan Leijen
3
This is free software; you can redistribute it and/or modify it
4
under the terms of the MIT License. A copy of the license can be
5
found in the "LICENSE" file at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include <string.h>
8
#include <stdio.h>
9
10
#include "../include/isocline.h"
11
#include "common.h"
12
#include "env.h"
13
#include "stringbuf.h"
14
#include "completions.h"
15
16
17
18
//-------------------------------------------------------------
19
// Word completion
20
//-------------------------------------------------------------
21
22
// free variables for word completion
23
typedef struct word_closure_s {
24
long delete_before_adjust;
25
void* prev_env;
26
ic_completion_fun_t* prev_complete;
27
} word_closure_t;
28
29
30
// word completion callback
31
static bool token_add_completion_ex(ic_env_t* env, void* closure, const char* replacement, const char* display, const char* help, long delete_before, long delete_after) {
32
word_closure_t* wenv = (word_closure_t*)(closure);
33
// call the previous completer with an adjusted delete-before
34
return (*wenv->prev_complete)(env, wenv->prev_env, replacement, display, help, wenv->delete_before_adjust + delete_before, delete_after);
35
}
36
37
38
ic_public void ic_complete_word(ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun,
39
ic_is_char_class_fun_t* is_word_char)
40
{
41
if (is_word_char == NULL) is_word_char = &ic_char_is_nonseparator;
42
43
ssize_t len = ic_strlen(prefix);
44
ssize_t pos = len; // will be start of the 'word' (excluding a potential start quote)
45
while (pos > 0) {
46
// go back one code point
47
ssize_t ofs = str_prev_ofs(prefix, pos, NULL);
48
if (ofs <= 0) break;
49
if (!(*is_word_char)(prefix + (pos - ofs), (long)ofs)) {
50
break;
51
}
52
pos -= ofs;
53
}
54
if (pos < 0) { pos = 0; }
55
56
// stop if empty word
57
// if (len == pos) return;
58
59
// set up the closure
60
word_closure_t wenv;
61
wenv.delete_before_adjust = (long)(len - pos);
62
wenv.prev_complete = cenv->complete;
63
wenv.prev_env = cenv->env;
64
cenv->complete = &token_add_completion_ex;
65
cenv->closure = &wenv;
66
67
// and call the user completion routine
68
(*fun)(cenv, prefix + pos);
69
70
// restore the original environment
71
cenv->complete = wenv.prev_complete;
72
cenv->closure = wenv.prev_env;
73
}
74
75
76
//-------------------------------------------------------------
77
// Quoted word completion (with escape characters)
78
//-------------------------------------------------------------
79
80
// free variables for word completion
81
typedef struct qword_closure_s {
82
char escape_char;
83
char quote;
84
long delete_before_adjust;
85
stringbuf_t* sbuf;
86
void* prev_env;
87
ic_is_char_class_fun_t* is_word_char;
88
ic_completion_fun_t* prev_complete;
89
} qword_closure_t;
90
91
92
// word completion callback
93
static bool qword_add_completion_ex(ic_env_t* env, void* closure, const char* replacement, const char* display, const char* help,
94
long delete_before, long delete_after) {
95
qword_closure_t* wenv = (qword_closure_t*)(closure);
96
sbuf_replace( wenv->sbuf, replacement );
97
if (wenv->quote != 0) {
98
// add end quote
99
sbuf_append_char( wenv->sbuf, wenv->quote);
100
}
101
else {
102
// escape non-word characters if it was not quoted
103
ssize_t pos = 0;
104
ssize_t next;
105
while ( (next = sbuf_next_ofs(wenv->sbuf, pos, NULL)) > 0 )
106
{
107
if (!(*wenv->is_word_char)(sbuf_string(wenv->sbuf) + pos, (long)next)) { // strchr(wenv->non_word_char, sbuf_char_at( wenv->sbuf, pos )) != NULL) {
108
sbuf_insert_char_at( wenv->sbuf, wenv->escape_char, pos);
109
pos++;
110
}
111
pos += next;
112
}
113
}
114
// and call the previous completion function
115
return (*wenv->prev_complete)( env, wenv->prev_env, sbuf_string(wenv->sbuf), display, help, wenv->delete_before_adjust + delete_before, delete_after );
116
}
117
118
119
ic_public void ic_complete_qword( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun, ic_is_char_class_fun_t* is_word_char ) {
120
ic_complete_qword_ex( cenv, prefix, fun, is_word_char, '\\', NULL);
121
}
122
123
124
ic_public void ic_complete_qword_ex( ic_completion_env_t* cenv, const char* prefix, ic_completer_fun_t* fun,
125
ic_is_char_class_fun_t* is_word_char, char escape_char, const char* quote_chars ) {
126
if (is_word_char == NULL) is_word_char = &ic_char_is_nonseparator ;
127
if (quote_chars == NULL) quote_chars = "'\"";
128
129
ssize_t len = ic_strlen(prefix);
130
ssize_t pos; // will be start of the 'word' (excluding a potential start quote)
131
char quote = 0;
132
ssize_t quote_len = 0;
133
134
// 1. look for a starting quote
135
if (quote_chars[0] != 0) {
136
// we go forward and count all quotes; if it is uneven, we need to complete quoted.
137
ssize_t qpos_open = -1;
138
ssize_t qpos_close = -1;
139
ssize_t qcount = 0;
140
pos = 0;
141
while(pos < len) {
142
if (prefix[pos] == escape_char && prefix[pos+1] != 0 &&
143
!(*is_word_char)(prefix + pos + 1, 1)) // strchr(non_word_char, prefix[pos+1]) != NULL
144
{
145
pos++; // skip escape and next char
146
}
147
else if (qcount % 2 == 0 && strchr(quote_chars, prefix[pos]) != NULL) {
148
// open quote
149
qpos_open = pos;
150
quote = prefix[pos];
151
qcount++;
152
}
153
else if (qcount % 2 == 1 && prefix[pos] == quote) {
154
// close quote
155
qpos_close = pos;
156
qcount++;
157
}
158
else if (!(*is_word_char)(prefix + pos, 1)) { // strchr(non_word_char, prefix[pos]) != NULL) {
159
qpos_close = -1;
160
}
161
ssize_t ofs = str_next_ofs( prefix, len, pos, NULL );
162
if (ofs <= 0) break;
163
pos += ofs;
164
}
165
if ((qcount % 2 == 0 && qpos_close >= 0) || // if the last quote is only followed by word chars, we still complete it
166
(qcount % 2 == 1)) // opening quote found
167
{
168
quote_len = (len - qpos_open - 1);
169
pos = qpos_open + 1; // pos points to the word start just after the quote.
170
}
171
else {
172
quote = 0;
173
}
174
}
175
176
// 2. if we did not find a quoted word, look for non-word-chars
177
if (quote == 0) {
178
pos = len;
179
while(pos > 0) {
180
// go back one code point
181
ssize_t ofs = str_prev_ofs(prefix, pos, NULL );
182
if (ofs <= 0) break;
183
if (!(*is_word_char)(prefix + (pos - ofs), (long)ofs)) { // strchr(non_word_char, prefix[pos - ofs]) != NULL) {
184
// non word char, break if it is not escaped
185
if (pos <= ofs || prefix[pos - ofs - 1] != escape_char) break;
186
// otherwise go on
187
pos--; // skip escaped char
188
}
189
pos -= ofs;
190
}
191
}
192
193
// stop if empty word
194
// if (len == pos) return;
195
196
// allocate new unescaped word prefix
197
char* word = mem_strndup( cenv->env->mem, prefix + pos, (quote==0 ? len - pos : quote_len));
198
if (word == NULL) return;
199
200
if (quote == 0) {
201
// unescape prefix
202
ssize_t wlen = len - pos;
203
ssize_t wpos = 0;
204
while (wpos < wlen) {
205
ssize_t ofs = str_next_ofs(word, wlen, wpos, NULL);
206
if (ofs <= 0) break;
207
if (word[wpos] == escape_char && word[wpos+1] != 0 &&
208
!(*is_word_char)(word + wpos + 1, (long)ofs)) // strchr(non_word_char, word[wpos+1]) != NULL) {
209
{
210
ic_memmove(word + wpos, word + wpos + 1, wlen - wpos /* including 0 */);
211
}
212
wpos += ofs;
213
}
214
}
215
#ifdef _WIN32
216
else {
217
// remove inner quote: "c:\Program Files\"Win
218
ssize_t wlen = len - pos;
219
ssize_t wpos = 0;
220
while (wpos < wlen) {
221
ssize_t ofs = str_next_ofs(word, wlen, wpos, NULL);
222
if (ofs <= 0) break;
223
if (word[wpos] == escape_char && word[wpos+1] == quote) {
224
word[wpos+1] = escape_char;
225
ic_memmove(word + wpos, word + wpos + 1, wlen - wpos /* including 0 */);
226
}
227
wpos += ofs;
228
}
229
}
230
#endif
231
232
// set up the closure
233
qword_closure_t wenv;
234
wenv.quote = quote;
235
wenv.is_word_char = is_word_char;
236
wenv.escape_char = escape_char;
237
wenv.delete_before_adjust = (long)(len - pos);
238
wenv.prev_complete = cenv->complete;
239
wenv.prev_env = cenv->env;
240
wenv.sbuf = sbuf_new(cenv->env->mem);
241
if (wenv.sbuf == NULL) { mem_free(cenv->env->mem, word); return; }
242
cenv->complete = &qword_add_completion_ex;
243
cenv->closure = &wenv;
244
245
// and call the user completion routine
246
(*fun)( cenv, word );
247
248
// restore the original environment
249
cenv->complete = wenv.prev_complete;
250
cenv->closure = wenv.prev_env;
251
252
sbuf_free(wenv.sbuf);
253
mem_free(cenv->env->mem, word);
254
}
255
256
257
258
259
//-------------------------------------------------------------
260
// Complete file names
261
// Listing files
262
//-------------------------------------------------------------
263
#include <stdlib.h>
264
265
typedef enum file_type_e {
266
// must follow BSD style LSCOLORS order
267
FT_DEFAULT = 0,
268
FT_DIR,
269
FT_SYM,
270
FT_SOCK,
271
FT_PIPE,
272
FT_BLOCK,
273
FT_CHAR,
274
FT_SETUID,
275
FT_SETGID,
276
FT_DIR_OW_STICKY,
277
FT_DIR_OW,
278
FT_DIR_STICKY,
279
FT_EXE,
280
FT_LAST
281
} file_type_t;
282
283
static int cli_color; // 1 enabled, 0 not initialized, -1 disabled
284
static const char* lscolors = "exfxcxdxbxegedabagacad"; // default BSD setting
285
static const char* ls_colors;
286
static const char* ls_colors_names[] = { "no=","di=","ln=","so=","pi=","bd=","cd=","su=","sg=","tw=","ow=","st=","ex=", NULL };
287
288
static bool ls_colors_init(void) {
289
if (cli_color != 0) return (cli_color >= 1);
290
// colors enabled?
291
const char* s = getenv("CLICOLOR");
292
if (s==NULL || (strcmp(s, "1")!=0 && strcmp(s, "") != 0)) {
293
cli_color = -1;
294
return false;
295
}
296
cli_color = 1;
297
s = getenv("LS_COLORS");
298
if (s != NULL) { ls_colors = s; }
299
s = getenv("LSCOLORS");
300
if (s != NULL) { lscolors = s; }
301
return true;
302
}
303
304
static bool ls_valid_esc(ssize_t c) {
305
return ((c==0 || c==1 || c==4 || c==7 || c==22 || c==24 || c==27) ||
306
(c >= 30 && c <= 37) || (c >= 40 && c <= 47) ||
307
(c >= 90 && c <= 97) || (c >= 100 && c <= 107));
308
}
309
310
static bool ls_colors_from_key(stringbuf_t* sb, const char* key) {
311
// find key
312
ssize_t keylen = ic_strlen(key);
313
if (keylen <= 0) return false;
314
const char* p = strstr(ls_colors, key);
315
if (p == NULL) return false;
316
p += keylen;
317
if (key[keylen-1] != '=') {
318
if (*p != '=') return false;
319
p++;
320
}
321
ssize_t len = 0;
322
while (p[len] != 0 && p[len] != ':') {
323
len++;
324
}
325
if (len <= 0) return false;
326
sbuf_append(sb, "[ansi-sgr=\"" );
327
sbuf_append_n(sb, p, len );
328
sbuf_append(sb, "\"]");
329
return true;
330
}
331
332
static int ls_colors_from_char(char c) {
333
if (c >= 'a' && c <= 'h') { return (c - 'a'); }
334
else if (c >= 'A' && c <= 'H') { return (c - 'A') + 8; }
335
else if (c == 'x') { return 256; }
336
else return 256; // default
337
}
338
339
static bool ls_colors_append(stringbuf_t* sb, file_type_t ft, const char* ext) {
340
if (!ls_colors_init()) return false;
341
if (ls_colors != NULL) {
342
// GNU style
343
if (ft == FT_DEFAULT && ext != NULL) {
344
// first try extension match
345
if (ls_colors_from_key(sb, ext)) return true;
346
}
347
if (ft >= FT_DEFAULT && ft < FT_LAST) {
348
// then a filetype match
349
const char* key = ls_colors_names[ft];
350
if (ls_colors_from_key(sb, key)) return true;
351
}
352
}
353
else if (lscolors != NULL) {
354
// BSD style
355
char fg = 'x';
356
char bg = 'x';
357
if (ic_strlen(lscolors) > (2*(ssize_t)ft)+1) {
358
fg = lscolors[2*ft];
359
bg = lscolors[2*ft + 1];
360
}
361
sbuf_appendf(sb, "[ansi-color=%d ansi-bgcolor=%d]", ls_colors_from_char(fg), ls_colors_from_char(bg) );
362
return true;
363
}
364
return false;
365
}
366
367
static void ls_colorize(bool no_lscolor, stringbuf_t* sb, file_type_t ft, const char* name, const char* ext, char dirsep) {
368
bool close = (no_lscolor ? false : ls_colors_append( sb, ft, ext));
369
sbuf_append(sb, "[!pre]" );
370
sbuf_append(sb, name);
371
if (dirsep != 0) sbuf_append_char(sb, dirsep);
372
sbuf_append(sb,"[/pre]" );
373
if (close) { sbuf_append(sb, "[/]"); }
374
}
375
376
#if defined(_WIN32)
377
#include <io.h>
378
#include <sys/stat.h>
379
380
static bool os_is_dir(const char* cpath) {
381
struct _stat64 st = { 0 };
382
_stat64(cpath, &st);
383
return ((st.st_mode & _S_IFDIR) != 0);
384
}
385
386
static file_type_t os_get_filetype(const char* cpath) {
387
struct _stat64 st = { 0 };
388
_stat64(cpath, &st);
389
if (((st.st_mode) & _S_IFDIR) != 0) return FT_DIR;
390
if (((st.st_mode) & _S_IFCHR) != 0) return FT_CHAR;
391
if (((st.st_mode) & _S_IFIFO) != 0) return FT_PIPE;
392
if (((st.st_mode) & _S_IEXEC) != 0) return FT_EXE;
393
return FT_DEFAULT;
394
}
395
396
397
#define dir_cursor intptr_t
398
#define dir_entry struct __finddata64_t
399
400
static bool os_findfirst(alloc_t* mem, const char* path, dir_cursor* d, dir_entry* entry) {
401
stringbuf_t* spath = sbuf_new(mem);
402
if (spath == NULL) return false;
403
sbuf_append(spath, path);
404
sbuf_append(spath, "\\*");
405
*d = _findfirsti64(sbuf_string(spath), entry);
406
mem_free(mem,spath);
407
return (*d != -1);
408
}
409
410
static bool os_findnext(dir_cursor d, dir_entry* entry) {
411
return (_findnexti64(d, entry) == 0);
412
}
413
414
static void os_findclose(dir_cursor d) {
415
_findclose(d);
416
}
417
418
static const char* os_direntry_name(dir_entry* entry) {
419
return entry->name;
420
}
421
422
static bool os_path_is_absolute( const char* path ) {
423
if (path != NULL && path[0] != 0 && path[1] == ':' && (path[2] == '\\' || path[2] == '/' || path[2] == 0)) {
424
char drive = path[0];
425
return ((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z'));
426
}
427
else return false;
428
}
429
430
ic_private char ic_dirsep(void) {
431
return '\\';
432
}
433
#else
434
435
#include <sys/types.h>
436
#include <sys/stat.h>
437
#include <dirent.h>
438
#include <errno.h>
439
440
static bool os_is_dir(const char* cpath) {
441
struct stat st;
442
memset(&st, 0, sizeof(st));
443
stat(cpath, &st);
444
return (S_ISDIR(st.st_mode));
445
}
446
447
static file_type_t os_get_filetype(const char* cpath) {
448
struct stat st;
449
memset(&st, 0, sizeof(st));
450
lstat(cpath, &st);
451
switch ((st.st_mode)&S_IFMT) {
452
case S_IFSOCK: return FT_SOCK;
453
case S_IFLNK: {
454
return FT_SYM;
455
}
456
case S_IFIFO: return FT_PIPE;
457
case S_IFCHR: return FT_CHAR;
458
case S_IFBLK: return FT_BLOCK;
459
case S_IFDIR: {
460
if ((st.st_mode & S_ISUID) != 0) return FT_SETUID;
461
if ((st.st_mode & S_ISGID) != 0) return FT_SETGID;
462
if ((st.st_mode & S_IWGRP) != 0 && (st.st_mode & S_ISVTX) != 0) return FT_DIR_OW_STICKY;
463
if ((st.st_mode & S_IWGRP)) return FT_DIR_OW;
464
if ((st.st_mode & S_ISVTX)) return FT_DIR_STICKY;
465
return FT_DIR;
466
}
467
case S_IFREG:
468
default: {
469
if ((st.st_mode & S_IXUSR) != 0) return FT_EXE;
470
return FT_DEFAULT;
471
}
472
}
473
}
474
475
476
#define dir_cursor DIR*
477
#define dir_entry struct dirent*
478
479
static bool os_findnext(dir_cursor d, dir_entry* entry) {
480
*entry = readdir(d);
481
return (*entry != NULL);
482
}
483
484
static bool os_findfirst(alloc_t* mem, const char* cpath, dir_cursor* d, dir_entry* entry) {
485
ic_unused(mem);
486
*d = opendir(cpath);
487
if (*d == NULL) {
488
return false;
489
}
490
else {
491
return os_findnext(*d, entry);
492
}
493
}
494
495
static void os_findclose(dir_cursor d) {
496
closedir(d);
497
}
498
499
static const char* os_direntry_name(dir_entry* entry) {
500
return (*entry)->d_name;
501
}
502
503
static bool os_path_is_absolute( const char* path ) {
504
return (path != NULL && path[0] == '/');
505
}
506
507
ic_private char ic_dirsep(void) {
508
return '/';
509
}
510
#endif
511
512
513
514
//-------------------------------------------------------------
515
// File completion
516
//-------------------------------------------------------------
517
518
static bool ends_with_n(const char* name, ssize_t name_len, const char* ending, ssize_t len) {
519
if (name_len < len) return false;
520
if (ending == NULL || len <= 0) return true;
521
for (ssize_t i = 1; i <= len; i++) {
522
char c1 = name[name_len - i];
523
char c2 = ending[len - i];
524
#ifdef _WIN32
525
if (ic_tolower(c1) != ic_tolower(c2)) return false;
526
#else
527
if (c1 != c2) return false;
528
#endif
529
}
530
return true;
531
}
532
533
static bool match_extension(const char* name, const char* extensions) {
534
if (extensions == NULL || extensions[0] == 0) return true;
535
if (name == NULL) return false;
536
ssize_t name_len = ic_strlen(name);
537
ssize_t len = ic_strlen(extensions);
538
ssize_t cur = 0;
539
//debug_msg("match extensions: %s ~ %s", name, extensions);
540
for (ssize_t end = 0; end <= len; end++) {
541
if (extensions[end] == ';' || extensions[end] == 0) {
542
if (ends_with_n(name, name_len, extensions+cur, (end - cur))) {
543
return true;
544
}
545
cur = end+1;
546
}
547
}
548
return false;
549
}
550
551
static bool filename_complete_indir( ic_completion_env_t* cenv, stringbuf_t* dir,
552
stringbuf_t* dir_prefix, stringbuf_t* display,
553
const char* base_prefix,
554
char dir_sep, const char* extensions )
555
{
556
dir_cursor d = 0;
557
dir_entry entry;
558
bool cont = true;
559
if (os_findfirst(cenv->env->mem, sbuf_string(dir), &d, &entry)) {
560
do {
561
const char* name = os_direntry_name(&entry);
562
if (name != NULL && strcmp(name, ".") != 0 && strcmp(name, "..") != 0 &&
563
ic_istarts_with(name, base_prefix))
564
{
565
// possible match, first check if it is a directory
566
file_type_t ft;
567
bool isdir;
568
const ssize_t plen = sbuf_len(dir_prefix);
569
sbuf_append(dir_prefix, name);
570
{ // check directory and potentially add a dirsep to the dir_prefix
571
const ssize_t dlen = sbuf_len(dir);
572
sbuf_append_char(dir,ic_dirsep());
573
sbuf_append(dir,name);
574
ft = os_get_filetype(sbuf_string(dir));
575
isdir = os_is_dir(sbuf_string(dir));
576
if (isdir && dir_sep != 0) {
577
sbuf_append_char(dir_prefix,dir_sep);
578
}
579
sbuf_delete_from(dir,dlen); // restore dir
580
}
581
if (isdir || match_extension(name, extensions)) {
582
// add completion
583
sbuf_clear(display);
584
ls_colorize(cenv->env->no_lscolors, display, ft, name, NULL, (isdir ? dir_sep : 0));
585
cont = ic_add_completion_ex(cenv, sbuf_string(dir_prefix), sbuf_string(display), NULL);
586
}
587
sbuf_delete_from( dir_prefix, plen ); // restore dir_prefix
588
}
589
} while (cont && os_findnext(d, &entry));
590
os_findclose(d);
591
}
592
return cont;
593
}
594
595
typedef struct filename_closure_s {
596
const char* roots;
597
const char* extensions;
598
char dir_sep;
599
} filename_closure_t;
600
601
static void filename_completer( ic_completion_env_t* cenv, const char* prefix ) {
602
if (prefix == NULL) return;
603
filename_closure_t* fclosure = (filename_closure_t*)cenv->arg;
604
stringbuf_t* root_dir = sbuf_new(cenv->env->mem);
605
stringbuf_t* dir_prefix = sbuf_new(cenv->env->mem);
606
stringbuf_t* display = sbuf_new(cenv->env->mem);
607
if (root_dir!=NULL && dir_prefix != NULL && display != NULL)
608
{
609
// split prefix in dir_prefix / base.
610
const char* base = strrchr(prefix,'/');
611
#ifdef _WIN32
612
const char* base2 = strrchr(prefix,'\\');
613
if (base == NULL || base2 > base) base = base2;
614
#endif
615
if (base != NULL) {
616
base++;
617
sbuf_append_n(dir_prefix, prefix, base - prefix ); // includes dir separator
618
}
619
620
// absolute path
621
if (os_path_is_absolute(prefix)) {
622
// do not use roots but try to complete directly
623
if (base != NULL) {
624
sbuf_append_n( root_dir, prefix, (base - prefix)); // include dir separator
625
}
626
filename_complete_indir( cenv, root_dir, dir_prefix, display,
627
(base != NULL ? base : prefix),
628
fclosure->dir_sep, fclosure->extensions );
629
}
630
else {
631
// relative path, complete with respect to every root.
632
const char* next;
633
const char* root = fclosure->roots;
634
while ( root != NULL ) {
635
// create full root in `root_dir`
636
sbuf_clear(root_dir);
637
next = strchr(root,';');
638
if (next == NULL) {
639
sbuf_append( root_dir, root );
640
root = NULL;
641
}
642
else {
643
sbuf_append_n( root_dir, root, next - root );
644
root = next + 1;
645
}
646
sbuf_append_char( root_dir, ic_dirsep());
647
648
// add the dir_prefix to the root
649
if (base != NULL) {
650
sbuf_append_n( root_dir, prefix, (base - prefix) - 1);
651
}
652
653
// and complete in this directory
654
filename_complete_indir( cenv, root_dir, dir_prefix, display,
655
(base != NULL ? base : prefix),
656
fclosure->dir_sep, fclosure->extensions);
657
}
658
}
659
}
660
sbuf_free(display);
661
sbuf_free(root_dir);
662
sbuf_free(dir_prefix);
663
}
664
665
ic_public void ic_complete_filename( ic_completion_env_t* cenv, const char* prefix, char dir_sep, const char* roots, const char* extensions ) {
666
if (roots == NULL) roots = ".";
667
if (extensions == NULL) extensions = "";
668
if (dir_sep == 0) dir_sep = ic_dirsep();
669
filename_closure_t fclosure;
670
fclosure.dir_sep = dir_sep;
671
fclosure.roots = roots;
672
fclosure.extensions = extensions;
673
cenv->arg = &fclosure;
674
ic_complete_qword_ex( cenv, prefix, &filename_completer, &ic_char_is_filename_letter, '\\', "'\"");
675
}
676
677