Finally get the terminal size via ansi codes for bun
This commit is contained in:
parent
1723219452
commit
61d222a9af
3
justfile
3
justfile
@ -5,6 +5,9 @@ default:
|
|||||||
# Typescript checking
|
# Typescript checking
|
||||||
check: deno-check bun-check
|
check: deno-check bun-check
|
||||||
|
|
||||||
|
docs:
|
||||||
|
deno doc --html --unstable-ffi --private --name="Scroll" ./src/common/mod.ts
|
||||||
|
|
||||||
# Reformat the code
|
# Reformat the code
|
||||||
fmt:
|
fmt:
|
||||||
deno fmt
|
deno fmt
|
||||||
|
@ -2,8 +2,26 @@
|
|||||||
* The main entrypoint when using Bun as the runtime
|
* The main entrypoint when using Bun as the runtime
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './terminal_io.ts';
|
import { getTermios, IRuntime, RunTimeType } from '../common/mod.ts';
|
||||||
|
import BunFFI from './ffi.ts';
|
||||||
|
import BunTerminalIO from './terminal_io.ts';
|
||||||
|
|
||||||
export const onExit = (cb: () => void): void => {
|
process.on('error', async (e) => {
|
||||||
process.on('beforeExit', cb);
|
(await getTermios()).disableRawMode();
|
||||||
|
console.error(e);
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
const BunRuntime: IRuntime = {
|
||||||
|
name: RunTimeType.Bun,
|
||||||
|
ffi: BunFFI,
|
||||||
|
io: BunTerminalIO,
|
||||||
|
onExit: (cb: () => void): void => {
|
||||||
|
process.on('beforeExit', cb);
|
||||||
|
process.on('exit', cb);
|
||||||
|
process.on('SIGINT', cb);
|
||||||
|
},
|
||||||
|
exit: (code?: number) => process.exit(code),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default BunRuntime;
|
||||||
|
@ -2,22 +2,7 @@
|
|||||||
* Wrap the runtime-specific hook into stdin
|
* Wrap the runtime-specific hook into stdin
|
||||||
*/
|
*/
|
||||||
import { ITerminalIO, ITerminalSize } from '../common/mod.ts';
|
import { ITerminalIO, ITerminalSize } from '../common/mod.ts';
|
||||||
|
import Ansi from '../common/editor/ansi.ts';
|
||||||
function getSizeFromTput(): ITerminalSize {
|
|
||||||
const rows = parseInt(
|
|
||||||
Bun.spawnSync(['tput', 'lines']).stdout.toString().trim(),
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
const cols = parseInt(
|
|
||||||
Bun.spawnSync(['tput', 'cols']).stdout.toString().trim(),
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows: (rows > 0) ? rows + 1 : 25,
|
|
||||||
cols: (cols > 0) ? cols + 1 : 80,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const BunTerminalIO: ITerminalIO = {
|
const BunTerminalIO: ITerminalIO = {
|
||||||
inputLoop: async function* inputLoop() {
|
inputLoop: async function* inputLoop() {
|
||||||
@ -25,8 +10,40 @@ const BunTerminalIO: ITerminalIO = {
|
|||||||
yield chunk;
|
yield chunk;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getSize: function getSize(): ITerminalSize {
|
getTerminalSize: async function getTerminalSize(): Promise<ITerminalSize> {
|
||||||
return getSizeFromTput();
|
const encoder = new TextEncoder();
|
||||||
|
const write = (s: string) => Bun.write(Bun.stdout, encoder.encode(s));
|
||||||
|
|
||||||
|
// Tell the cursor to move to Row 999 and Column 999
|
||||||
|
// Since this command specifically doesn't go off the screen
|
||||||
|
// When we ask where the cursor is, we should get the size of the screen
|
||||||
|
await write(Ansi.moveCursorForward(999) + Ansi.moveCursorDown(999));
|
||||||
|
|
||||||
|
// Ask where the cursor is
|
||||||
|
await write(Ansi.GetCursorLocation);
|
||||||
|
|
||||||
|
// Get the first chunk from stdin
|
||||||
|
// The response is \x1b[(rows);(cols)R..
|
||||||
|
for await (const chunk of Bun.stdin.stream()) {
|
||||||
|
const rawCode = (new TextDecoder()).decode(chunk);
|
||||||
|
const res = rawCode.trim().replace(/^.\[([0-9]+;[0-9]+)R$/, '$1');
|
||||||
|
const [srows, scols] = res.split(';');
|
||||||
|
const rows = parseInt(srows, 10);
|
||||||
|
const cols = parseInt(scols, 10);
|
||||||
|
|
||||||
|
// Clear the screen
|
||||||
|
await write(Ansi.ClearScreen + Ansi.ResetCursor);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
cols,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: 24,
|
||||||
|
cols: 80,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
write: async function write(s: string): Promise<void> {
|
write: async function write(s: string): Promise<void> {
|
||||||
const buffer = new TextEncoder().encode(s);
|
const buffer = new TextEncoder().encode(s);
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
|
export const ANSI_PREFIX = '\x1b[';
|
||||||
|
|
||||||
function esc(pieces: TemplateStringsArray): string {
|
function esc(pieces: TemplateStringsArray): string {
|
||||||
return '\x1b[' + pieces[0];
|
return ANSI_PREFIX + pieces[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ANSI escapes for various inputs
|
||||||
|
*/
|
||||||
|
export enum KeyCommand {
|
||||||
|
ArrowUp = `${ANSI_PREFIX}A`,
|
||||||
|
ArrowDown = `${ANSI_PREFIX}B`,
|
||||||
|
ArrowRight = `${ANSI_PREFIX}C`,
|
||||||
|
ArrowLeft = `${ANSI_PREFIX}D`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Ansi = {
|
export const Ansi = {
|
||||||
@ -8,9 +20,16 @@ export const Ansi = {
|
|||||||
ResetCursor: esc`H`,
|
ResetCursor: esc`H`,
|
||||||
HideCursor: esc`?25l`,
|
HideCursor: esc`?25l`,
|
||||||
ShowCursor: esc`?25h`,
|
ShowCursor: esc`?25h`,
|
||||||
|
GetCursorLocation: esc`6n`,
|
||||||
moveCursor: function moveCursor(row: number, col: number): string {
|
moveCursor: function moveCursor(row: number, col: number): string {
|
||||||
|
// Convert to 1-based counting
|
||||||
|
row++;
|
||||||
|
col++;
|
||||||
|
|
||||||
return `\x1b[${row};${col}H`;
|
return `\x1b[${row};${col}H`;
|
||||||
},
|
},
|
||||||
|
moveCursorForward: (col: number): string => `${ANSI_PREFIX}${col}C`,
|
||||||
|
moveCursorDown: (row: number): string => `${ANSI_PREFIX}${row}B`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Ansi;
|
export default Ansi;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { strlen } from '../utils.ts';
|
import { strlen } from '../utils.ts';
|
||||||
|
import { getRuntime } from '../runtime.ts';
|
||||||
|
|
||||||
class Buffer {
|
class Buffer {
|
||||||
#b = '';
|
#b = '';
|
||||||
@ -22,6 +23,12 @@ class Buffer {
|
|||||||
return this.#b;
|
return this.#b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async flush() {
|
||||||
|
const { io } = await getRuntime();
|
||||||
|
await io.write(this.#b);
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
strlen(): number {
|
strlen(): number {
|
||||||
return strlen(this.#b);
|
return strlen(this.#b);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import Ansi from './ansi.ts';
|
import Ansi, { KeyCommand } from './ansi.ts';
|
||||||
import Buffer from './buffer.ts';
|
import Buffer from './buffer.ts';
|
||||||
import {
|
import { ctrl_key, IPoint, ITerminalSize, truncate, VERSION } from '../mod.ts';
|
||||||
ctrl_key,
|
|
||||||
importDefaultForRuntime,
|
|
||||||
IPoint,
|
|
||||||
ITerminalSize,
|
|
||||||
truncate,
|
|
||||||
VERSION,
|
|
||||||
} from '../mod.ts';
|
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
#buffer: Buffer;
|
#buffer: Buffer;
|
||||||
@ -36,12 +29,12 @@ export class Editor {
|
|||||||
this.clearScreen().then(() => {});
|
this.clearScreen().then(() => {});
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 'w':
|
case KeyCommand.ArrowUp:
|
||||||
case 's':
|
case KeyCommand.ArrowDown:
|
||||||
case 'a':
|
case KeyCommand.ArrowRight:
|
||||||
case 'd':
|
case KeyCommand.ArrowLeft:
|
||||||
this.moveCursor(input);
|
this.moveCursor(input);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -49,16 +42,16 @@ export class Editor {
|
|||||||
|
|
||||||
private moveCursor(char: string): void {
|
private moveCursor(char: string): void {
|
||||||
switch (char) {
|
switch (char) {
|
||||||
case 'a':
|
case KeyCommand.ArrowLeft:
|
||||||
this.#cursor.x--;
|
this.#cursor.x--;
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case KeyCommand.ArrowRight:
|
||||||
this.#cursor.x++;
|
this.#cursor.x++;
|
||||||
break;
|
break;
|
||||||
case 'w':
|
case KeyCommand.ArrowUp:
|
||||||
this.#cursor.y--;
|
this.#cursor.y--;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case KeyCommand.ArrowDown:
|
||||||
this.#cursor.y++;
|
this.#cursor.y++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -76,18 +69,18 @@ export class Editor {
|
|||||||
this.#buffer.append(Ansi.ResetCursor);
|
this.#buffer.append(Ansi.ResetCursor);
|
||||||
this.drawRows();
|
this.drawRows();
|
||||||
this.#buffer.append(
|
this.#buffer.append(
|
||||||
Ansi.moveCursor(this.#cursor.y + 1, this.#cursor.x + 1),
|
Ansi.moveCursor(this.#cursor.y, this.#cursor.x),
|
||||||
);
|
);
|
||||||
this.#buffer.append(Ansi.ShowCursor);
|
this.#buffer.append(Ansi.ShowCursor);
|
||||||
|
|
||||||
await this.writeToScreen();
|
await this.#buffer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async clearScreen(): Promise<void> {
|
private async clearScreen(): Promise<void> {
|
||||||
this.#buffer.append(Ansi.ClearScreen);
|
this.#buffer.append(Ansi.ClearScreen);
|
||||||
this.#buffer.append(Ansi.ResetCursor);
|
this.#buffer.append(Ansi.ResetCursor);
|
||||||
|
|
||||||
await this.writeToScreen();
|
await this.#buffer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawRows(): void {
|
private drawRows(): void {
|
||||||
@ -120,11 +113,4 @@ export class Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async writeToScreen(): Promise<void> {
|
|
||||||
const io = await importDefaultForRuntime('terminal_io');
|
|
||||||
|
|
||||||
await io.write(this.#buffer.getBuffer());
|
|
||||||
this.#buffer.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ export * from './editor/mod.ts';
|
|||||||
export * from './runtime.ts';
|
export * from './runtime.ts';
|
||||||
export * from './termios.ts';
|
export * from './termios.ts';
|
||||||
export * from './utils.ts';
|
export * from './utils.ts';
|
||||||
|
export * from './types.ts';
|
||||||
export type * from './types.ts';
|
export type * from './types.ts';
|
||||||
|
|
||||||
export const VERSION = '0.0.1';
|
export const VERSION = '0.0.1';
|
||||||
|
@ -1,31 +1,47 @@
|
|||||||
import { getTermios } from './termios.ts';
|
import { getTermios } from './termios.ts';
|
||||||
|
import { IRuntime, RunTimeType } from './types.ts';
|
||||||
|
|
||||||
export enum RunTime {
|
let scrollRuntime: IRuntime | null = null;
|
||||||
Bun = 'bun',
|
|
||||||
Deno = 'deno',
|
|
||||||
Unknown = 'common',
|
|
||||||
}
|
|
||||||
|
|
||||||
export function die(s: string): void {
|
/**
|
||||||
getTermios().then((t) => t.disableRawMode());
|
* Kill program, displaying an error message
|
||||||
|
* @param s
|
||||||
|
*/
|
||||||
|
export async function die(s: string | Error): Promise<void> {
|
||||||
|
(await getTermios()).disableRawMode();
|
||||||
console.error(s);
|
console.error(s);
|
||||||
|
(await getRuntime()).exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine which Typescript runtime we are operating under
|
* Determine which Typescript runtime we are operating under
|
||||||
*/
|
*/
|
||||||
export const getRuntime = (): RunTime => {
|
export function getRuntimeType(): RunTimeType {
|
||||||
let runtime = RunTime.Unknown;
|
let runtime = RunTimeType.Unknown;
|
||||||
|
|
||||||
if ('Deno' in globalThis) {
|
if ('Deno' in globalThis) {
|
||||||
runtime = RunTime.Deno;
|
runtime = RunTimeType.Deno;
|
||||||
}
|
}
|
||||||
if ('Bun' in globalThis) {
|
if ('Bun' in globalThis) {
|
||||||
runtime = RunTime.Bun;
|
runtime = RunTimeType.Bun;
|
||||||
}
|
}
|
||||||
|
|
||||||
return runtime;
|
return runtime;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export async function getRuntime(): Promise<IRuntime> {
|
||||||
|
if (scrollRuntime === null) {
|
||||||
|
const runtime = getRuntimeType();
|
||||||
|
const path = `../${runtime}/mod.ts`;
|
||||||
|
|
||||||
|
const pkg = await import(path);
|
||||||
|
if ('default' in pkg) {
|
||||||
|
scrollRuntime = pkg.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(scrollRuntime!);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a runtime-specific module
|
* Import a runtime-specific module
|
||||||
@ -36,7 +52,7 @@ export const getRuntime = (): RunTime => {
|
|||||||
* @param path - the path within the runtime module
|
* @param path - the path within the runtime module
|
||||||
*/
|
*/
|
||||||
export const importForRuntime = async (path: string) => {
|
export const importForRuntime = async (path: string) => {
|
||||||
const runtime = getRuntime();
|
const runtime = getRuntimeType();
|
||||||
const suffix = '.ts';
|
const suffix = '.ts';
|
||||||
const base = `../${runtime}/`;
|
const base = `../${runtime}/`;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { die, IFFI, importDefaultForRuntime } from './mod.ts';
|
import { die, getRuntime, IFFI } from './mod.ts';
|
||||||
|
|
||||||
export const STDIN_FILENO = 0;
|
export const STDIN_FILENO = 0;
|
||||||
export const TCSANOW = 0;
|
export const TCSANOW = 0;
|
||||||
@ -63,7 +63,7 @@ class Termios {
|
|||||||
// Get the current termios settings
|
// Get the current termios settings
|
||||||
let res = this.#ffi.tcgetattr(STDIN_FILENO, this.#ptr);
|
let res = this.#ffi.tcgetattr(STDIN_FILENO, this.#ptr);
|
||||||
if (res === -1) {
|
if (res === -1) {
|
||||||
die('Failed to get terminal settings');
|
die('Failed to get terminal settings').then(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The #ptr property is pointing to the #termios TypedArray. As the pointer
|
// The #ptr property is pointing to the #termios TypedArray. As the pointer
|
||||||
@ -80,7 +80,9 @@ class Termios {
|
|||||||
// Actually set the new termios settings
|
// Actually set the new termios settings
|
||||||
res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
|
res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
|
||||||
if (res === -1) {
|
if (res === -1) {
|
||||||
die('Failed to update terminal settings. Can\'t enter raw mode');
|
die('Failed to update terminal settings. Can\'t enter raw mode').then(
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#inRawMode = true;
|
this.#inRawMode = true;
|
||||||
@ -96,7 +98,7 @@ class Termios {
|
|||||||
const oldTermiosPtr = this.#ffi.getPointer(this.#cookedTermios);
|
const oldTermiosPtr = this.#ffi.getPointer(this.#cookedTermios);
|
||||||
const res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
|
const res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
|
||||||
if (res === -1) {
|
if (res === -1) {
|
||||||
die('Failed to restore canonical mode.');
|
die('Failed to restore canonical mode.').then(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#inRawMode = false;
|
this.#inRawMode = false;
|
||||||
@ -111,7 +113,7 @@ export const getTermios = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the runtime-specific ffi wrappers
|
// Get the runtime-specific ffi wrappers
|
||||||
const ffi: IFFI = await importDefaultForRuntime('ffi');
|
const { ffi } = await getRuntime();
|
||||||
termiosSingleton = new Termios(ffi);
|
termiosSingleton = new Termios(ffi);
|
||||||
|
|
||||||
return termiosSingleton;
|
return termiosSingleton;
|
||||||
|
@ -10,6 +10,12 @@ export interface IPoint {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Runtime adapter interfaces
|
// Runtime adapter interfaces
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
export enum RunTimeType {
|
||||||
|
Bun = 'bun',
|
||||||
|
Deno = 'deno',
|
||||||
|
Unknown = 'common',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The native functions for terminal settings
|
* The native functions for terminal settings
|
||||||
*/
|
*/
|
||||||
@ -52,7 +58,7 @@ export interface ITerminalIO {
|
|||||||
/**
|
/**
|
||||||
* Get the size of the terminal
|
* Get the size of the terminal
|
||||||
*/
|
*/
|
||||||
getSize(): ITerminalSize;
|
getTerminalSize(): Promise<ITerminalSize>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pipe a string to stdout
|
* Pipe a string to stdout
|
||||||
@ -61,11 +67,31 @@ export interface ITerminalIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IRuntime {
|
export interface IRuntime {
|
||||||
|
/**
|
||||||
|
* The name of the runtime
|
||||||
|
*/
|
||||||
|
name: RunTimeType;
|
||||||
|
/**
|
||||||
|
* Runtime-specific FFI
|
||||||
|
*/
|
||||||
|
ffi: IFFI;
|
||||||
|
/**
|
||||||
|
* Runtime-specific terminal functionality
|
||||||
|
*/
|
||||||
|
io: ITerminalIO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a beforeExit/beforeUnload event handler for the runtime
|
* Set a beforeExit/beforeUnload event handler for the runtime
|
||||||
* @param cb - The event handler
|
* @param cb - The event handler
|
||||||
*/
|
*/
|
||||||
onExit(cb: () => void): void;
|
onExit(cb: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop execution
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
*/
|
||||||
|
exit(code?: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -55,11 +55,7 @@ export function is_control(char: string): boolean {
|
|||||||
export function ctrl_key(char: string): string {
|
export function ctrl_key(char: string): string {
|
||||||
// This is the normal use case, of course
|
// This is the normal use case, of course
|
||||||
if (is_ascii(char)) {
|
if (is_ascii(char)) {
|
||||||
const point = char.codePointAt(0);
|
const point = char.codePointAt(0)!;
|
||||||
if (point === undefined) {
|
|
||||||
return char;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.fromCodePoint(point & 0x1f);
|
return String.fromCodePoint(point & 0x1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* The main entrypoint when using Deno as the runtime
|
* The main entrypoint when using Deno as the runtime
|
||||||
*/
|
*/
|
||||||
import { IRuntime } from '../common/types.ts';
|
import { IRuntime, RunTimeType } from '../common/mod.ts';
|
||||||
|
import DenoFFI from './ffi.ts';
|
||||||
export * from './terminal_io.ts';
|
import DenoTerminalIO from './terminal_io.ts';
|
||||||
|
|
||||||
export const onExit = (cb: () => void): void => {
|
|
||||||
globalThis.addEventListener('onbeforeunload', cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DenoRuntime: IRuntime = {
|
const DenoRuntime: IRuntime = {
|
||||||
onExit,
|
name: RunTimeType.Deno,
|
||||||
|
ffi: DenoFFI,
|
||||||
|
io: DenoTerminalIO,
|
||||||
|
onExit: (cb: () => void): void => {
|
||||||
|
globalThis.addEventListener('onbeforeunload', cb);
|
||||||
|
globalThis.onbeforeunload = cb;
|
||||||
|
},
|
||||||
|
exit: (code?: number) => Deno.exit(code),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DenoRuntime;
|
export default DenoRuntime;
|
||||||
|
@ -4,29 +4,27 @@ const DenoTerminalIO: ITerminalIO = {
|
|||||||
/**
|
/**
|
||||||
* Wrap the runtime-specific hook into stdin
|
* Wrap the runtime-specific hook into stdin
|
||||||
*/
|
*/
|
||||||
inputLoop: async function* inputLoop(): AsyncGenerator<
|
inputLoop: async function* inputLoop() {
|
||||||
Uint8Array,
|
|
||||||
void,
|
|
||||||
unknown
|
|
||||||
> {
|
|
||||||
for await (const chunk of Deno.stdin.readable) {
|
for await (const chunk of Deno.stdin.readable) {
|
||||||
yield chunk;
|
yield chunk;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getSize: function getSize(): ITerminalSize {
|
getTerminalSize: function getSize(): Promise<ITerminalSize> {
|
||||||
const size: { rows: number; columns: number } = Deno.consoleSize();
|
const size: { rows: number; columns: number } = Deno.consoleSize();
|
||||||
|
|
||||||
return {
|
return Promise.resolve({
|
||||||
rows: size.rows,
|
rows: size.rows,
|
||||||
cols: size.columns,
|
cols: size.columns,
|
||||||
};
|
});
|
||||||
},
|
},
|
||||||
write: async function write(s: string): Promise<void> {
|
write: async function write(s: string): Promise<void> {
|
||||||
const buffer = new TextEncoder().encode(s);
|
const buffer: Uint8Array = new TextEncoder().encode(s);
|
||||||
|
const stdout: WritableStream<Uint8Array> = Deno.stdout.writable;
|
||||||
|
|
||||||
const stdout = Deno.stdout.writable.getWriter();
|
const writer: WritableStreamDefaultWriter<Uint8Array> = stdout.getWriter();
|
||||||
await stdout.write(buffer);
|
await writer.ready;
|
||||||
stdout.releaseLock();
|
await writer.write(buffer);
|
||||||
|
writer.releaseLock();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,47 +1,38 @@
|
|||||||
/**
|
/**
|
||||||
* The starting point for running scroll
|
* The starting point for running scroll
|
||||||
*/
|
*/
|
||||||
import {
|
import { Editor, getRuntime, getTermios } from './common/mod.ts';
|
||||||
Editor,
|
|
||||||
getTermios,
|
|
||||||
importDefaultForRuntime,
|
|
||||||
importForRuntime,
|
|
||||||
} from './common/mod.ts';
|
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const { inputLoop, getSize } = await importDefaultForRuntime(
|
const runTime = await getRuntime();
|
||||||
'terminal_io.ts',
|
const { io, onExit } = runTime;
|
||||||
);
|
|
||||||
const { onExit } = await importForRuntime('mod.ts');
|
|
||||||
|
|
||||||
// Setup raw mode, and tear down on error or normal exit
|
// Setup raw mode, and tear down on error or normal exit
|
||||||
const t = await getTermios();
|
const t = await getTermios();
|
||||||
t.enableRawMode();
|
t.enableRawMode();
|
||||||
onExit(() => {
|
onExit(() => {
|
||||||
console.log('Exit handler called, disabling raw mode\r\n');
|
|
||||||
t.disableRawMode();
|
t.disableRawMode();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const terminalSize = await io.getTerminalSize();
|
||||||
|
|
||||||
// Create the editor itself
|
// Create the editor itself
|
||||||
const terminalSize = getSize();
|
|
||||||
const editor = new Editor(terminalSize);
|
const editor = new Editor(terminalSize);
|
||||||
await editor.refreshScreen();
|
await editor.refreshScreen();
|
||||||
|
|
||||||
// The main event loop
|
// The main event loop
|
||||||
for await (const chunk of inputLoop()) {
|
for await (const chunk of io.inputLoop()) {
|
||||||
const char = String(decoder.decode(chunk));
|
|
||||||
|
|
||||||
// Clear the screen for output
|
|
||||||
await editor.refreshScreen();
|
|
||||||
|
|
||||||
// Process input
|
// Process input
|
||||||
|
const char = String(decoder.decode(chunk));
|
||||||
const shouldLoop = editor.processKeyPress(char);
|
const shouldLoop = editor.processKeyPress(char);
|
||||||
|
|
||||||
if (!shouldLoop) {
|
if (!shouldLoop) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render output
|
||||||
|
await editor.refreshScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
|
Loading…
Reference in New Issue
Block a user