summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs2
-rw-r--r--src/msg.fbs22
-rw-r--r--src/ops.rs72
-rw-r--r--src/repl.rs122
-rw-r--r--src/resources.rs33
5 files changed, 245 insertions, 6 deletions
diff --git a/src/main.rs b/src/main.rs
index af84b19be..ca15d468b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,6 +8,7 @@ extern crate libc;
extern crate rand;
extern crate remove_dir_all;
extern crate ring;
+extern crate rustyline;
extern crate tempfile;
extern crate tokio;
extern crate tokio_executor;
@@ -35,6 +36,7 @@ pub mod msg;
pub mod msg_util;
pub mod ops;
pub mod permissions;
+mod repl;
pub mod resources;
pub mod snapshot;
mod tokio_util;
diff --git a/src/msg.fbs b/src/msg.fbs
index 5f86ad56a..9686c76cb 100644
--- a/src/msg.fbs
+++ b/src/msg.fbs
@@ -24,6 +24,10 @@ union Any {
Rename,
Readlink,
ReadlinkRes,
+ ReplStart,
+ ReplStartRes,
+ ReplReadline,
+ ReplReadlineRes,
Resources,
ResourcesRes,
Symlink,
@@ -273,6 +277,24 @@ table ReadlinkRes {
path: string;
}
+table ReplStart {
+ history_file: string;
+ // TODO add config
+}
+
+table ReplStartRes {
+ rid: int;
+}
+
+table ReplReadline {
+ rid: int;
+ prompt: string;
+}
+
+table ReplReadlineRes {
+ line: string;
+}
+
table Resources {}
table Resource {
diff --git a/src/ops.rs b/src/ops.rs
index 9266646ef..f645f1d01 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -20,6 +20,7 @@ use futures::Poll;
use hyper;
use hyper::rt::{Future, Stream};
use remove_dir_all::remove_dir_all;
+use repl;
use resources::table_entries;
use std;
use std::fs;
@@ -96,6 +97,8 @@ pub fn dispatch(
msg::Any::Read => op_read,
msg::Any::Remove => op_remove,
msg::Any::Rename => op_rename,
+ msg::Any::ReplReadline => op_repl_readline,
+ msg::Any::ReplStart => op_repl_start,
msg::Any::Resources => op_resources,
msg::Any::SetEnv => op_set_env,
msg::Any::Shutdown => op_shutdown,
@@ -1086,6 +1089,75 @@ fn op_read_link(
})
}
+fn op_repl_start(
+ state: &Arc<IsolateState>,
+ base: &msg::Base,
+ data: &'static mut [u8],
+) -> Box<Op> {
+ assert_eq!(data.len(), 0);
+ let inner = base.inner_as_repl_start().unwrap();
+ let cmd_id = base.cmd_id();
+ let history_file = String::from(inner.history_file().unwrap());
+
+ debug!("op_repl_start {}", history_file);
+ let history_path = repl::history_path(&state.dir, &history_file);
+ let repl = repl::Repl::new(history_path);
+ let resource = resources::add_repl(repl);
+
+ let builder = &mut FlatBufferBuilder::new();
+ let inner = msg::ReplStartRes::create(
+ builder,
+ &msg::ReplStartResArgs { rid: resource.rid },
+ );
+ ok_future(serialize_response(
+ cmd_id,
+ builder,
+ msg::BaseArgs {
+ inner: Some(inner.as_union_value()),
+ inner_type: msg::Any::ReplStartRes,
+ ..Default::default()
+ },
+ ))
+}
+
+fn op_repl_readline(
+ _state: &Arc<IsolateState>,
+ base: &msg::Base,
+ data: &'static mut [u8],
+) -> Box<Op> {
+ assert_eq!(data.len(), 0);
+ let inner = base.inner_as_repl_readline().unwrap();
+ let cmd_id = base.cmd_id();
+ let rid = inner.rid();
+ let prompt = inner.prompt().unwrap().to_owned();
+ debug!("op_repl_readline {} {}", rid, prompt);
+
+ // Ignore this clippy warning until this issue is addressed:
+ // https://github.com/rust-lang-nursery/rust-clippy/issues/1684
+ #[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
+ Box::new(futures::future::result((move || {
+ let line = resources::readline(rid, &prompt)?;
+
+ let builder = &mut FlatBufferBuilder::new();
+ let line_off = builder.create_string(&line);
+ let inner = msg::ReplReadlineRes::create(
+ builder,
+ &msg::ReplReadlineResArgs {
+ line: Some(line_off),
+ },
+ );
+ Ok(serialize_response(
+ cmd_id,
+ builder,
+ msg::BaseArgs {
+ inner: Some(inner.as_union_value()),
+ inner_type: msg::Any::ReplReadlineRes,
+ ..Default::default()
+ },
+ ))
+ })()))
+}
+
fn op_truncate(
state: &Arc<IsolateState>,
base: &msg::Base,
diff --git a/src/repl.rs b/src/repl.rs
new file mode 100644
index 000000000..af1679194
--- /dev/null
+++ b/src/repl.rs
@@ -0,0 +1,122 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+extern crate rustyline;
+
+use rustyline::error::ReadlineError::Interrupted;
+
+use msg::ErrorKind;
+use std::error::Error;
+
+use deno_dir::DenoDir;
+use errors::new as deno_error;
+use errors::DenoResult;
+use std::path::PathBuf;
+use std::process::exit;
+
+#[cfg(not(windows))]
+use rustyline::Editor;
+
+// Work around the issue that on Windows, `struct Editor` does not implement the
+// `Send` trait, because it embeds a windows HANDLE which is a type alias for
+// *mut c_void. This value isn't actually a pointer and there's nothing that
+// can be mutated through it, so hack around it. TODO: a prettier solution.
+#[cfg(windows)]
+use std::ops::{Deref, DerefMut};
+
+#[cfg(windows)]
+struct Editor<T: rustyline::Helper> {
+ inner: rustyline::Editor<T>,
+}
+
+#[cfg(windows)]
+unsafe impl<T: rustyline::Helper> Send for Editor<T> {}
+
+#[cfg(windows)]
+impl<T: rustyline::Helper> Editor<T> {
+ pub fn new() -> Editor<T> {
+ Editor {
+ inner: rustyline::Editor::<T>::new(),
+ }
+ }
+}
+
+#[cfg(windows)]
+impl<T: rustyline::Helper> Deref for Editor<T> {
+ type Target = rustyline::Editor<T>;
+
+ fn deref(&self) -> &rustyline::Editor<T> {
+ &self.inner
+ }
+}
+
+#[cfg(windows)]
+impl<T: rustyline::Helper> DerefMut for Editor<T> {
+ fn deref_mut(&mut self) -> &mut rustyline::Editor<T> {
+ &mut self.inner
+ }
+}
+
+pub struct Repl {
+ editor: Editor<()>,
+ history_file: PathBuf,
+}
+
+impl Repl {
+ pub fn new(history_file: PathBuf) -> Repl {
+ let mut repl = Repl {
+ editor: Editor::<()>::new(),
+ history_file,
+ };
+
+ repl.load_history();
+ repl
+ }
+
+ 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(())
+ }
+
+ fn save_history(&mut self) -> DenoResult<()> {
+ 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);
+ deno_error(ErrorKind::Other, e.description().to_string())
+ })
+ }
+
+ pub fn readline(&mut self, prompt: &str) -> DenoResult<String> {
+ self
+ .editor
+ .readline(&prompt)
+ .map(|line| {
+ self.editor.add_history_entry(line.as_ref());
+ line
+ }).map_err(|e| match e {
+ Interrupted => {
+ self.save_history().unwrap();
+ exit(1)
+ }
+ e => deno_error(ErrorKind::Other, e.description().to_string()),
+ })
+ }
+}
+
+impl Drop for Repl {
+ fn drop(&mut self) {
+ self.save_history().unwrap();
+ }
+}
+
+pub fn history_path(dir: &DenoDir, history_file: &str) -> PathBuf {
+ let mut p: PathBuf = dir.root.clone();
+ p.push(history_file);
+ p
+}
diff --git a/src/resources.rs b/src/resources.rs
index 1e3b0a9b7..5d472adc7 100644
--- a/src/resources.rs
+++ b/src/resources.rs
@@ -10,7 +10,10 @@
#[cfg(unix)]
use eager_unix as eager;
+use errors::bad_resource;
use errors::DenoError;
+use errors::DenoResult;
+use repl::Repl;
use tokio_util;
use tokio_write;
@@ -56,6 +59,7 @@ enum Repr {
FsFile(tokio::fs::File),
TcpListener(tokio::net::TcpListener),
TcpStream(tokio::net::TcpStream),
+ Repl(Repl),
}
pub fn table_entries() -> Vec<(i32, String)> {
@@ -85,6 +89,7 @@ fn inspect_repr(repr: &Repr) -> String {
Repr::FsFile(_) => "fsFile",
Repr::TcpListener(_) => "tcpListener",
Repr::TcpStream(_) => "tcpStream",
+ Repr::Repl(_) => "repl",
};
String::from(h_repr)
@@ -150,10 +155,7 @@ impl AsyncRead for Resource {
Repr::FsFile(ref mut f) => f.poll_read(buf),
Repr::Stdin(ref mut f) => f.poll_read(buf),
Repr::TcpStream(ref mut f) => f.poll_read(buf),
- Repr::Stdout(_) | Repr::Stderr(_) => {
- panic!("Cannot read from stdout/stderr")
- }
- Repr::TcpListener(_) => panic!("Cannot read"),
+ _ => panic!("Cannot read"),
},
}
}
@@ -180,8 +182,7 @@ impl AsyncWrite for Resource {
Repr::Stdout(ref mut f) => f.poll_write(buf),
Repr::Stderr(ref mut f) => f.poll_write(buf),
Repr::TcpStream(ref mut f) => f.poll_write(buf),
- Repr::Stdin(_) => panic!("Cannot write to stdin"),
- Repr::TcpListener(_) => panic!("Cannot write"),
+ _ => panic!("Cannot write"),
},
}
}
@@ -221,6 +222,26 @@ pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
Resource { rid }
}
+pub fn add_repl(repl: Repl) -> Resource {
+ let rid = new_rid();
+ let mut tg = RESOURCE_TABLE.lock().unwrap();
+ let r = tg.insert(rid, Repr::Repl(repl));
+ assert!(r.is_none());
+ Resource { rid }
+}
+
+pub fn readline(rid: ResourceId, prompt: &str) -> DenoResult<String> {
+ let mut table = RESOURCE_TABLE.lock().unwrap();
+ let maybe_repr = table.get_mut(&rid);
+ match maybe_repr {
+ Some(Repr::Repl(ref mut r)) => {
+ let line = r.readline(&prompt)?;
+ Ok(line)
+ }
+ _ => Err(bad_resource()),
+ }
+}
+
pub fn lookup(rid: ResourceId) -> Option<Resource> {
let table = RESOURCE_TABLE.lock().unwrap();
table.get(&rid).map(|_| Resource { rid })