summaryrefslogtreecommitdiff
path: root/modules/console.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/console.cpp')
-rw-r--r--modules/console.cpp334
1 files changed, 133 insertions, 201 deletions
diff --git a/modules/console.cpp b/modules/console.cpp
index 35df0d2a..f2f6c140 100644
--- a/modules/console.cpp
+++ b/modules/console.cpp
@@ -3,29 +3,24 @@
// SPDX-License-Identifier: MPL-1.1 OR GPL-2.0-or-later OR LGPL-2.1-or-later
// SPDX-FileCopyrightText: 1998 Netscape Communications Corporation
-#include <config.h> // for HAVE_READLINE_READLINE_H
-
-#ifdef HAVE_SIGNAL_H
-# include <setjmp.h>
-# include <signal.h>
-# ifdef _WIN32
-# define sigjmp_buf jmp_buf
-# define siglongjmp(e, v) longjmp (e, v)
-# define sigsetjmp(v, m) setjmp (v)
-# 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 <config.h>
#include <string>
#include <glib.h>
+
+#include <glib.h>
#include <glib/gprintf.h> // for g_fprintf
+#include <stdio.h>
+
+#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_UNISTD_H)
+# include <fcntl.h>
+# include <sys/ioctl.h>
+# include <unistd.h>
+# if defined(TIOCGWINSZ)
+# define GET_SIZE_USE_IOCTL
+# endif
+#endif
#include <js/CallAndConstruct.h>
#include <js/CallArgs.h>
@@ -35,6 +30,8 @@
#include <js/ErrorReport.h>
#include <js/Exception.h>
#include <js/PropertyAndElement.h>
+#include <js/PropertyDescriptor.h>
+#include <js/PropertySpec.h>
#include <js/RootingAPI.h>
#include <js/SourceText.h>
#include <js/TypeDecls.h>
@@ -47,18 +44,16 @@
#include "gjs/atoms.h"
#include "gjs/context-private.h"
#include "gjs/global.h"
+#include "gjs/jsapi-util-args.h"
#include "gjs/jsapi-util.h"
#include "gjs/macros.h"
#include "modules/console.h"
+#include "util/console.h"
namespace mozilla {
union Utf8Unit;
}
-static void gjs_console_warning_reporter(JSContext*, JSErrorReport* report) {
- JS::PrintError(stderr, report, /* reportWarnings = */ true);
-}
-
/* Based on js::shell::AutoReportException from SpiderMonkey. */
class AutoReportException {
JSContext *m_cx;
@@ -98,84 +93,20 @@ 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;
}
-std::string print_string_value(JSContext* cx, JS::HandleValue v_string) {
- if (!v_string.isString())
- return "[unexpected result from printing value]";
-
- JS::RootedString printed_string(cx, v_string.toString());
- JS::AutoSaveExceptionState exc_state(cx);
- JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, printed_string));
- exc_state.restore();
- if (!chars)
- return "[error printing value]";
-
- return chars.get();
-}
-
-/* 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,
- JS::HandleObject global,
- const std::string& bytes,
- int lineno) {
+[[nodiscard]] static bool gjs_console_eval(JSContext* cx,
+ const std::string& bytes, int lineno,
+ JS::MutableHandleValue result) {
JS::SourceText<mozilla::Utf8Unit> source;
if (!source.init(cx, bytes.c_str(), bytes.size(),
JS::SourceOwnership::Borrowed))
@@ -184,132 +115,86 @@ std::string print_string_value(JSContext* cx, JS::HandleValue v_string) {
JS::CompileOptions options(cx);
options.setFileAndLine("typein", lineno);
- JS::RootedValue result(cx);
- if (!JS::Evaluate(cx, options, source, &result)) {
- if (!JS_IsExceptionPending(cx))
- return false;
- }
+ JS::RootedValue eval_result(cx);
+ if (!JS::Evaluate(cx, options, source, &eval_result))
+ return false;
GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
gjs->schedule_gc_if_needed();
- if (result.isUndefined())
- return true;
+ result.set(eval_result);
+ return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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));
- JS::AutoSaveExceptionState exc_state(cx);
- JS::RootedValue v_printed_string(cx);
- JS::RootedValue v_pretty_print(
- cx, gjs_get_global_slot(global, GjsGlobalSlot::PRETTY_PRINT_FUNC));
- bool ok = JS::Call(cx, global, v_pretty_print, JS::HandleValueArray(result),
- &v_printed_string);
- if (!ok)
- gjs_log_exception(cx);
- exc_state.restore();
-
- if (ok) {
- g_fprintf(stdout, "%s\n",
- print_string_value(cx, v_printed_string).c_str());
- } else {
- g_fprintf(stdout, "[error printing value]\n");
+ JS::UniqueChars prompt;
+ if (!gjs_parse_call_args(context, "interact", args, "s", "prompt", &prompt))
+ return false;
+
+ GjsAutoChar buffer;
+ if (!gjs_console_readline(buffer.out(), prompt.get())) {
+ return true;
}
+ return gjs_string_from_utf8(context, buffer, args.rval());
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_console_enable_raw_mode(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ if (!gjs_parse_call_args(cx, "enableRawMode", args, ""))
+ return false;
+
+ args.rval().setBoolean(Gjs::Console::enable_raw_mode());
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);
- volatile bool eof, exit_warning; // accessed after setjmp()
- JS::RootedObject global(context, gjs_get_import_global(context));
- char* temp_buf;
- volatile int lineno; // accessed after setjmp()
- volatile int startline; // accessed after setjmp()
+static bool gjs_console_disable_raw_mode(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ if (!gjs_parse_call_args(cx, "disableRawMode", args, ""))
+ return false;
-#ifndef HAVE_READLINE_READLINE_H
- int rl_end = 0; // nonzero if using readline and any text is typed in
-#endif
+ args.rval().setBoolean(Gjs::Console::disable_raw_mode());
+ return true;
+}
- 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, global, 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);
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_console_eval_js(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::UniqueChars expr;
+ int lineno;
+ if (!gjs_parse_call_args(cx, "eval", args, "si", "expression", &expr,
+ "lineNumber", &lineno))
+ return false;
+
+ return gjs_console_eval(cx, std::string(expr.get()), lineno, args.rval());
+}
- g_fprintf(stdout, "\n");
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_console_is_valid_js(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedString str(cx);
+ if (!gjs_parse_call_args(cx, "isValid", args, "S", "code", &str))
+ return false;
+
+ JS::UniqueChars code;
+ size_t code_len;
+ if (!gjs_string_to_utf8_n(cx, str, &code, &code_len))
+ return false;
- argv.rval().setUndefined();
+ JS::RootedObject global(cx, gjs_get_import_global(cx));
+
+ args.rval().setBoolean(
+ JS_Utf8BufferIsCompilableUnit(cx, global, code.get(), code_len));
return true;
}
@@ -329,10 +214,57 @@ static bool gjs_console_clear_terminal(JSContext* cx, unsigned argc,
return true;
}
+bool gjs_console_get_terminal_size(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj)
+ return false;
+
+ // Use 'int' because Windows uses int values, whereas most Unix systems
+ // use 'short'
+ unsigned int width, height;
+
+ JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+#ifdef GET_SIZE_USE_IOCTL
+ struct winsize ws;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
+ gjs_throw(cx, "No terminal output is present.\n");
+ return false;
+ }
+
+ width = ws.ws_col;
+ height = ws.ws_row;
+#else
+ // TODO(ewlsh): Implement Windows equivalent.
+ // See
+ // https://docs.microsoft.com/en-us/windows/console/window-and-screen-buffer-size.
+ gjs_throw(cx, "Unable to retrieve terminal size on this platform.\n");
+ return false;
+#endif
+
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+ if (!JS_DefinePropertyById(cx, obj, atoms.height(), height,
+ JSPROP_READONLY) ||
+ !JS_DefinePropertyById(cx, obj, atoms.width(), width, JSPROP_READONLY))
+ return false;
+
+ argv.rval().setObject(*obj);
+ return true;
+}
+
static JSFunctionSpec console_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("getDimensions", gjs_console_get_terminal_size, 0,
+ GJS_MODULE_PROP_FLAGS),
+ JS_FN("disableRawMode", gjs_console_disable_raw_mode, 0,
+ GJS_MODULE_PROP_FLAGS),
+ JS_FN("eval", gjs_console_eval_js, 2, GJS_MODULE_PROP_FLAGS),
+ JS_FN("isValid", gjs_console_is_valid_js, 1, GJS_MODULE_PROP_FLAGS),
JS_FN("clearTerminal", gjs_console_clear_terminal, 1,
GJS_MODULE_PROP_FLAGS),
- JS_FN("interact", gjs_console_interact, 1, GJS_MODULE_PROP_FLAGS),
JS_FS_END,
};