remove gcc for MacOS in conan.yml
[liba.git] / lua / isocline / src / completions.c
blobc1d88c69550c5872e57ae0a1426764762239fd66
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 #include <stdlib.h>
11 #include "../include/isocline.h"
12 #include "common.h"
13 #include "env.h"
14 #include "stringbuf.h"
15 #include "completions.h"
18 //-------------------------------------------------------------
19 // Completions
20 //-------------------------------------------------------------
22 typedef struct completion_s {
23 const char* replacement;
24 const char* display;
25 const char* help;
26 ssize_t delete_before;
27 ssize_t delete_after;
28 } completion_t;
30 struct completions_s {
31 ic_completer_fun_t* completer;
32 void* completer_arg;
33 ssize_t completer_max;
34 ssize_t count;
35 ssize_t len;
36 completion_t* elems;
37 alloc_t* mem;
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; }
45 cms->mem = mem;
46 cms->completer = &default_filename_completer;
47 return cms;
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);
55 cms->elems = NULL;
56 cms->count = 0;
57 cms->len = 0;
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));
70 cms->count--;
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;
81 cms->len = newlen;
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;
90 cms->count++;
93 ic_private ssize_t completions_count(completions_t* cms) {
94 return cms->count;
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; }
102 return false;
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);
112 return true;
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; }
131 return cm->help;
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; }
143 return hint;
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) {
177 // no changes
178 return -1;
180 else {
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
225 prefix[0] = 0;
226 break;
228 // check if it is still a prefix
229 const char* r = cm->replacement;
230 ssize_t j;
231 for(j = 0; prefix[j] != 0 && r[j] != 0; j++) {
232 if (prefix[j] != r[j]) { break; }
234 prefix[j] = 0;
235 if (j <= 0) { break; }
238 // check the length
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;
256 return newpos;
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; }
270 return true;
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) {
286 ic_unused(funenv);
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; }
299 // set up env
300 ic_completion_env_t cenv;
301 cenv.env = env;
302 cenv.input = input,
303 cenv.cursor = (long)pos;
304 cenv.arg = cms->completer_arg;
305 cenv.complete = &prim_add_completion;
306 cenv.closure = NULL;
307 const char* prefix = mem_strndup(cms->mem, input, pos);
308 cms->completer_max = max;
310 // and complete
311 cms->completer(&cenv,prefix);
313 // restore
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 ) {
320 #ifdef _WIN32
321 const char sep = '\\';
322 #else
323 const char sep = '/';
324 #endif
325 ic_complete_filename( cenv, prefix, sep, ".", NULL);