summaryrefslogtreecommitdiff
path: root/cli/repl.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/repl.rs')
-rw-r--r--cli/repl.rs298
1 files changed, 240 insertions, 58 deletions
diff --git a/cli/repl.rs b/cli/repl.rs
index 7873f7d0f..57e517bd7 100644
--- a/cli/repl.rs
+++ b/cli/repl.rs
@@ -1,73 +1,255 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
-use crate::deno_dir::DenoDir;
+use crate::global_state::GlobalState;
+use crate::inspector::InspectorSession;
use deno_core::error::AnyError;
+use deno_core::serde_json::json;
+use rustyline::error::ReadlineError;
+use rustyline::validate::MatchingBracketValidator;
+use rustyline::validate::ValidationContext;
+use rustyline::validate::ValidationResult;
+use rustyline::validate::Validator;
use rustyline::Editor;
-use std::fs;
-use std::path::PathBuf;
+use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
+use std::sync::Arc;
+use std::sync::Mutex;
-pub struct Repl {
- editor: Editor<()>,
- history_file: PathBuf,
+// Provides syntax specific helpers to the editor like validation for multi-line edits.
+#[derive(Completer, Helper, Highlighter, Hinter)]
+struct Helper {
+ validator: MatchingBracketValidator,
}
-impl Repl {
- pub fn new(history_file: PathBuf) -> Self {
- let mut repl = Self {
- editor: Editor::<()>::new(),
- history_file,
- };
-
- repl.load_history();
- repl
+impl Validator for Helper {
+ fn validate(
+ &self,
+ ctx: &mut ValidationContext,
+ ) -> Result<ValidationResult, ReadlineError> {
+ self.validator.validate(ctx)
}
+}
- fn load_history(&mut self) {
- debug!("Loading REPL history: {:?}", self.history_file);
- self
- .editor
- .load_history(&self.history_file.to_str().unwrap())
- .map_err(|e| {
- debug!("Unable to load history file: {:?} {}", self.history_file, e)
- })
- // ignore this error (e.g. it occurs on first load)
- .unwrap_or(())
- }
+pub async fn run(
+ global_state: &GlobalState,
+ mut session: Box<InspectorSession>,
+) -> Result<(), AnyError> {
+ // Our inspector is unable to default to the default context id so we have to specify it here.
+ let context_id: u32 = 1;
- fn save_history(&mut self) -> Result<(), AnyError> {
- fs::create_dir_all(self.history_file.parent().unwrap())?;
- self
- .editor
- .save_history(&self.history_file.to_str().unwrap())
- .map(|_| debug!("Saved REPL history to: {:?}", self.history_file))
- .map_err(|e| {
- eprintln!("Unable to save REPL history: {:?} {}", self.history_file, e);
- e.into()
- })
- }
+ let history_file = global_state.dir.root.join("deno_history.txt");
- pub fn readline(&mut self, prompt: &str) -> Result<String, AnyError> {
- self
- .editor
- .readline(&prompt)
- .map(|line| {
- self.editor.add_history_entry(line.clone());
- line
- })
- .map_err(AnyError::from)
-
- // Forward error to TS side for processing
- }
-}
+ session
+ .post_message("Runtime.enable".to_string(), None)
+ .await?;
+
+ let helper = Helper {
+ validator: MatchingBracketValidator::new(),
+ };
+
+ let editor = Arc::new(Mutex::new(Editor::new()));
+
+ editor.lock().unwrap().set_helper(Some(helper));
+
+ editor
+ .lock()
+ .unwrap()
+ .load_history(history_file.to_str().unwrap())
+ .unwrap_or(());
+
+ println!("Deno {}", crate::version::DENO);
+ println!("exit using ctrl+d or close()");
+
+ let prelude = r#"
+ Object.defineProperty(globalThis, "_", {
+ configurable: true,
+ get: () => Deno[Deno.internal].lastEvalResult,
+ set: (value) => {
+ Object.defineProperty(globalThis, "_", {
+ value: value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+ console.log("Last evaluation result is no longer saved to _.");
+ },
+ });
+
+ Object.defineProperty(globalThis, "_error", {
+ configurable: true,
+ get: () => Deno[Deno.internal].lastThrownError,
+ set: (value) => {
+ Object.defineProperty(globalThis, "_error", {
+ value: value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+
+ console.log("Last thrown error is no longer saved to _error.");
+ },
+ });
+ "#;
-impl Drop for Repl {
- fn drop(&mut self) {
- self.save_history().unwrap();
+ session
+ .post_message(
+ "Runtime.evaluate".to_string(),
+ Some(json!({
+ "expression": prelude,
+ "contextId": context_id,
+ })),
+ )
+ .await?;
+
+ loop {
+ let editor2 = editor.clone();
+ let line = tokio::task::spawn_blocking(move || {
+ editor2.lock().unwrap().readline("> ")
+ })
+ .await?;
+
+ match line {
+ Ok(line) => {
+ // It is a bit unexpected that { "foo": "bar" } is interpreted as a block
+ // statement rather than an object literal so we interpret it as an expression statement
+ // to match the behavior found in a typical prompt including browser developer tools.
+ let wrapped_line = if line.trim_start().starts_with('{')
+ && !line.trim_end().ends_with(';')
+ {
+ format!("({})", &line)
+ } else {
+ line.clone()
+ };
+
+ let evaluate_response = session
+ .post_message(
+ "Runtime.evaluate".to_string(),
+ Some(json!({
+ "expression": format!("'use strict'; void 0;\n{}", &wrapped_line),
+ "contextId": context_id,
+ // TODO(caspervonb) set repl mode to true to enable const redeclarations and top
+ // level await
+ "replMode": false,
+ })),
+ )
+ .await?;
+
+ // If that fails, we retry it without wrapping in parens letting the error bubble up to the
+ // user if it is still an error.
+ let evaluate_response =
+ if evaluate_response.get("exceptionDetails").is_some()
+ && wrapped_line != line
+ {
+ session
+ .post_message(
+ "Runtime.evaluate".to_string(),
+ Some(json!({
+ "expression": format!("'use strict'; void 0;\n{}", &line),
+ "contextId": context_id,
+ // TODO(caspervonb) set repl mode to true to enable const redeclarations and top
+ // level await
+ "replMode": false,
+ })),
+ )
+ .await?
+ } else {
+ evaluate_response
+ };
+
+ let is_closing = session
+ .post_message(
+ "Runtime.evaluate".to_string(),
+ Some(json!({
+ "expression": "(globalThis.closed)",
+ "contextId": context_id,
+ })),
+ )
+ .await?
+ .get("result")
+ .unwrap()
+ .get("value")
+ .unwrap()
+ .as_bool()
+ .unwrap();
+
+ if is_closing {
+ break;
+ }
+
+ let evaluate_result = evaluate_response.get("result").unwrap();
+ let evaluate_exception_details =
+ evaluate_response.get("exceptionDetails");
+
+ if evaluate_exception_details.is_some() {
+ session
+ .post_message(
+ "Runtime.callFunctionOn".to_string(),
+ Some(json!({
+ "executionContextId": context_id,
+ "functionDeclaration": "function (object) { Deno[Deno.internal].lastThrownError = object; }",
+ "arguments": [
+ evaluate_result,
+ ],
+ }))).await?;
+ } else {
+ session
+ .post_message(
+ "Runtime.callFunctionOn".to_string(),
+ Some(json!({
+ "executionContextId": context_id,
+ "functionDeclaration": "function (object) { Deno[Deno.internal].lastEvalResult = object; }",
+ "arguments": [
+ evaluate_result,
+ ],
+ }))).await?;
+ }
+
+ // TODO(caspervonb) we should investigate using previews here but to keep things
+ // consistent with the previous implementation we just get the preview result from
+ // Deno.inspectArgs.
+ let inspect_response = session
+ .post_message(
+ "Runtime.callFunctionOn".to_string(),
+ Some(json!({
+ "executionContextId": context_id,
+ "functionDeclaration": "function (object) { return Deno[Deno.internal].inspectArgs(['%o', object]); }",
+ "arguments": [
+ evaluate_result,
+ ],
+ }))).await?;
+
+ let inspect_result = inspect_response.get("result").unwrap();
+
+ match evaluate_exception_details {
+ Some(_) => eprintln!(
+ "Uncaught {}",
+ inspect_result.get("value").unwrap().as_str().unwrap()
+ ),
+ None => println!(
+ "{}",
+ inspect_result.get("value").unwrap().as_str().unwrap()
+ ),
+ }
+
+ editor.lock().unwrap().add_history_entry(line.as_str());
+ }
+ Err(ReadlineError::Interrupted) => {
+ break;
+ }
+ Err(ReadlineError::Eof) => {
+ break;
+ }
+ Err(err) => {
+ println!("Error: {:?}", err);
+ break;
+ }
+ }
}
-}
-pub fn history_path(dir: &DenoDir, history_file: &str) -> PathBuf {
- let mut p: PathBuf = dir.root.clone();
- p.push(history_file);
- p
+ std::fs::create_dir_all(history_file.parent().unwrap())?;
+ editor
+ .lock()
+ .unwrap()
+ .save_history(history_file.to_str().unwrap())?;
+
+ Ok(())
}