/*
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