diff --git a/cli/dts/lib.deno.window.d.ts b/cli/dts/lib.deno.window.d.ts index 03341636f3..541eee370e 100644 --- a/cli/dts/lib.deno.window.d.ts +++ b/cli/dts/lib.deno.window.d.ts @@ -15,6 +15,9 @@ declare class Window extends EventTarget { onunload: ((this: Window, ev: Event) => any) | null; close: () => void; readonly closed: boolean; + alert: (message?: string) => void; + confirm: (message?: string) => boolean; + prompt: (message?: string, defaultValue?: string) => string | null; Deno: typeof Deno; } @@ -23,4 +26,30 @@ declare var self: Window & typeof globalThis; declare var onload: ((this: Window, ev: Event) => any) | null; declare var onunload: ((this: Window, ev: Event) => any) | null; +/** + * Shows the given message and waits for the enter key pressed. + * If the stdin is not interactive, it does nothing. + * @param message + */ +declare function alert(message?: string): void; + +/** + * Shows the given message and waits for the answer. Returns the user's answer as boolean. + * Only `y` and `Y` are considered as true. + * If the stdin is not interactive, it returns false. + * @param message + */ +declare function confirm(message?: string): boolean; + +/** + * Shows the given message and waits for the user's input. Returns the user's input as string. + * If the default value is given and the user inputs the empty string, then it returns the given + * default value. + * If the default value is not given and the user inputs the empty string, it returns null. + * If the stdin is not interactive, it returns null. + * @param message + * @param defaultValue + */ +declare function prompt(message?: string, defaultValue?: string): string | null; + /* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/cli/rt/41_prompt.js b/cli/rt/41_prompt.js new file mode 100644 index 0000000000..5a588def15 --- /dev/null +++ b/cli/rt/41_prompt.js @@ -0,0 +1,66 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +((window) => { + const { stdin, stdout } = window.__bootstrap.files; + const { isatty } = window.__bootstrap.tty; + const LF = "\n".charCodeAt(0); + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + + function alert(message = "Alert") { + if (!isatty(stdin.rid)) { + return; + } + + stdout.writeSync(encoder.encode(`${message} [Enter] `)); + + readLineFromStdinSync(); + } + + function confirm(message = "Confirm") { + if (!isatty(stdin.rid)) { + return false; + } + + stdout.writeSync(encoder.encode(`${message} [y/N] `)); + + const answer = readLineFromStdinSync(); + + return answer === "Y" || answer === "y"; + } + + function prompt(message = "Prompt", defaultValue) { + defaultValue ??= null; + + if (!isatty(stdin.rid)) { + return null; + } + + stdout.writeSync(encoder.encode(`${message} `)); + + if (defaultValue) { + stdout.writeSync(encoder.encode(`[${defaultValue}] `)); + } + + return readLineFromStdinSync() || defaultValue; + } + + function readLineFromStdinSync() { + const c = new Uint8Array(1); + const buf = []; + + while (true) { + const n = stdin.readSync(c); + if (n === 0 || c[0] === LF) { + break; + } + buf.push(c[0]); + } + return decoder.decode(new Uint8Array(buf)); + } + + window.__bootstrap.prompt = { + alert, + confirm, + prompt, + }; +})(this); diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index d8c34bcb35..e332647267 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -27,6 +27,7 @@ delete Object.prototype.__proto__; const fileReader = window.__bootstrap.fileReader; const webSocket = window.__bootstrap.webSocket; const fetch = window.__bootstrap.fetch; + const prompt = window.__bootstrap.prompt; const denoNs = window.__bootstrap.denoNs; const denoNsUnstable = window.__bootstrap.denoNsUnstable; const errors = window.__bootstrap.errors.errors; @@ -285,6 +286,9 @@ delete Object.prototype.__proto__; onunload: util.writable(null), close: util.writable(windowClose), closed: util.getterOnly(() => windowIsClosing), + alert: util.writable(prompt.alert), + confirm: util.writable(prompt.confirm), + prompt: util.writable(prompt.prompt), }; const workerRuntimeGlobalProperties = { diff --git a/cli/tests/066_prompt.ts b/cli/tests/066_prompt.ts new file mode 100644 index 0000000000..1c4a11f989 --- /dev/null +++ b/cli/tests/066_prompt.ts @@ -0,0 +1,17 @@ +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}`); +alert("Hi"); +alert(); +console.log("The end of test"); diff --git a/cli/tests/066_prompt.ts.out b/cli/tests/066_prompt.ts.out new file mode 100644 index 0000000000..88d73f34fd --- /dev/null +++ b/cli/tests/066_prompt.ts.out @@ -0,0 +1,8 @@ +[WILDCARD]What is your name? [Jane Doe] Your name is John Doe. +What is your name? [Jane Doe] Your name is Jane Doe. +Prompt Your input is foo. +Question 0 [y/N] Your answer is true +Question 1 [y/N] Your answer is false +Question 2 [y/N] Your answer is false +Confirm [y/N] Your answer is false +Hi [Enter] Alert [Enter] The end of test diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 1a5e48adaf..90dc5a4a89 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1917,6 +1917,17 @@ itest!(_065_import_map_info { output: "065_import_map_info.out", }); +#[cfg(unix)] +#[test] +fn _066_prompt() { + let args = "run --unstable 066_prompt.ts"; + let output = "066_prompt.ts.out"; + // These are answers to prompt, confirm, and alert calls. + let input = b"John Doe\n\nfoo\nY\nN\nyes\n\n\n\n"; + + util::test_pty(args, output, input); +} + itest!(js_import_detect { args: "run --quiet --reload js_import_detect.ts", output: "js_import_detect.ts.out",