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:
parent
217b9ecb20
commit
97604ef522
8 changed files with 104 additions and 253 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
21
cli/tests/testdata/run/066_prompt.ts
vendored
Normal 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)}`);
|
|
@ -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"
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue