Get parsed keypresses from input loop
This commit is contained in:
parent
c466788b9e
commit
820d383c3a
@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Wrap the runtime-specific hook into stdin
|
||||
*/
|
||||
import Ansi from '../common/ansi.ts';
|
||||
import {
|
||||
defaultTerminalSize,
|
||||
ITerminal,
|
||||
ITerminalSize,
|
||||
readKey,
|
||||
} from '../common/mod.ts';
|
||||
import Ansi from '../common/ansi.ts';
|
||||
|
||||
const BunTerminalIO: ITerminal = {
|
||||
// Deno only returns arguments passed to the script, so
|
||||
@ -15,7 +16,7 @@ const BunTerminalIO: ITerminal = {
|
||||
argv: (Bun.argv.length > 2) ? Bun.argv.slice(2) : [],
|
||||
inputLoop: async function* inputLoop() {
|
||||
for await (const chunk of Bun.stdin.stream()) {
|
||||
yield chunk;
|
||||
yield readKey(chunk);
|
||||
}
|
||||
},
|
||||
getTerminalSize: async function getTerminalSize(): Promise<ITerminalSize> {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Ansi, KeyCommand, readKey } from './ansi.ts';
|
||||
import { Ansi, KeyCommand } from './ansi.ts';
|
||||
import Buffer from './buffer.ts';
|
||||
import Document from './document.ts';
|
||||
import Editor from './editor.ts';
|
||||
import Row from './row.ts';
|
||||
import { getTestRunner } from './runtime.ts';
|
||||
import { getTestRunner, readKey } from './runtime.ts';
|
||||
import { defaultTerminalSize } from './termios.ts';
|
||||
import { Position } from './types.ts';
|
||||
import * as Util from './utils.ts';
|
||||
@ -18,9 +18,10 @@ const {
|
||||
testSuite,
|
||||
} = await getTestRunner();
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const testKeyMap = (codes: string[], expected: string) => {
|
||||
codes.forEach((code) => {
|
||||
assertEquals(readKey(code), expected);
|
||||
assertEquals(readKey(encoder.encode(code)), expected);
|
||||
});
|
||||
};
|
||||
|
||||
@ -39,15 +40,21 @@ testSuite({
|
||||
'ANSI::readKey()': {
|
||||
'readKey passthrough': () => {
|
||||
// Ignore unhandled escape sequences
|
||||
assertEquals(readKey('\x1b[]'), '\x1b[]');
|
||||
assertEquals(readKey(encoder.encode('\x1b[]')), '\x1b[]');
|
||||
|
||||
// Pass explicitly mapped values right through
|
||||
assertEquals(readKey(KeyCommand.ArrowUp), KeyCommand.ArrowUp);
|
||||
assertEquals(readKey(KeyCommand.Home), KeyCommand.Home);
|
||||
assertEquals(readKey(KeyCommand.Delete), KeyCommand.Delete);
|
||||
assertEquals(
|
||||
readKey(encoder.encode(KeyCommand.ArrowUp)),
|
||||
KeyCommand.ArrowUp,
|
||||
);
|
||||
assertEquals(readKey(encoder.encode(KeyCommand.Home)), KeyCommand.Home);
|
||||
assertEquals(
|
||||
readKey(encoder.encode(KeyCommand.Delete)),
|
||||
KeyCommand.Delete,
|
||||
);
|
||||
|
||||
// And pass through whatever else
|
||||
assertEquals(readKey('foobaz'), 'foobaz');
|
||||
assertEquals(readKey(encoder.encode('foobaz')), 'foobaz');
|
||||
},
|
||||
'readKey Esc': () =>
|
||||
testKeyMap(['\x1b', Util.ctrlKey('l')], KeyCommand.Escape),
|
||||
|
@ -1,7 +1,6 @@
|
||||
/**
|
||||
* ANSI/VT terminal escape code handling
|
||||
*/
|
||||
import { ctrlKey } from './utils.ts';
|
||||
|
||||
export const ANSI_PREFIX = '\x1b[';
|
||||
|
||||
@ -45,45 +44,4 @@ export const Ansi = {
|
||||
moveCursorDown: (row: number): string => ANSI_PREFIX + `${row}B`,
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert input from ANSI escape sequences into a form
|
||||
* that can be more easily mapped to editor commands
|
||||
*
|
||||
* @param parsed - the decoded chunk of input
|
||||
*/
|
||||
export function readKey(parsed: string): string {
|
||||
// Return the input if it's unambiguous
|
||||
if (parsed in KeyCommand) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Some keycodes have multiple potential inputs
|
||||
switch (parsed) {
|
||||
case '\x1b[1~':
|
||||
case '\x1b[7~':
|
||||
case '\x1bOH':
|
||||
case '\x1b[H':
|
||||
return KeyCommand.Home;
|
||||
|
||||
case '\x1b[4~':
|
||||
case '\x1b[8~':
|
||||
case '\x1bOF':
|
||||
case '\x1b[F':
|
||||
return KeyCommand.End;
|
||||
|
||||
case '\n':
|
||||
case '\v':
|
||||
return KeyCommand.Enter;
|
||||
|
||||
case ctrlKey('l'):
|
||||
return KeyCommand.Escape;
|
||||
|
||||
case ctrlKey('h'):
|
||||
return KeyCommand.Backspace;
|
||||
|
||||
default:
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
export default Ansi;
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { readKey } from './ansi.ts';
|
||||
import { getRuntime } from './runtime.ts';
|
||||
import { getTermios } from './termios.ts';
|
||||
import Editor from './editor.ts';
|
||||
|
||||
export async function main() {
|
||||
const decoder = new TextDecoder();
|
||||
const { term, file, onExit, onEvent } = await getRuntime();
|
||||
|
||||
// Setup raw mode, and tear down on error or normal exit
|
||||
@ -38,9 +36,8 @@ export async function main() {
|
||||
// Clear the screen
|
||||
await editor.refreshScreen();
|
||||
|
||||
for await (const chunk of term.inputLoop()) {
|
||||
for await (const char of term.inputLoop()) {
|
||||
// Process input
|
||||
const char = readKey(decoder.decode(chunk));
|
||||
const shouldLoop = await editor.processKeyPress(char);
|
||||
if (!shouldLoop) {
|
||||
return 0;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getTermios } from './termios.ts';
|
||||
import { noop } from './utils.ts';
|
||||
import { ctrlKey, noop } from './utils.ts';
|
||||
import { ITestBase } from './types.ts';
|
||||
import { KeyCommand } from './ansi.ts';
|
||||
|
||||
export enum RunTimeType {
|
||||
Bun = 'bun',
|
||||
@ -63,7 +64,7 @@ export interface ITerminal {
|
||||
/**
|
||||
* The generator function returning chunks of input from the stdin stream
|
||||
*/
|
||||
inputLoop(): AsyncGenerator<Uint8Array, void>;
|
||||
inputLoop(): AsyncGenerator<string, void>;
|
||||
|
||||
/**
|
||||
* Get the size of the terminal
|
||||
@ -134,9 +135,52 @@ export interface IRuntime {
|
||||
// ----------------------------------------------------------------------------
|
||||
// Misc runtime functions
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let scrollRuntime: IRuntime | null = null;
|
||||
|
||||
/**
|
||||
* Convert input from ANSI escape sequences into a form
|
||||
* that can be more easily mapped to editor commands
|
||||
*
|
||||
* @param raw - the raw chunk of input
|
||||
*/
|
||||
export function readKey(raw: Uint8Array): string {
|
||||
const parsed = decoder.decode(raw);
|
||||
|
||||
// Return the input if it's unambiguous
|
||||
if (parsed in KeyCommand) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// Some keycodes have multiple potential inputs
|
||||
switch (parsed) {
|
||||
case '\x1b[1~':
|
||||
case '\x1b[7~':
|
||||
case '\x1bOH':
|
||||
case '\x1b[H':
|
||||
return KeyCommand.Home;
|
||||
|
||||
case '\x1b[4~':
|
||||
case '\x1b[8~':
|
||||
case '\x1bOF':
|
||||
case '\x1b[F':
|
||||
return KeyCommand.End;
|
||||
|
||||
case '\n':
|
||||
case '\v':
|
||||
return KeyCommand.Enter;
|
||||
|
||||
case ctrlKey('l'):
|
||||
return KeyCommand.Escape;
|
||||
|
||||
case ctrlKey('h'):
|
||||
return KeyCommand.Backspace;
|
||||
|
||||
default:
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
export function logToFile(s: unknown) {
|
||||
importForRuntime('file_io').then((f) => {
|
||||
const raw = (typeof s === 'string') ? s : JSON.stringify(s, null, 2);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ITerminal, ITerminalSize } from '../common/runtime.ts';
|
||||
import { ITerminal, ITerminalSize, readKey } from '../common/runtime.ts';
|
||||
|
||||
const DenoTerminalIO: ITerminal = {
|
||||
argv: Deno.args,
|
||||
@ -7,7 +7,7 @@ const DenoTerminalIO: ITerminal = {
|
||||
*/
|
||||
inputLoop: async function* inputLoop() {
|
||||
for await (const chunk of Deno.stdin.readable) {
|
||||
yield chunk;
|
||||
yield readKey(chunk);
|
||||
}
|
||||
},
|
||||
getTerminalSize: function getSize(): Promise<ITerminalSize> {
|
||||
|
Loading…
Reference in New Issue
Block a user