From 1bcc35b84a78fb052b8092b7ed57c2ce763f5d4b Mon Sep 17 00:00:00 2001 From: Sebastien Filion Date: Fri, 10 Jul 2020 10:07:12 -0400 Subject: [PATCH] feat(unstable): add Deno.consoleSize (#6520) --- cli/js/deno_unstable.ts | 2 +- cli/js/lib.deno.unstable.d.ts | 15 +++++++ cli/js/ops/tty.ts | 4 ++ cli/ops/tty.rs | 80 ++++++++++++++++++++++++++++++++++- cli/tests/unit/tty_test.ts | 17 +++++++- 5 files changed, 115 insertions(+), 3 deletions(-) diff --git a/cli/js/deno_unstable.ts b/cli/js/deno_unstable.ts index 7d75c1c6ab..f92d767b39 100644 --- a/cli/js/deno_unstable.ts +++ b/cli/js/deno_unstable.ts @@ -12,7 +12,7 @@ export { openPlugin } from "./ops/plugins.ts"; export { transpileOnly, compile, bundle } from "./compiler_api.ts"; export { applySourceMap, formatDiagnostics } from "./ops/errors.ts"; export { signal, signals, Signal, SignalStream } from "./signals.ts"; -export { setRaw } from "./ops/tty.ts"; +export { setRaw, consoleSize } from "./ops/tty.ts"; export { utimeSync, utime } from "./ops/fs/utime.ts"; export { ftruncateSync, ftruncate } from "./ops/fs/truncate.ts"; export { shutdown, ShutdownMode } from "./net.ts"; diff --git a/cli/js/lib.deno.unstable.d.ts b/cli/js/lib.deno.unstable.d.ts index f237cd4bec..a6547ebcaf 100644 --- a/cli/js/lib.deno.unstable.d.ts +++ b/cli/js/lib.deno.unstable.d.ts @@ -43,6 +43,21 @@ declare namespace Deno { * Requires `allow-read` and `allow-write` permissions. */ export function link(oldpath: string, newpath: string): Promise; + /** **UNSTABLE**: New API, yet to be vetted. + * + * Gets the size of the console as columns/rows. + * + * ```ts + * const { columns, rows } = await Deno.consoleSize(Deno.stdout.rid); + * ``` + */ + export function consoleSize( + rid: number + ): { + columns: number; + rows: number; + }; + export type SymlinkOptions = { type: "file" | "dir"; }; diff --git a/cli/js/ops/tty.ts b/cli/js/ops/tty.ts index 8899ca5b80..f9da7bd0d8 100644 --- a/cli/js/ops/tty.ts +++ b/cli/js/ops/tty.ts @@ -2,6 +2,10 @@ import { sendSync } from "./dispatch_json.ts"; +export function consoleSize(rid: number): [number, number] { + return sendSync("op_console_size", { rid }); +} + export function isatty(rid: number): boolean { return sendSync("op_isatty", { rid }); } diff --git a/cli/ops/tty.rs b/cli/ops/tty.rs index bf94ec17f3..d86100232f 100644 --- a/cli/ops/tty.rs +++ b/cli/ops/tty.rs @@ -8,7 +8,7 @@ use deno_core::CoreIsolateState; use deno_core::ZeroCopyBuf; #[cfg(unix)] use nix::sys::termios; -use serde_derive::Deserialize; +use serde_derive::{Deserialize, Serialize}; use serde_json::Value; #[cfg(windows)] @@ -38,6 +38,7 @@ fn get_windows_handle( pub fn init(i: &mut CoreIsolate, s: &State) { i.register_op("op_set_raw", s.stateful_json_op2(op_set_raw)); i.register_op("op_isatty", s.stateful_json_op2(op_isatty)); + i.register_op("op_console_size", s.stateful_json_op2(op_console_size)); } #[derive(Deserialize)] @@ -250,3 +251,80 @@ pub fn op_isatty( })?; Ok(JsonOp::Sync(json!(isatty))) } + +#[derive(Deserialize)] +struct ConsoleSizeArgs { + rid: u32, +} + +#[derive(Serialize)] +struct ConsoleSize { + columns: u32, + rows: u32, +} + +pub fn op_console_size( + isolate_state: &mut CoreIsolateState, + state: &State, + args: Value, + _zero_copy: &mut [ZeroCopyBuf], +) -> Result { + state.check_unstable("Deno.consoleSize"); + let args: ConsoleSizeArgs = serde_json::from_value(args)?; + let rid = args.rid; + + let mut resource_table = isolate_state.resource_table.borrow_mut(); + let size = + std_file_resource(&mut resource_table, rid as u32, move |r| match r { + Ok(std_file) => { + #[cfg(windows)] + { + use std::os::windows::io::AsRawHandle; + let handle = std_file.as_raw_handle(); + + unsafe { + let mut bufinfo: winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO = + std::mem::zeroed(); + + if winapi::um::wincon::GetConsoleScreenBufferInfo( + handle, + &mut bufinfo, + ) == 0 + { + // TODO (caspervonb) use GetLastError + return Err(OpError::other( + winapi::um::errhandlingapi::GetLastError().to_string(), + )); + } + + Ok(ConsoleSize { + columns: bufinfo.dwSize.X as u32, + rows: bufinfo.dwSize.Y as u32, + }) + } + } + + #[cfg(unix)] + { + use std::os::unix::io::AsRawFd; + + let fd = std_file.as_raw_fd(); + unsafe { + let mut size: libc::winsize = std::mem::zeroed(); + if libc::ioctl(fd, libc::TIOCGWINSZ, &mut size as *mut _) != 0 { + return Err(OpError::from(std::io::Error::last_os_error())); + } + + // TODO (caspervonb) return a tuple instead + Ok(ConsoleSize { + columns: size.ws_col as u32, + rows: size.ws_row as u32, + }) + } + } + } + Err(_) => Err(OpError::bad_resource_id()), + })?; + + Ok(JsonOp::Sync(json!(size))) +} diff --git a/cli/tests/unit/tty_test.ts b/cli/tests/unit/tty_test.ts index 116b0dfe95..7e98737915 100644 --- a/cli/tests/unit/tty_test.ts +++ b/cli/tests/unit/tty_test.ts @@ -1,8 +1,23 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { unitTest, assert } from "./test_util.ts"; +import { unitTest, assert, assertThrows } from "./test_util.ts"; // Note tests for Deno.setRaw is in integration tests. +unitTest({ perms: { read: true } }, function consoleSizeFile(): void { + const file = Deno.openSync("cli/tests/hello.txt"); + assertThrows(() => { + Deno.consoleSize(file.rid); + }, Error); + file.close(); +}); + +unitTest(function consoleSizeError(): void { + assertThrows(() => { + // Absurdly large rid. + Deno.consoleSize(0x7fffffff); + }, Deno.errors.BadResource); +}); + unitTest({ perms: { read: true } }, function isatty(): void { // CI not under TTY, so cannot test stdin/stdout/stderr. const f = Deno.openSync("cli/tests/hello.txt");