summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/Cargo.toml1
-rw-r--r--runtime/js/41_prompt.js107
-rw-r--r--runtime/ops/tty.rs36
3 files changed, 98 insertions, 46 deletions
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 39d907a61..0ae26d811 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -109,6 +109,7 @@ 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 e665aae07..d73f9d26b 100644
--- a/runtime/js/41_prompt.js
+++ b/runtime/js/41_prompt.js
@@ -1,78 +1,95 @@
// Copyright 2018-2023 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";
-const { ArrayPrototypePush, StringPrototypeCharCodeAt, Uint8Array } =
- primordials;
-const LF = StringPrototypeCharCodeAt("\n", 0);
-const CR = StringPrototypeCharCodeAt("\r", 0);
+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`;
+}
function alert(message = "Alert") {
if (!isatty(stdin.rid)) {
return;
}
- core.print(`${message} [Enter] `, false);
+ core.print(
+ `${yellow(bold(`${message}`))} [${italic("Press any key to continue")}] `,
+ );
+
+ try {
+ stdin.setRaw(true);
+ stdin.readSync(new Uint8Array(1024));
+ } finally {
+ stdin.setRaw(false);
+ }
- readLineFromStdinSync();
+ core.print("\n");
}
-function confirm(message = "Confirm") {
+function prompt(message = "Prompt", defaultValue = "") {
if (!isatty(stdin.rid)) {
- return false;
+ return null;
}
- core.print(`${message} [y/N] `, false);
-
- const answer = readLineFromStdinSync();
-
- return answer === "Y" || answer === "y";
+ return ops.op_read_line_prompt(
+ `${message} `,
+ `${defaultValue}`,
+ );
}
-function prompt(message = "Prompt", defaultValue) {
- defaultValue ??= null;
+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") {
if (!isatty(stdin.rid)) {
- return null;
+ return false;
}
- if (defaultValue) {
- message += ` [${defaultValue}]`;
- }
+ core.print(`${yellow(bold(`${message}`))} [${italic("Y/n")}] `);
- message += " ";
+ let val = false;
+ try {
+ stdin.setRaw(true);
- // output in one shot to make the tests more reliable
- core.print(message, false);
+ while (true) {
+ const b = new Uint8Array(1024);
+ stdin.readSync(b);
+ let byteString = "";
- return readLineFromStdinSync() || defaultValue;
-}
+ let i = 0;
+ while (b[i]) byteString += StringFromCodePoint(b[i++]);
-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) {
+ if (inputMap.has(byteString)) {
+ val = inputMap.get(byteString);
break;
}
}
- if (c[0] === LF) {
- break;
- }
- ArrayPrototypePush(buf, c[0]);
+ } finally {
+ stdin.setRaw(false);
}
- return core.decode(new Uint8Array(buf));
+
+ core.print(`${val ? "y" : "n"}\n`);
+ return val;
}
export { alert, confirm, prompt };
diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs
index 477af9741..b0047eb85 100644
--- a/runtime/ops/tty.rs
+++ b/runtime/ops/tty.rs
@@ -5,6 +5,13 @@ 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;
@@ -43,7 +50,12 @@ use winapi::um::wincon;
deno_core::extension!(
deno_tty,
- ops = [op_stdin_set_raw, op_isatty, op_console_size],
+ ops = [
+ op_stdin_set_raw,
+ op_isatty,
+ op_console_size,
+ op_read_line_prompt,
+ ],
state = |state| {
#[cfg(unix)]
state.put(TtyModeStore::default());
@@ -320,3 +332,25 @@ 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()),
+ }
+}