Browse Source

Add a minimal linenoise completion cb for 'duk'

pull/679/head
Sami Vaarala 9 years ago
parent
commit
732db20b9e
  1. 204
      examples/cmdline/duk_cmdline.c
  2. 4
      tests/ecmascript/test-misc-hello-world.js

204
examples/cmdline/duk_cmdline.c

@ -9,6 +9,7 @@
/* Helper define to enable a feature set; can also use separate defines. */
#if defined(DUK_CMDLINE_FANCY)
#define DUK_CMDLINE_LINENOISE
#define DUK_CMDLINE_LINENOISE_COMPLETION
#define DUK_CMDLINE_RLIMIT
#define DUK_CMDLINE_SIGNAL
#endif
@ -292,6 +293,199 @@ static int wrapped_compile_execute(duk_context *ctx) {
return 0; /* duk_safe_call() cleans up */
}
/*
* Minimal Linenoise completion support
*/
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
static duk_context *completion_ctx;
static int completion_idpart(unsigned char c) {
/* Very simplified "is identifier part" check. */
if ((c >= (unsigned char) 'a' && c <= (unsigned char) 'z') ||
(c >= (unsigned char) 'A' && c <= (unsigned char) 'Z') ||
(c >= (unsigned char) '0' && c <= (unsigned char) '9') ||
c == (unsigned char) '$' || c == (unsigned char) '_') {
return 1;
}
return 0;
}
static int completion_digit(unsigned char c) {
return (c >= (unsigned char) '0' && c <= (unsigned char) '9');
}
static duk_ret_t linenoise_completion_lookup(duk_context *ctx) {
duk_size_t len;
const char *orig;
const unsigned char *p;
const unsigned char *p_curr;
const unsigned char *p_end;
const char *key;
const char *prefix;
linenoiseCompletions *lc;
duk_idx_t idx_obj;
orig = duk_require_string(ctx, -3);
p_curr = (const unsigned char *) duk_require_lstring(ctx, -2, &len);
p_end = p_curr + len;
lc = duk_require_pointer(ctx, -1);
duk_push_global_object(ctx);
idx_obj = duk_require_top_index(ctx);
while (p_curr <= p_end) {
/* p_curr == p_end allowed on purpose, to handle 'Math.' for example. */
p = p_curr;
while (p < p_end && p[0] != (unsigned char) '.') {
p++;
}
/* 'p' points to a NUL (p == p_end) or a period. */
prefix = duk_push_lstring(ctx, (const char *) p_curr, (duk_size_t) (p - p_curr));
#if 0
fprintf(stderr, "Completion check: '%s'\n", prefix);
fflush(stderr);
#endif
if (p == p_end) {
/* 'idx_obj' points to the object matching the last
* full component, use [p_curr,p[ as a filter for
* that object.
*/
duk_enum(ctx, idx_obj, DUK_ENUM_INCLUDE_NONENUMERABLE);
while (duk_next(ctx, -1, 0 /*get_value*/)) {
key = duk_get_string(ctx, -1);
#if 0
fprintf(stderr, "Key: %s\n", key ? key : "");
fflush(stderr);
#endif
if (!key) {
/* Should never happen, just in case. */
goto next;
}
/* Ignore array index keys: usually not desirable, and would
* also require ['0'] quoting.
*/
if (completion_digit(key[0])) {
goto next;
}
/* XXX: There's no key quoting now, it would require replacing the
* last component with a ['foo\nbar'] style lookup when appropriate.
*/
if (strlen(prefix) == 0) {
/* Partial ends in a period, e.g. 'Math.' -> complete all Math properties. */
duk_push_string(ctx, orig); /* original, e.g. 'Math.' */
duk_push_string(ctx, key);
duk_concat(ctx, 2);
linenoiseAddCompletion(lc, duk_require_string(ctx, -1));
duk_pop(ctx);
} else if (prefix && strcmp(key, prefix) == 0) {
/* Full completion, add a period, e.g. input 'Math' -> 'Math.'. */
duk_push_string(ctx, orig); /* original, including partial last component */
duk_push_string(ctx, ".");
duk_concat(ctx, 2);
linenoiseAddCompletion(lc, duk_require_string(ctx, -1));
duk_pop(ctx);
} else if (prefix && strncmp(key, prefix, strlen(prefix)) == 0) {
/* Last component is partial, complete. */
duk_push_string(ctx, orig); /* original, including partial last component */
duk_push_string(ctx, key + strlen(prefix)); /* completion to last component */
duk_concat(ctx, 2);
linenoiseAddCompletion(lc, duk_require_string(ctx, -1));
duk_pop(ctx);
}
next:
duk_pop(ctx);
}
return 0;
} else {
if (duk_get_prop(ctx, idx_obj)) {
duk_to_object(ctx, -1); /* for properties of plain strings etc */
duk_replace(ctx, idx_obj);
p_curr = p + 1;
} else {
/* Not found. */
return 0;
}
}
}
return 0;
}
static void linenoise_completion(const char *buf, linenoiseCompletions *lc) {
duk_context *ctx;
const unsigned char *p_start;
const unsigned char *p_end;
const unsigned char *p;
duk_int_t rc;
if (!buf) {
return;
}
ctx = completion_ctx;
if (!ctx) {
return;
}
p_start = (const unsigned char *) buf;
p_end = (const unsigned char *) (buf + strlen(buf));
p = p_end;
/* Scan backwards for a maximal string which looks like a property
* chain (e.g. foo.bar.quux).
*/
while (--p >= p_start) {
if (p[0] == (unsigned char) '.') {
if (p <= p_start) {
break;
}
if (!completion_idpart(p[-1])) {
/* Catches e.g. 'foo..bar' -> we want 'bar' only. */
break;
}
} else if (!completion_idpart(p[0])) {
break;
}
}
/* 'p' will either be p_start - 1 (ran out of buffer) or point to
* the first offending character.
*/
p++;
if (p < p_start || p >= p_end) {
return; /* should never happen, but just in case */
}
/* 'p' now points to a string of the form 'foo.bar.quux'. Look up
* all the components except the last; treat the last component as
* a partial name which is used as a filter for the previous full
* component. All lookups are from the global object now.
*/
#if 0
fprintf(stderr, "Completion starting point: '%s'\n", p);
fflush(stderr);
#endif
duk_push_string(ctx, (const char *) buf);
duk_push_lstring(ctx, (const char *) p, (duk_size_t) (p_end - p));
duk_push_pointer(ctx, (void *) lc);
rc = duk_safe_call(ctx, linenoise_completion_lookup, 3 /*nargs*/, 1 /*nrets*/);
if (rc != DUK_EXEC_SUCCESS) {
fprintf(stderr, "Completion handling failure: %s\n", duk_safe_to_string(ctx, -1));
}
duk_pop(ctx);
}
#endif /* DUK_CMDLINE_LINENOISE_COMPLETION */
/*
* Execute from file handle etc
*/
@ -498,6 +692,9 @@ static int handle_interactive(duk_context *ctx) {
linenoiseSetMultiLine(1);
linenoiseHistorySetMaxLen(64);
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
linenoiseSetCompletionCallback(linenoise_completion);
#endif
for (;;) {
if (buffer) {
@ -505,7 +702,14 @@ static int handle_interactive(duk_context *ctx) {
buffer = NULL;
}
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
completion_ctx = ctx;
#endif
buffer = linenoise(prompt);
#if defined(DUK_CMDLINE_LINENOISE_COMPLETION)
completion_ctx = NULL;
#endif
if (!buffer) {
break;
}

4
tests/ecmascript/test-misc-hello-world.js

@ -3,7 +3,7 @@
*/
/*===
Hello world!
Hello Hulda!
===*/
print('Hello world!');
print('Hello Hulda!');

Loading…
Cancel
Save