Get raw mode working in Deno
This commit is contained in:
parent
52632ad9a9
commit
b30c4d40d6
@ -1,7 +1,6 @@
|
||||
{
|
||||
"imports": {
|
||||
"std": "https://deno.land/std@0.204.0/",
|
||||
// "/": "./src/",
|
||||
},
|
||||
"lint": {
|
||||
"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
|
||||
*/
|
||||
import { Termios } from './termios.ts'
|
||||
|
||||
export async function main(): Promise<number> {
|
||||
const t = new Termios();
|
||||
t.enableRawMode();
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
for await (const chunk of Deno.stdin.readable) {
|
||||
const char = String(decoder.decode(chunk)).trim();
|
||||
|
||||
if (char === 'q') {
|
||||
t.disableRawMode();
|
||||
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.
|
||||
// import { termiosStruct } from "../common/termios.ts";
|
||||
/**
|
||||
* Implementation to toggle raw mode with Deno runtime
|
||||
*/
|
||||
export class Termios implements ITermios {
|
||||
/**
|
||||
* Are we in raw mode?
|
||||
* @private
|
||||
*/
|
||||
#inRawMode:boolean;
|
||||
|
||||
let libSuffix = '';
|
||||
switch (Deno.build.os) {
|
||||
case 'windows':
|
||||
libSuffix = 'dll';
|
||||
break;
|
||||
case 'darwin':
|
||||
libSuffix = 'dylib';
|
||||
break;
|
||||
default:
|
||||
libSuffix = 'so.6';
|
||||
break;
|
||||
/**
|
||||
* The saved version of the termios struct for cooked/canonical mode
|
||||
* @private
|
||||
*/
|
||||
#cookedTermios: Uint8Array;
|
||||
|
||||
/**
|
||||
* The data for the termios struct we are manipulating
|
||||
* @private
|
||||
*/
|
||||
#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}`;
|
||||
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,
|
||||
);
|
||||
get inRawMode() {
|
||||
return this.#inRawMode;
|
||||
}
|
||||
|
||||
enableRawMode() {
|
||||
if (this.#inRawMode) {
|
||||
throw new Error('Can not enable raw mode when in raw mode');
|
||||
}
|
||||
|
||||
// Get the current termios settings
|
||||
tcgetattr(STDIN_FILENO, this.#ptr);
|
||||
|
||||
// The #ptr property is pointing to the #termios TypedArray. As the pointer
|
||||
// is manipulated, the TypedArray is as well. We will use this to save
|
||||
// the original canonical/cooked terminal settings for disabling raw mode later.
|
||||
this.#cookedTermios = new Uint8Array(this.#termios, 0, 60);
|
||||
|
||||
// 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',
|
||||
}
|
||||
|
||||
import { importForRuntime } from "./common/index.ts";
|
||||
|
||||
/**
|
||||
* Determine the runtime strategy, and go!
|
||||
*/
|
||||
(async () => {
|
||||
let RUNTIME = RunTime.Unknown;
|
||||
if ('Deno' in globalThis) {
|
||||
RUNTIME = RunTime.Deno;
|
||||
}
|
||||
if ('Bun' in globalThis) {
|
||||
RUNTIME = RunTime.Bun;
|
||||
}
|
||||
|
||||
const { main } = await import(`./${RUNTIME}/index.ts`);
|
||||
const { main } = await importForRuntime('./index.ts');
|
||||
await main();
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user