From 6a780543a43d4d370c42b557955200c59bcb21e8 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 25 Nov 2021 14:05:12 -0500 Subject: [PATCH] refactor(repl): move rustyline sync channel communication into struct (#12900) --- cli/tools/repl/channel.rs | 74 ++++++++++++++++++++++++++++++ cli/tools/{repl.rs => repl/mod.rs} | 49 ++++++++------------ 2 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 cli/tools/repl/channel.rs rename cli/tools/{repl.rs => repl/mod.rs} (95%) diff --git a/cli/tools/repl/channel.rs b/cli/tools/repl/channel.rs new file mode 100644 index 0000000000..54ec6869d4 --- /dev/null +++ b/cli/tools/repl/channel.rs @@ -0,0 +1,74 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::anyhow::anyhow; +use deno_core::error::AnyError; +use deno_core::serde_json::Value; +use std::cell::RefCell; +use tokio::sync::mpsc::channel; +use tokio::sync::mpsc::unbounded_channel; +use tokio::sync::mpsc::Receiver; +use tokio::sync::mpsc::Sender; +use tokio::sync::mpsc::UnboundedReceiver; +use tokio::sync::mpsc::UnboundedSender; + +/// Rustyline uses synchronous methods in its interfaces, but we need to call +/// async methods. To get around this, we communicate with async code by using +/// a channel and blocking on the result. +pub fn rustyline_channel( +) -> (RustylineSyncMessageSender, RustylineSyncMessageHandler) { + let (message_tx, message_rx) = channel(1); + let (response_tx, response_rx) = unbounded_channel(); + + ( + RustylineSyncMessageSender { + message_tx, + response_rx: RefCell::new(response_rx), + }, + RustylineSyncMessageHandler { + response_tx, + message_rx, + }, + ) +} + +pub type RustylineSyncMessage = (String, Option); +pub type RustylineSyncResponse = Result; + +pub struct RustylineSyncMessageSender { + message_tx: Sender, + response_rx: RefCell>, +} + +impl RustylineSyncMessageSender { + pub fn post_message( + &self, + method: &str, + params: Option, + ) -> Result { + if let Err(err) = + self.message_tx.blocking_send((method.to_string(), params)) + { + Err(anyhow!("{}", err)) + } else { + self.response_rx.borrow_mut().blocking_recv().unwrap() + } + } +} + +pub struct RustylineSyncMessageHandler { + message_rx: Receiver, + response_tx: UnboundedSender, +} + +impl RustylineSyncMessageHandler { + pub async fn recv(&mut self) -> Option { + self.message_rx.recv().await + } + + pub fn send(&self, response: RustylineSyncResponse) -> Result<(), AnyError> { + self + .response_tx + .send(response) + .map_err(|err| anyhow!("{}", err)) + } +} diff --git a/cli/tools/repl.rs b/cli/tools/repl/mod.rs similarity index 95% rename from cli/tools/repl.rs rename to cli/tools/repl/mod.rs index b6874f574d..925ede6543 100644 --- a/cli/tools/repl.rs +++ b/cli/tools/repl/mod.rs @@ -5,7 +5,8 @@ use crate::ast::ImportsNotUsedAsValues; use crate::colors; use crate::proc_state::ProcState; use deno_ast::swc::parser::error::SyntaxError; -use deno_ast::swc::parser::token::{Token, Word}; +use deno_ast::swc::parser::token::Token; +use deno_ast::swc::parser::token::Word; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::parking_lot::Mutex; @@ -25,28 +26,27 @@ use rustyline::Context; use rustyline::Editor; use rustyline_derive::{Helper, Hinter}; use std::borrow::Cow; -use std::cell::RefCell; use std::path::PathBuf; use std::sync::Arc; -use tokio::sync::mpsc::channel; -use tokio::sync::mpsc::unbounded_channel; -use tokio::sync::mpsc::Receiver; -use tokio::sync::mpsc::Sender; -use tokio::sync::mpsc::UnboundedReceiver; -use tokio::sync::mpsc::UnboundedSender; + +mod channel; + +use channel::rustyline_channel; +use channel::RustylineSyncMessageHandler; +use channel::RustylineSyncMessageSender; // Provides helpers to the editor like validation for multi-line edits, completion candidates for // tab completion. #[derive(Helper, Hinter)] struct EditorHelper { context_id: u64, - message_tx: Sender<(String, Option)>, - response_rx: RefCell>>, + sync_sender: RustylineSyncMessageSender, } impl EditorHelper { pub fn get_global_lexical_scope_names(&self) -> Vec { let evaluate_response = self + .sync_sender .post_message( "Runtime.globalLexicalScopeNames", Some(json!({ @@ -106,6 +106,7 @@ impl EditorHelper { let object_id = evaluate_result.get("result")?.get("objectId")?; let get_properties_response = self + .sync_sender .post_message( "Runtime.getProperties", Some(json!({ @@ -127,6 +128,7 @@ impl EditorHelper { fn evaluate_expression(&self, expr: &str) -> Option { let evaluate_response = self + .sync_sender .post_message( "Runtime.evaluate", Some(json!({ @@ -144,17 +146,6 @@ impl EditorHelper { Some(evaluate_response) } } - - fn post_message( - &self, - method: &str, - params: Option, - ) -> Result { - self - .message_tx - .blocking_send((method.to_string(), params))?; - self.response_rx.borrow_mut().blocking_recv().unwrap() - } } fn is_word_boundary(c: char) -> bool { @@ -705,8 +696,7 @@ impl ReplSession { async fn read_line_and_poll( repl_session: &mut ReplSession, - message_rx: &mut Receiver<(String, Option)>, - response_tx: &UnboundedSender>, + message_handler: &mut RustylineSyncMessageHandler, editor: ReplEditor, ) -> Result { let mut line_fut = tokio::task::spawn_blocking(move || editor.readline()); @@ -717,12 +707,12 @@ async fn read_line_and_poll( result = &mut line_fut => { return result.unwrap(); } - result = message_rx.recv() => { + result = message_handler.recv() => { if let Some((method, params)) = result { let result = repl_session .post_message_with_event_loop(&method, params) .await; - response_tx.send(result).unwrap(); + message_handler.send(result).unwrap(); } poll_worker = true; @@ -740,13 +730,11 @@ pub async fn run( maybe_eval: Option, ) -> Result<(), AnyError> { let mut repl_session = ReplSession::initialize(worker).await?; - let (message_tx, mut message_rx) = channel(1); - let (response_tx, response_rx) = unbounded_channel(); + let mut rustyline_channel = rustyline_channel(); let helper = EditorHelper { context_id: repl_session.context_id, - message_tx, - response_rx: RefCell::new(response_rx), + sync_sender: rustyline_channel.0, }; let history_file_path = ps.dir.root.join("deno_history.txt"); @@ -766,8 +754,7 @@ pub async fn run( loop { let line = read_line_and_poll( &mut repl_session, - &mut message_rx, - &response_tx, + &mut rustyline_channel.1, editor.clone(), ) .await;