Get raw mode working in Deno
This commit is contained in:
parent
52632ad9a9
commit
b30c4d40d6
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"std": "https://deno.land/std@0.204.0/",
|
"std": "https://deno.land/std@0.204.0/",
|
||||||
// "/": "./src/",
|
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"include": ["src/"],
|
"include": ["src/"],
|
||||||
|
44
src/common/index.ts
Normal file
44
src/common/index.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
export enum RunTime {
|
||||||
|
Bun = 'bun',
|
||||||
|
Deno = 'deno',
|
||||||
|
Unknown = 'common',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine which Typescript runtime we are operating under
|
||||||
|
*/
|
||||||
|
export const getRuntime = (): RunTime => {
|
||||||
|
let runtime = RunTime.Unknown;
|
||||||
|
|
||||||
|
if ('Deno' in globalThis) {
|
||||||
|
runtime = RunTime.Deno;
|
||||||
|
}
|
||||||
|
if ('Bun' in globalThis) {
|
||||||
|
runtime = RunTime.Bun;
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtime;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a runtime-specific module
|
||||||
|
*
|
||||||
|
* eg. to load "src/bun/index.ts", if the runtime is bun,
|
||||||
|
* you can use like so `await importForRuntime('index')`;
|
||||||
|
*
|
||||||
|
* @param path - the path within the runtime module
|
||||||
|
*/
|
||||||
|
export const importForRuntime = async (path: string) => {
|
||||||
|
const runtime = getRuntime();
|
||||||
|
const suffix = (runtime === RunTime.Deno) ? '.ts' : '';
|
||||||
|
const base = `../${runtime}/`;
|
||||||
|
|
||||||
|
const pathParts = path.split('/')
|
||||||
|
.filter((part) => part !== '' && part !== '.' && part !== suffix)
|
||||||
|
.map((part) => part.replace(suffix, ''));
|
||||||
|
|
||||||
|
const cleanedPath = pathParts.join('/');
|
||||||
|
const importPath = base + cleanedPath + suffix;
|
||||||
|
|
||||||
|
return await import(importPath);
|
||||||
|
}
|
41
src/deno/ffi.ts
Normal file
41
src/deno/ffi.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Deno-specific ffi code
|
||||||
|
|
||||||
|
// Determine library extension based on
|
||||||
|
// your OS.
|
||||||
|
// import { termiosStruct } from "../common/termios.ts";
|
||||||
|
|
||||||
|
let libSuffix = '';
|
||||||
|
switch (Deno.build.os) {
|
||||||
|
case 'windows':
|
||||||
|
libSuffix = 'dll';
|
||||||
|
break;
|
||||||
|
case 'darwin':
|
||||||
|
libSuffix = 'dylib';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
libSuffix = 'so.6';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cSharedLib = `libc.${libSuffix}`;
|
||||||
|
const cStdLib = Deno.dlopen(
|
||||||
|
cSharedLib,
|
||||||
|
{
|
||||||
|
tcgetattr: {
|
||||||
|
parameters: ['i32', 'pointer'],
|
||||||
|
result: 'i32',
|
||||||
|
},
|
||||||
|
tcsetattr: {
|
||||||
|
parameters: ['i32', 'i32', 'pointer'],
|
||||||
|
result: 'i32',
|
||||||
|
},
|
||||||
|
cfmakeraw: {
|
||||||
|
parameters: ['pointer'],
|
||||||
|
result: 'void',
|
||||||
|
},
|
||||||
|
} as const,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const { tcgetattr, tcsetattr, cfmakeraw} = cStdLib.symbols;
|
||||||
|
|
||||||
|
export const getPointer = Deno.UnsafePointer.of;
|
@ -1,12 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* The main entrypoint when using Deno as the runtime
|
* The main entrypoint when using Deno as the runtime
|
||||||
*/
|
*/
|
||||||
|
import { Termios } from './termios.ts'
|
||||||
|
|
||||||
export async function main(): Promise<number> {
|
export async function main(): Promise<number> {
|
||||||
|
const t = new Termios();
|
||||||
|
t.enableRawMode();
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
for await (const chunk of Deno.stdin.readable) {
|
for await (const chunk of Deno.stdin.readable) {
|
||||||
const char = String(decoder.decode(chunk)).trim();
|
const char = String(decoder.decode(chunk)).trim();
|
||||||
|
|
||||||
if (char === 'q') {
|
if (char === 'q') {
|
||||||
|
t.disableRawMode();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,81 @@
|
|||||||
// Deno-specific ffi code
|
import { STDIN_FILENO, TCSANOW, ITermios, TERMIOS_SIZE } from "../common/termios.ts";
|
||||||
|
import { cfmakeraw, tcgetattr, tcsetattr, getPointer } from "./ffi.ts";
|
||||||
|
|
||||||
// Determine library extension based on
|
/**
|
||||||
// your OS.
|
* Implementation to toggle raw mode with Deno runtime
|
||||||
// import { termiosStruct } from "../common/termios.ts";
|
*/
|
||||||
|
export class Termios implements ITermios {
|
||||||
|
/**
|
||||||
|
* Are we in raw mode?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#inRawMode:boolean;
|
||||||
|
|
||||||
let libSuffix = '';
|
/**
|
||||||
switch (Deno.build.os) {
|
* The saved version of the termios struct for cooked/canonical mode
|
||||||
case 'windows':
|
* @private
|
||||||
libSuffix = 'dll';
|
*/
|
||||||
break;
|
#cookedTermios: Uint8Array;
|
||||||
case 'darwin':
|
|
||||||
libSuffix = 'dylib';
|
/**
|
||||||
break;
|
* The data for the termios struct we are manipulating
|
||||||
default:
|
* @private
|
||||||
libSuffix = 'so.6';
|
*/
|
||||||
break;
|
#termios: Uint8Array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pointer to the termios struct
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#ptr: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#inRawMode = false;
|
||||||
|
|
||||||
|
// These are the TypedArrays linked to the raw pointer data
|
||||||
|
this.#cookedTermios = new Uint8Array(TERMIOS_SIZE);
|
||||||
|
this.#termios = new Uint8Array(TERMIOS_SIZE);
|
||||||
|
|
||||||
|
// The current pointer for C
|
||||||
|
this.#ptr = getPointer(this.#termios);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cSharedLib = `libc.${libSuffix}`;
|
get inRawMode() {
|
||||||
const cStdLib = Deno.dlopen(
|
return this.#inRawMode;
|
||||||
cSharedLib,
|
}
|
||||||
{
|
|
||||||
tcgetattr: {
|
enableRawMode() {
|
||||||
parameters: ['i32', 'pointer'],
|
if (this.#inRawMode) {
|
||||||
result: 'i32',
|
throw new Error('Can not enable raw mode when in raw mode');
|
||||||
},
|
}
|
||||||
tcsetattr: {
|
|
||||||
parameters: ['i32', 'i32', 'pointer'],
|
// Get the current termios settings
|
||||||
result: 'i32',
|
tcgetattr(STDIN_FILENO, this.#ptr);
|
||||||
},
|
|
||||||
cfmakeraw: {
|
// The #ptr property is pointing to the #termios TypedArray. As the pointer
|
||||||
parameters: ['pointer'],
|
// is manipulated, the TypedArray is as well. We will use this to save
|
||||||
result: 'void',
|
// the original canonical/cooked terminal settings for disabling raw mode later.
|
||||||
},
|
this.#cookedTermios = new Uint8Array(this.#termios, 0, 60);
|
||||||
} as const,
|
|
||||||
);
|
// Update termios struct with raw settings
|
||||||
|
cfmakeraw(this.#ptr);
|
||||||
|
|
||||||
|
// Actually set the new termios settings
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
|
||||||
|
this.#inRawMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableRawMode() {
|
||||||
|
// Don't even bother throwing an error if we try to disable raw mode
|
||||||
|
// and aren't in raw mode. It just doesn't really matter.
|
||||||
|
if (!this.#inRawMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldTermiosPtr = getPointer(this.#cookedTermios);
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
|
||||||
|
|
||||||
|
this.#inRawMode = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default cStdLib.symbols;
|
|
||||||
export const tcgetattr = cStdLib.symbols.tcgetattr;
|
|
||||||
export const tcsetattr = cStdLib.symbols.tcsetattr;
|
|
||||||
export const cfmakeraw = cStdLib.symbols.cfmakeraw;
|
|
||||||
|
@ -8,18 +8,12 @@ export enum RunTime {
|
|||||||
Unknown = 'common',
|
Unknown = 'common',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { importForRuntime } from "./common/index.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the runtime strategy, and go!
|
* Determine the runtime strategy, and go!
|
||||||
*/
|
*/
|
||||||
(async () => {
|
(async () => {
|
||||||
let RUNTIME = RunTime.Unknown;
|
const { main } = await importForRuntime('./index.ts');
|
||||||
if ('Deno' in globalThis) {
|
|
||||||
RUNTIME = RunTime.Deno;
|
|
||||||
}
|
|
||||||
if ('Bun' in globalThis) {
|
|
||||||
RUNTIME = RunTime.Bun;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { main } = await import(`./${RUNTIME}/index.ts`);
|
|
||||||
await main();
|
await main();
|
||||||
})();
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user