mirror of
https://github.com/luau-lang/luau.git
synced 2025-01-24 19:48:12 +00:00
326 lines
11 KiB
C
326 lines
11 KiB
C
/* ----------------------------------------------------------------------------
|
|
Copyright (c) 2021, Daan Leijen
|
|
This is free software; you can redistribute it and/or modify it
|
|
under the terms of the MIT License. A copy of the license can be
|
|
found in the "LICENSE" file at the root of this distribution.
|
|
-----------------------------------------------------------------------------*/
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "../include/isocline.h"
|
|
#include "common.h"
|
|
#include "env.h"
|
|
#include "stringbuf.h"
|
|
#include "completions.h"
|
|
|
|
|
|
//-------------------------------------------------------------
|
|
// Completions
|
|
//-------------------------------------------------------------
|
|
|
|
typedef struct completion_s {
|
|
const char* replacement;
|
|
const char* display;
|
|
const char* help;
|
|
ssize_t delete_before;
|
|
ssize_t delete_after;
|
|
} completion_t;
|
|
|
|
struct completions_s {
|
|
ic_completer_fun_t* completer;
|
|
void* completer_arg;
|
|
ssize_t completer_max;
|
|
ssize_t count;
|
|
ssize_t len;
|
|
completion_t* elems;
|
|
alloc_t* mem;
|
|
};
|
|
|
|
static void default_filename_completer( ic_completion_env_t* cenv, const char* prefix );
|
|
|
|
ic_private completions_t* completions_new(alloc_t* mem) {
|
|
completions_t* cms = mem_zalloc_tp(mem, completions_t);
|
|
if (cms == NULL) return NULL;
|
|
cms->mem = mem;
|
|
cms->completer = &default_filename_completer;
|
|
return cms;
|
|
}
|
|
|
|
ic_private void completions_free(completions_t* cms) {
|
|
if (cms == NULL) return;
|
|
completions_clear(cms);
|
|
if (cms->elems != NULL) {
|
|
mem_free(cms->mem, cms->elems);
|
|
cms->elems = NULL;
|
|
cms->count = 0;
|
|
cms->len = 0;
|
|
}
|
|
mem_free(cms->mem, cms); // free ourselves
|
|
}
|
|
|
|
|
|
ic_private void completions_clear(completions_t* cms) {
|
|
while (cms->count > 0) {
|
|
completion_t* cm = cms->elems + cms->count - 1;
|
|
mem_free( cms->mem, cm->display);
|
|
mem_free( cms->mem, cm->replacement);
|
|
mem_free( cms->mem, cm->help);
|
|
memset(cm,0,sizeof(*cm));
|
|
cms->count--;
|
|
}
|
|
}
|
|
|
|
static void completions_push(completions_t* cms, const char* replacement, const char* display, const char* help, ssize_t delete_before, ssize_t delete_after)
|
|
{
|
|
if (cms->count >= cms->len) {
|
|
ssize_t newlen = (cms->len <= 0 ? 32 : cms->len*2);
|
|
completion_t* newelems = mem_realloc_tp(cms->mem, completion_t, cms->elems, newlen );
|
|
if (newelems == NULL) return;
|
|
cms->elems = newelems;
|
|
cms->len = newlen;
|
|
}
|
|
assert(cms->count < cms->len);
|
|
completion_t* cm = cms->elems + cms->count;
|
|
cm->replacement = mem_strdup(cms->mem,replacement);
|
|
cm->display = mem_strdup(cms->mem,display);
|
|
cm->help = mem_strdup(cms->mem,help);
|
|
cm->delete_before = delete_before;
|
|
cm->delete_after = delete_after;
|
|
cms->count++;
|
|
}
|
|
|
|
ic_private ssize_t completions_count(completions_t* cms) {
|
|
return cms->count;
|
|
}
|
|
|
|
static bool completions_contains(completions_t* cms, const char* replacement) {
|
|
for( ssize_t i = 0; i < cms->count; i++ ) {
|
|
const completion_t* c = cms->elems + i;
|
|
if (strcmp(replacement,c->replacement) == 0) { return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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) {
|
|
if (cms->completer_max <= 0) return false;
|
|
cms->completer_max--;
|
|
//debug_msg("completion: add: %d,%d, %s\n", delete_before, delete_after, replacement);
|
|
if (!completions_contains(cms,replacement)) {
|
|
completions_push(cms, replacement, display, help, delete_before, delete_after);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static completion_t* completions_get(completions_t* cms, ssize_t index) {
|
|
if (index < 0 || cms->count <= 0 || index >= cms->count) return NULL;
|
|
return &cms->elems[index];
|
|
}
|
|
|
|
ic_private const char* completions_get_display( completions_t* cms, ssize_t index, const char** help ) {
|
|
if (help != NULL) { *help = NULL; }
|
|
completion_t* cm = completions_get(cms, index);
|
|
if (cm == NULL) return NULL;
|
|
if (help != NULL) { *help = cm->help; }
|
|
return (cm->display != NULL ? cm->display : cm->replacement);
|
|
}
|
|
|
|
ic_private const char* completions_get_help( completions_t* cms, ssize_t index ) {
|
|
completion_t* cm = completions_get(cms, index);
|
|
if (cm == NULL) return NULL;
|
|
return cm->help;
|
|
}
|
|
|
|
ic_private const char* completions_get_hint(completions_t* cms, ssize_t index, const char** help) {
|
|
if (help != NULL) { *help = NULL; }
|
|
completion_t* cm = completions_get(cms, index);
|
|
if (cm == NULL) return NULL;
|
|
ssize_t len = ic_strlen(cm->replacement);
|
|
if (len < cm->delete_before) return NULL;
|
|
const char* hint = (cm->replacement + cm->delete_before);
|
|
if (*hint == 0 || utf8_is_cont((uint8_t)(*hint))) return NULL; // utf8 boundary?
|
|
if (help != NULL) { *help = cm->help; }
|
|
return hint;
|
|
}
|
|
|
|
ic_private void completions_set_completer(completions_t* cms, ic_completer_fun_t* completer, void* arg) {
|
|
cms->completer = completer;
|
|
cms->completer_arg = arg;
|
|
}
|
|
|
|
ic_private void completions_get_completer(completions_t* cms, ic_completer_fun_t** completer, void** arg) {
|
|
*completer = cms->completer;
|
|
*arg = cms->completer_arg;
|
|
}
|
|
|
|
|
|
ic_public void* ic_completion_arg( const ic_completion_env_t* cenv ) {
|
|
return (cenv == NULL ? NULL : cenv->env->completions->completer_arg);
|
|
}
|
|
|
|
ic_public bool ic_has_completions( const ic_completion_env_t* cenv ) {
|
|
return (cenv == NULL ? false : cenv->env->completions->count > 0);
|
|
}
|
|
|
|
ic_public bool ic_stop_completing( const ic_completion_env_t* cenv) {
|
|
return (cenv == NULL ? true : cenv->env->completions->completer_max <= 0);
|
|
}
|
|
|
|
|
|
static ssize_t completion_apply( completion_t* cm, stringbuf_t* sbuf, ssize_t pos ) {
|
|
if (cm == NULL) return -1;
|
|
debug_msg( "completion: apply: %s at %zd\n", cm->replacement, pos);
|
|
ssize_t start = pos - cm->delete_before;
|
|
if (start < 0) start = 0;
|
|
ssize_t n = cm->delete_before + cm->delete_after;
|
|
if (ic_strlen(cm->replacement) == n && strncmp(sbuf_string_at(sbuf,start), cm->replacement, to_size_t(n)) == 0) {
|
|
// no changes
|
|
return -1;
|
|
}
|
|
else {
|
|
sbuf_delete_from_to( sbuf, start, pos + cm->delete_after );
|
|
return sbuf_insert_at(sbuf, cm->replacement, start);
|
|
}
|
|
}
|
|
|
|
ic_private ssize_t completions_apply( completions_t* cms, ssize_t index, stringbuf_t* sbuf, ssize_t pos ) {
|
|
completion_t* cm = completions_get(cms, index);
|
|
return completion_apply( cm, sbuf, pos );
|
|
}
|
|
|
|
|
|
static int completion_compare(const void* p1, const void* p2) {
|
|
if (p1 == NULL || p2 == NULL) return 0;
|
|
const completion_t* cm1 = (const completion_t*)p1;
|
|
const completion_t* cm2 = (const completion_t*)p2;
|
|
return ic_stricmp(cm1->replacement, cm2->replacement);
|
|
}
|
|
|
|
ic_private void completions_sort(completions_t* cms) {
|
|
if (cms->count <= 0) return;
|
|
qsort(cms->elems, to_size_t(cms->count), sizeof(cms->elems[0]), &completion_compare);
|
|
}
|
|
|
|
#define IC_MAX_PREFIX (256)
|
|
|
|
// find longest common prefix and complete with that.
|
|
ic_private ssize_t completions_apply_longest_prefix(completions_t* cms, stringbuf_t* sbuf, ssize_t pos) {
|
|
if (cms->count <= 1) {
|
|
return completions_apply(cms,0,sbuf,pos);
|
|
}
|
|
|
|
// set initial prefix to the first entry
|
|
completion_t* cm = completions_get(cms, 0);
|
|
if (cm == NULL) return -1;
|
|
|
|
char prefix[IC_MAX_PREFIX+1];
|
|
ssize_t delete_before = cm->delete_before;
|
|
ic_strncpy( prefix, IC_MAX_PREFIX+1, cm->replacement, IC_MAX_PREFIX );
|
|
prefix[IC_MAX_PREFIX] = 0;
|
|
|
|
// and visit all others to find the longest common prefix
|
|
for(ssize_t i = 1; i < cms->count; i++) {
|
|
cm = completions_get(cms,i);
|
|
if (cm->delete_before != delete_before) { // deletions must match delete_before
|
|
prefix[0] = 0;
|
|
break;
|
|
}
|
|
// check if it is still a prefix
|
|
const char* r = cm->replacement;
|
|
ssize_t j;
|
|
for(j = 0; prefix[j] != 0 && r[j] != 0; j++) {
|
|
if (prefix[j] != r[j]) break;
|
|
}
|
|
prefix[j] = 0;
|
|
if (j <= 0) break;
|
|
}
|
|
|
|
// check the length
|
|
ssize_t len = ic_strlen(prefix);
|
|
if (len <= 0 || len < delete_before) return -1;
|
|
|
|
// we found a prefix :-)
|
|
completion_t cprefix;
|
|
memset(&cprefix,0,sizeof(cprefix));
|
|
cprefix.delete_before = delete_before;
|
|
cprefix.replacement = prefix;
|
|
ssize_t newpos = completion_apply( &cprefix, sbuf, pos);
|
|
if (newpos < 0) return newpos;
|
|
|
|
// adjust all delete_before for the new replacement
|
|
for( ssize_t i = 0; i < cms->count; i++) {
|
|
cm = completions_get(cms,i);
|
|
cm->delete_before = len;
|
|
}
|
|
|
|
return newpos;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------
|
|
// Completer functions
|
|
//-------------------------------------------------------------
|
|
|
|
ic_public bool ic_add_completions(ic_completion_env_t* cenv, const char* prefix, const char** completions) {
|
|
for (const char** pc = completions; *pc != NULL; pc++) {
|
|
if (ic_istarts_with(*pc, prefix)) {
|
|
if (!ic_add_completion_ex(cenv, *pc, NULL, NULL)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ic_public bool ic_add_completion(ic_completion_env_t* cenv, const char* replacement) {
|
|
return ic_add_completion_ex(cenv, replacement, NULL, NULL);
|
|
}
|
|
|
|
ic_public bool ic_add_completion_ex( ic_completion_env_t* cenv, const char* replacement, const char* display, const char* help ) {
|
|
return ic_add_completion_prim(cenv,replacement,display,help,0,0);
|
|
}
|
|
|
|
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) {
|
|
return (*cenv->complete)(cenv->env, cenv->closure, replacement, display, help, delete_before, delete_after );
|
|
}
|
|
|
|
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) {
|
|
ic_unused(funenv);
|
|
return completions_add(env->completions, replacement, display, help, delete_before, delete_after);
|
|
}
|
|
|
|
ic_public void ic_set_default_completer(ic_completer_fun_t* completer, void* arg) {
|
|
ic_env_t* env = ic_get_env(); if (env == NULL) return;
|
|
completions_set_completer(env->completions, completer, arg);
|
|
}
|
|
|
|
ic_private ssize_t completions_generate(struct ic_env_s* env, completions_t* cms, const char* input, ssize_t pos, ssize_t max) {
|
|
completions_clear(cms);
|
|
if (cms->completer == NULL || input == NULL || ic_strlen(input) < pos) return 0;
|
|
|
|
// set up env
|
|
ic_completion_env_t cenv;
|
|
cenv.env = env;
|
|
cenv.input = input,
|
|
cenv.cursor = (long)pos;
|
|
cenv.arg = cms->completer_arg;
|
|
cenv.complete = &prim_add_completion;
|
|
cenv.closure = NULL;
|
|
const char* prefix = mem_strndup(cms->mem, input, pos);
|
|
cms->completer_max = max;
|
|
|
|
// and complete
|
|
cms->completer(&cenv,prefix);
|
|
|
|
// restore
|
|
mem_free(cms->mem,prefix);
|
|
return completions_count(cms);
|
|
}
|
|
|
|
// The default completer is no completion is set
|
|
static void default_filename_completer( ic_completion_env_t* cenv, const char* prefix ) {
|
|
#ifdef _WIN32
|
|
const char sep = '\\';
|
|
#else
|
|
const char sep = '/';
|
|
#endif
|
|
ic_complete_filename( cenv, prefix, sep, ".", NULL);
|
|
}
|