1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

feat(unstable): add cbreak option to setRaw (#8383)

This commit is contained in:
Marcus Hultman 2020-11-30 10:08:03 -06:00 committed by GitHub
parent 7a4d0fc22b
commit c7276e15e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 15 deletions

View file

@ -815,6 +815,10 @@ declare namespace Deno {
windowChange: () => SignalStream; windowChange: () => SignalStream;
}; };
export type SetRawOptions = {
cbreak: boolean;
};
/** **UNSTABLE**: new API, yet to be vetted /** **UNSTABLE**: new API, yet to be vetted
* *
* Set TTY to be under raw mode or not. In raw mode, characters are read and * Set TTY to be under raw mode or not. In raw mode, characters are read and
@ -823,11 +827,19 @@ declare namespace Deno {
* Reading from a TTY device in raw mode is faster than reading from a TTY * Reading from a TTY device in raw mode is faster than reading from a TTY
* device in canonical mode. * device in canonical mode.
* *
* The `cbreak` option can be used to indicate that characters that correspond
* to a signal should still be generated. When disabling raw mode, this option
* is ignored. This functionality currently only works on Linux and Mac OS.
*
* ```ts * ```ts
* Deno.setRaw(myTTY.rid, true); * Deno.setRaw(myTTY.rid, true, { cbreak: true });
* ``` * ```
*/ */
export function setRaw(rid: number, mode: boolean): void; export function setRaw(
rid: number,
mode: boolean,
options?: SetRawOptions,
): void;
/** **UNSTABLE**: needs investigation into high precision time. /** **UNSTABLE**: needs investigation into high precision time.
* *

View file

@ -5,6 +5,7 @@ use super::io::StreamResource;
use super::io::StreamResourceHolder; use super::io::StreamResourceHolder;
use deno_core::error::bad_resource_id; use deno_core::error::bad_resource_id;
use deno_core::error::last_os_error; use deno_core::error::last_os_error;
use deno_core::error::not_supported;
use deno_core::error::resource_unavailable; use deno_core::error::resource_unavailable;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json;
@ -15,8 +16,6 @@ use deno_core::ZeroCopyBuf;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
#[cfg(unix)]
use deno_core::error::not_supported;
#[cfg(unix)] #[cfg(unix)]
use nix::sys::termios; use nix::sys::termios;
@ -53,10 +52,17 @@ pub fn init(rt: &mut deno_core::JsRuntime) {
super::reg_json_sync(rt, "op_console_size", op_console_size); super::reg_json_sync(rt, "op_console_size", op_console_size);
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SetRawOptions {
cbreak: bool,
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct SetRawArgs { struct SetRawArgs {
rid: u32, rid: u32,
mode: bool, mode: bool,
options: SetRawOptions,
} }
fn op_set_raw( fn op_set_raw(
@ -69,6 +75,7 @@ fn op_set_raw(
let args: SetRawArgs = serde_json::from_value(args)?; let args: SetRawArgs = serde_json::from_value(args)?;
let rid = args.rid; let rid = args.rid;
let is_raw = args.mode; let is_raw = args.mode;
let cbreak = args.options.cbreak;
// From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs // 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/kkawakam/rustyline/blob/master/src/tty/unix.rs
@ -86,6 +93,9 @@ fn op_set_raw(
if resource_holder.is_none() { if resource_holder.is_none() {
return Err(bad_resource_id()); return Err(bad_resource_id());
} }
if cbreak {
return Err(not_supported());
}
let resource_holder = resource_holder.unwrap(); let resource_holder = resource_holder.unwrap();
// For now, only stdin. // For now, only stdin.
@ -164,15 +174,13 @@ fn op_set_raw(
} }
}; };
if maybe_tty_mode.is_some() { if maybe_tty_mode.is_none() {
// Already raw. Skip. // Save original mode.
return Ok(json!({})); let original_mode = termios::tcgetattr(raw_fd)?;
maybe_tty_mode.replace(original_mode);
} }
let original_mode = termios::tcgetattr(raw_fd)?; let mut raw = maybe_tty_mode.clone().unwrap();
let mut raw = original_mode.clone();
// Save original mode.
maybe_tty_mode.replace(original_mode);
raw.input_flags &= !(termios::InputFlags::BRKINT raw.input_flags &= !(termios::InputFlags::BRKINT
| termios::InputFlags::ICRNL | termios::InputFlags::ICRNL
@ -184,8 +192,10 @@ fn op_set_raw(
raw.local_flags &= !(termios::LocalFlags::ECHO raw.local_flags &= !(termios::LocalFlags::ECHO
| termios::LocalFlags::ICANON | termios::LocalFlags::ICANON
| termios::LocalFlags::IEXTEN | termios::LocalFlags::IEXTEN);
| termios::LocalFlags::ISIG); if !cbreak {
raw.local_flags &= !(termios::LocalFlags::ISIG);
}
raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1;
raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0;
termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?;

View file

@ -11,8 +11,13 @@
return core.jsonOpSync("op_isatty", { rid }); return core.jsonOpSync("op_isatty", { rid });
} }
function setRaw(rid, mode) { const DEFAULT_SET_RAW_OPTIONS = {
core.jsonOpSync("op_set_raw", { rid, mode }); cbreak: false,
};
function setRaw(rid, mode, options = {}) {
const rOptions = { ...DEFAULT_SET_RAW_OPTIONS, ...options };
core.jsonOpSync("op_set_raw", { rid, mode, options: rOptions });
} }
window.__bootstrap.tty = { window.__bootstrap.tty = {

View file

@ -218,6 +218,39 @@ pub fn test_raw_tty() {
} }
} }
#[cfg(unix)]
#[test]
pub fn test_raw_tty_cbreak() {
use std::io::{Read, Write};
use util::pty::fork::*;
let deno_exe = util::deno_exe_path();
let root_path = util::root_path();
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(&[3]).unwrap(); // send SIGINT
master.flush().unwrap();
nread = master.read(&mut obytes).unwrap();
assert_eq!(String::from_utf8_lossy(&obytes[0..nread]), "A");
fork.wait().unwrap();
} else {
// Keep echo enabled such that 'C^' would be printed in non-raw mode.
std::env::set_current_dir(root_path).unwrap();
let err = exec::Command::new(deno_exe)
.arg("run")
.arg("--unstable")
.arg("--quiet")
.arg("--no-check")
.arg("cli/tests/raw_mode_cbreak.ts")
.exec();
println!("err {}", err);
unreachable!()
}
}
#[test] #[test]
fn test_pattern_match() { fn test_pattern_match() {
// foo, bar, baz, qux, quux, quuz, corge, grault, garply, waldo, fred, plugh, xyzzy // foo, bar, baz, qux, quux, quuz, corge, grault, garply, waldo, fred, plugh, xyzzy

View file

@ -0,0 +1,15 @@
Deno.setRaw(0, true);
Deno.setRaw(0, true, { cbreak: true }); // Can be called multiple times
const signal = Deno.signals.interrupt();
Deno.stdout.writeSync(new TextEncoder().encode("S"));
await signal;
Deno.stdout.writeSync(new TextEncoder().encode("A"));
signal.dispose();
Deno.setRaw(0, false); // restores old mode.
Deno.setRaw(0, false); // Can be safely called multiple times