Remove buggy FFI implementation in favor of Node API (implemented by Bun and Deno)
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
This commit is contained in:
parent
9458794fa3
commit
ab42873182
@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* This is all the nasty ffi setup for the bun runtime
|
|
||||||
*/
|
|
||||||
import { dlopen, ptr, suffix } from 'bun:ffi';
|
|
||||||
import { IFFI } from '../common/runtime.ts';
|
|
||||||
|
|
||||||
const getLib = (name: string) => {
|
|
||||||
return dlopen(
|
|
||||||
name,
|
|
||||||
{
|
|
||||||
tcgetattr: {
|
|
||||||
args: ['i32', 'pointer'],
|
|
||||||
returns: 'i32',
|
|
||||||
},
|
|
||||||
tcsetattr: {
|
|
||||||
args: ['i32', 'i32', 'pointer'],
|
|
||||||
returns: 'i32',
|
|
||||||
},
|
|
||||||
cfmakeraw: {
|
|
||||||
args: ['pointer'],
|
|
||||||
returns: 'void',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let cStdLib: any = { symbols: {} };
|
|
||||||
|
|
||||||
try {
|
|
||||||
cStdLib = getLib(`libc.${suffix}`);
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
cStdLib = getLib(`libc.${suffix}.6`);
|
|
||||||
} catch {
|
|
||||||
throw new Error('Could not find c standard library');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { tcgetattr, tcsetattr, cfmakeraw } = cStdLib.symbols;
|
|
||||||
let closed = false;
|
|
||||||
const BunFFI: IFFI = {
|
|
||||||
tcgetattr,
|
|
||||||
tcsetattr,
|
|
||||||
cfmakeraw,
|
|
||||||
getPointer: ptr,
|
|
||||||
close: () => {
|
|
||||||
if (!closed) {
|
|
||||||
cStdLib.close();
|
|
||||||
closed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do nothing if FFI library was already closed
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BunFFI;
|
|
@ -1,28 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Wrap the runtime-specific hook into stdin
|
* Wrap the runtime-specific hook into stdin
|
||||||
*/
|
*/
|
||||||
|
import process from 'node:process';
|
||||||
import Ansi from '../common/ansi.ts';
|
import Ansi from '../common/ansi.ts';
|
||||||
import { defaultTerminalSize } from '../common/config.ts';
|
import { defaultTerminalSize } from '../common/config.ts';
|
||||||
import { readKey } from '../common/fns.ts';
|
import { readKey } from '../common/fns.ts';
|
||||||
import { ITerminal, ITerminalSize } from '../common/types.ts';
|
import { ITerminal, ITerminalSize } from '../common/types.ts';
|
||||||
|
|
||||||
const BunTerminalIO: ITerminal = {
|
async function _getTerminalSizeFromAnsi(): Promise<ITerminalSize> {
|
||||||
// Deno only returns arguments passed to the script, so
|
|
||||||
// remove the bun runtime executable, and entry script arguments
|
|
||||||
// to have consistent argument lists
|
|
||||||
argv: (Bun.argv.length > 2) ? Bun.argv.slice(2) : [],
|
|
||||||
inputLoop: async function* inputLoop() {
|
|
||||||
for await (const chunk of Bun.stdin.stream()) {
|
|
||||||
yield chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get the size of the terminal window via ANSI codes
|
|
||||||
* @see https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html#window-size-the-hard-way
|
|
||||||
*/
|
|
||||||
getTerminalSize: async function getTerminalSize(): Promise<ITerminalSize> {
|
|
||||||
// Tell the cursor to move to Row 999 and Column 999
|
// Tell the cursor to move to Row 999 and Column 999
|
||||||
// Since this command specifically doesn't go off the screen
|
// 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
|
// When we ask where the cursor is, we should get the size of the screen
|
||||||
@ -53,6 +38,31 @@ const BunTerminalIO: ITerminal = {
|
|||||||
rows,
|
rows,
|
||||||
cols,
|
cols,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const BunTerminalIO: ITerminal = {
|
||||||
|
// Deno only returns arguments passed to the script, so
|
||||||
|
// remove the bun runtime executable, and entry script arguments
|
||||||
|
// to have consistent argument lists
|
||||||
|
argv: (Bun.argv.length > 2) ? Bun.argv.slice(2) : [],
|
||||||
|
inputLoop: async function* inputLoop() {
|
||||||
|
for await (const chunk of Bun.stdin.stream()) {
|
||||||
|
yield chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the size of the terminal window via ANSI codes
|
||||||
|
* @see https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html#window-size-the-hard-way
|
||||||
|
*/
|
||||||
|
getTerminalSize: function getTerminalSize(): Promise<ITerminalSize> {
|
||||||
|
const [cols, rows] = process.stdout.getWindowSize();
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
rows,
|
||||||
|
cols,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
readStdin: async function (): Promise<string | null> {
|
readStdin: async function (): Promise<string | null> {
|
||||||
const raw = await BunTerminalIO.readStdinRaw();
|
const raw = await BunTerminalIO.readStdinRaw();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import process from 'node:process';
|
||||||
import { readKey } from './fns.ts';
|
import { readKey } from './fns.ts';
|
||||||
import { getRuntime, logError } from './runtime.ts';
|
import { getRuntime, logError } from './runtime.ts';
|
||||||
import { getTermios } from './termios.ts';
|
|
||||||
import Editor from './editor.ts';
|
import Editor from './editor.ts';
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
@ -8,16 +8,14 @@ export async function main() {
|
|||||||
const { term } = rt;
|
const { term } = rt;
|
||||||
|
|
||||||
// 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();
|
process.stdin.setRawMode(true);
|
||||||
t.enableRawMode();
|
|
||||||
rt.onExit(() => {
|
rt.onExit(() => {
|
||||||
t.disableRawMode();
|
process.stdin.setRawMode(false);
|
||||||
t.cleanup();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup error handler to log to file
|
// Setup error handler to log to file
|
||||||
rt.onEvent('error', (error) => {
|
rt.onEvent('error', (error) => {
|
||||||
t.disableRawMode();
|
process.stdin.setRawMode(false);
|
||||||
logError(JSON.stringify(error, null, 2));
|
logError(JSON.stringify(error, null, 2));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import process from 'node:process';
|
||||||
import { IRuntime, ITestBase } from './types.ts';
|
import { IRuntime, ITestBase } from './types.ts';
|
||||||
import { getTermios } from './termios.ts';
|
|
||||||
import { noop } from './fns.ts';
|
import { noop } from './fns.ts';
|
||||||
import { SCROLL_ERR_FILE, SCROLL_LOG_FILE } from './config.ts';
|
import { SCROLL_ERR_FILE, SCROLL_LOG_FILE } from './config.ts';
|
||||||
|
|
||||||
export type { IFFI, IFileIO, IRuntime, ITerminal } from './types.ts';
|
export type { IFileIO, IRuntime, ITerminal } from './types.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which Typescript runtime is currently being used
|
* Which Typescript runtime is currently being used
|
||||||
@ -53,12 +53,9 @@ export function logError(s: unknown): void {
|
|||||||
*/
|
*/
|
||||||
export function die(s: string | Error): void {
|
export function die(s: string | Error): void {
|
||||||
logError(s);
|
logError(s);
|
||||||
getTermios().then((t) => {
|
process.stdin.setRawMode(false);
|
||||||
t.disableRawMode();
|
|
||||||
t.cleanup();
|
|
||||||
console.error(s);
|
console.error(s);
|
||||||
getRuntime().then((r) => r.exit());
|
getRuntime().then((r) => r.exit());
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
import { die, IFFI, importForRuntime, log, LogLevel } from './runtime.ts';
|
|
||||||
|
|
||||||
export const STDIN_FILENO = 0;
|
|
||||||
export const TCSANOW = 0;
|
|
||||||
|
|
||||||
export const TERMIOS_SIZE = 60;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation to toggle raw mode
|
|
||||||
*/
|
|
||||||
class Termios {
|
|
||||||
/**
|
|
||||||
* The ffi implementation for the current runtime
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
#ffi: IFFI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Are we in raw mode?
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
#inRawMode: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Has the nasty ffi stuff been cleaned up?
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
#cleaned: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The pointer to the termios struct
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
readonly #ptr: unknown;
|
|
||||||
|
|
||||||
constructor(ffi: IFFI) {
|
|
||||||
this.#ffi = ffi;
|
|
||||||
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 = ffi.getPointer(this.#termios);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if (!this.#cleaned) {
|
|
||||||
this.#ffi.close();
|
|
||||||
|
|
||||||
this.#cleaned = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
log('Attempting to cleanup Termios class again', LogLevel.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
enableRawMode() {
|
|
||||||
if (this.#inRawMode) {
|
|
||||||
throw new Error('Can not enable raw mode when in raw mode');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current termios settings
|
|
||||||
let res = this.#ffi.tcgetattr(STDIN_FILENO, this.#ptr);
|
|
||||||
if (res === -1) {
|
|
||||||
die('Failed to get terminal settings');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// @ts-ignore: bad type definition
|
|
||||||
this.#cookedTermios = new Uint8Array(this.#termios, 0, 60);
|
|
||||||
|
|
||||||
// Update termios struct with the raw settings
|
|
||||||
this.#ffi.cfmakeraw(this.#ptr);
|
|
||||||
|
|
||||||
// Actually set the new termios settings
|
|
||||||
res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
|
|
||||||
if (res === -1) {
|
|
||||||
die("Failed to update terminal settings. Can't enter raw mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
log(
|
|
||||||
'Attempting to disable raw mode when not in raw mode',
|
|
||||||
LogLevel.Warning,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldTermiosPtr = this.#ffi.getPointer(this.#cookedTermios);
|
|
||||||
const res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
|
|
||||||
if (res === -1) {
|
|
||||||
die('Failed to restore canonical mode.');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#inRawMode = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let termiosSingleton: Termios | null = null;
|
|
||||||
|
|
||||||
export const getTermios = async () => {
|
|
||||||
if (termiosSingleton !== null) {
|
|
||||||
return termiosSingleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the runtime-specific ffi wrappers
|
|
||||||
const ffi = await importForRuntime('ffi');
|
|
||||||
termiosSingleton = new Termios(ffi);
|
|
||||||
|
|
||||||
return termiosSingleton;
|
|
||||||
};
|
|
@ -8,37 +8,6 @@ export interface ITerminalSize {
|
|||||||
cols: number;
|
cols: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The native functions for getting/setting terminal settings
|
|
||||||
*/
|
|
||||||
export interface IFFI {
|
|
||||||
/**
|
|
||||||
* Get the existing termios settings (for canonical mode)
|
|
||||||
*/
|
|
||||||
tcgetattr(fd: number, termiosPtr: unknown): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the termios settings
|
|
||||||
*/
|
|
||||||
tcsetattr(fd: number, act: number, termiosPtr: unknown): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the termios pointer with raw mode settings
|
|
||||||
*/
|
|
||||||
cfmakeraw(termiosPtr: unknown): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a TypedArray to an opaque pointer for ffi calls
|
|
||||||
*/
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
getPointer(ta: any): unknown;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the FFI handle
|
|
||||||
*/
|
|
||||||
close(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Runtime adapter interfaces
|
// Runtime adapter interfaces
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
// Deno-specific ffi code
|
|
||||||
import { IFFI } from '../common/runtime.ts';
|
|
||||||
|
|
||||||
let suffix = '';
|
|
||||||
switch (Deno.build.os) {
|
|
||||||
case 'windows':
|
|
||||||
suffix = 'dll';
|
|
||||||
break;
|
|
||||||
case 'darwin':
|
|
||||||
suffix = 'dylib';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
suffix = 'so.6';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cSharedLib = `libc.${suffix}`;
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { tcgetattr, tcsetattr, cfmakeraw } = cStdLib.symbols;
|
|
||||||
let closed = false;
|
|
||||||
const DenoFFI: IFFI = {
|
|
||||||
tcgetattr,
|
|
||||||
tcsetattr,
|
|
||||||
cfmakeraw,
|
|
||||||
getPointer: Deno.UnsafePointer.of,
|
|
||||||
close: () => {
|
|
||||||
if (!closed) {
|
|
||||||
cStdLib.close();
|
|
||||||
closed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do nothing if FFI library was already closed
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DenoFFI;
|
|
Loading…
Reference in New Issue
Block a user