/* This file implements a th1ish binding for linenoise: https://github.com/msteveb/linenoise */ #include <assert.h> #include <memory.h> /* strlen() */ #include "linenoise/linenoise.h" #ifdef TH1ISH_AMALGAMATION_BUILD #include "th1ish_amalgamation.h" #else #include "th1ish.h" #endif #include <stdlib.h> /* free() */ #if 0 #include "../cwal_internal.h" /* only for debuggering */ #endif #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) /** This is essentially a placeholder structure to a unique-per-interpreter handle to the linenoise lib. We need a per-instance handle only so that we can implement auto-save when the value is cleaned up. */ struct ln_native { cwal_value * vSelf; #ifndef NO_COMPLETION linenoiseCompletions * lc; #else void * lc; #endif }; typedef struct ln_native ln_native; /** Serves as a copy-constructable default instance and as a type ID pointer for cwal_new_native() and friends. */ static const ln_native ln_native_empty = {NULL, NULL}; static ln_native ln_native_shared = {NULL, NULL}; #define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args); \ assert(ie) #define THIS_LN \ cwal_native * nself = cwal_value_get_native(args->self); \ ln_native * self = nself \ ? (ln_native *)cwal_native_get(nself, &ln_native_empty) : NULL; \ ARGS_IE; \ if(!self) return th1ish_toss(ie, CWAL_RC_TYPE, \ "'this' is not (or is no longer) "\ "a linenoise instance.") static int ln_hist_load( cwal_callback_args const * args, cwal_value **rv ){ int rc; cwal_value * vfn = NULL; char const * fn; char setProp = 0; THIS_LN; if(!args->argc){ vfn = cwal_prop_get(self->vSelf, "historyFile", 11); fn = vfn ? cwal_value_get_cstr(vfn, NULL) : NULL; }else{ vfn = args->argv[0]; fn = cwal_value_get_cstr(vfn, NULL); if(fn) setProp = 1; } if(!fn || !*fn){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a non-empty string " "argument or a this.historyFile " "property."); } rc = linenoiseHistoryLoad(fn); if(!rc && setProp){ cwal_prop_set( args->self, "historyFile", 11, vfn ); } return rc ? th1ish_toss(ie, CWAL_RC_ERROR, "linenoiseHistoryLoad('%s') failed " "with code %d.", fn, rc) : 0; } static int ln_hist_save( cwal_callback_args const * args, cwal_value **rv ){ cwal_value * hName = NULL; char const * fn = NULL; THIS_LN; if(!args->argc){ hName = cwal_prop_get(self->vSelf, "historyFile", 11); fn = hName ? cwal_value_get_cstr(hName, NULL) : NULL; }else{ fn = cwal_value_get_cstr(args->argv[0], NULL); } if(!fn || !*fn){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a non-empty string " "argument or a this.historyFile " "property."); } else{ int const rc = linenoiseHistorySave(fn); return rc ? th1ish_toss(ie, CWAL_RC_ERROR, "linenoiseHistorySave('%s') failed " "with code %d.", fn, rc) : 0; } } static int ln_hist_size( cwal_callback_args const * args, cwal_value **rv ){ if(!args->argc){ *rv = cwal_new_integer(args->engine, (cwal_int_t)linenoiseHistoryGetMaxLen()); return *rv ? 0 : CWAL_RC_OOM; }else{ linenoiseHistorySetMaxLen( cwal_value_get_integer(args->argv[0]) ); *rv = cwal_value_undefined(); return 0; } } static int ln_hist_add( cwal_callback_args const * args, cwal_value **rv ){ ARGS_IE; if(!args->argc || !cwal_value_is_string(args->argv[0])){ return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a string argument."); }else{ cwal_size_t slen = 0; char const * str = cwal_value_get_cstr(args->argv[0], &slen); cwal_buffer * buf = &ie->buffer; cwal_size_t const oldUsed = buf->used; int rc = cwal_buffer_append(args->engine, buf, str, slen); /* We use a buffer here or this will fail if str is not NUL-terminated (an x-string can/will trigger this). That said, we don't really expect X-strings to ever be fed in to the CLI history. */ if(rc) return rc; else if(buf->used == oldUsed){ *rv = cwal_value_false(); }else{ *rv = cwal_new_bool( linenoiseHistoryAdd((char const *)(buf->mem+oldUsed)) ? 1 : 0); buf->used = oldUsed; } return 0; } } #ifndef NO_COMPLETION static struct { th1ish_interp * ie; ln_native * ln; cwal_function * cb; int rc; } LnCompletion = { NULL, NULL, NULL, 0 }; static void ln_completion_add(linenoiseCompletions *lc, cwal_value const * v) { char const * str = v ? cwal_value_get_cstr(v, NULL) : NULL; if(str){ linenoiseAddCompletion(lc,str); } } static void ln_completion(const char *buf, linenoiseCompletions *lc) { int rc; cwal_value * str; cwal_scope * sc = cwal_scope_current_get(LnCompletion.ie->e); cwal_value * rv = NULL; str = cwal_new_string_value(LnCompletion.ie->e, buf, 0); if(!str){ LnCompletion.rc = CWAL_RC_OOM; return; } rc = cwal_function_call_in_scope(sc, LnCompletion.cb, LnCompletion.ln->vSelf, &rv, 1, &str); str = NULL; if(rc){ LnCompletion.rc = rc; }else if(cwal_value_is_array(rv)){ cwal_array * ar = cwal_value_get_array(rv); if(!ar) return; else{ cwal_size_t i; cwal_size_t const n = cwal_array_length_get(ar); for(i = 0; i < n; ++i){ ln_completion_add(lc, cwal_array_get(ar, i)); } } }else if(rv){ ln_completion_add(lc, rv); } } #endif /* NO_COMPLETION */ static int ln_columns( cwal_callback_args const * args, cwal_value **rv ){ *rv = cwal_new_integer(args->engine, (cwal_int_t)linenoiseColumns()); return *rv ? 0 : CWAL_RC_OOM; } static int ln_readline( cwal_callback_args const * args, cwal_value **rv ){ int rc = 0; char const * cprompt = NULL; char * z; THIS_LN; if(!args->argc){ cwal_value * vprompt = cwal_prop_get(self->vSelf, "prompt", 6); cprompt = vprompt ? cwal_value_get_cstr(vprompt, NULL) : NULL; }else{ cprompt = cwal_value_get_cstr(args->argv[0], NULL); } if(!cprompt) cprompt = ""; #ifndef NO_COMPLETION /* Thanks to Andreas Kupries for this trick... */ { /* If this.customCompletions() is-a Function then install it has our TAB handler for the duration of this lineloise() call. */ cwal_value * cbv = cwal_prop_get(self->vSelf, "customCompletions", 17); LnCompletion.cb = cbv ? cwal_value_get_function(cbv) : 0; } if(LnCompletion.cb){ linenoiseSetCompletionCallback(ln_completion); LnCompletion.ie = ie; LnCompletion.ln = self; } #endif z = linenoise(cprompt); #ifndef NO_COMPLETION if(LnCompletion.cb){ linenoiseSetCompletionCallback(NULL); LnCompletion.ie = NULL; LnCompletion.cb = NULL; rc = LnCompletion.rc; LnCompletion.rc = 0; } #endif if(rc) return rc; if(!z){ *rv = cwal_value_undefined(); } else{ *rv = cwal_new_string_value(args->engine, z, 0); if(*rv && *z){ /* Shame that we can't just _give_ z back to linenoise here, since it came from there. */ linenoiseHistoryAdd(z); /* TODO: save here: linenoiseHistorySave(...); */ } free(z) /* reminder: z came from linenoise, i.e. malloc(), not cwal_malloc() */; } return *rv ? 0 : CWAL_RC_OOM; } static int ln_history_list( cwal_callback_args const * args, cwal_value **rv ){ cwal_array * ar; cwal_size_t i; int rc = 0; int hCount = 0; char ** h; THIS_LN; ar = th1ish_new_array(ie); if(!ar) return CWAL_RC_OOM; h = linenoiseHistory(&hCount); if(hCount>0){ rc = cwal_array_reserve(ar, (cwal_size_t)hCount); if(rc) goto end; for( i = 0; i < (cwal_size_t)hCount; ++i ){ cwal_value * v = cwal_new_string_value(args->engine, h[i], 0); if(!v){ rc = CWAL_RC_OOM; goto end; }else{ rc = cwal_array_append(ar, v); if(rc){ cwal_value_unref(v); goto end; } } } } end: if(rc){ cwal_array_unref(ar); return rc; }else{ *rv = cwal_array_value(ar); return 0; } } static void ln_native_finalize( cwal_engine * e, void * v ){ ln_native * ln = (ln_native *)v; cwal_value * hName = cwal_prop_get(ln->vSelf, "historyFile", 11); ln->vSelf = NULL; if(hName && cwal_value_is_string(hName)){ char const * fn = cwal_value_get_cstr(hName, NULL); linenoiseHistorySave(fn); } linenoiseHistoryFree(); if(&ln_native_shared != ln){ cwal_free( e, ln ); } } int ln_install_to_interp( th1ish_interp * ie, cwal_value * ns ){ int rc; cwal_value * v; cwal_value * mod; static char const * modKey = "linenoise"; static cwal_size_t const keyLen = 9; ln_native * ln; if(!cwal_props_can(ns)) return CWAL_RC_MISUSE; v = cwal_prop_get( ns, modKey, keyLen); if(v) return 0; #if 0 ln = &ln_native_shared; #else ln = (ln_native*)cwal_malloc(ie->e, sizeof(ln_native)); if(!ln) return CWAL_RC_OOM; #endif mod = cwal_new_native_value(ie->e, ln, ln_native_finalize, &ln_native_empty); if(!mod){ cwal_free(ie->e, ln); return CWAL_RC_OOM; } ln->vSelf = mod; rc = cwal_prop_set( ns, modKey, keyLen, mod ); if(rc){ cwal_value_unref(mod); goto end; } cwal_value_prototype_set( mod, th1ish_prototype_object(ie) ); #define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0 #define SET(KEY) rc = cwal_prop_set( mod, KEY, strlen(KEY), v ); \ assert(!rc); if(rc) goto end #define FUNC2(NAME,FP) \ v = th1ish_new_function2( ie, FP ); \ CHECKV; \ SET(NAME); \ assert(!rc); \ if(rc) goto end FUNC2("add", ln_hist_add); FUNC2("consoleWidth", ln_columns); FUNC2("getHistoryList", ln_history_list); FUNC2("load", ln_hist_load); FUNC2("read", ln_readline); FUNC2("save", ln_hist_save); FUNC2("size", ln_hist_size); FUNC2("canCompile", th1ish_f_basic_compile_check); #undef SET #undef FUNC2 #undef CHECKV end: return rc; } /* Install th1ish_module_load() support. */ /*TH1ISH_MODULE_DECL(linenoiseish);*/ TH1ISH_MODULE_IMPL(linenoiseish,ln_install_to_interp); TH1ISH_MODULE_IMPL1(linenoiseish,ln_install_to_interp); #undef MARKER #undef ARGS_IE #undef THIS_LN