From f9de475e96f0590b389c29be9de06b00ac43dff9 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Sun, 9 Feb 2020 17:48:51 -0800 Subject: [PATCH 01/16] tty: Deno.stdin.setRaw() --- cli/js/dispatch.ts | 1 + cli/js/files.ts | 28 ++++++- cli/js/lib.deno.ns.d.ts | 12 ++- cli/ops/mod.rs | 1 + cli/ops/tty.rs | 170 ++++++++++++++++++++++++++++++++++++++++ cli/worker.rs | 1 + 6 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 cli/ops/tty.rs diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index 93f6dc0552eec2..be9bd5098caa1b 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -81,6 +81,7 @@ export let OP_SIGNAL_BIND: number; export let OP_SIGNAL_UNBIND: number; export let OP_SIGNAL_POLL: number; export let OP_LOADAVG: number; +export let OP_SET_RAW: number; const PLUGIN_ASYNC_HANDLER_MAP: Map = new Map(); diff --git a/cli/js/files.ts b/cli/js/files.ts index c966b9fa04699b..9ce997737f666e 100644 --- a/cli/js/files.ts +++ b/cli/js/files.ts @@ -14,7 +14,8 @@ import { sendAsyncMinimal, sendSyncMinimal } from "./dispatch_minimal.ts"; import * as dispatch from "./dispatch.ts"; import { sendSync as sendSyncJson, - sendAsync as sendAsyncJson + sendAsync as sendAsyncJson, + sendSync } from "./dispatch_json.ts"; /** Open a file and return an instance of the `File` object @@ -256,8 +257,29 @@ export class File } } -/** An instance of `File` for stdin. */ -export const stdin = new File(0); +export type RestoreModeFunc = () => void; +/** Extended file abstraction with setRaw() */ +export class Stdin extends File { + /** Set input mode to raw (non-canonical). + * Returns a function that when called, restores previous mode. + */ + setRaw(): RestoreModeFunc { + const restoreInfo = sendSync(dispatch.OP_SET_RAW, { + rid: this.rid, + raw: true + }); + return (): void => { + sendSync(dispatch.OP_SET_RAW, { + rid: this.rid, + raw: false, + ...restoreInfo + }); + }; + } +} + +/** An instance of `Stdin` for stdin. */ +export const stdin = new Stdin(0); /** An instance of `File` for stdout. */ export const stdout = new File(1); /** An instance of `File` for stderr. */ diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index d1770a15a766dd..a9c5ebaa4d761b 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -486,8 +486,16 @@ declare namespace Deno { seekSync(offset: number, whence: SeekMode): void; close(): void; } - /** An instance of `File` for stdin. */ - export const stdin: File; + export type RestoreModeFunc = () => void; + /** Extended file abstraction with setRaw() */ + export class Stdin extends File { + /** Set input mode to raw (non-canonical). + * Returns a function that when called, restores previous mode. + */ + setRaw(): RestoreModeFunc; + } + /** An instance of `Stdin` for stdin. */ + export const stdin: Stdin; /** An instance of `File` for stdout. */ export const stdout: File; /** An instance of `File` for stderr. */ diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs index 7746143dbe708a..32d4e3b96589cb 100644 --- a/cli/ops/mod.rs +++ b/cli/ops/mod.rs @@ -28,5 +28,6 @@ pub mod runtime_compiler; pub mod signal; pub mod timers; pub mod tls; +pub mod tty; pub mod web_worker; pub mod worker_host; diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs new file mode 100644 index 00000000000000..3cbe172c5d3340 --- /dev/null +++ b/cli/ops/tty.rs @@ -0,0 +1,170 @@ +use super::dispatch_json::JsonOp; +use super::io::StreamResource; +use crate::deno_error::bad_resource; +use crate::deno_error::other_error; +use crate::ops::json_op; +use crate::state::State; +use deno_core::*; +use nix::sys::termios; +use serde_derive::{Deserialize, Serialize}; +use serde_json::Value; + +pub fn init(i: &mut Isolate, s: &State) { + i.register_op("set_raw", s.core_op(json_op(s.stateful_op(op_set_raw)))); +} + +#[cfg(windows)] +macro_rules! wincheck { + ($funcall:expr) => {{ + let rc = unsafe { $funcall }; + if rc == 0 { + Err(std::io::Error::last_os_error())?; + } + rc + }}; +} + +// libc::termios cannot be serialized. +// Create a similar one for our use. +#[derive(Serialize, Deserialize)] +struct SerializedTermios { + iflags: u64, + oflags: u64, + cflags: u64, + lflags: u64, + cc: [u8; libc::NCCS], +} + +impl From for SerializedTermios { + fn from(t: termios::Termios) -> Self { + Self { + iflags: t.input_flags.bits(), + oflags: t.output_flags.bits(), + cflags: t.control_flags.bits(), + lflags: t.local_flags.bits(), + cc: t.control_chars, + } + } +} + +impl Into for SerializedTermios { + fn into(self) -> termios::Termios { + let mut t = unsafe { termios::Termios::default_uninit() }; + t.input_flags = termios::InputFlags::from_bits_truncate(self.iflags); + t.output_flags = termios::OutputFlags::from_bits_truncate(self.oflags); + t.control_flags = termios::ControlFlags::from_bits_truncate(self.cflags); + t.local_flags = termios::LocalFlags::from_bits_truncate(self.lflags); + t.control_chars = self.cc; + t + } +} + +#[derive(Deserialize)] +struct SetRawArgs { + rid: u32, + raw: bool, + #[cfg(not(windows))] + restore: Option, // Only used for *nix + // Saved as string in case of u64 problem in JS +} + +pub fn op_set_raw( + state_: &State, + args: Value, + _zero_copy: Option, +) -> Result { + let args: SetRawArgs = serde_json::from_value(args)?; + let rid = args.rid; + let is_raw = args.raw; + + let state = state_.borrow_mut(); + let resource = state.resource_table.get::(rid); + if resource.is_none() { + return Err(bad_resource()); + } + + // For now, only stdin + match resource.unwrap() { + StreamResource::Stdin(_) => (), + _ => { + return Err(other_error( + "Resource other than stdin is not implemented".to_owned(), + )) + } + } + + // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs + // and https://github.com/crossterm-rs/crossterm/blob/e35d4d2c1cc4c919e36d242e014af75f6127ab50/src/terminal/sys/windows.rs + #[cfg(windows)] + { + use std::os::windows::io::AsRawHandle; + use winapi::um::{consoleapi, handleapi, winbase, wincon, winuser}; + + let handle = std::io::stdin().as_raw_handle(); + if handle == handleapi::INVALID_HANDLE_VALUE { + return Err(std::io::Error::last_os_error()); + } else if handle.is_null() { + return Err(other_error("null handle".to_owned())); + } + let mut original_mode = 0; + let RAW_MODE_MASK = + ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT; + wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode)); + let new_mode = if is_raw { + original_mode & !RAW_MODE_MASK; + } else { + original_mode | RAW_MODE_MASK; + }; + wincheck!(consoleapi::SetConsoleMode(handle, new_mode)); + + return Ok(JsonOp::Sync(json!({}))); + } + // From https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs + #[cfg(not(windows))] + { + use std::os::unix::io::AsRawFd; + let raw_fd = std::io::stdin().as_raw_fd(); + + if is_raw { + let original_mode = termios::tcgetattr(raw_fd)?; + let mut raw = original_mode.clone(); + + raw.input_flags &= !(termios::InputFlags::BRKINT + | termios::InputFlags::ICRNL + | termios::InputFlags::INPCK + | termios::InputFlags::ISTRIP + | termios::InputFlags::IXON); + + raw.control_flags |= termios::ControlFlags::CS8; + + raw.local_flags &= !(termios::LocalFlags::ECHO + | termios::LocalFlags::ICANON + | termios::LocalFlags::IEXTEN + | termios::LocalFlags::ISIG); + raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; + raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; + return Ok(JsonOp::Sync(json!({ + "restore": + serde_json::to_string(&SerializedTermios::from(original_mode)).unwrap(), + }))); + } else { + // Restore old mode. + if args.restore.is_none() { + return Err(other_error("no termios to restore".to_owned())); + } + let old_termios = + serde_json::from_str::(&args.restore.unwrap()); + if old_termios.is_err() { + return Err(other_error("bad termios to restore".to_owned())); + } + let old_termios = old_termios.unwrap(); + termios::tcsetattr( + raw_fd, + termios::SetArg::TCSADRAIN, + &old_termios.into(), + )?; + return Ok(JsonOp::Sync(json!({}))); + } + } +} diff --git a/cli/worker.rs b/cli/worker.rs index a4a35dfaab74eb..b619b2a67b8558 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -219,6 +219,7 @@ impl MainWorker { ops::resources::init(isolate, &state); ops::signal::init(isolate, &state); ops::timers::init(isolate, &state); + ops::tty::init(isolate, &state); ops::worker_host::init(isolate, &state); ops::web_worker::init(isolate, &state, &worker.internal_channels.sender); } From 0ec22bd6ca82c8731762ad0bfa286d1a04685356 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Sun, 9 Feb 2020 18:03:12 -0800 Subject: [PATCH 02/16] Re-call safe --- cli/js/files.ts | 17 ++++++++++++----- cli/ops/tty.rs | 8 ++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/cli/js/files.ts b/cli/js/files.ts index 9ce997737f666e..6a95b94380a7c4 100644 --- a/cli/js/files.ts +++ b/cli/js/files.ts @@ -14,8 +14,7 @@ import { sendAsyncMinimal, sendSyncMinimal } from "./dispatch_minimal.ts"; import * as dispatch from "./dispatch.ts"; import { sendSync as sendSyncJson, - sendAsync as sendAsyncJson, - sendSync + sendAsync as sendAsyncJson } from "./dispatch_json.ts"; /** Open a file and return an instance of the `File` object @@ -260,21 +259,29 @@ export class File export type RestoreModeFunc = () => void; /** Extended file abstraction with setRaw() */ export class Stdin extends File { + private _isRaw = false; + private _restoreFunc?: RestoreModeFunc; /** Set input mode to raw (non-canonical). * Returns a function that when called, restores previous mode. */ setRaw(): RestoreModeFunc { - const restoreInfo = sendSync(dispatch.OP_SET_RAW, { + if (this._isRaw) { + return this._restoreFunc!; + } + const restoreInfo = sendSyncJson(dispatch.OP_SET_RAW, { rid: this.rid, raw: true }); - return (): void => { - sendSync(dispatch.OP_SET_RAW, { + this._isRaw = true; + this._restoreFunc = (): void => { + sendSyncJson(dispatch.OP_SET_RAW, { rid: this.rid, raw: false, ...restoreInfo }); + this._isRaw = false; }; + return this._restoreFunc!; } } diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index 3cbe172c5d3340..95ccac22538fb0 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -117,7 +117,7 @@ pub fn op_set_raw( }; wincheck!(consoleapi::SetConsoleMode(handle, new_mode)); - return Ok(JsonOp::Sync(json!({}))); + Ok(JsonOp::Sync(json!({}))) } // From https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs #[cfg(not(windows))] @@ -144,10 +144,10 @@ pub fn op_set_raw( raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; - return Ok(JsonOp::Sync(json!({ + Ok(JsonOp::Sync(json!({ "restore": serde_json::to_string(&SerializedTermios::from(original_mode)).unwrap(), - }))); + }))) } else { // Restore old mode. if args.restore.is_none() { @@ -164,7 +164,7 @@ pub fn op_set_raw( termios::SetArg::TCSADRAIN, &old_termios.into(), )?; - return Ok(JsonOp::Sync(json!({}))); + Ok(JsonOp::Sync(json!({}))) } } } From b7803c4cfb9a50c5dbc9ee9858b4ec207ef7608e Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Sun, 9 Feb 2020 18:20:30 -0800 Subject: [PATCH 03/16] types --- cli/ops/tty.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index 95ccac22538fb0..ae4d0a8611d36f 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -28,11 +28,11 @@ macro_rules! wincheck { // Create a similar one for our use. #[derive(Serialize, Deserialize)] struct SerializedTermios { - iflags: u64, - oflags: u64, - cflags: u64, - lflags: u64, - cc: [u8; libc::NCCS], + iflags: libc::tcflag_t, + oflags: libc::tcflag_t, + cflags: libc::tcflag_t, + lflags: libc::tcflag_t, + cc: [libc::cc_t; libc::NCCS], } impl From for SerializedTermios { @@ -98,6 +98,7 @@ pub fn op_set_raw( #[cfg(windows)] { use std::os::windows::io::AsRawHandle; + use winapi::shared::minwindef::DWORD; use winapi::um::{consoleapi, handleapi, winbase, wincon, winuser}; let handle = std::io::stdin().as_raw_handle(); @@ -106,7 +107,7 @@ pub fn op_set_raw( } else if handle.is_null() { return Err(other_error("null handle".to_owned())); } - let mut original_mode = 0; + let mut original_mode: DWORD = 0; let RAW_MODE_MASK = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT; wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode)); From c68ea392d6981ac49c3cc63f014654b1bd7120f4 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Sun, 9 Feb 2020 19:26:50 -0800 Subject: [PATCH 04/16] windows 1 --- cli/ops/tty.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index ae4d0a8611d36f..f594ee87e298b1 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -5,6 +5,7 @@ use crate::deno_error::other_error; use crate::ops::json_op; use crate::state::State; use deno_core::*; +#[cfg(unix)] use nix::sys::termios; use serde_derive::{Deserialize, Serialize}; use serde_json::Value; @@ -18,7 +19,7 @@ macro_rules! wincheck { ($funcall:expr) => {{ let rc = unsafe { $funcall }; if rc == 0 { - Err(std::io::Error::last_os_error())?; + Err(ErrBox::from(std::io::Error::last_os_error()))?; } rc }}; @@ -26,6 +27,7 @@ macro_rules! wincheck { // libc::termios cannot be serialized. // Create a similar one for our use. +#[cfg(unix)] #[derive(Serialize, Deserialize)] struct SerializedTermios { iflags: libc::tcflag_t, @@ -35,6 +37,7 @@ struct SerializedTermios { cc: [libc::cc_t; libc::NCCS], } +#[cfg(unix)] impl From for SerializedTermios { fn from(t: termios::Termios) -> Self { Self { @@ -47,6 +50,7 @@ impl From for SerializedTermios { } } +#[cfg(unix)] impl Into for SerializedTermios { fn into(self) -> termios::Termios { let mut t = unsafe { termios::Termios::default_uninit() }; @@ -63,7 +67,7 @@ impl Into for SerializedTermios { struct SetRawArgs { rid: u32, raw: bool, - #[cfg(not(windows))] + #[cfg(unix)] restore: Option, // Only used for *nix // Saved as string in case of u64 problem in JS } @@ -99,17 +103,18 @@ pub fn op_set_raw( { use std::os::windows::io::AsRawHandle; use winapi::shared::minwindef::DWORD; - use winapi::um::{consoleapi, handleapi, winbase, wincon, winuser}; + use winapi::um::{consoleapi, handleapi, wincon}; let handle = std::io::stdin().as_raw_handle(); if handle == handleapi::INVALID_HANDLE_VALUE { - return Err(std::io::Error::last_os_error()); + return Err(ErrBox::from(std::io::Error::last_os_error())); } else if handle.is_null() { - return Err(other_error("null handle".to_owned())); + return Err(ErrBox::from(other_error("null handle".to_owned()))); } let mut original_mode: DWORD = 0; - let RAW_MODE_MASK = - ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT; + let RAW_MODE_MASK = wincon::ENABLE_LINE_INPUT + | wincon::ENABLE_ECHO_INPUT + | wincon::ENABLE_PROCESSED_INPUT; wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode)); let new_mode = if is_raw { original_mode & !RAW_MODE_MASK; @@ -121,7 +126,7 @@ pub fn op_set_raw( Ok(JsonOp::Sync(json!({}))) } // From https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs - #[cfg(not(windows))] + #[cfg(unix)] { use std::os::unix::io::AsRawFd; let raw_fd = std::io::stdin().as_raw_fd(); From a54a3173b412127626fffb1e3125dbf7b03c3b72 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Sun, 9 Feb 2020 19:56:16 -0800 Subject: [PATCH 05/16] windows 2 --- cli/ops/tty.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index f594ee87e298b1..2a9c6f123481de 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -7,7 +7,9 @@ use crate::state::State; use deno_core::*; #[cfg(unix)] use nix::sys::termios; -use serde_derive::{Deserialize, Serialize}; +use serde_derive::Deserialize; +#[cfg(unix)] +use serde_derive::Serialize; use serde_json::Value; pub fn init(i: &mut Isolate, s: &State) { @@ -117,9 +119,9 @@ pub fn op_set_raw( | wincon::ENABLE_PROCESSED_INPUT; wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode)); let new_mode = if is_raw { - original_mode & !RAW_MODE_MASK; + original_mode & !RAW_MODE_MASK } else { - original_mode | RAW_MODE_MASK; + original_mode | RAW_MODE_MASK }; wincheck!(consoleapi::SetConsoleMode(handle, new_mode)); From c732193132b178f55fce46287ec65de792e63890 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Sun, 9 Feb 2020 20:31:57 -0800 Subject: [PATCH 06/16] windows 3 --- cli/ops/tty.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index 2a9c6f123481de..cb51cc59bef82d 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -12,6 +12,15 @@ use serde_derive::Deserialize; use serde_derive::Serialize; use serde_json::Value; +#[cfg(windows)] +use winapi::shared::minwindef::DWORD; +#[cfg(windows)] +use winapi::um::wincon; +#[cfg(windows)] +const RAW_MODE_MASK: DWORD = wincon::ENABLE_LINE_INPUT + | wincon::ENABLE_ECHO_INPUT + | wincon::ENABLE_PROCESSED_INPUT; + pub fn init(i: &mut Isolate, s: &State) { i.register_op("set_raw", s.core_op(json_op(s.stateful_op(op_set_raw)))); } @@ -89,7 +98,7 @@ pub fn op_set_raw( return Err(bad_resource()); } - // For now, only stdin + // For now, only stdin. match resource.unwrap() { StreamResource::Stdin(_) => (), _ => { @@ -104,8 +113,7 @@ pub fn op_set_raw( #[cfg(windows)] { use std::os::windows::io::AsRawHandle; - use winapi::shared::minwindef::DWORD; - use winapi::um::{consoleapi, handleapi, wincon}; + use winapi::um::{consoleapi, handleapi}; let handle = std::io::stdin().as_raw_handle(); if handle == handleapi::INVALID_HANDLE_VALUE { @@ -114,9 +122,6 @@ pub fn op_set_raw( return Err(ErrBox::from(other_error("null handle".to_owned()))); } let mut original_mode: DWORD = 0; - let RAW_MODE_MASK = wincon::ENABLE_LINE_INPUT - | wincon::ENABLE_ECHO_INPUT - | wincon::ENABLE_PROCESSED_INPUT; wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode)); let new_mode = if is_raw { original_mode & !RAW_MODE_MASK From 2dec632d7f743bff3b050a1d26c6ba9fcff39f6e Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Mon, 10 Feb 2020 18:44:26 -0800 Subject: [PATCH 07/16] Start option 2 --- cli/js/console.ts | 3 +- cli/js/deno.ts | 4 +- cli/js/dispatch.ts | 1 + cli/js/files.ts | 36 ------------- cli/js/lib.deno.ns.d.ts | 32 ++++++----- cli/js/tty.ts | 55 +++++++++++++++++++ cli/ops/tty.rs | 116 +++++++++++++++++++++++++++++++++++----- core/resources.rs | 4 ++ 8 files changed, 184 insertions(+), 67 deletions(-) create mode 100644 cli/js/tty.ts diff --git a/cli/js/console.ts b/cli/js/console.ts index f5830b4a1a272c..51a39447822047 100644 --- a/cli/js/console.ts +++ b/cli/js/console.ts @@ -2,7 +2,8 @@ import { isTypedArray } from "./util.ts"; import { TypedArray } from "./types.ts"; import { TextEncoder } from "./text_encoding.ts"; -import { File, stdout } from "./files.ts"; +import { File } from "./files.ts"; +import { stdout } from "./tty.ts"; import { cliTable } from "./console_table.ts"; import { exposeForTest } from "./internals.ts"; diff --git a/cli/js/deno.ts b/cli/js/deno.ts index c1b074f3d52e0f..7d68d0053e040d 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -30,9 +30,6 @@ export { openSync, create, createSync, - stdin, - stdout, - stderr, read, readSync, write, @@ -114,6 +111,7 @@ export { statSync, lstatSync, stat, lstat } from "./stat.ts"; export { symlinkSync, symlink } from "./symlink.ts"; export { connectTLS, listenTLS } from "./tls.ts"; export { truncateSync, truncate } from "./truncate.ts"; +export { TTYInput, isatty, stdin, stdout, stderr } from "./tty.ts"; export { utimeSync, utime } from "./utime.ts"; export { version } from "./version.ts"; export { writeFileSync, writeFile, WriteFileOptions } from "./write_file.ts"; diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index be9bd5098caa1b..fc52dc2f656347 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -82,6 +82,7 @@ export let OP_SIGNAL_UNBIND: number; export let OP_SIGNAL_POLL: number; export let OP_LOADAVG: number; export let OP_SET_RAW: number; +export let OP_ISATTY: number; const PLUGIN_ASYNC_HANDLER_MAP: Map = new Map(); diff --git a/cli/js/files.ts b/cli/js/files.ts index 6a95b94380a7c4..6004d2658c6464 100644 --- a/cli/js/files.ts +++ b/cli/js/files.ts @@ -256,42 +256,6 @@ export class File } } -export type RestoreModeFunc = () => void; -/** Extended file abstraction with setRaw() */ -export class Stdin extends File { - private _isRaw = false; - private _restoreFunc?: RestoreModeFunc; - /** Set input mode to raw (non-canonical). - * Returns a function that when called, restores previous mode. - */ - setRaw(): RestoreModeFunc { - if (this._isRaw) { - return this._restoreFunc!; - } - const restoreInfo = sendSyncJson(dispatch.OP_SET_RAW, { - rid: this.rid, - raw: true - }); - this._isRaw = true; - this._restoreFunc = (): void => { - sendSyncJson(dispatch.OP_SET_RAW, { - rid: this.rid, - raw: false, - ...restoreInfo - }); - this._isRaw = false; - }; - return this._restoreFunc!; - } -} - -/** An instance of `Stdin` for stdin. */ -export const stdin = new Stdin(0); -/** An instance of `File` for stdout. */ -export const stdout = new File(1); -/** An instance of `File` for stderr. */ -export const stderr = new File(2); - export interface OpenOptions { /** Sets the option for read access. This option, when true, will indicate that the file should be read-able if opened. */ read?: boolean; diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index a9c5ebaa4d761b..ec33d751078300 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -486,20 +486,6 @@ declare namespace Deno { seekSync(offset: number, whence: SeekMode): void; close(): void; } - export type RestoreModeFunc = () => void; - /** Extended file abstraction with setRaw() */ - export class Stdin extends File { - /** Set input mode to raw (non-canonical). - * Returns a function that when called, restores previous mode. - */ - setRaw(): RestoreModeFunc; - } - /** An instance of `Stdin` for stdin. */ - export const stdin: Stdin; - /** An instance of `File` for stdout. */ - export const stdout: File; - /** An instance of `File` for stderr. */ - export const stderr: File; export interface OpenOptions { /** Sets the option for read access. This option, when true, will indicate that the file should be read-able if opened. */ @@ -557,6 +543,24 @@ declare namespace Deno { /** Read-write. Behaves like `x` and allows to read from file. */ | "x+"; + // @url js/tty.d.ts + + /** Check if a given resource is TTY. */ + export function isatty(rid: number): boolean; + /** Extended file abstraction for TTY input */ + export class TTYInput extends File { + /** Is TTY under raw mode. */ + get isRaw(): boolean; + /** Set TTY to be under raw mode. */ + setRaw(mode: boolean): void; + } + /** An instance of `TTYInput` for stdin. */ + export const stdin: TTYInput; + /** An instance of `File` for stdout. */ + export const stdout: File; + /** An instance of `File` for stderr. */ + export const stderr: File; + // @url js/buffer.d.ts /** A Buffer is a variable-sized buffer of bytes with read() and write() diff --git a/cli/js/tty.ts b/cli/js/tty.ts new file mode 100644 index 00000000000000..532ca5ad747a5e --- /dev/null +++ b/cli/js/tty.ts @@ -0,0 +1,55 @@ +import { File } from "./files.ts"; +import { sendSync } from "./dispatch_json.ts"; +import * as dispatch from "./dispatch.ts"; + +/** Check if a given resource is TTY. */ +export function isatty(rid: number): boolean { + return sendSync(dispatch.OP_ISATTY, { rid }); +} + +/** Extended file abstraction for TTY input */ +export class TTYInput extends File { + constructor(rid: number) { + super(rid); + if (rid !== 0 && !isatty(rid)) { + throw new Error("Given resource is not a TTY"); + } + } + + /** Is TTY under raw mode. */ + get isRaw(): boolean { + return this._isRaw; + } + private _isRaw = false; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _restoreInfo: { [key: string]: any } = {}; + + /** Set TTY to be under raw mode. */ + setRaw(mode: boolean): void { + if (this._isRaw === mode) { + return; + } + if (mode) { + this._restoreInfo = sendSync(dispatch.OP_SET_RAW, { + rid: this.rid, + raw: true + }); + this._isRaw = true; + } else { + sendSync(dispatch.OP_SET_RAW, { + rid: this.rid, + raw: false, + ...this._restoreInfo + }); + this._isRaw = false; + } + } +} + +/** An instance of `TTYInput` for stdin. */ +export const stdin = new TTYInput(0); +/** An instance of `File` for stdout. */ +export const stdout = new File(1); +/** An instance of `File` for stderr. */ +export const stderr = new File(2); diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index cb51cc59bef82d..f4f6750952d1d4 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -4,6 +4,7 @@ use crate::deno_error::bad_resource; use crate::deno_error::other_error; use crate::ops::json_op; use crate::state::State; +use atty; use deno_core::*; #[cfg(unix)] use nix::sys::termios; @@ -20,9 +21,25 @@ use winapi::um::wincon; const RAW_MODE_MASK: DWORD = wincon::ENABLE_LINE_INPUT | wincon::ENABLE_ECHO_INPUT | wincon::ENABLE_PROCESSED_INPUT; +#[cfg(windows)] +fn get_windows_handle( + f: &std::fs::File, +) -> Result { + use std::os::windows::io::AsRawHandle; + use winapi::um::handleapi; + + let handle = f.as_raw_handle(); + if handle == handleapi::INVALID_HANDLE_VALUE { + return Err(ErrBox::from(std::io::Error::last_os_error())); + } else if handle.is_null() { + return Err(ErrBox::from(other_error("null handle".to_owned()))); + } + Ok(handle) +} pub fn init(i: &mut Isolate, s: &State) { i.register_op("set_raw", s.core_op(json_op(s.stateful_op(op_set_raw)))); + i.register_op("isatty", s.core_op(json_op(s.stateful_op(op_isatty)))); } #[cfg(windows)] @@ -98,24 +115,29 @@ pub fn op_set_raw( return Err(bad_resource()); } - // For now, only stdin. - match resource.unwrap() { - StreamResource::Stdin(_) => (), - _ => { - return Err(other_error( - "Resource other than stdin is not implemented".to_owned(), - )) - } - } - // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs + // and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs // and https://github.com/crossterm-rs/crossterm/blob/e35d4d2c1cc4c919e36d242e014af75f6127ab50/src/terminal/sys/windows.rs + // Copyright (c) 2015 Katsu Kawakami & Rustyline authors. MIT license. + // Copyright (c) 2019 Timon. MIT license. #[cfg(windows)] { use std::os::windows::io::AsRawHandle; use winapi::um::{consoleapi, handleapi}; - let handle = std::io::stdin().as_raw_handle(); + // For now, only stdin. + let handle = match resource.unwrap() { + StreamResource::Stdin(_) => std::io::stdin().as_raw_handle(), + StreamResource::FsFile(f) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + std_file.as_raw_handle() + } + _ => { + return Err(other_error("Not implemented".to_owned())); + } + }; + if handle == handleapi::INVALID_HANDLE_VALUE { return Err(ErrBox::from(std::io::Error::last_os_error())); } else if handle.is_null() { @@ -132,11 +154,20 @@ pub fn op_set_raw( Ok(JsonOp::Sync(json!({}))) } - // From https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs #[cfg(unix)] { use std::os::unix::io::AsRawFd; - let raw_fd = std::io::stdin().as_raw_fd(); + let raw_fd = match resource.unwrap() { + StreamResource::Stdin(_) => std::io::stdin().as_raw_fd(), + StreamResource::FsFile(f) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + std_file.as_raw_fd() + } + _ => { + return Err(other_error("Not implemented".to_owned())); + } + }; if is_raw { let original_mode = termios::tcgetattr(raw_fd)?; @@ -181,3 +212,62 @@ pub fn op_set_raw( } } } + +#[derive(Deserialize)] +struct IsattyArgs { + rid: u32, +} + +pub fn op_isatty( + state_: &State, + args: Value, + _zero_copy: Option, +) -> Result { + let args: IsattyArgs = serde_json::from_value(args)?; + let rid = args.rid; + + let state = state_.borrow_mut(); + if !state.resource_table.has(rid) { + return Err(bad_resource()); + } + + let resource = state.resource_table.get::(rid); + if resource.is_none() { + return Ok(JsonOp::Sync(json!(false))); + } + + match resource.unwrap() { + StreamResource::Stdin(_) => { + Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stdin)))) + } + StreamResource::Stdout(_) => { + Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stdout)))) + } + StreamResource::Stderr(_) => { + Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stderr)))) + } + StreamResource::FsFile(f) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + #[cfg(windows)] + { + use winapi::um::consoleapi; + + let handle = get_windows_handle(&std_file)?; + let mut test_mode: DWORD = 0; + // If I cannot get mode out of console, it is not a console. + let result = + unsafe { consoleapi::GetConsoleMode(handle, &mut test_mode) != 0 }; + Ok(JsonOp::Sync(json!(result))) + } + #[cfg(unix)] + { + use std::os::unix::io::AsRawFd; + let raw_fd = std_file.as_raw_fd(); + let result = unsafe { libc::isatty(raw_fd as libc::c_int) == 1 }; + Ok(JsonOp::Sync(json!(result))) + } + } + _ => Ok(JsonOp::Sync(json!(false))), + } +} diff --git a/core/resources.rs b/core/resources.rs index 51b66c4bc5aee6..a5563bed355cfd 100644 --- a/core/resources.rs +++ b/core/resources.rs @@ -26,6 +26,10 @@ pub struct ResourceTable { } impl ResourceTable { + pub fn has(&self, rid: ResourceId) -> bool { + self.map.contains_key(&rid) + } + pub fn get(&self, rid: ResourceId) -> Option<&T> { if let Some((_name, resource)) = self.map.get(&rid) { return resource.downcast_ref::(); From 87d5eb701da8a7c5e3fbe21d10ba1f0e5634bf14 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Thu, 13 Feb 2020 15:55:08 -0800 Subject: [PATCH 08/16] Deno.setRaw(rid, mode) --- cli/js/console.ts | 3 +- cli/js/deno.ts | 5 +- cli/js/files.ts | 7 ++ cli/js/lib.deno.ns.d.ts | 31 +++++---- cli/js/tty.ts | 52 ++------------- cli/ops/files.rs | 13 ++-- cli/ops/io.rs | 28 ++++++-- cli/ops/process.rs | 2 +- cli/ops/tty.rs | 144 ++++++++++++++++------------------------ 9 files changed, 124 insertions(+), 161 deletions(-) diff --git a/cli/js/console.ts b/cli/js/console.ts index 51a39447822047..f5830b4a1a272c 100644 --- a/cli/js/console.ts +++ b/cli/js/console.ts @@ -2,8 +2,7 @@ import { isTypedArray } from "./util.ts"; import { TypedArray } from "./types.ts"; import { TextEncoder } from "./text_encoding.ts"; -import { File } from "./files.ts"; -import { stdout } from "./tty.ts"; +import { File, stdout } from "./files.ts"; import { cliTable } from "./console_table.ts"; import { exposeForTest } from "./internals.ts"; diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 7d68d0053e040d..248e9cdc903d56 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -30,6 +30,9 @@ export { openSync, create, createSync, + stdin, + stdout, + stderr, read, readSync, write, @@ -111,7 +114,7 @@ export { statSync, lstatSync, stat, lstat } from "./stat.ts"; export { symlinkSync, symlink } from "./symlink.ts"; export { connectTLS, listenTLS } from "./tls.ts"; export { truncateSync, truncate } from "./truncate.ts"; -export { TTYInput, isatty, stdin, stdout, stderr } from "./tty.ts"; +export { isatty, setRaw } from "./tty.ts"; export { utimeSync, utime } from "./utime.ts"; export { version } from "./version.ts"; export { writeFileSync, writeFile, WriteFileOptions } from "./write_file.ts"; diff --git a/cli/js/files.ts b/cli/js/files.ts index 6004d2658c6464..c966b9fa04699b 100644 --- a/cli/js/files.ts +++ b/cli/js/files.ts @@ -256,6 +256,13 @@ export class File } } +/** An instance of `File` for stdin. */ +export const stdin = new File(0); +/** An instance of `File` for stdout. */ +export const stdout = new File(1); +/** An instance of `File` for stderr. */ +export const stderr = new File(2); + export interface OpenOptions { /** Sets the option for read access. This option, when true, will indicate that the file should be read-able if opened. */ read?: boolean; diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index ec33d751078300..df30080931f514 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -487,6 +487,13 @@ declare namespace Deno { close(): void; } + /** An instance of `File` for stdin. */ + export const stdin: File; + /** An instance of `File` for stdout. */ + export const stdout: File; + /** An instance of `File` for stderr. */ + export const stderr: File; + export interface OpenOptions { /** Sets the option for read access. This option, when true, will indicate that the file should be read-able if opened. */ read?: boolean; @@ -545,21 +552,17 @@ declare namespace Deno { // @url js/tty.d.ts - /** Check if a given resource is TTY. */ + /** UNSTABLE: newly added API + * + * Check if a given resource is TTY + */ export function isatty(rid: number): boolean; - /** Extended file abstraction for TTY input */ - export class TTYInput extends File { - /** Is TTY under raw mode. */ - get isRaw(): boolean; - /** Set TTY to be under raw mode. */ - setRaw(mode: boolean): void; - } - /** An instance of `TTYInput` for stdin. */ - export const stdin: TTYInput; - /** An instance of `File` for stdout. */ - export const stdout: File; - /** An instance of `File` for stderr. */ - export const stderr: File; + + /** UNSTABLE: newly added API + * + * Set TTY to be under raw mode or not. + */ + export function setRaw(rid: number, mode: boolean): void; // @url js/buffer.d.ts diff --git a/cli/js/tty.ts b/cli/js/tty.ts index 532ca5ad747a5e..8cddcd7e001d83 100644 --- a/cli/js/tty.ts +++ b/cli/js/tty.ts @@ -1,4 +1,3 @@ -import { File } from "./files.ts"; import { sendSync } from "./dispatch_json.ts"; import * as dispatch from "./dispatch.ts"; @@ -7,49 +6,10 @@ export function isatty(rid: number): boolean { return sendSync(dispatch.OP_ISATTY, { rid }); } -/** Extended file abstraction for TTY input */ -export class TTYInput extends File { - constructor(rid: number) { - super(rid); - if (rid !== 0 && !isatty(rid)) { - throw new Error("Given resource is not a TTY"); - } - } - - /** Is TTY under raw mode. */ - get isRaw(): boolean { - return this._isRaw; - } - private _isRaw = false; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _restoreInfo: { [key: string]: any } = {}; - - /** Set TTY to be under raw mode. */ - setRaw(mode: boolean): void { - if (this._isRaw === mode) { - return; - } - if (mode) { - this._restoreInfo = sendSync(dispatch.OP_SET_RAW, { - rid: this.rid, - raw: true - }); - this._isRaw = true; - } else { - sendSync(dispatch.OP_SET_RAW, { - rid: this.rid, - raw: false, - ...this._restoreInfo - }); - this._isRaw = false; - } - } +/** Set TTY to be under raw mode or not. */ +export function setRaw(rid: number, mode: boolean): void { + sendSync(dispatch.OP_SET_RAW, { + rid, + mode + }); } - -/** An instance of `TTYInput` for stdin. */ -export const stdin = new TTYInput(0); -/** An instance of `File` for stdout. */ -export const stdout = new File(1); -/** An instance of `File` for stderr. */ -export const stderr = new File(2); diff --git a/cli/ops/files.rs b/cli/ops/files.rs index 2fc41bc342b9b4..cc06ea69b1a364 100644 --- a/cli/ops/files.rs +++ b/cli/ops/files.rs @@ -1,6 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{Deserialize, JsonOp, Value}; -use super::io::StreamResource; +use super::io::{FileMetadata, StreamResource}; +use crate::deno_error::DenoError; +use crate::deno_error::ErrorKind; use crate::fs as deno_fs; use crate::op_error::OpError; use crate::ops::json_op; @@ -126,9 +128,10 @@ fn op_open( let fut = async move { let fs_file = open_options.open(filename).await?; let mut state = state_.borrow_mut(); - let rid = state - .resource_table - .add("fsFile", Box::new(StreamResource::FsFile(fs_file))); + let rid = state.resource_table.add( + "fsFile", + Box::new(StreamResource::FsFile(fs_file, FileMetadata::default())), + ); Ok(json!(rid)) }; @@ -198,7 +201,7 @@ fn op_seek( .ok_or_else(OpError::bad_resource)?; let tokio_file = match resource { - StreamResource::FsFile(ref file) => file, + StreamResource::FsFile(ref file, _) => file, _ => return Err(OpError::bad_resource()), }; let mut file = futures::executor::block_on(tokio_file.try_clone())?; diff --git a/cli/ops/io.rs b/cli/ops/io.rs index b150fa978d5e49..3aeb283ffd3e19 100644 --- a/cli/ops/io.rs +++ b/cli/ops/io.rs @@ -57,7 +57,7 @@ pub fn init(i: &mut Isolate, s: &State) { } pub fn get_stdio() -> (StreamResource, StreamResource, StreamResource) { - let stdin = StreamResource::Stdin(tokio::io::stdin()); + let stdin = StreamResource::Stdin(tokio::io::stdin(), TTYMetadata::default()); let stdout = StreamResource::Stdout({ let stdout = STDOUT_HANDLE .try_clone() @@ -69,11 +69,25 @@ pub fn get_stdio() -> (StreamResource, StreamResource, StreamResource) { (stdin, stdout, stderr) } +#[cfg(unix)] +use nix::sys::termios; + +#[derive(Default)] +pub struct TTYMetadata { + #[cfg(unix)] + pub mode: Option, +} + +#[derive(Default)] +pub struct FileMetadata { + pub tty: TTYMetadata, +} + pub enum StreamResource { - Stdin(tokio::io::Stdin), + Stdin(tokio::io::Stdin, TTYMetadata), Stdout(tokio::fs::File), Stderr(tokio::io::Stderr), - FsFile(tokio::fs::File), + FsFile(tokio::fs::File, FileMetadata), TcpStream(tokio::net::TcpStream), ServerTlsStream(Box>), ClientTlsStream(Box>), @@ -101,8 +115,8 @@ impl DenoAsyncRead for StreamResource { ) -> Poll> { use StreamResource::*; let mut f: Pin> = match self { - FsFile(f) => Box::pin(f), - Stdin(f) => Box::pin(f), + FsFile(f, _) => Box::pin(f), + Stdin(f, _) => Box::pin(f), TcpStream(f) => Box::pin(f), ClientTlsStream(f) => Box::pin(f), ServerTlsStream(f) => Box::pin(f), @@ -216,7 +230,7 @@ impl DenoAsyncWrite for StreamResource { ) -> Poll> { use StreamResource::*; let mut f: Pin> = match self { - FsFile(f) => Box::pin(f), + FsFile(f, _) => Box::pin(f), Stdout(f) => Box::pin(f), Stderr(f) => Box::pin(f), TcpStream(f) => Box::pin(f), @@ -233,7 +247,7 @@ impl DenoAsyncWrite for StreamResource { fn poll_flush(&mut self, cx: &mut Context) -> Poll> { use StreamResource::*; let mut f: Pin> = match self { - FsFile(f) => Box::pin(f), + FsFile(f, _) => Box::pin(f), Stdout(f) => Box::pin(f), Stderr(f) => Box::pin(f), TcpStream(f) => Box::pin(f), diff --git a/cli/ops/process.rs b/cli/ops/process.rs index fe461bd28c126a..4bd4ccc582e097 100644 --- a/cli/ops/process.rs +++ b/cli/ops/process.rs @@ -37,7 +37,7 @@ fn clone_file(rid: u32, state: &State) -> Result { .get_mut::(rid) .ok_or_else(OpError::bad_resource)?; let file = match repr { - StreamResource::FsFile(ref mut file) => file, + StreamResource::FsFile(ref mut file, _) => file, _ => return Err(OpError::bad_resource()), }; let tokio_file = futures::executor::block_on(file.try_clone())?; diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index f4f6750952d1d4..f38becaf6f1b4c 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -9,8 +9,6 @@ use deno_core::*; #[cfg(unix)] use nix::sys::termios; use serde_derive::Deserialize; -#[cfg(unix)] -use serde_derive::Serialize; use serde_json::Value; #[cfg(windows)] @@ -53,51 +51,10 @@ macro_rules! wincheck { }}; } -// libc::termios cannot be serialized. -// Create a similar one for our use. -#[cfg(unix)] -#[derive(Serialize, Deserialize)] -struct SerializedTermios { - iflags: libc::tcflag_t, - oflags: libc::tcflag_t, - cflags: libc::tcflag_t, - lflags: libc::tcflag_t, - cc: [libc::cc_t; libc::NCCS], -} - -#[cfg(unix)] -impl From for SerializedTermios { - fn from(t: termios::Termios) -> Self { - Self { - iflags: t.input_flags.bits(), - oflags: t.output_flags.bits(), - cflags: t.control_flags.bits(), - lflags: t.local_flags.bits(), - cc: t.control_chars, - } - } -} - -#[cfg(unix)] -impl Into for SerializedTermios { - fn into(self) -> termios::Termios { - let mut t = unsafe { termios::Termios::default_uninit() }; - t.input_flags = termios::InputFlags::from_bits_truncate(self.iflags); - t.output_flags = termios::OutputFlags::from_bits_truncate(self.oflags); - t.control_flags = termios::ControlFlags::from_bits_truncate(self.cflags); - t.local_flags = termios::LocalFlags::from_bits_truncate(self.lflags); - t.control_chars = self.cc; - t - } -} - #[derive(Deserialize)] struct SetRawArgs { rid: u32, - raw: bool, - #[cfg(unix)] - restore: Option, // Only used for *nix - // Saved as string in case of u64 problem in JS + mode: bool, } pub fn op_set_raw( @@ -107,13 +64,7 @@ pub fn op_set_raw( ) -> Result { let args: SetRawArgs = serde_json::from_value(args)?; let rid = args.rid; - let is_raw = args.raw; - - let state = state_.borrow_mut(); - let resource = state.resource_table.get::(rid); - if resource.is_none() { - return Err(bad_resource()); - } + let is_raw = args.mode; // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs // and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs @@ -125,16 +76,22 @@ pub fn op_set_raw( use std::os::windows::io::AsRawHandle; use winapi::um::{consoleapi, handleapi}; + let state = state_.borrow_mut(); + let resource = state.resource_table.get::(rid); + if resource.is_none() { + return Err(bad_resource()); + } + // For now, only stdin. let handle = match resource.unwrap() { - StreamResource::Stdin(_) => std::io::stdin().as_raw_handle(), - StreamResource::FsFile(f) => { + StreamResource::Stdin(_, _) => std::io::stdin().as_raw_handle(), + StreamResource::FsFile(f, _) => { let tokio_file = futures::executor::block_on(f.try_clone())?; let std_file = futures::executor::block_on(tokio_file.into_std()); std_file.as_raw_handle() } _ => { - return Err(other_error("Not implemented".to_owned())); + return Err(other_error("Not supported".to_owned())); } }; @@ -157,21 +114,37 @@ pub fn op_set_raw( #[cfg(unix)] { use std::os::unix::io::AsRawFd; - let raw_fd = match resource.unwrap() { - StreamResource::Stdin(_) => std::io::stdin().as_raw_fd(), - StreamResource::FsFile(f) => { - let tokio_file = futures::executor::block_on(f.try_clone())?; - let std_file = futures::executor::block_on(tokio_file.into_std()); - std_file.as_raw_fd() - } - _ => { - return Err(other_error("Not implemented".to_owned())); - } - }; + + let mut state = state_.borrow_mut(); + let resource = state.resource_table.get_mut::(rid); + if resource.is_none() { + return Err(bad_resource()); + } if is_raw { + let (raw_fd, maybe_tty_mode) = match resource.unwrap() { + StreamResource::Stdin(_, ref mut metadata) => { + (std::io::stdin().as_raw_fd(), &mut metadata.mode) + } + StreamResource::FsFile(f, ref mut metadata) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + (std_file.as_raw_fd(), &mut metadata.tty.mode) + } + _ => { + return Err(other_error("Not supported".to_owned())); + } + }; + + if maybe_tty_mode.is_some() { + // Already raw. Skip. + return Ok(JsonOp::Sync(json!({}))); + } + let original_mode = termios::tcgetattr(raw_fd)?; let mut raw = original_mode.clone(); + // Save original mode. + maybe_tty_mode.replace(original_mode); raw.input_flags &= !(termios::InputFlags::BRKINT | termios::InputFlags::ICRNL @@ -188,26 +161,27 @@ pub fn op_set_raw( raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; - Ok(JsonOp::Sync(json!({ - "restore": - serde_json::to_string(&SerializedTermios::from(original_mode)).unwrap(), - }))) + Ok(JsonOp::Sync(json!({}))) } else { - // Restore old mode. - if args.restore.is_none() { - return Err(other_error("no termios to restore".to_owned())); + // Try restore saved mode. + let (raw_fd, maybe_tty_mode) = match resource.unwrap() { + StreamResource::Stdin(_, ref mut metadata) => { + (std::io::stdin().as_raw_fd(), &mut metadata.mode) + } + StreamResource::FsFile(f, ref mut metadata) => { + let tokio_file = futures::executor::block_on(f.try_clone())?; + let std_file = futures::executor::block_on(tokio_file.into_std()); + (std_file.as_raw_fd(), &mut metadata.tty.mode) + } + _ => { + return Err(other_error("Not supported".to_owned())); + } + }; + + if let Some(mode) = maybe_tty_mode.take() { + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?; } - let old_termios = - serde_json::from_str::(&args.restore.unwrap()); - if old_termios.is_err() { - return Err(other_error("bad termios to restore".to_owned())); - } - let old_termios = old_termios.unwrap(); - termios::tcsetattr( - raw_fd, - termios::SetArg::TCSADRAIN, - &old_termios.into(), - )?; + Ok(JsonOp::Sync(json!({}))) } } @@ -237,7 +211,7 @@ pub fn op_isatty( } match resource.unwrap() { - StreamResource::Stdin(_) => { + StreamResource::Stdin(_, _) => { Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stdin)))) } StreamResource::Stdout(_) => { @@ -246,7 +220,7 @@ pub fn op_isatty( StreamResource::Stderr(_) => { Ok(JsonOp::Sync(json!(atty::is(atty::Stream::Stderr)))) } - StreamResource::FsFile(f) => { + StreamResource::FsFile(f, _) => { let tokio_file = futures::executor::block_on(f.try_clone())?; let std_file = futures::executor::block_on(tokio_file.into_std()); #[cfg(windows)] From 6b48c391750f303c12182a24bb2a71444b160e80 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Fri, 21 Feb 2020 16:00:54 -0800 Subject: [PATCH 09/16] Add basic test (unix only) --- Cargo.lock | 22 ++++++++++++ cli/Cargo.toml | 1 + cli/tests/integration_tests.rs | 61 ++++++++++++++++++++++++++++++++++ cli/tests/raw_mode.ts | 20 +++++++++++ 4 files changed, 104 insertions(+) create mode 100644 cli/tests/raw_mode.ts diff --git a/Cargo.lock b/Cargo.lock index 94e5d3240e769d..04cabc41cfa6ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,6 +455,7 @@ dependencies = [ "nix", "notify", "os_pipe", + "pty", "rand 0.7.3", "regex", "remove_dir_all", @@ -620,6 +621,17 @@ dependencies = [ "syn 1.0.14", ] +[[package]] +name = "errno" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e2b2decb0484e15560df3210cf0d78654bb0864b2c138977c07e377a1bae0e2" +dependencies = [ + "kernel32-sys", + "libc", + "winapi 0.2.8", +] + [[package]] name = "failure" version = "0.1.6" @@ -1509,6 +1521,16 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "pty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50f3d255966981eb4e4c5df3e983e6f7d163221f547406d83b6a460ff5c5ee8" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "qadapt-spin" version = "1.0.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index fdca9cceb179a2..e31076bda26baf 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -63,6 +63,7 @@ utime = "0.2.1" webpki = "0.21.0" webpki-roots = "0.17.0" walkdir = "2.3.1" +pty = "0.2" [target.'cfg(windows)'.dependencies] winapi = "0.3.8" diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 5429170c714056..b48d73c2de0545 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1,8 +1,69 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. #[macro_use] extern crate lazy_static; +#[cfg(unix)] +extern crate nix; +extern crate pty; extern crate tempfile; +#[cfg(unix)] +#[test] +pub fn test_raw_tty() { + use pty::fork::*; + use std::io::{Read, Write}; + + let fork = Fork::from_ptmx().unwrap(); + + if let Ok(mut master) = fork.is_parent() { + let mut obytes: [u8; 100] = [0; 100]; + let mut nread = master.read(&mut obytes).unwrap(); + assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "BEGIN\r\n"); + master.write_all(b"a").unwrap(); + nread = master.read(&mut obytes).unwrap(); + assert_eq!( + String::from_utf8_lossy(&obytes[0..nread]), + "READ 1 byte: a\r\n" + ); + master.write_all(b"b").unwrap(); + nread = master.read(&mut obytes).unwrap(); + assert_eq!( + String::from_utf8_lossy(&obytes[0..nread]), + "READ 1 byte: b\r\n" + ); + master.write_all(b"c").unwrap(); + nread = master.read(&mut obytes).unwrap(); + assert_eq!( + String::from_utf8_lossy(&obytes[0..nread]), + "READ 1 byte: c\r\n" + ); + } else { + use deno::test_util::*; + use nix::sys::termios; + use std::os::unix::io::AsRawFd; + use std::process::*; + use tempfile::TempDir; + + // Turn off echo such that parent is reading works properly. + let stdin_fd = std::io::stdin().as_raw_fd(); + let mut t = termios::tcgetattr(stdin_fd).unwrap(); + t.local_flags.remove(termios::LocalFlags::ECHO); + termios::tcsetattr(stdin_fd, termios::SetArg::TCSANOW, &t).unwrap(); + + let deno_dir = TempDir::new().expect("tempdir fail"); + let mut child = Command::new(deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::root_path()) + .arg("run") + .arg("cli/tests/raw_mode.ts") + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::null()) + .spawn() + .expect("Failed to spawn script"); + child.wait().unwrap(); + } +} + #[test] fn test_pattern_match() { assert!(util::pattern_match("foo[BAR]baz", "foobarbaz", "[BAR]")); diff --git a/cli/tests/raw_mode.ts b/cli/tests/raw_mode.ts new file mode 100644 index 00000000000000..691d98946f7c8e --- /dev/null +++ b/cli/tests/raw_mode.ts @@ -0,0 +1,20 @@ +Deno.setRaw(0, true); +Deno.setRaw(0, true); // Can be called multiple times + +console.log("BEGIN"); + +const buf = new Uint8Array(3); +for (let i = 0; i < 3; i++) { + const nread = await Deno.stdin.read(buf); + if (nread === Deno.EOF) { + break; + } else { + console.log( + `READ ${nread} byte:`, + new TextDecoder().decode(buf.subarray(0, nread)) + ); + } +} + +Deno.setRaw(0, false); // restores old mode. +Deno.setRaw(0, false); // restores old mode. Can be safely called multiple times From 482271b86c9dc088e6f9968a9a58868083e56b78 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Fri, 21 Feb 2020 16:09:13 -0800 Subject: [PATCH 10/16] Move pty to dev deps --- cli/Cargo.toml | 4 +++- cli/tests/integration_tests.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e31076bda26baf..117dc95b0da426 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -63,7 +63,6 @@ utime = "0.2.1" webpki = "0.21.0" webpki-roots = "0.17.0" walkdir = "2.3.1" -pty = "0.2" [target.'cfg(windows)'.dependencies] winapi = "0.3.8" @@ -74,3 +73,6 @@ nix = "0.14.1" [dev-dependencies] os_pipe = "0.9.1" + +[target.'cfg(unix)'.dev-dependencies] +pty = "0.2" diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index b48d73c2de0545..27e2b347d83262 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -3,6 +3,7 @@ extern crate lazy_static; #[cfg(unix)] extern crate nix; +#[cfg(unix)] extern crate pty; extern crate tempfile; From dcc832d0d99d0b4d58225473c39b98201aca406e Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Fri, 21 Feb 2020 17:59:27 -0800 Subject: [PATCH 11/16] De-flake test --- cli/tests/integration_tests.rs | 17 ++++------------- cli/tests/raw_mode.ts | 10 ++++------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 27e2b347d83262..ec18045e633eb6 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -18,25 +18,16 @@ pub fn test_raw_tty() { if let Ok(mut master) = fork.is_parent() { let mut obytes: [u8; 100] = [0; 100]; let mut nread = master.read(&mut obytes).unwrap(); - assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "BEGIN\r\n"); + assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "S"); master.write_all(b"a").unwrap(); nread = master.read(&mut obytes).unwrap(); - assert_eq!( - String::from_utf8_lossy(&obytes[0..nread]), - "READ 1 byte: a\r\n" - ); + assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "A"); master.write_all(b"b").unwrap(); nread = master.read(&mut obytes).unwrap(); - assert_eq!( - String::from_utf8_lossy(&obytes[0..nread]), - "READ 1 byte: b\r\n" - ); + assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "B"); master.write_all(b"c").unwrap(); nread = master.read(&mut obytes).unwrap(); - assert_eq!( - String::from_utf8_lossy(&obytes[0..nread]), - "READ 1 byte: c\r\n" - ); + assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "C"); } else { use deno::test_util::*; use nix::sys::termios; diff --git a/cli/tests/raw_mode.ts b/cli/tests/raw_mode.ts index 691d98946f7c8e..125601b2b5cff5 100644 --- a/cli/tests/raw_mode.ts +++ b/cli/tests/raw_mode.ts @@ -1,7 +1,7 @@ Deno.setRaw(0, true); Deno.setRaw(0, true); // Can be called multiple times -console.log("BEGIN"); +Deno.stdout.writeSync(new TextEncoder().encode("S")); const buf = new Uint8Array(3); for (let i = 0; i < 3; i++) { @@ -9,12 +9,10 @@ for (let i = 0; i < 3; i++) { if (nread === Deno.EOF) { break; } else { - console.log( - `READ ${nread} byte:`, - new TextDecoder().decode(buf.subarray(0, nread)) - ); + const data = new TextDecoder().decode(buf.subarray(0, nread)); + Deno.stdout.writeSync(new TextEncoder().encode(data.toUpperCase())); } } Deno.setRaw(0, false); // restores old mode. -Deno.setRaw(0, false); // restores old mode. Can be safely called multiple times +Deno.setRaw(0, false); // Can be safely called multiple times From c6c9bb754d1008ca6ff30b938c6516a3ffd2bc3c Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Sun, 23 Feb 2020 12:50:59 -0800 Subject: [PATCH 12/16] isTTY uses isatty; add some unit test --- cli/js/deno.ts | 4 ++-- cli/js/dispatch.ts | 1 - cli/js/lib.deno.ns.d.ts | 20 ++++++++++---------- cli/js/os.ts | 7 ------- cli/js/os_test.ts | 4 ---- cli/js/tty.ts | 12 ++++++++++++ cli/js/tty_test.ts | 27 +++++++++++++++++++++++++++ cli/js/unit_tests.ts | 1 + cli/ops/files.rs | 2 -- cli/ops/os.rs | 14 -------------- cli/ops/tty.rs | 31 +++++++++++++++---------------- 11 files changed, 67 insertions(+), 56 deletions(-) create mode 100644 cli/js/tty_test.ts diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 248e9cdc903d56..ebd1c8382689aa 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -85,7 +85,7 @@ export { ShutdownMode, shutdown } from "./net.ts"; -export { dir, env, exit, isTTY, execPath, hostname, loadavg } from "./os.ts"; +export { dir, env, exit, execPath, hostname, loadavg } from "./os.ts"; export { permissions, PermissionName, @@ -114,7 +114,7 @@ export { statSync, lstatSync, stat, lstat } from "./stat.ts"; export { symlinkSync, symlink } from "./symlink.ts"; export { connectTLS, listenTLS } from "./tls.ts"; export { truncateSync, truncate } from "./truncate.ts"; -export { isatty, setRaw } from "./tty.ts"; +export { isatty, isTTY, setRaw } from "./tty.ts"; export { utimeSync, utime } from "./utime.ts"; export { version } from "./version.ts"; export { writeFileSync, writeFile, WriteFileOptions } from "./write_file.ts"; diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index fc52dc2f656347..ae67afc80ba901 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -7,7 +7,6 @@ import { AsyncHandler } from "./plugins.ts"; export let OP_READ: number; export let OP_WRITE: number; export let OP_EXIT: number; -export let OP_IS_TTY: number; export let OP_ENV: number; export let OP_EXEC_PATH: number; export let OP_UTIME: number; diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index df30080931f514..489ad3a80b6183 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -30,16 +30,6 @@ declare namespace Deno { export function runTests(opts?: RunTestsOptions): Promise; - /** Check if running in terminal. - * - * console.log(Deno.isTTY().stdout); - */ - export function isTTY(): { - stdin: boolean; - stdout: boolean; - stderr: boolean; - }; - /** Get the loadavg. Requires the `--allow-env` flag. * * console.log(Deno.loadavg()); @@ -564,6 +554,16 @@ declare namespace Deno { */ export function setRaw(rid: number, mode: boolean): void; + /** Check if running in terminal. + * + * console.log(Deno.isTTY().stdout); + */ + export function isTTY(): { + stdin: boolean; + stdout: boolean; + stderr: boolean; + }; + // @url js/buffer.d.ts /** A Buffer is a variable-sized buffer of bytes with read() and write() diff --git a/cli/js/os.ts b/cli/js/os.ts index d3c0d1b7243945..4a48ae15eafb68 100644 --- a/cli/js/os.ts +++ b/cli/js/os.ts @@ -4,13 +4,6 @@ import { sendSync } from "./dispatch_json.ts"; import { Err } from "./errors.ts"; import * as util from "./util.ts"; -/** Check if running in terminal. - * - * console.log(Deno.isTTY().stdout); - */ -export function isTTY(): { stdin: boolean; stdout: boolean; stderr: boolean } { - return sendSync(dispatch.OP_IS_TTY); -} /** Get the loadavg. * Requires the `--allow-env` flag. * diff --git a/cli/js/os_test.ts b/cli/js/os_test.ts index 325cbdaa6d5e48..8175d3184e4383 100644 --- a/cli/js/os_test.ts +++ b/cli/js/os_test.ts @@ -115,10 +115,6 @@ test(function osPid(): void { assert(Deno.pid > 0); }); -test(function osIsTTYSmoke(): void { - console.log(Deno.isTTY()); -}); - testPerm({ env: true }, function getDir(): void { type supportOS = "mac" | "win" | "linux"; diff --git a/cli/js/tty.ts b/cli/js/tty.ts index 8cddcd7e001d83..b4d3e07e582ec4 100644 --- a/cli/js/tty.ts +++ b/cli/js/tty.ts @@ -13,3 +13,15 @@ export function setRaw(rid: number, mode: boolean): void { mode }); } + +/** Check if running in terminal. + * + * console.log(Deno.isTTY().stdout); + */ +export function isTTY(): { stdin: boolean; stdout: boolean; stderr: boolean } { + return { + stdin: isatty(0), + stdout: isatty(1), + stderr: isatty(2) + }; +} diff --git a/cli/js/tty_test.ts b/cli/js/tty_test.ts new file mode 100644 index 00000000000000..c91d5173025f01 --- /dev/null +++ b/cli/js/tty_test.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { test, testPerm, assert } from "./test_util.ts"; + +// setRaw test is in integration tests. + +// Smoke test to ensure no error. +test(function isTTYSmoke(): void { + console.log(Deno.isTTY()); +}); + +testPerm({ read: true }, function isatty(): void { + // CI not under TTY, so cannot test stdin/stdout/stderr. + const f = Deno.openSync("cli/tests/hello.txt"); + assert(!Deno.isatty(f.rid)); +}); + +test(function isattyError(): void { + let caught = false; + try { + // Absurdly large rid. + Deno.isatty(0x7fffffff); + } catch (e) { + caught = true; + assert(e instanceof Deno.Err.BadResource); + } + assert(caught); +}); diff --git a/cli/js/unit_tests.ts b/cli/js/unit_tests.ts index ec4505c219bfce..d2237477707001 100644 --- a/cli/js/unit_tests.ts +++ b/cli/js/unit_tests.ts @@ -53,6 +53,7 @@ import "./text_encoding_test.ts"; import "./timers_test.ts"; import "./tls_test.ts"; import "./truncate_test.ts"; +import "./tty_test.ts"; import "./url_test.ts"; import "./url_search_params_test.ts"; import "./utime_test.ts"; diff --git a/cli/ops/files.rs b/cli/ops/files.rs index cc06ea69b1a364..ba4aca27f6433a 100644 --- a/cli/ops/files.rs +++ b/cli/ops/files.rs @@ -1,8 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{Deserialize, JsonOp, Value}; use super::io::{FileMetadata, StreamResource}; -use crate::deno_error::DenoError; -use crate::deno_error::ErrorKind; use crate::fs as deno_fs; use crate::op_error::OpError; use crate::ops::json_op; diff --git a/cli/ops/os.rs b/cli/ops/os.rs index 10c5e247e07047..7d7cc8a9b6db80 100644 --- a/cli/ops/os.rs +++ b/cli/ops/os.rs @@ -3,7 +3,6 @@ use super::dispatch_json::{Deserialize, JsonOp, Value}; use crate::op_error::OpError; use crate::ops::json_op; use crate::state::State; -use atty; use deno_core::*; use std::collections::HashMap; use std::env; @@ -13,7 +12,6 @@ use url::Url; pub fn init(i: &mut Isolate, s: &State) { i.register_op("exit", s.core_op(json_op(s.stateful_op(op_exit)))); - i.register_op("is_tty", s.core_op(json_op(s.stateful_op(op_is_tty)))); i.register_op("env", s.core_op(json_op(s.stateful_op(op_env)))); i.register_op("exec_path", s.core_op(json_op(s.stateful_op(op_exec_path)))); i.register_op("set_env", s.core_op(json_op(s.stateful_op(op_set_env)))); @@ -151,18 +149,6 @@ fn op_exit( std::process::exit(args.code) } -fn op_is_tty( - _s: &State, - _args: Value, - _zero_copy: Option, -) -> Result { - Ok(JsonOp::Sync(json!({ - "stdin": atty::is(atty::Stream::Stdin), - "stdout": atty::is(atty::Stream::Stdout), - "stderr": atty::is(atty::Stream::Stderr), - }))) -} - fn op_loadavg( state: &State, _args: Value, diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index f38becaf6f1b4c..677fd7dea03573 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -1,7 +1,6 @@ use super::dispatch_json::JsonOp; use super::io::StreamResource; -use crate::deno_error::bad_resource; -use crate::deno_error::other_error; +use crate::op_error::OpError; use crate::ops::json_op; use crate::state::State; use atty; @@ -22,15 +21,15 @@ const RAW_MODE_MASK: DWORD = wincon::ENABLE_LINE_INPUT #[cfg(windows)] fn get_windows_handle( f: &std::fs::File, -) -> Result { +) -> Result { use std::os::windows::io::AsRawHandle; use winapi::um::handleapi; let handle = f.as_raw_handle(); if handle == handleapi::INVALID_HANDLE_VALUE { - return Err(ErrBox::from(std::io::Error::last_os_error())); + return Err(OpError::from(std::io::Error::last_os_error())); } else if handle.is_null() { - return Err(ErrBox::from(other_error("null handle".to_owned()))); + return Err(OpError::other("null handle".to_owned())); } Ok(handle) } @@ -45,7 +44,7 @@ macro_rules! wincheck { ($funcall:expr) => {{ let rc = unsafe { $funcall }; if rc == 0 { - Err(ErrBox::from(std::io::Error::last_os_error()))?; + Err(OpError::from(std::io::Error::last_os_error()))?; } rc }}; @@ -61,7 +60,7 @@ pub fn op_set_raw( state_: &State, args: Value, _zero_copy: Option, -) -> Result { +) -> Result { let args: SetRawArgs = serde_json::from_value(args)?; let rid = args.rid; let is_raw = args.mode; @@ -79,7 +78,7 @@ pub fn op_set_raw( let state = state_.borrow_mut(); let resource = state.resource_table.get::(rid); if resource.is_none() { - return Err(bad_resource()); + return Err(OpError::bad_resource()); } // For now, only stdin. @@ -91,14 +90,14 @@ pub fn op_set_raw( std_file.as_raw_handle() } _ => { - return Err(other_error("Not supported".to_owned())); + return Err(OpError::other("Not supported".to_owned())); } }; if handle == handleapi::INVALID_HANDLE_VALUE { - return Err(ErrBox::from(std::io::Error::last_os_error())); + return Err(OpError::from(std::io::Error::last_os_error())); } else if handle.is_null() { - return Err(ErrBox::from(other_error("null handle".to_owned()))); + return Err(OpError::other("null handle".to_owned())); } let mut original_mode: DWORD = 0; wincheck!(consoleapi::GetConsoleMode(handle, &mut original_mode)); @@ -118,7 +117,7 @@ pub fn op_set_raw( let mut state = state_.borrow_mut(); let resource = state.resource_table.get_mut::(rid); if resource.is_none() { - return Err(bad_resource()); + return Err(OpError::bad_resource()); } if is_raw { @@ -132,7 +131,7 @@ pub fn op_set_raw( (std_file.as_raw_fd(), &mut metadata.tty.mode) } _ => { - return Err(other_error("Not supported".to_owned())); + return Err(OpError::other("Not supported".to_owned())); } }; @@ -174,7 +173,7 @@ pub fn op_set_raw( (std_file.as_raw_fd(), &mut metadata.tty.mode) } _ => { - return Err(other_error("Not supported".to_owned())); + return Err(OpError::other("Not supported".to_owned())); } }; @@ -196,13 +195,13 @@ pub fn op_isatty( state_: &State, args: Value, _zero_copy: Option, -) -> Result { +) -> Result { let args: IsattyArgs = serde_json::from_value(args)?; let rid = args.rid; let state = state_.borrow_mut(); if !state.resource_table.has(rid) { - return Err(bad_resource()); + return Err(OpError::bad_resource()); } let resource = state.resource_table.get::(rid); From d4f5cb418a6866112276ff13955a8a3ab74a7744 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 24 Feb 2020 08:29:27 -0500 Subject: [PATCH 13/16] Remove isTTY --- cli/js/deno.ts | 2 +- cli/js/lib.deno.ns.d.ts | 10 ---------- cli/js/tty.ts | 12 ------------ cli/js/tty_test.ts | 7 +------ 4 files changed, 2 insertions(+), 29 deletions(-) diff --git a/cli/js/deno.ts b/cli/js/deno.ts index ebd1c8382689aa..01cf8f0220c39d 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -114,7 +114,7 @@ export { statSync, lstatSync, stat, lstat } from "./stat.ts"; export { symlinkSync, symlink } from "./symlink.ts"; export { connectTLS, listenTLS } from "./tls.ts"; export { truncateSync, truncate } from "./truncate.ts"; -export { isatty, isTTY, setRaw } from "./tty.ts"; +export { isatty, setRaw } from "./tty.ts"; export { utimeSync, utime } from "./utime.ts"; export { version } from "./version.ts"; export { writeFileSync, writeFile, WriteFileOptions } from "./write_file.ts"; diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index 489ad3a80b6183..073c1f6515bc28 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -554,16 +554,6 @@ declare namespace Deno { */ export function setRaw(rid: number, mode: boolean): void; - /** Check if running in terminal. - * - * console.log(Deno.isTTY().stdout); - */ - export function isTTY(): { - stdin: boolean; - stdout: boolean; - stderr: boolean; - }; - // @url js/buffer.d.ts /** A Buffer is a variable-sized buffer of bytes with read() and write() diff --git a/cli/js/tty.ts b/cli/js/tty.ts index b4d3e07e582ec4..8cddcd7e001d83 100644 --- a/cli/js/tty.ts +++ b/cli/js/tty.ts @@ -13,15 +13,3 @@ export function setRaw(rid: number, mode: boolean): void { mode }); } - -/** Check if running in terminal. - * - * console.log(Deno.isTTY().stdout); - */ -export function isTTY(): { stdin: boolean; stdout: boolean; stderr: boolean } { - return { - stdin: isatty(0), - stdout: isatty(1), - stderr: isatty(2) - }; -} diff --git a/cli/js/tty_test.ts b/cli/js/tty_test.ts index c91d5173025f01..2116a29289da37 100644 --- a/cli/js/tty_test.ts +++ b/cli/js/tty_test.ts @@ -1,12 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { test, testPerm, assert } from "./test_util.ts"; -// setRaw test is in integration tests. - -// Smoke test to ensure no error. -test(function isTTYSmoke(): void { - console.log(Deno.isTTY()); -}); +// Note tests for Deno.setRaw is in integration tests. testPerm({ read: true }, function isatty(): void { // CI not under TTY, so cannot test stdin/stdout/stderr. From b9cbbe0270e1a1960d0b7c6bb0238169cf063b0d Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 24 Feb 2020 12:22:12 -0500 Subject: [PATCH 14/16] fix --- cli/js/dispatch.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index c0e28428d4cb49..da882002e64fe4 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -80,6 +80,7 @@ export let OP_SIGNAL_BIND: number; export let OP_SIGNAL_UNBIND: number; export let OP_SIGNAL_POLL: number; export let OP_LOADAVG: number; +export let OP_ISATTY: number; export let OP_SET_RAW: number; export let OP_OS_RELEASE: number; From dde70398c7fba47e9465fc6e78c988d6113cb03d Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Mon, 24 Feb 2020 12:59:21 -0800 Subject: [PATCH 15/16] Err -> errors --- cli/js/os_test.ts | 2 +- cli/js/tty_test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/js/os_test.ts b/cli/js/os_test.ts index 44712849b6a668..cdf72fdd7ebdc5 100644 --- a/cli/js/os_test.ts +++ b/cli/js/os_test.ts @@ -322,7 +322,7 @@ testPerm({ env: false }, function releasePerm(): void { Deno.osRelease(); } catch (err) { caughtError = true; - assert(err instanceof Deno.Err.PermissionDenied); + assert(err instanceof Deno.errors.PermissionDenied); assertEquals(err.name, "PermissionDenied"); } assert(caughtError); diff --git a/cli/js/tty_test.ts b/cli/js/tty_test.ts index 2116a29289da37..f58784a7cd8e07 100644 --- a/cli/js/tty_test.ts +++ b/cli/js/tty_test.ts @@ -16,7 +16,7 @@ test(function isattyError(): void { Deno.isatty(0x7fffffff); } catch (e) { caught = true; - assert(e instanceof Deno.Err.BadResource); + assert(e instanceof Deno.errors.BadResource); } assert(caught); }); From 49cc79bd4eb7a1250c9160e6e668346ad8eb3118 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) Kassimo Qian" Date: Tue, 25 Feb 2020 16:02:29 -0800 Subject: [PATCH 16/16] Fix with new master... again --- cli/js/tty.ts | 5 ++--- cli/ops/os.rs | 21 +++++++++------------ cli/ops/tty.rs | 4 ++-- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/cli/js/tty.ts b/cli/js/tty.ts index 8cddcd7e001d83..2ad44d025e38ac 100644 --- a/cli/js/tty.ts +++ b/cli/js/tty.ts @@ -1,14 +1,13 @@ import { sendSync } from "./dispatch_json.ts"; -import * as dispatch from "./dispatch.ts"; /** Check if a given resource is TTY. */ export function isatty(rid: number): boolean { - return sendSync(dispatch.OP_ISATTY, { rid }); + return sendSync("op_isatty", { rid }); } /** Set TTY to be under raw mode or not. */ export function setRaw(rid: number, mode: boolean): void { - sendSync(dispatch.OP_SET_RAW, { + sendSync("op_set_raw", { rid, mode }); diff --git a/cli/ops/os.rs b/cli/ops/os.rs index b55917c16a896f..2df9470ddbd2e6 100644 --- a/cli/ops/os.rs +++ b/cli/ops/os.rs @@ -10,18 +10,15 @@ use sys_info; use url::Url; pub fn init(i: &mut Isolate, s: &State) { - i.register_op("exit", s.core_op(json_op(s.stateful_op(op_exit)))); - i.register_op("env", s.core_op(json_op(s.stateful_op(op_env)))); - i.register_op("exec_path", s.core_op(json_op(s.stateful_op(op_exec_path)))); - i.register_op("set_env", s.core_op(json_op(s.stateful_op(op_set_env)))); - i.register_op("get_env", s.core_op(json_op(s.stateful_op(op_get_env)))); - i.register_op("get_dir", s.core_op(json_op(s.stateful_op(op_get_dir)))); - i.register_op("hostname", s.core_op(json_op(s.stateful_op(op_hostname)))); - i.register_op("loadavg", s.core_op(json_op(s.stateful_op(op_loadavg)))); - i.register_op( - "os_release", - s.core_op(json_op(s.stateful_op(op_os_release))), - ); + i.register_op("op_exit", s.stateful_json_op(op_exit)); + i.register_op("op_env", s.stateful_json_op(op_env)); + i.register_op("op_exec_path", s.stateful_json_op(op_exec_path)); + i.register_op("op_set_env", s.stateful_json_op(op_set_env)); + i.register_op("op_get_env", s.stateful_json_op(op_get_env)); + i.register_op("op_get_dir", s.stateful_json_op(op_get_dir)); + i.register_op("op_hostname", s.stateful_json_op(op_hostname)); + i.register_op("op_loadavg", s.stateful_json_op(op_loadavg)); + i.register_op("op_os_release", s.stateful_json_op(op_os_release)); } #[derive(Deserialize)] diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index 677fd7dea03573..e83d2edfdc0e11 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -35,8 +35,8 @@ fn get_windows_handle( } pub fn init(i: &mut Isolate, s: &State) { - i.register_op("set_raw", s.core_op(json_op(s.stateful_op(op_set_raw)))); - i.register_op("isatty", s.core_op(json_op(s.stateful_op(op_isatty)))); + i.register_op("op_set_raw", s.core_op(json_op(s.stateful_op(op_set_raw)))); + i.register_op("op_isatty", s.core_op(json_op(s.stateful_op(op_isatty)))); } #[cfg(windows)]