From 4eedac3604dad9f366d28868077eb02eddc22661 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 24 Jan 2024 10:01:56 +1100 Subject: [PATCH] feat: `Deno.{stdin,stdout,stderr}.isTerminal()`, deprecate `Deno.isatty()` (#22011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change: 1. Implements `Deno.stdin.isTerminal()`, `Deno.stdout.isTerminal()` and `Deno.stderr.isTerminal()`. 2. Deprecates `Deno.isatty()` for removal in Deno v2, in favour of the above instance methods. 3. Replaces use of `Deno.isatty()` with the above instance methods. Related #21995 --------- Co-authored-by: Bartek IwaƄczuk Co-authored-by: Divy Srivastava --- cli/tests/unit/tty_test.ts | 2 +- cli/tests/unit_node/process_test.ts | 8 +++--- cli/tests/unit_node/tty_test.ts | 10 +++---- cli/tsc/dts/lib.deno.ns.d.ts | 37 +++++++++++++++++++++++++ ext/io/12_io.js | 13 +++++++++ ext/node/polyfills/_process/streams.mjs | 15 ++++------ ext/node/polyfills/assertion_error.ts | 4 +-- ext/node/polyfills/tty.js | 12 ++++++-- runtime/js/40_tty.js | 13 ++++++--- runtime/js/41_prompt.js | 7 ++--- runtime/ops/tty.rs | 4 +-- 11 files changed, 92 insertions(+), 33 deletions(-) diff --git a/cli/tests/unit/tty_test.ts b/cli/tests/unit/tty_test.ts index f6dc33b6a3..8ca9a5d5ba 100644 --- a/cli/tests/unit/tty_test.ts +++ b/cli/tests/unit/tty_test.ts @@ -4,7 +4,7 @@ import { assert } from "./test_util.ts"; // Note tests for Deno.stdin.setRaw is in integration tests. Deno.test(function consoleSize() { - if (!Deno.isatty(Deno.stdout.rid)) { + if (!Deno.stdout.isTerminal()) { return; } const result = Deno.consoleSize(); diff --git a/cli/tests/unit_node/process_test.ts b/cli/tests/unit_node/process_test.ts index 4cea31ed6e..23bf738649 100644 --- a/cli/tests/unit_node/process_test.ts +++ b/cli/tests/unit_node/process_test.ts @@ -356,14 +356,14 @@ Deno.test({ name: "process.stdin", fn() { assertEquals(process.stdin.fd, Deno.stdin.rid); - assertEquals(process.stdin.isTTY, Deno.isatty(Deno.stdin.rid)); + assertEquals(process.stdin.isTTY, Deno.stdin.isTerminal()); }, }); Deno.test({ name: "process.stdin readable with a TTY", // TODO(PolarETech): Run this test even in non tty environment - ignore: !Deno.isatty(Deno.stdin.rid), + ignore: !Deno.stdin.isTerminal(), // stdin resource is present before the test starts. sanitizeResources: false, async fn() { @@ -535,7 +535,7 @@ Deno.test({ name: "process.stdout", fn() { assertEquals(process.stdout.fd, Deno.stdout.rid); - const isTTY = Deno.isatty(Deno.stdout.rid); + const isTTY = Deno.stdout.isTerminal(); assertEquals(process.stdout.isTTY, isTTY); const consoleSize = isTTY ? Deno.consoleSize() : undefined; assertEquals(process.stdout.columns, consoleSize?.columns); @@ -563,7 +563,7 @@ Deno.test({ name: "process.stderr", fn() { assertEquals(process.stderr.fd, Deno.stderr.rid); - const isTTY = Deno.isatty(Deno.stderr.rid); + const isTTY = Deno.stderr.isTerminal(); assertEquals(process.stderr.isTTY, isTTY); const consoleSize = isTTY ? Deno.consoleSize() : undefined; assertEquals(process.stderr.columns, consoleSize?.columns); diff --git a/cli/tests/unit_node/tty_test.ts b/cli/tests/unit_node/tty_test.ts index c393da5b33..8e2f66f9ef 100644 --- a/cli/tests/unit_node/tty_test.ts +++ b/cli/tests/unit_node/tty_test.ts @@ -6,9 +6,9 @@ import { isatty } from "node:tty"; import process from "node:process"; Deno.test("[node/tty isatty] returns true when fd is a tty, false otherwise", () => { - assert(Deno.isatty(Deno.stdin.rid) === isatty(Deno.stdin.rid)); - assert(Deno.isatty(Deno.stdout.rid) === isatty(Deno.stdout.rid)); - assert(Deno.isatty(Deno.stderr.rid) === isatty(Deno.stderr.rid)); + assert(Deno.stdin.isTerminal() === isatty(Deno.stdin.rid)); + assert(Deno.stdout.isTerminal() === isatty(Deno.stdout.rid)); + assert(Deno.stderr.isTerminal() === isatty(Deno.stderr.rid)); const file = Deno.openSync("README.md"); assert(!isatty(file.rid)); @@ -32,6 +32,6 @@ Deno.test("[node/tty isatty] returns false for irrelevant values", () => { }); Deno.test("[node/tty WriteStream.isTTY] returns true when fd is a tty", () => { - assert(Deno.isatty(Deno.stdin.rid) === process.stdin.isTTY); - assert(Deno.isatty(Deno.stdout.rid) === process.stdout.isTTY); + assert(Deno.stdin.isTerminal() === process.stdin.isTTY); + assert(Deno.stdout.isTerminal() === process.stdout.isTTY); }); diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 483c5c3d01..bc3248d028 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -2612,6 +2612,17 @@ declare namespace Deno { * @category I/O */ setRaw(mode: boolean, options?: SetRawOptions): void; + /** + * Checks if `stdin` is a TTY (terminal). + * + * ```ts + * // This example is system and context specific + * Deno.stdin.isTerminal(); // true + * ``` + * + * @category I/O + */ + isTerminal(): boolean; }; /** A reference to `stdout` which can be used to write directly to `stdout`. * It implements the Deno specific {@linkcode Writer}, {@linkcode WriterSync}, @@ -2629,6 +2640,17 @@ declare namespace Deno { readonly rid: number; /** A writable stream interface to `stdout`. */ readonly writable: WritableStream; + /** + * Checks if `stdout` is a TTY (terminal). + * + * ```ts + * // This example is system and context specific + * Deno.stdout.isTerminal(); // true + * ``` + * + * @category I/O + */ + isTerminal(): boolean; }; /** A reference to `stderr` which can be used to write directly to `stderr`. * It implements the Deno specific {@linkcode Writer}, {@linkcode WriterSync}, @@ -2646,6 +2668,17 @@ declare namespace Deno { readonly rid: number; /** A writable stream interface to `stderr`. */ readonly writable: WritableStream; + /** + * Checks if `stderr` is a TTY (terminal). + * + * ```ts + * // This example is system and context specific + * Deno.stderr.isTerminal(); // true + * ``` + * + * @category I/O + */ + isTerminal(): boolean; }; /** @@ -2728,6 +2761,10 @@ declare namespace Deno { * Deno.close(ttyRid); * ``` * + * @deprecated Use `Deno.stdin.isTerminal()`, `Deno.stdout.isTerminal()` or + * `Deno.stderr.isTerminal()` instead. + * {@linkcode Deno.isatty} will be removed in v2.0.0. + * * @category I/O */ export function isatty(rid: number): boolean; diff --git a/ext/io/12_io.js b/ext/io/12_io.js index d9b91a9470..9f4a3766b7 100644 --- a/ext/io/12_io.js +++ b/ext/io/12_io.js @@ -7,6 +7,7 @@ import { core, primordials } from "ext:core/mod.js"; const { op_stdin_set_raw, + op_is_terminal, } = core.ensureFastOps(true); const { Uint8Array, @@ -197,6 +198,10 @@ class Stdin { const cbreak = !!(options.cbreak ?? false); op_stdin_set_raw(mode, cbreak); } + + isTerminal() { + return op_is_terminal(this.rid); + } } class Stdout { @@ -227,6 +232,10 @@ class Stdout { } return this.#writable; } + + isTerminal() { + return op_is_terminal(this.rid); + } } class Stderr { @@ -257,6 +266,10 @@ class Stderr { } return this.#writable; } + + isTerminal() { + return op_is_terminal(this.rid); + } } const stdin = new Stdin(); diff --git a/ext/node/polyfills/_process/streams.mjs b/ext/node/polyfills/_process/streams.mjs index 14d58fcab5..166d099c83 100644 --- a/ext/node/polyfills/_process/streams.mjs +++ b/ext/node/polyfills/_process/streams.mjs @@ -46,30 +46,27 @@ export function createWritableStdioStream(writer, name) { enumerable: true, configurable: true, get: () => - Deno.isatty?.(writer?.rid) ? Deno.consoleSize?.().columns : undefined, + writer?.isTerminal() ? Deno.consoleSize?.().columns : undefined, }, rows: { enumerable: true, configurable: true, - get: () => - Deno.isatty?.(writer?.rid) ? Deno.consoleSize?.().rows : undefined, + get: () => writer?.isTerminal() ? Deno.consoleSize?.().rows : undefined, }, isTTY: { enumerable: true, configurable: true, - get: () => Deno.isatty?.(writer?.rid), + get: () => writer?.isTerminal(), }, getWindowSize: { enumerable: true, configurable: true, value: () => - Deno.isatty?.(writer?.rid) - ? Object.values(Deno.consoleSize?.()) - : undefined, + writer?.isTerminal() ? Object.values(Deno.consoleSize?.()) : undefined, }, }); - if (Deno.isatty?.(writer?.rid)) { + if (writer?.isTerminal()) { // These belong on tty.WriteStream(), but the TTY streams currently have // following problems: // 1. Using them here introduces a circular dependency. @@ -180,7 +177,7 @@ export const initStdin = () => { enumerable: true, configurable: true, get() { - return Deno.isatty?.(io.stdin.rid); + return io.stdin.isTerminal(); }, }); stdin._isRawMode = false; diff --git a/ext/node/polyfills/assertion_error.ts b/ext/node/polyfills/assertion_error.ts index bec1f6ba5c..ff1168dc30 100644 --- a/ext/node/polyfills/assertion_error.ts +++ b/ext/node/polyfills/assertion_error.ts @@ -163,7 +163,7 @@ export function createErrDiff( // If the stderr is a tty and the input length is lower than the current // columns per line, add a mismatch indicator below the output. If it is // not a tty, use a default value of 80 characters. - const maxLength = Deno.isatty(io.stderr.rid) ? getConsoleWidth() : 80; + const maxLength = io.stderr.isTerminal() ? getConsoleWidth() : 80; if (inputLength < maxLength) { while (actualRaw[i] === expectedRaw[i]) { i++; @@ -406,7 +406,7 @@ export class AssertionError extends Error { if (message != null) { super(String(message)); } else { - if (Deno.isatty(io.stderr.rid)) { + if (io.stderr.isTerminal()) { // Reset on each call to make sure we handle dynamically set environment // variables correct. if (Deno.noColor) { diff --git a/ext/node/polyfills/tty.js b/ext/node/polyfills/tty.js index 86fd34d7c1..eae1db2d92 100644 --- a/ext/node/polyfills/tty.js +++ b/ext/node/polyfills/tty.js @@ -1,9 +1,12 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { primordials } from "ext:core/mod.js"; +import { core, primordials } from "ext:core/mod.js"; const { Error, } = primordials; +const { + op_is_terminal, +} = core.ensureFastOps(true); import { ERR_INVALID_FD } from "ext:deno_node/internal/errors.ts"; import { LibuvStreamWrap } from "ext:deno_node/internal_binding/stream_wrap.ts"; @@ -17,7 +20,12 @@ function isatty(fd) { return false; } try { - return Deno.isatty(fd); + /** + * TODO: Treat `fd` as real file descriptors. Currently, `rid` 0, 1, 2 + * correspond to `fd` 0, 1, 2 (stdin, stdout, stderr). This may change in + * the future. + */ + return op_is_terminal(fd); } catch (_) { return false; } diff --git a/runtime/js/40_tty.js b/runtime/js/40_tty.js index e94fc0374d..97e35e0c35 100644 --- a/runtime/js/40_tty.js +++ b/runtime/js/40_tty.js @@ -1,9 +1,9 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { core, primordials } from "ext:core/mod.js"; +import { core, internals, primordials } from "ext:core/mod.js"; const { op_console_size, - op_isatty, -} = core.ensureFastOps(); + op_is_terminal, +} = core.ensureFastOps(true); const { Uint32Array, } = primordials; @@ -16,7 +16,12 @@ function consoleSize() { } function isatty(rid) { - return op_isatty(rid); + internals.warnOnDeprecatedApi( + "Deno.isatty()", + new Error().stack, + "Use `stdStream.isTerminal()` instead.", + ); + return op_is_terminal(rid); } export { consoleSize, isatty }; diff --git a/runtime/js/41_prompt.js b/runtime/js/41_prompt.js index fce1ac9ac9..d0e0655380 100644 --- a/runtime/js/41_prompt.js +++ b/runtime/js/41_prompt.js @@ -9,14 +9,13 @@ const { Uint8Array, } = primordials; -import { isatty } from "ext:runtime/40_tty.js"; import { stdin } from "ext:deno_io/12_io.js"; const LF = StringPrototypeCharCodeAt("\n", 0); const CR = StringPrototypeCharCodeAt("\r", 0); function alert(message = "Alert") { - if (!isatty(stdin.rid)) { + if (!stdin.isTerminal()) { return; } @@ -26,7 +25,7 @@ function alert(message = "Alert") { } function confirm(message = "Confirm") { - if (!isatty(stdin.rid)) { + if (!stdin.isTerminal()) { return false; } @@ -40,7 +39,7 @@ function confirm(message = "Confirm") { function prompt(message = "Prompt", defaultValue) { defaultValue ??= ""; - if (!isatty(stdin.rid)) { + if (!stdin.isTerminal()) { return null; } diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 6cc129883b..40aa28494f 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -52,7 +52,7 @@ deno_core::extension!( deno_tty, ops = [ op_stdin_set_raw, - op_isatty, + op_is_terminal, op_console_size, op_read_line_prompt ], @@ -210,7 +210,7 @@ fn op_stdin_set_raw( } #[op2(fast)] -fn op_isatty(state: &mut OpState, rid: u32) -> Result { +fn op_is_terminal(state: &mut OpState, rid: u32) -> Result { let handle = state.resource_table.get_handle(rid)?; Ok(handle.is_terminal()) }