1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

Revert "fix(runtime): Make native modal keyboard interaction consistent with browsers" (#21739)

Reverts denoland/deno#18453

Fixes https://github.com/denoland/deno/issues/21602
https://github.com/denoland/deno/issues/21631
https://github.com/denoland/deno/issues/21641

Reasons for revert:
- alert() and confirm() swallowed ^C with raw mode.
- prompt() did not re-raise the interrupt signal from rustyline. 
- Default 'Y' on confirm() is a bad default and breaking change.

cc @lionel-rowe
This commit is contained in:
Divy Srivastava 2024-01-02 09:36:05 +05:30 committed by Bartek Iwańczuk
parent 217b9ecb20
commit 97604ef522
No known key found for this signature in database
GPG key ID: 0C6BCDDC3B3AD750
8 changed files with 104 additions and 253 deletions

2
Cargo.lock generated
View file

@ -1635,7 +1635,6 @@ dependencies = [
"once_cell", "once_cell",
"regex", "regex",
"ring", "ring",
"rustyline",
"serde", "serde",
"signal-hook-registry", "signal-hook-registry",
"termcolor", "termcolor",
@ -4990,7 +4989,6 @@ dependencies = [
"cfg-if", "cfg-if",
"clipboard-win", "clipboard-win",
"fd-lock", "fd-lock",
"home",
"libc", "libc",
"log", "log",
"memchr", "memchr",

View file

@ -138,7 +138,6 @@ rustls = "0.21.8"
rustls-pemfile = "1.0.0" rustls-pemfile = "1.0.0"
rustls-tokio-stream = "=0.2.16" rustls-tokio-stream = "=0.2.16"
rustls-webpki = "0.101.4" rustls-webpki = "0.101.4"
rustyline = "=13.0.0"
webpki-roots = "0.25.2" webpki-roots = "0.25.2"
scopeguard = "1.2.0" scopeguard = "1.2.0"
saffron = "=0.1.0" saffron = "=0.1.0"

View file

@ -118,7 +118,7 @@ quick-junit = "^0.3.5"
rand = { workspace = true, features = ["small_rng"] } rand = { workspace = true, features = ["small_rng"] }
regex.workspace = true regex.workspace = true
ring.workspace = true ring.workspace = true
rustyline.workspace = true rustyline = { version = "=13.0.0", default-features = false, features = ["custom-bindings", "with-file-history"] }
rustyline-derive = "=0.7.0" rustyline-derive = "=0.7.0"
serde.workspace = true serde.workspace = true
serde_repr.workspace = true serde_repr.workspace = true

View file

@ -2807,155 +2807,40 @@ mod permissions {
fn _066_prompt() { fn _066_prompt() {
TestContext::default() TestContext::default()
.new_command() .new_command()
.args_vec(["repl"]) .args_vec(["run", "--quiet", "--unstable", "run/066_prompt.ts"])
.with_pty(|mut console| { .with_pty(|mut console| {
// alert with no message displays default "Alert" console.expect("What is your name? [Jane Doe] ");
// alert displays "[Press any key to continue]" console.write_line_raw("John Doe");
// alert can be closed with Enter key console.expect("Your name is John Doe.");
console.write_line_raw("alert()"); console.expect("What is your name? [Jane Doe] ");
console.expect("Alert [Press any key to continue]"); console.write_line_raw("");
console.write_raw("\r"); // Enter console.expect("Your name is Jane Doe.");
console.expect("undefined");
// alert can be closed with Escape key
console.write_line_raw("alert()");
console.expect("Alert [Press any key to continue]");
console.write_raw("\x1b"); // Escape
console.expect("undefined");
// alert can display custom text
// alert can be closed with arbitrary keyboard key (x)
if !cfg!(windows) {
// it seems to work on windows, just not in the tests
console.write_line_raw("alert('foo')");
console.expect("foo [Press any key to continue]");
console.write_raw("x");
console.expect("undefined");
}
// confirm with no message displays default "Confirm"
// confirm returns true by immediately pressing Enter
console.write_line_raw("confirm()");
console.expect("Confirm [Y/n]");
console.write_raw("\r"); // Enter
console.expect("true");
// tese seem to work on windows, just not in the tests
if !cfg!(windows) {
// confirm returns false by pressing Escape
console.write_line_raw("confirm()");
console.expect("Confirm [Y/n]");
console.write_raw("\x1b"); // Escape
console.expect("false");
// confirm can display custom text
// confirm returns true by pressing y
console.write_line_raw("confirm('continue?')");
console.expect("continue? [Y/n]");
console.write_raw("y");
console.expect("true");
// confirm returns false by pressing n
console.write_line_raw("confirm('continue?')");
console.expect("continue? [Y/n]");
console.write_raw("n");
console.expect("false");
// confirm can display custom text
// confirm returns true by pressing Y
console.write_line_raw("confirm('continue?')");
console.expect("continue? [Y/n]");
console.write_raw("Y");
console.expect("true");
// confirm returns false by pressing N
console.write_line_raw("confirm('continue?')");
console.expect("continue? [Y/n]");
console.write_raw("N");
console.expect("false");
}
// prompt with no message displays default "Prompt"
// prompt returns user-inserted text
console.write_line_raw("prompt()");
console.expect("Prompt "); console.expect("Prompt ");
console.write_line_raw("abc"); console.write_line_raw("foo");
console.expect("\"abc\""); console.expect("Your input is foo.");
console.expect("Question 0 [y/N] ");
// prompt can display custom text console.write_line_raw("Y");
// prompt with no default value returns empty string when immediately pressing Enter console.expect("Your answer is true");
console.write_line_raw("prompt('foo')"); console.expect("Question 1 [y/N] ");
console.expect("foo "); console.write_line_raw("N");
console.write_raw("\r"); // Enter console.expect("Your answer is false");
console.expect("\"\""); console.expect("Question 2 [y/N] ");
console.write_line_raw("yes");
// prompt with non-string default value converts it to string console.expect("Your answer is false");
console.write_line_raw("prompt('foo', 1)"); console.expect("Confirm [y/N] ");
console.expect("foo 1"); console.write_line("");
console.write_raw("\r"); // Enter console.expect("Your answer is false");
console.expect("\"1\""); console.expect("What is Windows EOL? ");
console.write_line("windows");
// prompt with non-string default value that can't be converted throws an error console.expect("Your answer is \"windows\"");
console.write_line_raw("prompt('foo', Symbol())"); console.expect("Hi [Enter] ");
console.expect( console.write_line("");
"Uncaught TypeError: Cannot convert a Symbol value to a string", console.expect("Alert [Enter] ");
); console.write_line("");
console.expect("The end of test");
// prompt with empty-string default value returns empty string when immediately pressing Enter console.expect("What is EOF? ");
console.write_line_raw("prompt('foo', '')"); console.write_line("");
console.expect("foo "); console.expect("Your answer is null");
console.write_raw("\r"); // Enter
console.expect("\"\"");
// prompt with contentful default value returns default value when immediately pressing Enter
console.write_line_raw("prompt('foo', 'bar')");
console.expect("foo bar");
console.write_raw("\r"); // Enter
console.expect("\"bar\"");
// prompt with contentful default value allows editing of default value
console.write_line_raw("prompt('foo', 'bar')");
console.expect("foo bar");
console.write_raw("\x1b[D"); // Left arrow
console.write_raw("\x1b[D"); // Left arrow
console.write_raw("\x7f"); // Backspace
console.write_raw("c");
console.expect("foo car");
console.write_raw("\r"); // Enter
console.expect("\"car\"");
// prompt returns null by pressing Escape
console.write_line_raw("prompt()");
console.expect("Prompt ");
console.write_raw("\x1b"); // Escape
console.expect("null");
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
// confirm returns false by pressing Ctrl+C
console.write_line_raw("confirm()");
console.expect("Confirm [Y/n] ");
console.write_raw("\x03"); // Ctrl+C
console.expect("false");
// confirm returns false by pressing Ctrl+D
console.write_line_raw("confirm()");
console.expect("Confirm [Y/n] ");
console.write_raw("\x04"); // Ctrl+D
console.expect("false");
// prompt returns null by pressing Ctrl+C
console.write_line_raw("prompt()");
console.expect("Prompt ");
console.write_raw("\x03"); // Ctrl+C
console.expect("null");
// prompt returns null by pressing Ctrl+D
console.write_line_raw("prompt()");
console.expect("Prompt ");
console.write_raw("\x04"); // Ctrl+D
console.expect("null");
}
}); });
} }

21
cli/tests/testdata/run/066_prompt.ts vendored Normal file
View file

@ -0,0 +1,21 @@
const name0 = prompt("What is your name?", "Jane Doe"); // Answer John Doe
console.log(`Your name is ${name0}.`);
const name1 = prompt("What is your name?", "Jane Doe"); // Answer with default
console.log(`Your name is ${name1}.`);
const input = prompt(); // Answer foo
console.log(`Your input is ${input}.`);
const answer0 = confirm("Question 0"); // Answer y
console.log(`Your answer is ${answer0}`);
const answer1 = confirm("Question 1"); // Answer n
console.log(`Your answer is ${answer1}`);
const answer2 = confirm("Question 2"); // Answer with yes (returns false)
console.log(`Your answer is ${answer2}`);
const answer3 = confirm(); // Answer with default
console.log(`Your answer is ${answer3}`);
const windows = prompt("What is Windows EOL?");
console.log(`Your answer is ${JSON.stringify(windows)}`);
alert("Hi");
alert();
console.log("The end of test");
const eof = prompt("What is EOF?");
console.log(`Your answer is ${JSON.stringify(eof)}`);

View file

@ -112,7 +112,6 @@ notify.workspace = true
once_cell.workspace = true once_cell.workspace = true
regex.workspace = true regex.workspace = true
ring.workspace = true ring.workspace = true
rustyline = { workspace = true, features = ["custom-bindings"] }
serde.workspace = true serde.workspace = true
signal-hook-registry = "1.4.0" signal-hook-registry = "1.4.0"
termcolor = "1.1.3" termcolor = "1.1.3"

View file

@ -1,95 +1,78 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { core, primordials } from "ext:core/mod.js"; import { core, primordials } from "ext:core/mod.js";
const ops = core.ops;
import { isatty } from "ext:runtime/40_tty.js"; import { isatty } from "ext:runtime/40_tty.js";
import { stdin } from "ext:deno_io/12_io.js"; import { stdin } from "ext:deno_io/12_io.js";
import { getNoColor } from "ext:deno_console/01_console.js"; const { ArrayPrototypePush, StringPrototypeCharCodeAt, Uint8Array } =
const { Uint8Array, StringFromCodePoint } = primordials; primordials;
const LF = StringPrototypeCharCodeAt("\n", 0);
const ESC = "\x1b"; const CR = StringPrototypeCharCodeAt("\r", 0);
const CTRL_C = "\x03";
const CTRL_D = "\x04";
const bold = ansi(1, 22);
const italic = ansi(3, 23);
const yellow = ansi(33, 0);
function ansi(start, end) {
return (str) => getNoColor() ? str : `\x1b[${start}m${str}\x1b[${end}m`;
}
function alert(message = "Alert") { function alert(message = "Alert") {
if (!isatty(stdin.rid)) { if (!isatty(stdin.rid)) {
return; return;
} }
core.print( core.print(`${message} [Enter] `, false);
`${yellow(bold(`${message}`))} [${italic("Press any key to continue")}] `,
);
try { readLineFromStdinSync();
stdin.setRaw(true);
stdin.readSync(new Uint8Array(1024));
} finally {
stdin.setRaw(false);
}
core.print("\n");
} }
function prompt(message = "Prompt", defaultValue = "") {
if (!isatty(stdin.rid)) {
return null;
}
return ops.op_read_line_prompt(
`${message} `,
`${defaultValue}`,
);
}
const inputMap = new primordials.Map([
["Y", true],
["y", true],
["\r", true],
["\n", true],
["\r\n", true],
["N", false],
["n", false],
[ESC, false],
[CTRL_C, false],
[CTRL_D, false],
]);
function confirm(message = "Confirm") { function confirm(message = "Confirm") {
if (!isatty(stdin.rid)) { if (!isatty(stdin.rid)) {
return false; return false;
} }
core.print(`${yellow(bold(`${message}`))} [${italic("Y/n")}] `); core.print(`${message} [y/N] `, false);
let val = false; const answer = readLineFromStdinSync();
try {
stdin.setRaw(true);
while (true) { return answer === "Y" || answer === "y";
const b = new Uint8Array(1024); }
stdin.readSync(b);
let byteString = "";
let i = 0; function prompt(message = "Prompt", defaultValue) {
while (b[i]) byteString += StringFromCodePoint(b[i++]); defaultValue ??= null;
if (inputMap.has(byteString)) { if (!isatty(stdin.rid)) {
val = inputMap.get(byteString); return null;
}
if (defaultValue) {
message += ` [${defaultValue}]`;
}
message += " ";
// output in one shot to make the tests more reliable
core.print(message, false);
return readLineFromStdinSync() || defaultValue;
}
function readLineFromStdinSync() {
const c = new Uint8Array(1);
const buf = [];
while (true) {
const n = stdin.readSync(c);
if (n === null || n === 0) {
break;
}
if (c[0] === CR) {
const n = stdin.readSync(c);
if (c[0] === LF) {
break;
}
ArrayPrototypePush(buf, CR);
if (n === null || n === 0) {
break; break;
} }
} }
} finally { if (c[0] === LF) {
stdin.setRaw(false); break;
}
ArrayPrototypePush(buf, c[0]);
} }
return core.decode(new Uint8Array(buf));
core.print(`${val ? "y" : "n"}\n`);
return val;
} }
export { alert, confirm, prompt }; export { alert, confirm, prompt };

View file

@ -5,13 +5,6 @@ use std::io::Error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::op2; use deno_core::op2;
use deno_core::OpState; use deno_core::OpState;
use rustyline::config::Configurer;
use rustyline::error::ReadlineError;
use rustyline::Cmd;
use rustyline::Editor;
use rustyline::KeyCode;
use rustyline::KeyEvent;
use rustyline::Modifiers;
#[cfg(unix)] #[cfg(unix)]
use deno_core::ResourceId; use deno_core::ResourceId;
@ -50,12 +43,7 @@ use winapi::um::wincon;
deno_core::extension!( deno_core::extension!(
deno_tty, deno_tty,
ops = [ ops = [op_stdin_set_raw, op_isatty, op_console_size],
op_stdin_set_raw,
op_isatty,
op_console_size,
op_read_line_prompt,
],
state = |state| { state = |state| {
#[cfg(unix)] #[cfg(unix)]
state.put(TtyModeStore::default()); state.put(TtyModeStore::default());
@ -332,25 +320,3 @@ mod tests {
); );
} }
} }
#[op2]
#[string]
pub fn op_read_line_prompt(
#[string] prompt_text: String,
#[string] default_value: String,
) -> Result<Option<String>, AnyError> {
let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new()
.expect("Failed to create editor.");
editor.set_keyseq_timeout(1);
editor
.bind_sequence(KeyEvent(KeyCode::Esc, Modifiers::empty()), Cmd::Interrupt);
let read_result =
editor.readline_with_initial(&prompt_text, (&default_value, ""));
match read_result {
Ok(line) => Ok(Some(line)),
Err(ReadlineError::Interrupted | ReadlineError::Eof) => Ok(None),
Err(err) => Err(err.into()),
}
}