Implement methods to get current console size

This commit is contained in:
Timothy Warren 2023-11-08 17:02:59 -05:00
parent 9cca55b101
commit d99656de66
7 changed files with 90 additions and 50 deletions

View File

@ -1,7 +1,7 @@
/** /**
* Wrap the runtime-specific hook into stdin * Wrap the runtime-specific hook into stdin
*/ */
import { ITerminalIO } from '../common/mod.ts'; import { ITerminalIO, ITerminalSize } from '../common/mod.ts';
export async function* inputLoop() { export async function* inputLoop() {
for await (const chunk of Bun.stdin.stream()) { for await (const chunk of Bun.stdin.stream()) {
@ -15,8 +15,20 @@ export async function write(s: string): Promise<void> {
await Bun.write(Bun.stdout, buffer); await Bun.write(Bun.stdout, buffer);
} }
export function getSize(): ITerminalSize {
// @TODO implement
// Check for tput
// If has tput, use it to get terminal size
// If not, try FFI fallback
// Otherwise, return 80x25 as a last resort
const fallback: ITerminalSize = { rows: 25, cols: 80 };
return fallback;
}
const BunTerminalIO: ITerminalIO = { const BunTerminalIO: ITerminalIO = {
inputLoop, inputLoop,
getSize,
write, write,
}; };

View File

@ -1,15 +1,13 @@
function escape(suffix: string): string { function esc(pieces: TemplateStringsArray): string {
return `\x1b[${suffix}`; return '\x1b[' + pieces[0];
}
function moveCursor(row: number, col: number): string {
return escape(`${row};${col}H`);
} }
export const Ansi = { export const Ansi = {
ClearScreen: escape('2J'), ClearScreen: esc`2J`,
ResetCursor: escape('H'), ResetCursor: esc`H`,
moveCursor, moveCursor: function moveCursor(row: number, col: number): string {
return `\x1b${row};${col}H`;
},
}; };
export default Ansi; export default Ansi;

View File

@ -12,6 +12,10 @@ class Buffer {
this.#b += s; this.#b += s;
} }
appendLine(s: string): void {
this.#b += s + '\r\n';
}
clear(): void { clear(): void {
this.#b = ''; this.#b = '';
} }
@ -49,10 +53,18 @@ export class Editor {
const { write } = await importForRuntime('terminal_io'); const { write } = await importForRuntime('terminal_io');
this.clearScreen(); this.clearScreen();
this.drawRows();
await write(this.#buffer.getBuffer()); await write(this.#buffer.getBuffer());
this.#buffer.clear(); this.#buffer.clear();
} }
private drawRows(): void {
for (let y = 0; y <= 24; y++) {
this.#buffer.appendLine('~');
}
}
private clearScreen(): void { private clearScreen(): void {
this.#buffer.append(Ansi.ClearScreen); this.#buffer.append(Ansi.ClearScreen);
this.#buffer.append(Ansi.ResetCursor); this.#buffer.append(Ansi.ResetCursor);

View File

@ -1,41 +1,3 @@
import { importForRuntime } from './runtime.ts';
import { Editor } from './editor.ts';
import { getTermios } from './termios.ts';
export * from './runtime.ts'; export * from './runtime.ts';
export * from './strings.ts'; export * from './strings.ts';
export type * from './types.ts'; export type * from './types.ts';
const decoder = new TextDecoder();
export async function main() {
const { inputLoop, onExit } = await importForRuntime('mod.ts');
// Setup raw mode, and tear down on error or normal exit
const t = await getTermios();
t.enableRawMode();
onExit(() => {
console.info('Exit handler called, disabling raw mode');
t.disableRawMode();
});
// Create the editor itself
const editor = new Editor();
// The main event loop
for await (const chunk of inputLoop()) {
const char = String(decoder.decode(chunk));
// Clear the screen for output
await editor.refreshScreen();
// Process input
const shouldLoop = editor.processKeyPress(char);
if (!shouldLoop) {
return 0;
}
}
return -1;
}

View File

@ -37,6 +37,11 @@ export interface IFFI {
getPointer(ta: any): unknown; getPointer(ta: any): unknown;
} }
export interface ITerminalSize {
rows: number;
cols: number;
}
/** /**
* Runtime-specific IO streams * Runtime-specific IO streams
*/ */
@ -46,6 +51,11 @@ export interface ITerminalIO {
*/ */
inputLoop(): AsyncGenerator<Uint8Array, void, unknown>; inputLoop(): AsyncGenerator<Uint8Array, void, unknown>;
/**
* Get the size of the terminal
*/
getSize(): ITerminalSize;
/** /**
* Pipe a string to stdout * Pipe a string to stdout
*/ */

View File

@ -1,4 +1,4 @@
import { ITerminalIO } from '../common/types.ts'; import { ITerminalIO, ITerminalSize } from '../common/types.ts';
/** /**
* Wrap the runtime-specific hook into stdin * Wrap the runtime-specific hook into stdin
@ -17,8 +17,18 @@ export async function write(s: string): Promise<void> {
stdout.releaseLock(); stdout.releaseLock();
} }
export function getSize(): ITerminalSize {
const size: { rows: number; columns: number } = Deno.consoleSize();
return {
rows: size.rows,
cols: size.columns,
};
}
const DenoTerminalIO: ITerminalIO = { const DenoTerminalIO: ITerminalIO = {
inputLoop, inputLoop,
getSize,
write, write,
}; };

View File

@ -1,7 +1,43 @@
/** /**
* The starting point for running scroll * The starting point for running scroll
*/ */
import { main } from './common/mod.ts'; import { importForRuntime } from './common/mod.ts';
import { getTermios } from './common/termios.ts';
import { Editor } from './common/editor.ts';
const decoder = new TextDecoder();
export async function main() {
const { inputLoop, onExit } = await importForRuntime('mod.ts');
// Setup raw mode, and tear down on error or normal exit
const t = await getTermios();
t.enableRawMode();
onExit(() => {
console.info('Exit handler called, disabling raw mode');
t.disableRawMode();
});
// Create the editor itself
const editor = new Editor();
// The main event loop
for await (const chunk of inputLoop()) {
const char = String(decoder.decode(chunk));
// Clear the screen for output
await editor.refreshScreen();
// Process input
const shouldLoop = editor.processKeyPress(char);
if (!shouldLoop) {
return 0;
}
}
return -1;
}
/** /**
* Start the event loop * Start the event loop