summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin (Kun) "Kassimo" Qian <kevinkassimo@gmail.com>2020-02-25 22:01:24 -0800
committerGitHub <noreply@github.com>2020-02-26 01:01:24 -0500
commit5946808f66aab1983ade3db2541734bb43626a72 (patch)
treeadb526497a9efc29d1b5744ae52449f08f453ef0
parente53064c4f22efeb8a4eda2712e15c77d2699a686 (diff)
tty: Deno.setRaw(rid, mode) to turn on/off raw mode (#3958)
-rw-r--r--Cargo.lock22
-rw-r--r--cli/Cargo.toml3
-rw-r--r--cli/js/deno.ts2
-rw-r--r--cli/js/lib.deno.ns.d.ts25
-rw-r--r--cli/js/os.ts7
-rw-r--r--cli/js/os_test.ts4
-rw-r--r--cli/js/tty.ts14
-rw-r--r--cli/js/tty_test.ts22
-rw-r--r--cli/js/unit_tests.ts1
-rw-r--r--cli/ops/files.rs11
-rw-r--r--cli/ops/io.rs28
-rw-r--r--cli/ops/mod.rs1
-rw-r--r--cli/ops/os.rs14
-rw-r--r--cli/ops/process.rs2
-rw-r--r--cli/ops/tty.rs246
-rw-r--r--cli/tests/integration_tests.rs53
-rw-r--r--cli/tests/raw_mode.ts18
-rw-r--r--cli/worker.rs1
-rw-r--r--core/resources.rs4
19 files changed, 429 insertions, 49 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 29f8e334c..b3d5f4814 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -454,6 +454,7 @@ dependencies = [
"nix",
"notify",
"os_pipe",
+ "pty",
"rand 0.7.3",
"regex",
"remove_dir_all",
@@ -620,6 +621,17 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1509,6 +1521,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 52bc4f2c3..f09f8b6e4 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -72,3 +72,6 @@ nix = "0.14.1"
[dev-dependencies]
os_pipe = "0.9.1"
+
+[target.'cfg(unix)'.dev-dependencies]
+pty = "0.2"
diff --git a/cli/js/deno.ts b/cli/js/deno.ts
index e2052f729..c563d5112 100644
--- a/cli/js/deno.ts
+++ b/cli/js/deno.ts
@@ -90,7 +90,6 @@ export {
dir,
env,
exit,
- isTTY,
execPath,
hostname,
loadavg,
@@ -124,6 +123,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 { 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 b22d89ebe..f94d28407 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<void>;
- /** 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());
@@ -492,6 +482,7 @@ declare namespace Deno {
seekSync(offset: number, whence: SeekMode): void;
close(): void;
}
+
/** An instance of `File` for stdin. */
export const stdin: File;
/** An instance of `File` for stdout. */
@@ -555,6 +546,20 @@ declare namespace Deno {
/** Read-write. Behaves like `x` and allows to read from file. */
| "x+";
+ // @url js/tty.d.ts
+
+ /** UNSTABLE: newly added API
+ *
+ * Check if a given resource is TTY
+ */
+ export function isatty(rid: number): boolean;
+
+ /** 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
/** 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 2a68ff8d3..309f5e1ff 100644
--- a/cli/js/os.ts
+++ b/cli/js/os.ts
@@ -3,13 +3,6 @@ import { sendSync } from "./dispatch_json.ts";
import { errors } 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("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 6e771fe98..cdf72fdd7 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
new file mode 100644
index 000000000..2ad44d025
--- /dev/null
+++ b/cli/js/tty.ts
@@ -0,0 +1,14 @@
+import { sendSync } from "./dispatch_json.ts";
+
+/** Check if a given resource is TTY. */
+export function isatty(rid: number): boolean {
+ return sendSync("op_isatty", { rid });
+}
+
+/** Set TTY to be under raw mode or not. */
+export function setRaw(rid: number, mode: boolean): void {
+ sendSync("op_set_raw", {
+ rid,
+ mode
+ });
+}
diff --git a/cli/js/tty_test.ts b/cli/js/tty_test.ts
new file mode 100644
index 000000000..f58784a7c
--- /dev/null
+++ b/cli/js/tty_test.ts
@@ -0,0 +1,22 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { test, testPerm, assert } from "./test_util.ts";
+
+// 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.
+ 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.errors.BadResource);
+ }
+ assert(caught);
+});
diff --git a/cli/js/unit_tests.ts b/cli/js/unit_tests.ts
index 1c8237466..2495c938b 100644
--- a/cli/js/unit_tests.ts
+++ b/cli/js/unit_tests.ts
@@ -54,6 +54,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 4bf8b1688..916cbdc69 100644
--- a/cli/ops/files.rs
+++ b/cli/ops/files.rs
@@ -1,6 +1,6 @@
// 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::fs as deno_fs;
use crate::op_error::OpError;
use crate::state::State;
@@ -125,9 +125,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))
};
@@ -197,7 +198,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 8edb1f748..ad3949a03 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<termios::Termios>,
+}
+
+#[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<ServerTlsStream<TcpStream>>),
ClientTlsStream(Box<ClientTlsStream<TcpStream>>),
@@ -101,8 +115,8 @@ impl DenoAsyncRead for StreamResource {
) -> Poll<Result<usize, OpError>> {
use StreamResource::*;
let mut f: Pin<Box<dyn AsyncRead>> = 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),
@@ -203,7 +217,7 @@ impl DenoAsyncWrite for StreamResource {
) -> Poll<Result<usize, OpError>> {
use StreamResource::*;
let mut f: Pin<Box<dyn AsyncWrite>> = 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),
@@ -220,7 +234,7 @@ impl DenoAsyncWrite for StreamResource {
fn poll_flush(&mut self, cx: &mut Context) -> Poll<Result<(), OpError>> {
use StreamResource::*;
let mut f: Pin<Box<dyn AsyncWrite>> = 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/mod.rs b/cli/ops/mod.rs
index 7746143db..32d4e3b96 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/os.rs b/cli/ops/os.rs
index c0479c656..2df9470dd 100644
--- a/cli/ops/os.rs
+++ b/cli/ops/os.rs
@@ -2,7 +2,6 @@
use super::dispatch_json::{Deserialize, JsonOp, Value};
use crate::op_error::OpError;
use crate::state::State;
-use atty;
use deno_core::*;
use std::collections::HashMap;
use std::env;
@@ -12,7 +11,6 @@ use url::Url;
pub fn init(i: &mut Isolate, s: &State) {
i.register_op("op_exit", s.stateful_json_op(op_exit));
- i.register_op("op_is_tty", s.stateful_json_op(op_is_tty));
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));
@@ -151,18 +149,6 @@ fn op_exit(
std::process::exit(args.code)
}
-fn op_is_tty(
- _s: &State,
- _args: Value,
- _zero_copy: Option<ZeroCopyBuf>,
-) -> Result<JsonOp, OpError> {
- 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/process.rs b/cli/ops/process.rs
index 9da87bd39..82ac25bbe 100644
--- a/cli/ops/process.rs
+++ b/cli/ops/process.rs
@@ -33,7 +33,7 @@ fn clone_file(rid: u32, state: &State) -> Result<std::fs::File, OpError> {
.get_mut::<StreamResource>(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
new file mode 100644
index 000000000..e83d2edfd
--- /dev/null
+++ b/cli/ops/tty.rs
@@ -0,0 +1,246 @@
+use super::dispatch_json::JsonOp;
+use super::io::StreamResource;
+use crate::op_error::OpError;
+use crate::ops::json_op;
+use crate::state::State;
+use atty;
+use deno_core::*;
+#[cfg(unix)]
+use nix::sys::termios;
+use serde_derive::Deserialize;
+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;
+#[cfg(windows)]
+fn get_windows_handle(
+ f: &std::fs::File,
+) -> Result<std::os::windows::io::RawHandle, OpError> {
+ use std::os::windows::io::AsRawHandle;
+ use winapi::um::handleapi;
+
+ let handle = f.as_raw_handle();
+ if handle == handleapi::INVALID_HANDLE_VALUE {
+ return Err(OpError::from(std::io::Error::last_os_error()));
+ } else if handle.is_null() {
+ return Err(OpError::other("null handle".to_owned()));
+ }
+ Ok(handle)
+}
+
+pub fn init(i: &mut Isolate, s: &State) {
+ 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)]
+macro_rules! wincheck {
+ ($funcall:expr) => {{
+ let rc = unsafe { $funcall };
+ if rc == 0 {
+ Err(OpError::from(std::io::Error::last_os_error()))?;
+ }
+ rc
+ }};
+}
+
+#[derive(Deserialize)]
+struct SetRawArgs {
+ rid: u32,
+ mode: bool,
+}
+
+pub fn op_set_raw(
+ state_: &State,
+ args: Value,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<JsonOp, OpError> {
+ let args: SetRawArgs = serde_json::from_value(args)?;
+ let rid = args.rid;
+ 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
+ // 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 state = state_.borrow_mut();
+ let resource = state.resource_table.get::<StreamResource>(rid);
+ if resource.is_none() {
+ return Err(OpError::bad_resource());
+ }
+
+ // 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(OpError::other("Not supported".to_owned()));
+ }
+ };
+
+ if handle == handleapi::INVALID_HANDLE_VALUE {
+ return Err(OpError::from(std::io::Error::last_os_error()));
+ } else if handle.is_null() {
+ return Err(OpError::other("null handle".to_owned()));
+ }
+ let mut original_mode: DWORD = 0;
+ 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));
+
+ Ok(JsonOp::Sync(json!({})))
+ }
+ #[cfg(unix)]
+ {
+ use std::os::unix::io::AsRawFd;
+
+ let mut state = state_.borrow_mut();
+ let resource = state.resource_table.get_mut::<StreamResource>(rid);
+ if resource.is_none() {
+ return Err(OpError::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(OpError::other("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
+ | 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)?;
+ Ok(JsonOp::Sync(json!({})))
+ } else {
+ // 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(OpError::other("Not supported".to_owned()));
+ }
+ };
+
+ if let Some(mode) = maybe_tty_mode.take() {
+ termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?;
+ }
+
+ Ok(JsonOp::Sync(json!({})))
+ }
+ }
+}
+
+#[derive(Deserialize)]
+struct IsattyArgs {
+ rid: u32,
+}
+
+pub fn op_isatty(
+ state_: &State,
+ args: Value,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<JsonOp, OpError> {
+ 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(OpError::bad_resource());
+ }
+
+ let resource = state.resource_table.get::<StreamResource>(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/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 29c4ac1d0..7fd531c29 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -1,8 +1,61 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
#[macro_use]
extern crate lazy_static;
+#[cfg(unix)]
+extern crate nix;
+#[cfg(unix)]
+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]), "S");
+ master.write_all(b"a").unwrap();
+ nread = master.read(&mut obytes).unwrap();
+ 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]), "B");
+ master.write_all(b"c").unwrap();
+ nread = master.read(&mut obytes).unwrap();
+ assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "C");
+ } 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 000000000..125601b2b
--- /dev/null
+++ b/cli/tests/raw_mode.ts
@@ -0,0 +1,18 @@
+Deno.setRaw(0, true);
+Deno.setRaw(0, true); // Can be called multiple times
+
+Deno.stdout.writeSync(new TextEncoder().encode("S"));
+
+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 {
+ 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); // Can be safely called multiple times
diff --git a/cli/worker.rs b/cli/worker.rs
index a4a35dfaa..b619b2a67 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);
}
diff --git a/core/resources.rs b/core/resources.rs
index 51b66c4bc..a5563bed3 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<T: Resource>(&self, rid: ResourceId) -> Option<&T> {
if let Some((_name, resource)) = self.map.get(&rid) {
return resource.downcast_ref::<T>();