diff --git a/src/bun/terminal_io.ts b/src/bun/terminal_io.ts index 5ed0798..e62d765 100644 --- a/src/bun/terminal_io.ts +++ b/src/bun/terminal_io.ts @@ -1,7 +1,7 @@ /** * Wrap the runtime-specific hook into stdin */ -import { ITerminalIO } from '../common/mod.ts'; +import { ITerminalIO, ITerminalSize } from '../common/mod.ts'; export async function* inputLoop() { for await (const chunk of Bun.stdin.stream()) { @@ -15,8 +15,20 @@ export async function write(s: string): Promise { 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 = { inputLoop, + getSize, write, }; diff --git a/src/common/ansi.ts b/src/common/ansi.ts index fd14d09..1da01af 100644 --- a/src/common/ansi.ts +++ b/src/common/ansi.ts @@ -1,15 +1,13 @@ -function escape(suffix: string): string { - return `\x1b[${suffix}`; -} - -function moveCursor(row: number, col: number): string { - return escape(`${row};${col}H`); +function esc(pieces: TemplateStringsArray): string { + return '\x1b[' + pieces[0]; } export const Ansi = { - ClearScreen: escape('2J'), - ResetCursor: escape('H'), - moveCursor, + ClearScreen: esc`2J`, + ResetCursor: esc`H`, + moveCursor: function moveCursor(row: number, col: number): string { + return `\x1b${row};${col}H`; + }, }; export default Ansi; diff --git a/src/common/editor.ts b/src/common/editor.ts index d1995ce..d21300c 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -12,6 +12,10 @@ class Buffer { this.#b += s; } + appendLine(s: string): void { + this.#b += s + '\r\n'; + } + clear(): void { this.#b = ''; } @@ -49,10 +53,18 @@ export class Editor { const { write } = await importForRuntime('terminal_io'); this.clearScreen(); + this.drawRows(); + await write(this.#buffer.getBuffer()); this.#buffer.clear(); } + private drawRows(): void { + for (let y = 0; y <= 24; y++) { + this.#buffer.appendLine('~'); + } + } + private clearScreen(): void { this.#buffer.append(Ansi.ClearScreen); this.#buffer.append(Ansi.ResetCursor); diff --git a/src/common/mod.ts b/src/common/mod.ts index 5743eb6..f7df8ba 100644 --- a/src/common/mod.ts +++ b/src/common/mod.ts @@ -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 './strings.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; -} diff --git a/src/common/types.ts b/src/common/types.ts index 12356dc..a9ce2ea 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -37,6 +37,11 @@ export interface IFFI { getPointer(ta: any): unknown; } +export interface ITerminalSize { + rows: number; + cols: number; +} + /** * Runtime-specific IO streams */ @@ -46,6 +51,11 @@ export interface ITerminalIO { */ inputLoop(): AsyncGenerator; + /** + * Get the size of the terminal + */ + getSize(): ITerminalSize; + /** * Pipe a string to stdout */ diff --git a/src/deno/terminal_io.ts b/src/deno/terminal_io.ts index ee264b7..a8ef931 100644 --- a/src/deno/terminal_io.ts +++ b/src/deno/terminal_io.ts @@ -1,4 +1,4 @@ -import { ITerminalIO } from '../common/types.ts'; +import { ITerminalIO, ITerminalSize } from '../common/types.ts'; /** * Wrap the runtime-specific hook into stdin @@ -17,8 +17,18 @@ export async function write(s: string): Promise { stdout.releaseLock(); } +export function getSize(): ITerminalSize { + const size: { rows: number; columns: number } = Deno.consoleSize(); + + return { + rows: size.rows, + cols: size.columns, + }; +} + const DenoTerminalIO: ITerminalIO = { inputLoop, + getSize, write, }; diff --git a/src/scroll.ts b/src/scroll.ts index e4c78a1..400d5d7 100644 --- a/src/scroll.ts +++ b/src/scroll.ts @@ -1,7 +1,43 @@ /** * 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