From 390a49ff2ddf5fbfa1b5d03b32b31f2d6d0f13da Mon Sep 17 00:00:00 2001 From: Lawrence Daniels Date: Thu, 30 Mar 2017 11:58:18 -0700 Subject: [PATCH] Improved the repl binding --- .../scala/io/scalajs/nodejs/Process.scala | 2 +- .../scala/io/scalajs/nodejs/package.scala | 2 + .../scala/io/scalajs/nodejs/repl/REPL.scala | 2 +- .../io/scalajs/nodejs/repl/REPLServer.scala | 36 ++++---- .../io/scalajs/nodejs/repl/package.scala | 88 +++++++++++++++++++ 5 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 src/main/scala/io/scalajs/nodejs/repl/package.scala diff --git a/src/main/scala/io/scalajs/nodejs/Process.scala b/src/main/scala/io/scalajs/nodejs/Process.scala index e00802b00..346c0634c 100644 --- a/src/main/scala/io/scalajs/nodejs/Process.scala +++ b/src/main/scala/io/scalajs/nodejs/Process.scala @@ -53,7 +53,7 @@ trait Process extends IEventEmitter { /** * An object containing the user environment. */ - def env: js.Dictionary[String] = js.native + def env: Environment = js.native /** * This is the set of Node.js-specific command line options from the executable that started the process. diff --git a/src/main/scala/io/scalajs/nodejs/package.scala b/src/main/scala/io/scalajs/nodejs/package.scala index f70401ff3..1646e31f9 100644 --- a/src/main/scala/io/scalajs/nodejs/package.scala +++ b/src/main/scala/io/scalajs/nodejs/package.scala @@ -18,6 +18,8 @@ package object nodejs { */ type FileDescriptor = Integer + type Environment = js.Dictionary[String] + ///////////////////////////////////////////////////////////////////////////////// // Built-in Properties ///////////////////////////////////////////////////////////////////////////////// diff --git a/src/main/scala/io/scalajs/nodejs/repl/REPL.scala b/src/main/scala/io/scalajs/nodejs/repl/REPL.scala index dc74c1f30..367a14a0a 100644 --- a/src/main/scala/io/scalajs/nodejs/repl/REPL.scala +++ b/src/main/scala/io/scalajs/nodejs/repl/REPL.scala @@ -18,7 +18,7 @@ import scala.scalajs.js.| trait REPL extends IEventEmitter { var REPL_MODE_SLOPPY: String = js.native var REPL_MODE_STRICT: String = js.native - var REPL_MODE_MAGIC: String = js.native + var REPL_MODE_MAGIC: String = js.native /** * @example repl.start([options]) diff --git a/src/main/scala/io/scalajs/nodejs/repl/REPLServer.scala b/src/main/scala/io/scalajs/nodejs/repl/REPLServer.scala index a3e98c2e7..a19cf7de4 100644 --- a/src/main/scala/io/scalajs/nodejs/repl/REPLServer.scala +++ b/src/main/scala/io/scalajs/nodejs/repl/REPLServer.scala @@ -15,30 +15,34 @@ trait REPLServer extends IEventEmitter with Interface { /** * The REPL's context */ - // TODO find document for this property - val context: js.Dynamic = js.native + val context: REPLContext = js.native /** - * Makes a command available in the REPL. The command is invoked by typing a . followed by the keyword. - * The cmd is an object with the following values: + * The replServer.defineCommand() method is used to add new .-prefixed commands to the REPL instance. + * Such commands are invoked by typing a period (.) followed by the keyword. The cmd is either a Function + * or an object with the following properties: * - * If a function is provided instead of an object for cmd, it is treated as the action. - * @example replServer.defineCommand(keyword, cmd) + * @param keyword The command keyword (without a leading . character). + * @param cmd The function to invoke when the command is processed. */ - def defineCommand(keyword: String, cmd: js.Any): Unit = js.native + def defineCommand(keyword: String, cmd: js.Function0[Any]): Unit = js.native /** - * Like readline.prompt except also adding indents with ellipses when inside blocks. The preserveCursor argument - * is passed to readline.prompt. This is used primarily with defineCommand. It's also used internally to render - * each prompt line. - * @example replServer.displayPrompt([preserveCursor]) - * @see [[prompt()]] + * The replServer.displayPrompt() method readies the REPL instance for input from the user, printing the + * configured prompt to a new line in the output and resuming the input to accept new input. + * + * When multi-line input is being entered, an ellipsis is printed rather than the 'prompt'. + * * + * When preserveCursor is true, the cursor placement will not be reset to 0. + * + * The replServer.displayPrompt method is primarily intended to be called from within the action function + * for commands registered using the replServer.defineCommand() method. + * @param preserveCursor indicates whether to preserver the cursor (position?) */ - def displayPrompt(preserveCursor: Boolean): Unit = js.native + def displayPrompt(preserveCursor: Boolean = js.native): Unit = js.native /** * Like readline.prompt except also adding indents with ellipses when inside blocks. The preserveCursor argument diff --git a/src/main/scala/io/scalajs/nodejs/repl/package.scala b/src/main/scala/io/scalajs/nodejs/repl/package.scala new file mode 100644 index 000000000..a73c36111 --- /dev/null +++ b/src/main/scala/io/scalajs/nodejs/repl/package.scala @@ -0,0 +1,88 @@ +package io.scalajs.nodejs + +import scala.scalajs.js + +/** + * repl package object + * @author lawrence.daniels@gmail.com + */ +package object repl { + + type REPLContext = js.Dynamic + + /** + * REPL Server events + * @param server the given [[REPLServer instance]] + */ + final implicit class REPLServerEvents(val server: REPLServer) extends AnyVal { + + @inline + def contextAs[T]: T = server.context.asInstanceOf[T] + + /** + * The 'exit' event is emitted when the REPL is exited either by receiving the .exit command as input, + * the user pressing CTRL-C twice to signal SIGINT, or by pressing CTRL-D to signal 'end' on the input stream. + * The listener callback is invoked without any arguments. + * @param listener The listener callback + */ + @inline + def onExit(listener: () => Any): server.type = server.on("exit", listener) + + /** + * The 'reset' event is emitted when the REPL's context is reset. This occurs whenever the .clear command + * is received as input unless the REPL is using the default evaluator and the repl.REPLServer instance + * was created with the useGlobal option set to true. The listener callback will be called with a reference + * to the context object as the only argument. + * @param listener The listener callback + */ + @inline + def onReset(listener: REPLContext => Any): server.type = server.on("reset", listener) + + } + + /** + * Various behaviors of the Node.js REPL can be customized using the following environment variables: + * + */ + final implicit class EnvironmentVariableOptions(val env: Environment) extends AnyVal { + + /** + * When a valid path is given, persistent REPL history will be saved to the specified file rather + * than .node_repl_history in the user's home directory. Setting this value to "" will disable persistent + * REPL history. Whitespace will be trimmed from the value. + */ + @inline + def NODE_REPL_HISTORY: Option[String] = env.get("NODE_REPL_HISTORY") + + /** + * Previously in Node.js/io.js v2.x, REPL history was controlled by using a NODE_REPL_HISTORY_FILE environment + * variable, and the history was saved in JSON format. This variable has now been deprecated, and the old + * JSON REPL history file will be automatically converted to a simplified plain text format. This new file + * will be saved to either the user's home directory, or a directory defined by the NODE_REPL_HISTORY variable, + * as documented in the Environment Variable Options. + */ + @inline + @deprecated("Use NODE_REPL_HISTORY instead.", since = "3.0.0") + def NODE_REPL_HISTORY_FILE: Option[String] = env.get("NODE_REPL_HISTORY_FILE") + + /** + * Defaults to 1000. Controls how many lines of history will be persisted if history is available. + * Must be a positive number. + */ + @inline + def NODE_REPL_HISTORY_SIZE: Option[Int] = env.get("NODE_REPL_HISTORY_SIZE").map(_.toInt) + + /** + * May be any of sloppy, strict, or magic. Defaults to magic, which will automatically run "strict mode only" + * statements in strict mode. + */ + @inline + def NODE_REPL_MODE: Option[String] = env.get("NODE_REPL_MODE") + + } + +}