diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 720f7fc948..787c1a2251 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -2666,6 +2666,31 @@ declare namespace Deno { * @category File System */ utimeSync(atime: number | Date, mtime: number | Date): void; + /** **UNSTABLE**: New API, yet to be vetted. + * + * Checks if the file resource is a TTY (terminal). + * + * ```ts + * // This example is system and context specific + * using file = await Deno.open("/dev/tty6"); + * file.isTerminal(); // true + * ``` + */ + isTerminal(): boolean; + /** **UNSTABLE**: New API, yet to be vetted. + * + * Set TTY to be under raw mode or not. In raw mode, characters are read and + * returned as is, without being processed. All special processing of + * characters by the terminal is disabled, including echoing input + * characters. Reading from a TTY device in raw mode is faster than reading + * from a TTY device in canonical mode. + * + * ```ts + * using file = await Deno.open("/dev/tty6"); + * file.setRaw(true, { cbreak: true }); + * ``` + */ + setRaw(mode: boolean, options?: SetRawOptions): void; /** **UNSTABLE**: New API, yet to be vetted. * * Acquire an advisory file-system lock for the file. diff --git a/ext/fs/30_fs.js b/ext/fs/30_fs.js index 46358c42ae..2efb0a8782 100644 --- a/ext/fs/30_fs.js +++ b/ext/fs/30_fs.js @@ -72,6 +72,8 @@ import { op_fs_utime_sync, op_fs_write_file_async, op_fs_write_file_sync, + op_is_terminal, + op_set_raw, } from "ext:core/ops"; const { ArrayPrototypeFilter, @@ -766,6 +768,15 @@ class FsFile { futimeSync(this.#rid, atime, mtime); } + isTerminal() { + return op_is_terminal(this.#rid); + } + + setRaw(mode, options = {}) { + const cbreak = !!(options.cbreak ?? false); + op_set_raw(this.#rid, mode, cbreak); + } + lockSync(exclusive = false) { op_fs_flock_sync(this.#rid, exclusive); } diff --git a/ext/io/12_io.js b/ext/io/12_io.js index 70b639d459..fbaf28de77 100644 --- a/ext/io/12_io.js +++ b/ext/io/12_io.js @@ -5,7 +5,7 @@ // Thank you! We love Go! <3 import { core, internals, primordials } from "ext:core/mod.js"; -import { op_is_terminal, op_stdin_set_raw } from "ext:core/ops"; +import { op_is_terminal, op_set_raw } from "ext:core/ops"; const { Uint8Array, ArrayPrototypePush, @@ -218,7 +218,7 @@ class Stdin { setRaw(mode, options = {}) { const cbreak = !!(options.cbreak ?? false); - op_stdin_set_raw(mode, cbreak); + op_set_raw(this.#rid, mode, cbreak); } isTerminal() { diff --git a/runtime/js/40_tty.js b/runtime/js/40_tty.js index 5b8bbc0be0..dd364f18d1 100644 --- a/runtime/js/40_tty.js +++ b/runtime/js/40_tty.js @@ -16,7 +16,7 @@ function isatty(rid) { internals.warnOnDeprecatedApi( "Deno.isatty()", new Error().stack, - "Use `Deno.stdin.isTerminal()`, `Deno.stdout.isTerminal()` or `Deno.stderr.isTerminal()` instead.", + "Use `Deno.stdin.isTerminal()`, `Deno.stdout.isTerminal()`, `Deno.stderr.isTerminal()` or `Deno.FsFile.isTerminal()` instead.", ); return op_is_terminal(rid); } diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 40aa28494f..e570754f26 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -51,7 +51,7 @@ use winapi::um::wincon; deno_core::extension!( deno_tty, ops = [ - op_stdin_set_raw, + op_set_raw, op_is_terminal, op_console_size, op_read_line_prompt @@ -83,12 +83,12 @@ fn mode_raw_input_off(original_mode: DWORD) -> DWORD { } #[op2(fast)] -fn op_stdin_set_raw( +fn op_set_raw( state: &mut OpState, + rid: u32, is_raw: bool, cbreak: bool, ) -> Result<(), AnyError> { - let rid = 0; // stdin is always rid=0 let handle_or_fd = state.resource_table.get_fd(rid)?; // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index 68b72ffed8..02fb915b5c 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -4304,6 +4304,25 @@ fn set_raw_should_not_panic_on_no_tty() { assert!(stderr.contains("BadResource")); } +#[cfg(not(windows))] +#[test] +fn fsfile_set_raw_should_not_panic_on_no_tty() { + let output = util::deno_cmd() + .arg("eval") + .arg("Deno.openSync(\"/dev/stdin\").setRaw(true)") + // stdin set to piped so it certainly does not refer to TTY + .stdin(std::process::Stdio::piped()) + // stderr is piped so we can capture output. + .stderr_piped() + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + let stderr = std::str::from_utf8(&output.stderr).unwrap().trim(); + assert!(stderr.contains("BadResource")); +} + #[test] fn timeout_clear() { // https://github.com/denoland/deno/issues/7599 diff --git a/tests/unit/files_test.ts b/tests/unit/files_test.ts index 47fa03ddd6..0034a84722 100644 --- a/tests/unit/files_test.ts +++ b/tests/unit/files_test.ts @@ -10,6 +10,8 @@ import { } from "./test_util.ts"; import { copy } from "@std/streams/copy.ts"; +// Note tests for Deno.FsFile.setRaw is in integration tests. + Deno.test(function filesStdioFileDescriptors() { assertEquals(Deno.stdin.rid, 0); assertEquals(Deno.stdout.rid, 1); @@ -899,6 +901,12 @@ Deno.test( }, ); +Deno.test({ permissions: { read: true } }, function fsFileIsTerminal() { + // CI not under TTY, so cannot test stdin/stdout/stderr. + using file = Deno.openSync("tests/testdata/assets/hello.txt"); + assert(!file.isTerminal()); +}); + Deno.test( { permissions: { read: true, run: true, hrtime: true } }, async function fsFileLockFileSync() {