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 -----------------------------------------------------------------------------*/
11 #include "../include/isocline.h"
14 #include "stringbuf.h"
15 #include "completions.h"
18 //-------------------------------------------------------------
20 //-------------------------------------------------------------
22 typedef struct completion_s
{
23 const char* replacement
;
26 ssize_t delete_before
;
30 struct completions_s
{
31 ic_completer_fun_t
* completer
;
33 ssize_t completer_max
;
40 static void default_filename_completer( ic_completion_env_t
* cenv
, const char* prefix
);
42 ic_private completions_t
* completions_new(alloc_t
* mem
) {
43 completions_t
* cms
= mem_zalloc_tp(mem
, completions_t
);
44 if (cms
== NULL
) { return NULL
; }
46 cms
->completer
= &default_filename_completer
;
50 ic_private
void completions_free(completions_t
* cms
) {
51 if (cms
== NULL
) { return; }
52 completions_clear(cms
);
53 if (cms
->elems
!= NULL
) {
54 mem_free(cms
->mem
, cms
->elems
);
59 mem_free(cms
->mem
, cms
); // free ourselves
63 ic_private
void completions_clear(completions_t
* cms
) {
64 while (cms
->count
> 0) {
65 completion_t
* cm
= cms
->elems
+ cms
->count
- 1;
66 mem_free( cms
->mem
, cm
->display
);
67 mem_free( cms
->mem
, cm
->replacement
);
68 mem_free( cms
->mem
, cm
->help
);
69 memset(cm
,0,sizeof(*cm
));
74 static void completions_push(completions_t
* cms
, const char* replacement
, const char* display
, const char* help
, ssize_t delete_before
, ssize_t delete_after
)
76 if (cms
->count
>= cms
->len
) {
77 ssize_t newlen
= (cms
->len
<= 0 ? 32 : cms
->len
*2);
78 completion_t
* newelems
= mem_realloc_tp(cms
->mem
, completion_t
, cms
->elems
, newlen
);
79 if (newelems
== NULL
) { return; }
80 cms
->elems
= newelems
;
83 assert(cms
->count
< cms
->len
);
84 completion_t
* cm
= cms
->elems
+ cms
->count
;
85 cm
->replacement
= mem_strdup(cms
->mem
,replacement
);
86 cm
->display
= mem_strdup(cms
->mem
,display
);
87 cm
->help
= mem_strdup(cms
->mem
,help
);
88 cm
->delete_before
= delete_before
;
89 cm
->delete_after
= delete_after
;
93 ic_private ssize_t
completions_count(completions_t
* cms
) {
97 static bool completions_contains(completions_t
* cms
, const char* replacement
) {
98 for( ssize_t i
= 0; i
< cms
->count
; i
++ ) {
99 const completion_t
* c
= cms
->elems
+ i
;
100 if (strcmp(replacement
,c
->replacement
) == 0) { return true; }
105 ic_private
bool completions_add(completions_t
* cms
, const char* replacement
, const char* display
, const char* help
, ssize_t delete_before
, ssize_t delete_after
) {
106 if (cms
->completer_max
<= 0) { return false; }
107 cms
->completer_max
--;
108 //debug_msg("completion: add: %d,%d, %s\n", delete_before, delete_after, replacement);
109 if (!completions_contains(cms
,replacement
)) {
110 completions_push(cms
, replacement
, display
, help
, delete_before
, delete_after
);
115 static completion_t
* completions_get(completions_t
* cms
, ssize_t index
) {
116 if (index
< 0 || cms
->count
<= 0 || index
>= cms
->count
) { return NULL
; }
117 return &cms
->elems
[index
];
120 ic_private
const char* completions_get_display( completions_t
* cms
, ssize_t index
, const char** help
) {
121 if (help
!= NULL
) { *help
= NULL
; }
122 completion_t
* cm
= completions_get(cms
, index
);
123 if (cm
== NULL
) { return NULL
; }
124 if (help
!= NULL
) { *help
= cm
->help
; }
125 return (cm
->display
!= NULL
? cm
->display
: cm
->replacement
);
128 ic_private
const char* completions_get_help( completions_t
* cms
, ssize_t index
) {
129 completion_t
* cm
= completions_get(cms
, index
);
130 if (cm
== NULL
) { return NULL
; }
134 ic_private
const char* completions_get_hint(completions_t
* cms
, ssize_t index
, const char** help
) {
135 if (help
!= NULL
) { *help
= NULL
; }
136 completion_t
* cm
= completions_get(cms
, index
);
137 if (cm
== NULL
) { return NULL
; }
138 ssize_t len
= ic_strlen(cm
->replacement
);
139 if (len
< cm
->delete_before
) { return NULL
; }
140 const char* hint
= (cm
->replacement
+ cm
->delete_before
);
141 if (*hint
== 0 || utf8_is_cont((uint8_t)(*hint
))) { return NULL
; } // utf8 boundary?
142 if (help
!= NULL
) { *help
= cm
->help
; }
146 ic_private
void completions_set_completer(completions_t
* cms
, ic_completer_fun_t
* completer
, void* arg
) {
147 cms
->completer
= completer
;
148 cms
->completer_arg
= arg
;
151 ic_private
void completions_get_completer(completions_t
* cms
, ic_completer_fun_t
** completer
, void** arg
) {
152 *completer
= cms
->completer
;
153 *arg
= cms
->completer_arg
;
157 ic_public
void* ic_completion_arg( const ic_completion_env_t
* cenv
) {
158 return (cenv
== NULL
? NULL
: cenv
->env
->completions
->completer_arg
);
161 ic_public
bool ic_has_completions( const ic_completion_env_t
* cenv
) {
162 return (cenv
== NULL
? false : cenv
->env
->completions
->count
> 0);
165 ic_public
bool ic_stop_completing( const ic_completion_env_t
* cenv
) {
166 return (cenv
== NULL
? true : cenv
->env
->completions
->completer_max
<= 0);
170 static ssize_t
completion_apply( completion_t
* cm
, stringbuf_t
* sbuf
, ssize_t pos
) {
171 if (cm
== NULL
) { return -1; }
172 debug_msg( "completion: apply: %s at %" PRIz
"d\n", cm
->replacement
, pos
);
173 ssize_t start
= pos
- cm
->delete_before
;
174 if (start
< 0) { start
= 0; }
175 ssize_t n
= cm
->delete_before
+ cm
->delete_after
;
176 if (ic_strlen(cm
->replacement
) == n
&& strncmp(sbuf_string_at(sbuf
,start
), cm
->replacement
, to_size_t(n
)) == 0) {
181 sbuf_delete_from_to( sbuf
, start
, pos
+ cm
->delete_after
);
182 return sbuf_insert_at(sbuf
, cm
->replacement
, start
);
186 ic_private ssize_t
completions_apply( completions_t
* cms
, ssize_t index
, stringbuf_t
* sbuf
, ssize_t pos
) {
187 completion_t
* cm
= completions_get(cms
, index
);
188 return completion_apply( cm
, sbuf
, pos
);
192 static int completion_compare(const void* p1
, const void* p2
) {
193 if (p1
== NULL
|| p2
== NULL
) { return 0; }
194 const completion_t
* cm1
= (const completion_t
*)p1
;
195 const completion_t
* cm2
= (const completion_t
*)p2
;
196 return ic_stricmp(cm1
->replacement
, cm2
->replacement
);
199 ic_private
void completions_sort(completions_t
* cms
) {
200 if (cms
->count
<= 0) { return; }
201 qsort(cms
->elems
, to_size_t(cms
->count
), sizeof(cms
->elems
[0]), &completion_compare
);
204 #define IC_MAX_PREFIX (256)
206 // find longest common prefix and complete with that.
207 ic_private ssize_t
completions_apply_longest_prefix(completions_t
* cms
, stringbuf_t
* sbuf
, ssize_t pos
) {
208 if (cms
->count
<= 1) {
209 return completions_apply(cms
,0,sbuf
,pos
);
212 // set initial prefix to the first entry
213 completion_t
* cm
= completions_get(cms
, 0);
214 if (cm
== NULL
) { return -1; }
216 char prefix
[IC_MAX_PREFIX
+1];
217 ssize_t delete_before
= cm
->delete_before
;
218 ic_strncpy( prefix
, IC_MAX_PREFIX
+1, cm
->replacement
, IC_MAX_PREFIX
);
219 prefix
[IC_MAX_PREFIX
] = 0;
221 // and visit all others to find the longest common prefix
222 for(ssize_t i
= 1; i
< cms
->count
; i
++) {
223 cm
= completions_get(cms
,i
);
224 if (cm
->delete_before
!= delete_before
) { // deletions must match delete_before
228 // check if it is still a prefix
229 const char* r
= cm
->replacement
;
231 for(j
= 0; prefix
[j
] != 0 && r
[j
] != 0; j
++) {
232 if (prefix
[j
] != r
[j
]) { break; }
235 if (j
<= 0) { break; }
239 ssize_t len
= ic_strlen(prefix
);
240 if (len
<= 0 || len
< delete_before
) { return -1; }
242 // we found a prefix :-)
243 completion_t cprefix
;
244 memset(&cprefix
,0,sizeof(cprefix
));
245 cprefix
.delete_before
= delete_before
;
246 cprefix
.replacement
= prefix
;
247 ssize_t newpos
= completion_apply( &cprefix
, sbuf
, pos
);
248 if (newpos
< 0) { return newpos
; }
250 // adjust all delete_before for the new replacement
251 for( ssize_t i
= 0; i
< cms
->count
; i
++) {
252 cm
= completions_get(cms
,i
);
253 cm
->delete_before
= len
;
260 //-------------------------------------------------------------
261 // Completer functions
262 //-------------------------------------------------------------
264 ic_public
bool ic_add_completions(ic_completion_env_t
* cenv
, const char* prefix
, const char** completions
) {
265 for (const char** pc
= completions
; *pc
!= NULL
; pc
++) {
266 if (ic_istarts_with(*pc
, prefix
)) {
267 if (!ic_add_completion_ex(cenv
, *pc
, NULL
, NULL
)) { return false; }
273 ic_public
bool ic_add_completion(ic_completion_env_t
* cenv
, const char* replacement
) {
274 return ic_add_completion_ex(cenv
, replacement
, NULL
, NULL
);
277 ic_public
bool ic_add_completion_ex( ic_completion_env_t
* cenv
, const char* replacement
, const char* display
, const char* help
) {
278 return ic_add_completion_prim(cenv
,replacement
,display
,help
,0,0);
281 ic_public
bool ic_add_completion_prim(ic_completion_env_t
* cenv
, const char* replacement
, const char* display
, const char* help
, long delete_before
, long delete_after
) {
282 return (*cenv
->complete
)(cenv
->env
, cenv
->closure
, replacement
, display
, help
, delete_before
, delete_after
);
285 static bool prim_add_completion(ic_env_t
* env
, void* funenv
, const char* replacement
, const char* display
, const char* help
, long delete_before
, long delete_after
) {
287 return completions_add(env
->completions
, replacement
, display
, help
, delete_before
, delete_after
);
290 ic_public
void ic_set_default_completer(ic_completer_fun_t
* completer
, void* arg
) {
291 ic_env_t
* env
= ic_get_env(); if (env
== NULL
) { return; }
292 completions_set_completer(env
->completions
, completer
, arg
);
295 ic_private ssize_t
completions_generate(struct ic_env_s
* env
, completions_t
* cms
, const char* input
, ssize_t pos
, ssize_t max
) {
296 completions_clear(cms
);
297 if (cms
->completer
== NULL
|| input
== NULL
|| ic_strlen(input
) < pos
) { return 0; }
300 ic_completion_env_t cenv
;
303 cenv
.cursor
= (long)pos
;
304 cenv
.arg
= cms
->completer_arg
;
305 cenv
.complete
= &prim_add_completion
;
307 const char* prefix
= mem_strndup(cms
->mem
, input
, pos
);
308 cms
->completer_max
= max
;
311 cms
->completer(&cenv
,prefix
);
314 mem_free(cms
->mem
,prefix
);
315 return completions_count(cms
);
318 // The default completer is no completion is set
319 static void default_filename_completer( ic_completion_env_t
* cenv
, const char* prefix
) {
321 const char sep
= '\\';
323 const char sep
= '/';
325 ic_complete_filename( cenv
, prefix
, sep
, ".", NULL
);