diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2024-01-02 09:36:05 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-02 09:36:05 +0530 |
commit | b21462355a61d69bedf15ae51304719f6014b8df (patch) | |
tree | f58b2e00551d9d2981882520d0f64f7e8cf73722 | |
parent | 96b581bdd2bedb31aa51c0a992686f27ed1a1f10 (diff) |
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
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | cli/Cargo.toml | 2 | ||||
-rw-r--r-- | cli/tests/integration/run_tests.rs | 179 | ||||
-rw-r--r-- | cli/tests/testdata/run/066_prompt.ts | 21 | ||||
-rw-r--r-- | runtime/Cargo.toml | 1 | ||||
-rw-r--r-- | runtime/js/41_prompt.js | 107 | ||||
-rw-r--r-- | runtime/ops/tty.rs | 36 |
8 files changed, 100 insertions, 249 deletions
diff --git a/Cargo.lock b/Cargo.lock index 6b491ac09..f8ef62fc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1635,7 +1635,6 @@ dependencies = [ "once_cell", "regex", "ring", - "rustyline", "serde", "signal-hook-registry", "termcolor", @@ -4990,7 +4989,6 @@ dependencies = [ "cfg-if", "clipboard-win", "fd-lock", - "home", "libc", "log", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 49c8a6567..b08064b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,7 +138,6 @@ rustls = "0.21.8" rustls-pemfile = "1.0.0" rustls-tokio-stream = "=0.2.16" rustls-webpki = "0.101.4" -rustyline = "=13.0.0" webpki-roots = "0.25.2" scopeguard = "1.2.0" saffron = "=0.1.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ad9974070..dc9bd1228 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -118,7 +118,7 @@ quick-junit = "^0.3.5" rand = { workspace = true, features = ["small_rng"] } regex.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" serde.workspace = true serde_repr.workspace = true diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 16425b14e..43bc212c6 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -2807,155 +2807,40 @@ mod permissions { fn _066_prompt() { TestContext::default() .new_command() - .args_vec(["repl"]) + .args_vec(["run", "--quiet", "--unstable", "run/066_prompt.ts"]) .with_pty(|mut console| { - // alert with no message displays default "Alert" - // alert displays "[Press any key to continue]" - // alert can be closed with Enter key - console.write_line_raw("alert()"); - console.expect("Alert [Press any key to continue]"); - console.write_raw("\r"); // Enter - 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("What is your name? [Jane Doe] "); + console.write_line_raw("John Doe"); + console.expect("Your name is John Doe."); + console.expect("What is your name? [Jane Doe] "); + console.write_line_raw(""); + console.expect("Your name is Jane Doe."); console.expect("Prompt "); - console.write_line_raw("abc"); - console.expect("\"abc\""); - - // prompt can display custom text - // prompt with no default value returns empty string when immediately pressing Enter - console.write_line_raw("prompt('foo')"); - console.expect("foo "); - console.write_raw("\r"); // Enter - console.expect("\"\""); - - // prompt with non-string default value converts it to string - console.write_line_raw("prompt('foo', 1)"); - console.expect("foo 1"); - console.write_raw("\r"); // Enter - console.expect("\"1\""); - - // prompt with non-string default value that can't be converted throws an error - console.write_line_raw("prompt('foo', Symbol())"); - console.expect( - "Uncaught TypeError: Cannot convert a Symbol value to a string", - ); - - // prompt with empty-string default value returns empty string when immediately pressing Enter - console.write_line_raw("prompt('foo', '')"); - console.expect("foo "); - 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"); - } + console.write_line_raw("foo"); + console.expect("Your input is foo."); + console.expect("Question 0 [y/N] "); + console.write_line_raw("Y"); + console.expect("Your answer is true"); + console.expect("Question 1 [y/N] "); + console.write_line_raw("N"); + console.expect("Your answer is false"); + console.expect("Question 2 [y/N] "); + console.write_line_raw("yes"); + console.expect("Your answer is false"); + console.expect("Confirm [y/N] "); + console.write_line(""); + console.expect("Your answer is false"); + console.expect("What is Windows EOL? "); + console.write_line("windows"); + console.expect("Your answer is \"windows\""); + console.expect("Hi [Enter] "); + console.write_line(""); + console.expect("Alert [Enter] "); + console.write_line(""); + console.expect("The end of test"); + console.expect("What is EOF? "); + console.write_line(""); + console.expect("Your answer is null"); }); } diff --git a/cli/tests/testdata/run/066_prompt.ts b/cli/tests/testdata/run/066_prompt.ts new file mode 100644 index 000000000..e3daa7ac0 --- /dev/null +++ b/cli/tests/testdata/run/066_prompt.ts @@ -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)}`); diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 456d2f76f..043899ecf 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -112,7 +112,6 @@ notify.workspace = true once_cell.workspace = true regex.workspace = true ring.workspace = true -rustyline = { workspace = true, features = ["custom-bindings"] } serde.workspace = true signal-hook-registry = "1.4.0" termcolor = "1.1.3" diff --git a/runtime/js/41_prompt.js b/runtime/js/41_prompt.js index ca2b82c21..787b9f9f7 100644 --- a/runtime/js/41_prompt.js +++ b/runtime/js/41_prompt.js @@ -1,95 +1,78 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { core, primordials } from "ext:core/mod.js"; -const ops = core.ops; import { isatty } from "ext:runtime/40_tty.js"; import { stdin } from "ext:deno_io/12_io.js"; -import { getNoColor } from "ext:deno_console/01_console.js"; -const { Uint8Array, StringFromCodePoint } = primordials; - -const ESC = "\x1b"; -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`; -} +const { ArrayPrototypePush, StringPrototypeCharCodeAt, Uint8Array } = + primordials; +const LF = StringPrototypeCharCodeAt("\n", 0); +const CR = StringPrototypeCharCodeAt("\r", 0); function alert(message = "Alert") { if (!isatty(stdin.rid)) { return; } - core.print( - `${yellow(bold(`${message}`))} [${italic("Press any key to continue")}] `, - ); - - try { - stdin.setRaw(true); - stdin.readSync(new Uint8Array(1024)); - } finally { - stdin.setRaw(false); - } + core.print(`${message} [Enter] `, false); - core.print("\n"); + readLineFromStdinSync(); } -function prompt(message = "Prompt", defaultValue = "") { +function confirm(message = "Confirm") { if (!isatty(stdin.rid)) { - return null; + return false; } - return ops.op_read_line_prompt( - `${message} `, - `${defaultValue}`, - ); + core.print(`${message} [y/N] `, false); + + const answer = readLineFromStdinSync(); + + return answer === "Y" || answer === "y"; } -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 prompt(message = "Prompt", defaultValue) { + defaultValue ??= null; -function confirm(message = "Confirm") { if (!isatty(stdin.rid)) { - return false; + return null; } - core.print(`${yellow(bold(`${message}`))} [${italic("Y/n")}] `); + if (defaultValue) { + message += ` [${defaultValue}]`; + } - let val = false; - try { - stdin.setRaw(true); + message += " "; - while (true) { - const b = new Uint8Array(1024); - stdin.readSync(b); - let byteString = ""; + // output in one shot to make the tests more reliable + core.print(message, false); - let i = 0; - while (b[i]) byteString += StringFromCodePoint(b[i++]); + return readLineFromStdinSync() || defaultValue; +} - if (inputMap.has(byteString)) { - val = inputMap.get(byteString); +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; } } - } finally { - stdin.setRaw(false); + if (c[0] === LF) { + break; + } + ArrayPrototypePush(buf, c[0]); } - - core.print(`${val ? "y" : "n"}\n`); - return val; + return core.decode(new Uint8Array(buf)); } export { alert, confirm, prompt }; diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 04192b90e..3227ee562 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -5,13 +5,6 @@ use std::io::Error; use deno_core::error::AnyError; use deno_core::op2; 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)] use deno_core::ResourceId; @@ -50,12 +43,7 @@ use winapi::um::wincon; deno_core::extension!( deno_tty, - ops = [ - op_stdin_set_raw, - op_isatty, - op_console_size, - op_read_line_prompt, - ], + ops = [op_stdin_set_raw, op_isatty, op_console_size], state = |state| { #[cfg(unix)] 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()), - } -} |