[gjs/ewlsh/nova-repl] Replace Console.interact with Repl-based API
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/nova-repl] Replace Console.interact with Repl-based API
- Date: Fri, 3 Sep 2021 18:50:34 +0000 (UTC)
commit 79d2d67509944144f18330d069204629a69f60d3
Author: Evan Welsh <contact evanwelsh com>
Date: Fri Sep 3 11:50:23 2021 -0700
Replace Console.interact with Repl-based API
js.gresource.xml | 1 +
modules/console.cpp | 184 +++-----------------------------------
modules/console.h | 4 -
modules/esm/_bootstrap/default.js | 2 +
modules/esm/repl.js | 172 +++++++++++++++++++++++++----------
modules/modules.cpp | 1 -
modules/script/console.js | 12 +++
7 files changed, 150 insertions(+), 226 deletions(-)
---
diff --git a/js.gresource.xml b/js.gresource.xml
index a5c057b0..efd07bc4 100644
--- a/js.gresource.xml
+++ b/js.gresource.xml
@@ -36,6 +36,7 @@
<file>modules/script/byteArray.js</file>
<file>modules/script/cairo.js</file>
+ <file>modules/script/console.js</file>
<file>modules/script/gettext.js</file>
<file>modules/script/lang.js</file>
<file>modules/script/_legacy.js</file>
diff --git a/modules/console.cpp b/modules/console.cpp
index 4daa45ef..ccef2bc3 100644
--- a/modules/console.cpp
+++ b/modules/console.cpp
@@ -15,13 +15,6 @@
# endif
#endif
-#ifdef HAVE_READLINE_READLINE_H
-# include <stdio.h> // include before readline/readline.h
-
-# include <readline/history.h>
-# include <readline/readline.h>
-#endif
-
#include <string>
#include <glib.h>
@@ -92,59 +85,14 @@ public:
}
};
-
-// Adapted from https://stackoverflow.com/a/17035073/172999
-class AutoCatchCtrlC {
-#ifdef HAVE_SIGNAL_H
- void (*m_prev_handler)(int);
-
- static void handler(int signal) {
- if (signal == SIGINT)
- siglongjmp(jump_buffer, 1);
- }
-
- public:
- static sigjmp_buf jump_buffer;
-
- AutoCatchCtrlC() {
- m_prev_handler = signal(SIGINT, &AutoCatchCtrlC::handler);
- }
-
- ~AutoCatchCtrlC() {
- if (m_prev_handler != SIG_ERR)
- signal(SIGINT, m_prev_handler);
- }
-
- void raise_default() {
- if (m_prev_handler != SIG_ERR)
- signal(SIGINT, m_prev_handler);
- raise(SIGINT);
- }
-#endif // HAVE_SIGNAL_H
-};
-
-#ifdef HAVE_SIGNAL_H
-sigjmp_buf AutoCatchCtrlC::jump_buffer;
-#endif // HAVE_SIGNAL_H
-
[[nodiscard]] static bool gjs_console_readline(char** bufp,
const char* prompt) {
-#ifdef HAVE_READLINE_READLINE_H
- char *line;
- line = readline(prompt);
- if (!line)
- return false;
- if (line[0] != '\0')
- add_history(line);
- *bufp = line;
-#else // !HAVE_READLINE_READLINE_H
char line[256];
fprintf(stdout, "%s", prompt);
fflush(stdout);
if (!fgets(line, sizeof line, stdin))
return false;
*bufp = g_strdup(line);
-#endif // !HAVE_READLINE_READLINE_H
return true;
}
@@ -172,120 +120,22 @@ sigjmp_buf AutoCatchCtrlC::jump_buffer;
return true;
}
-/* Return value of false indicates an uncatchable exception, rather than any
- * exception. (This is because the exception should be auto-printed around the
- * invocation of this function.)
- */
-[[nodiscard]] static bool gjs_console_eval_and_print(JSContext* cx,
- const std::string& bytes,
- int lineno) {
- JS::RootedValue result(cx);
- if (!gjs_console_eval(cx, bytes, lineno, &result)) {
- return false;
- }
-
- if (result.isUndefined())
- return true;
-
- g_fprintf(stdout, "%s\n", gjs_value_debug_string(cx, result).c_str());
- return true;
-}
-
GJS_JSAPI_RETURN_CONVENTION
-static bool
-gjs_console_interact(JSContext *context,
- unsigned argc,
- JS::Value *vp)
-{
- JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
- bool eof, exit_warning;
+static bool gjs_console_interact(JSContext* context, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject global(context, gjs_get_import_global(context));
- char* temp_buf;
- int lineno;
- int startline;
-
-#ifndef HAVE_READLINE_READLINE_H
- int rl_end = 0; // nonzero if using readline and any text is typed in
-#endif
- JS::SetWarningReporter(context, gjs_console_warning_reporter);
-
- AutoCatchCtrlC ctrl_c;
-
- // Separate initialization from declaration because of possible overwriting
- // when siglongjmp() jumps into this function
- eof = exit_warning = false;
- temp_buf = nullptr;
- lineno = 1;
- do {
- /*
- * Accumulate lines until we get a 'compilable unit' - one that either
- * generates an error (before running out of source) or that compiles
- * cleanly. This should be whenever we get a complete statement that
- * coincides with the end of a line.
- */
- startline = lineno;
- std::string buffer;
- do {
-#ifdef HAVE_SIGNAL_H
- // sigsetjmp() returns 0 if control flow encounters it normally, and
- // nonzero if it's been jumped to. In the latter case, use a while
- // loop so that we call sigsetjmp() a second time to reinit the jump
- // buffer.
- while (sigsetjmp(AutoCatchCtrlC::jump_buffer, 1) != 0) {
- g_fprintf(stdout, "\n");
- if (buffer.empty() && rl_end == 0) {
- if (!exit_warning) {
- g_fprintf(stdout,
- "(To exit, press Ctrl+C again or Ctrl+D)\n");
- exit_warning = true;
- } else {
- ctrl_c.raise_default();
- }
- } else {
- exit_warning = false;
- }
- buffer.clear();
- startline = lineno = 1;
- }
-#endif // HAVE_SIGNAL_H
-
- if (!gjs_console_readline(
- &temp_buf, startline == lineno ? "gjs> " : ".... ")) {
- eof = true;
- break;
- }
- buffer += temp_buf;
- buffer += "\n";
- g_free(temp_buf);
- lineno++;
- } while (!JS_Utf8BufferIsCompilableUnit(context, global, buffer.c_str(),
- buffer.size()));
-
- bool ok;
- {
- AutoReportException are(context);
- ok = gjs_console_eval_and_print(context, buffer, startline);
- }
- exit_warning = false;
-
- GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
- ok = gjs->run_jobs_fallible() && ok;
-
- if (!ok) {
- /* If this was an uncatchable exception, throw another uncatchable
- * exception on up to the surrounding JS::Evaluate() in main(). This
- * happens when you run gjs-console and type imports.system.exit(0);
- * at the prompt. If we don't throw another uncatchable exception
- * here, then it's swallowed and main() won't exit. */
- return false;
- }
- } while (!eof);
+ JS::UniqueChars prompt;
+ if (!gjs_parse_call_args(context, "interact", args, "s", "prompt", &prompt))
+ return false;
- g_fprintf(stdout, "\n");
+ GjsAutoChar buffer;
+ if (!gjs_console_readline(buffer.out(), prompt.get())) {
+ return true;
+ }
- argv.rval().setUndefined();
- return true;
+ return gjs_string_from_utf8(context, buffer, args.rval());
}
GJS_JSAPI_RETURN_CONVENTION
@@ -348,18 +198,8 @@ static bool gjs_console_is_valid_js(JSContext* cx, unsigned argc,
return true;
}
-bool
-gjs_define_console_stuff(JSContext *context,
- JS::MutableHandleObject module)
-{
- module.set(JS_NewPlainObject(context));
- const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
- return JS_DefineFunctionById(context, module, atoms.interact(),
- gjs_console_interact, 1,
- GJS_MODULE_PROP_FLAGS);
-}
-
static JSFunctionSpec private_module_funcs[] = {
+ JS_FN("interact", gjs_console_interact, 1, GJS_MODULE_PROP_FLAGS),
JS_FN("enableRawMode", gjs_console_enable_raw_mode, 0,
GJS_MODULE_PROP_FLAGS),
JS_FN("disableRawMode", gjs_console_disable_raw_mode, 0,
diff --git a/modules/console.h b/modules/console.h
index d63df22e..7df6a02d 100644
--- a/modules/console.h
+++ b/modules/console.h
@@ -11,10 +11,6 @@
#include "gjs/macros.h"
-GJS_JSAPI_RETURN_CONVENTION
-bool gjs_define_console_stuff(JSContext *context,
- JS::MutableHandleObject module);
-
GJS_JSAPI_RETURN_CONVENTION
bool gjs_define_console_private_stuff(JSContext* context,
JS::MutableHandleObject module);
diff --git a/modules/esm/_bootstrap/default.js b/modules/esm/_bootstrap/default.js
index afb155b0..242c38ef 100644
--- a/modules/esm/_bootstrap/default.js
+++ b/modules/esm/_bootstrap/default.js
@@ -7,3 +7,5 @@
import '_encoding/encoding';
// Bootstrap the Console API
import 'console';
+// Install the Repl constructor for Console.interact()
+import 'repl';
diff --git a/modules/esm/repl.js b/modules/esm/repl.js
index 794d789e..648f0b66 100644
--- a/modules/esm/repl.js
+++ b/modules/esm/repl.js
@@ -9,7 +9,6 @@ import Gio from 'gi://Gio';
import {Ansi, Keycode, Figures} from './_repl/cliffy.js';
const Console = import.meta.importSync('_consoleNative');
-const FallbackConsole = import.meta.importSync('console');
// TODO: Integrate with new printer once it is merged...
/**
@@ -84,6 +83,10 @@ class ReplInput {
_input => { };
}
+ [Symbol.toStringTag]() {
+ return 'Repl';
+ }
+
/**
* @param {string} prompt a string to tag each new line with
* @param {boolean} enableColors whether to print with color
@@ -93,7 +96,7 @@ class ReplInput {
const pointer = Figures.POINTER_SMALL;
const noColor = `${prefix} ${pointer} `;
const length = noColor.length;
- const withColor = `${Ansi.colors.yellow(prefix)} ${pointer} `;
+ const withColor = `${Ansi.colors.yellow(prefix)} ${pointer} `;
const renderedPrompt = enableColors ? withColor : noColor;
return {
@@ -280,6 +283,10 @@ class ReplInput {
}
}
+ get isRaw() {
+ return true;
+ }
+
processLine() {
const value = this.getValue();
// Rebuild the input...
@@ -291,9 +298,13 @@ class ReplInput {
this.currentInputChars = [];
this.cursorColumn = 0;
- // Append the new line...
- this.writeSync('\n');
- this.flush();
+ // In raw mode we need to manually write a
+ // new line...
+ if (this.isRaw) {
+ // Append the new line...
+ this.writeSync('\n');
+ this.flush();
+ }
// Only trigger input if this is a compilable unit...
if (this.validate(js)) {
@@ -410,6 +421,16 @@ class ReplInput {
this.cursorColumn = Math.min(this.cursorColumn, value.length);
}
+ getPrompt() {
+ if (this.pendingInputLines.length > 0) {
+ // Create a prefix like '... ' that matches the current
+ // prompt length...
+ return ' '.padStart(this._prompt.length, '.');
+ }
+
+ return this._prompt.renderedPrompt;
+ }
+
render() {
const value = this.getValue();
@@ -417,14 +438,8 @@ class ReplInput {
this.writeSync(Ansi.cursorHide);
this.clear();
- if (this.pendingInputLines.length > 0) {
- // Create a prefix like '... ' that matches the current
- // prompt length...
- const prefix = ' '.padStart(this._prompt.length, '.');
- this.writeSync(prefix + value);
- } else {
- this.writeSync(this._prompt.renderedPrompt + value);
- }
+ const prompt = this.getPrompt();
+ this.writeSync(prompt + value);
this.updateInputIndex(value);
this.writeSync(Ansi.cursorTo(this._prompt.length + this.cursorColumn + 1));
@@ -445,7 +460,21 @@ class ReplInput {
this.stdout.write_bytes(buffer, null);
}
- _readHandler(stream, result) {
+ handleInput(bytes) {
+ if (bytes.length === 0)
+ return;
+
+ for (const event of Keycode.parse(bytes)) {
+ this.handleEvent(event);
+
+ if (this._cancelled)
+ break;
+
+ this.render();
+ }
+ }
+
+ _asyncReadHandler(stream, result) {
this.cancellable = null;
if (this._cancelled)
@@ -454,21 +483,11 @@ class ReplInput {
if (result) {
const gbytes = stream.read_bytes_finish(result);
- const bytes = gbytes.toArray();
- if (bytes.length > 0) {
- for (const event of Keycode.parse(bytes)) {
- this.handleEvent(event);
-
- if (this._cancelled)
- break;
-
- this.render();
- }
- }
+ this.handleInput(gbytes.toArray());
}
this.cancellable = new Gio.Cancellable();
- stream.read_bytes_async(8, 0, this.cancellable, this._readHandler.bind(this));
+ stream.read_bytes_async(8, 0, this.cancellable, this._asyncReadHandler.bind(this));
}
cancel() {
@@ -484,7 +503,7 @@ class ReplInput {
this.render();
// Start the async read loop...
- this._readHandler(this.stdin);
+ this._asyncReadHandler(this.stdin);
}
/**
@@ -498,6 +517,35 @@ class ReplInput {
}
}
+class FallbackReplInput extends ReplInput {
+ constructor() {
+ super({});
+ }
+
+ read() {
+ while (!this._cancelled) {
+ const prompt = this.getPrompt();
+ this.editValue(() => {
+ return Console.interact(prompt).split('');
+ });
+
+ this.processLine();
+ }
+ }
+
+ get isRaw() {
+ return false;
+ }
+
+ writeSync(buffer) {
+ if (buffer instanceof Uint8Array)
+ buffer = new TextDecoder().decode(buffer);
+ print(buffer);
+ }
+
+ flush() { }
+}
+
const sCheckEnvironment = Symbol('check environment');
const sSupportsColor = Symbol('supports color');
const sMainLoop = Symbol('main loop');
@@ -505,6 +553,7 @@ const sMainLoop = Symbol('main loop');
export class Repl {
constructor() {
this.lineNumber = 0;
+ this.isRaw = false;
Object.defineProperties(this, {
[sCheckEnvironment]: {
@@ -529,14 +578,19 @@ export class Repl {
}
[sCheckEnvironment]() {
+ this[sSupportsColor] = GLib.log_writer_supports_color(1);
+ this[sSupportsColor] = this[sSupportsColor] && GLib.getenv('NO_COLOR') === null;
+
let hasUnixStreams = 'UnixInputStream' in Gio;
hasUnixStreams = hasUnixStreams && 'UnixOutputStream' in Gio;
if (!hasUnixStreams)
return false;
- this[sSupportsColor] = GLib.log_writer_supports_color(1);
- this[sSupportsColor] = this[sSupportsColor] && GLib.getenv('NO_COLOR') === null;
+ // TODO: Environment variable for testing.
+ if (GLib.getenv('GJS_REPL_NO_MAINLOOP'))
+ return false;
+
return true;
}
@@ -547,7 +601,9 @@ export class Repl {
const result = Console.eval(lines, this.lineNumber);
if (result !== undefined) {
- const encoded = new TextEncoder().encode(`${toString(result)}\n`);
+ const encoded = new TextEncoder().encode(
+ `${toString(result)}${this.isRaw ? '\n' : ''}`
+ );
this.input.writeSync(encoded);
this.input.flush();
}
@@ -556,34 +612,47 @@ export class Repl {
}
}
+ _registerInputHandler() {
+ // Start accepting input and rendering...
+ this.input.prompt(lines => {
+ if (lines.trim().startsWith('exit()'))
+ this.exit();
+ else
+ this.evaluate(lines);
+ });
+ }
+
run() {
if (!this[sCheckEnvironment]()) {
- FallbackConsole.interact();
+ this.input = new FallbackReplInput();
+
+ this._registerInputHandler();
return;
}
- const stdin = Gio.UnixInputStream.new(0, false);
- const stdout = Gio.UnixOutputStream.new(1, false);
- const stderr = Gio.UnixOutputStream.new(2, false);
+ try {
+ this.isRaw = Console.enableRawMode();
- this.input = new ReplInput({
- stdin,
- stdout,
- stderr,
- enableColor: this[sSupportsColor],
- });
+ if (!this.isRaw) {
+ this.input = new FallbackReplInput();
- try {
- Console.enableRawMode();
-
- // Start accepting input and rendering...
- this.input.prompt(lines => {
- if (lines.trim().startsWith('exit()'))
- this.exit();
- else
- this.evaluate(lines, stdout);
+ this._registerInputHandler();
+ return;
+ }
+
+ const stdin = Gio.UnixInputStream.new(0, false);
+ const stdout = Gio.UnixOutputStream.new(1, false);
+ const stderr = Gio.UnixOutputStream.new(2, false);
+
+ this.input = new ReplInput({
+ stdin,
+ stdout,
+ stderr,
+ enableColor: this[sSupportsColor],
});
+ this._registerInputHandler();
+
// Install our default mainloop...
this.replaceMainLoop(() => {
imports.mainloop.run('repl');
@@ -622,6 +691,9 @@ export class Repl {
// replacement mainloop's quit function.
System.exit(1);
}) {
+ if (!(this.input instanceof ReplInput))
+ return;
+
const mainloop = this[sMainLoop];
this[sMainLoop] = [start, quit];
@@ -632,3 +704,5 @@ export class Repl {
}
}
}
+
+imports.console.Repl = Repl;
diff --git a/modules/modules.cpp b/modules/modules.cpp
index 5ba66f98..f0a950d8 100644
--- a/modules/modules.cpp
+++ b/modules/modules.cpp
@@ -21,7 +21,6 @@ gjs_register_static_modules (void)
gjs_register_native_module("cairoNative", gjs_js_define_cairo_stuff);
#endif
gjs_register_native_module("system", gjs_js_define_system_stuff);
- gjs_register_native_module("console", gjs_define_console_stuff);
gjs_register_native_module("_consoleNative",
gjs_define_console_private_stuff);
gjs_register_native_module("_print", gjs_define_print_stuff);
diff --git a/modules/script/console.js b/modules/script/console.js
new file mode 100644
index 00000000..82ae75cc
--- /dev/null
+++ b/modules/script/console.js
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact evanwelsh com>
+
+/* exported Repl, interact */
+
+var Repl = null;
+
+function interact() {
+ const repl = new Repl();
+
+ repl.run();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]