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
|
||||
*/
|
||||
import process from 'node:process';
|
||||
import Ansi from '../common/ansi.ts';
|
||||
import { defaultTerminalSize } from '../common/config.ts';
|
||||
import { readKey } from '../common/fns.ts';
|
||||
import { ITerminal, ITerminalSize } from '../common/types.ts';
|
||||
|
||||
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: async function getTerminalSize(): Promise<ITerminalSize> {
|
||||
async function _getTerminalSizeFromAnsi(): Promise<ITerminalSize> {
|
||||
// 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
|
||||
@ -53,6 +38,31 @@ const BunTerminalIO: ITerminal = {
|
||||
rows,
|
||||
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> {
|
||||
const raw = await BunTerminalIO.readStdinRaw();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import process from 'node:process';
|
||||
import { readKey } from './fns.ts';
|
||||
import { getRuntime, logError } from './runtime.ts';
|
||||
import { getTermios } from './termios.ts';
|
||||
import Editor from './editor.ts';
|
||||
|
||||
export async function main() {
|
||||
@ -8,16 +8,14 @@ export async function main() {
|
||||
const { term } = rt;
|
||||
|
||||
// Setup raw mode, and tear down on error or normal exit
|
||||
const t = await getTermios();
|
||||
t.enableRawMode();
|
||||
process.stdin.setRawMode(true);
|
||||
rt.onExit(() => {
|
||||
t.disableRawMode();
|
||||
t.cleanup();
|
||||
process.stdin.setRawMode(false);
|
||||
});
|
||||
|
||||
// Setup error handler to log to file
|
||||
rt.onEvent('error', (error) => {
|
||||
t.disableRawMode();
|
||||
process.stdin.setRawMode(false);
|
||||
logError(JSON.stringify(error, null, 2));
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import process from 'node:process';
|
||||
import { IRuntime, ITestBase } from './types.ts';
|
||||
import { getTermios } from './termios.ts';
|
||||
import { noop } from './fns.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
|
||||
@ -53,12 +53,9 @@ export function logError(s: unknown): void {
|
||||
*/
|
||||
export function die(s: string | Error): void {
|
||||
logError(s);
|
||||
getTermios().then((t) => {
|
||||
t.disableRawMode();
|
||||
t.cleanup();
|
||||
process.stdin.setRawMode(false);
|
||||
console.error(s);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -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