change: selects now use inputmanager
fix: bad exit logic feat: field rename now supports renaming things with multiple widgets
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { callback } from "../types.ts";
|
||||
import { type CLICharEvent, InputManager } from "./InputManager.ts";
|
||||
import { colorize } from "./style.ts";
|
||||
import { TerminalBlock } from "./TerminalLayout.ts";
|
||||
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
|
||||
|
||||
interface ISelectMenuConfig {
|
||||
terminalBlock?: TerminalBlock;
|
||||
@@ -62,50 +63,65 @@ export async function selectMenuInteractive(
|
||||
|
||||
let inputBuffer = "";
|
||||
|
||||
// Function to handle input
|
||||
async function handleInput() {
|
||||
const buf = new Uint8Array(3); // arrow keys send 3 bytes
|
||||
while (true) {
|
||||
renderMenu();
|
||||
const n = await Deno.stdin.read(buf);
|
||||
if (n === null) break;
|
||||
const im = InputManager.getInstance();
|
||||
im.activate();
|
||||
|
||||
const [a, b, c] = buf;
|
||||
const onUp = (e: Event) => {
|
||||
e.stopImmediatePropagation();
|
||||
selected = (selected - 1 + options.length) % options.length;
|
||||
renderMenu();
|
||||
};
|
||||
|
||||
if (a === 3) {
|
||||
Deno.stdin.setRaw(false);
|
||||
terminalBlock?.["layout"]?.clearAll();
|
||||
Deno.exit(130);
|
||||
}
|
||||
|
||||
if (a === 13) { // Enter key
|
||||
if (inputBuffer) {
|
||||
const parsed = parseInt(inputBuffer);
|
||||
if (!isNaN(parsed)) {
|
||||
selected = parsed - 1;
|
||||
}
|
||||
inputBuffer = "";
|
||||
}
|
||||
break;
|
||||
} else if (a === 27 && b === 91) { // Arrow keys
|
||||
inputBuffer = "";
|
||||
if (c === 65) { // Up
|
||||
selected = (selected - 1 + options.length) % options.length;
|
||||
} else if (c === 66) { // Down
|
||||
selected = (selected + 1) % options.length;
|
||||
}
|
||||
} else if (a >= 48 && a <= 57) {
|
||||
inputBuffer += String.fromCharCode(a);
|
||||
} else if (a === 8) {
|
||||
inputBuffer = inputBuffer.slice(0, -1);
|
||||
const onDown = (e: Event) => {
|
||||
e.stopImmediatePropagation();
|
||||
selected = (selected + 1) % options.length;
|
||||
renderMenu();
|
||||
};
|
||||
|
||||
const onKey = (e: CLICharEvent) => {
|
||||
e.stopImmediatePropagation();
|
||||
const ke = e.detail;
|
||||
const char = String.fromCharCode(ke.key);
|
||||
inputBuffer += char;
|
||||
};
|
||||
|
||||
const onBackspace = (e: Event) => {
|
||||
e.stopImmediatePropagation();
|
||||
inputBuffer = inputBuffer.slice(0, -1);
|
||||
};
|
||||
|
||||
let resolve: null | ((value: string) => void) = null;
|
||||
|
||||
const onEnter = (e: Event) => {
|
||||
e.stopImmediatePropagation();
|
||||
if (inputBuffer) {
|
||||
const parsed = parseInt(inputBuffer);
|
||||
if (!isNaN(parsed)) {
|
||||
selected = parsed - 1;
|
||||
}
|
||||
inputBuffer = "";
|
||||
}
|
||||
im.removeEventListener("arrow-up", onUp);
|
||||
im.removeEventListener("arrow-down", onDown);
|
||||
im.removeEventListener("char", onKey);
|
||||
im.removeEventListener("backspace", onBackspace);
|
||||
im.removeEventListener("enter", onEnter);
|
||||
resolve?.(options[selected]);
|
||||
};
|
||||
|
||||
renderMenu();
|
||||
await new Promise<string>((res) => {
|
||||
resolve = res;
|
||||
im.addEventListener("char", onKey);
|
||||
im.addEventListener("backspace", onBackspace);
|
||||
im.addEventListener("enter", onEnter);
|
||||
im.addEventListener("arrow-up", onUp);
|
||||
im.addEventListener("arrow-down", onDown);
|
||||
});
|
||||
|
||||
Deno.stdin.setRaw(false);
|
||||
return options[selected];
|
||||
}
|
||||
terminalBlock.setLines(["Selected: " + options[selected]], range);
|
||||
return await handleInput();
|
||||
|
||||
return options[selected];
|
||||
}
|
||||
|
||||
export async function multiSelectMenuInteractive(
|
||||
@@ -117,9 +133,7 @@ export async function multiSelectMenuInteractive(
|
||||
let selected = 0;
|
||||
let selectedOptions: number[] = config?.initialSelections || [];
|
||||
|
||||
const rawValues = new Set(
|
||||
options.map((i) => typeof i === "string" ? i : i[0]),
|
||||
).values().toArray();
|
||||
const rawValues = options.map((i) => typeof i === "string" ? i : i[0]);
|
||||
|
||||
if (rawValues.length !== options.length) {
|
||||
throw new Error("Duplicate options in multi-select menu");
|
||||
@@ -158,44 +172,52 @@ export async function multiSelectMenuInteractive(
|
||||
range = terminalBlock.setLines(lines, range);
|
||||
}
|
||||
|
||||
// Function to handle input
|
||||
async function handleInput() {
|
||||
const buf = new Uint8Array(3); // arrow keys send 3 bytes
|
||||
while (true) {
|
||||
renderMenu();
|
||||
const n = await Deno.stdin.read(buf);
|
||||
if (n === null) break;
|
||||
const im = InputManager.getInstance();
|
||||
im.activate();
|
||||
|
||||
const [a, b, c] = buf;
|
||||
let resolve = null as null | ((value: number[]) => void);
|
||||
|
||||
if (a === 3) {
|
||||
Deno.stdin.setRaw(false);
|
||||
terminalBlock?.["layout"]?.clearAll();
|
||||
Deno.exit(130);
|
||||
}
|
||||
const onUp = (e: Event) => {
|
||||
e.stopImmediatePropagation();
|
||||
selected = (selected - 1 + options.length) % options.length;
|
||||
renderMenu();
|
||||
};
|
||||
|
||||
if (a === 13) { // Enter key
|
||||
break;
|
||||
} else if (a === 27 && b === 91) { // Arrow keys
|
||||
if (c === 65) { // Up
|
||||
selected = (selected - 1 + options.length) % options.length;
|
||||
} else if (c === 66) { // Down
|
||||
selected = (selected + 1) % options.length;
|
||||
}
|
||||
} else if (a === 32) { // Space
|
||||
Deno.stdout.writeSync(new TextEncoder().encode("\x07"));
|
||||
if (selectedOptions.includes(selected)) {
|
||||
selectedOptions = selectedOptions.filter((i) => i !== selected);
|
||||
} else {
|
||||
selectedOptions.push(selected);
|
||||
}
|
||||
}
|
||||
const onDown = (e: Event) => {
|
||||
e.stopImmediatePropagation();
|
||||
selected = (selected + 1) % options.length;
|
||||
renderMenu();
|
||||
};
|
||||
|
||||
const onSpace = (e: CLICharEvent) => {
|
||||
if (e.detail.char !== " ") return;
|
||||
e.stopImmediatePropagation();
|
||||
if (selectedOptions.includes(selected)) {
|
||||
selectedOptions = selectedOptions.filter((i) => i !== selected);
|
||||
} else {
|
||||
selectedOptions.push(selected);
|
||||
}
|
||||
renderMenu();
|
||||
};
|
||||
|
||||
Deno.stdin.setRaw(false);
|
||||
return selectedOptions;
|
||||
}
|
||||
const selections = await handleInput();
|
||||
const onEnter = (e: Event) => {
|
||||
e.stopImmediatePropagation();
|
||||
resolve?.(selectedOptions);
|
||||
im.removeEventListener("arrow-up", onUp);
|
||||
im.removeEventListener("arrow-down", onDown);
|
||||
im.removeEventListener("char", onSpace);
|
||||
im.removeEventListener("enter", onEnter);
|
||||
};
|
||||
|
||||
renderMenu();
|
||||
|
||||
const selections = await new Promise<number[]>((res) => {
|
||||
resolve = res;
|
||||
im.addEventListener("arrow-up", onUp);
|
||||
im.addEventListener("arrow-down", onDown);
|
||||
im.addEventListener("char", onSpace);
|
||||
im.addEventListener("enter", onEnter);
|
||||
});
|
||||
for (const optionI of selections) {
|
||||
const option = options[optionI];
|
||||
if (Array.isArray(option)) {
|
||||
@@ -208,17 +230,17 @@ export async function multiSelectMenuInteractive(
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
// const layout = new TerminalLayout();
|
||||
// const block = new TerminalBlock();
|
||||
// const titleBlock = new TerminalBlock();
|
||||
// const postBlock = new TerminalBlock();
|
||||
// titleBlock.setLines(["An incredible fruit menu!"]);
|
||||
// postBlock.setLines(["I'm here too!"]);
|
||||
// titleBlock.setFixedHeight(1);
|
||||
// postBlock.setFixedHeight(1);
|
||||
// layout.register("title", titleBlock);
|
||||
// layout.register("block", block);
|
||||
// layout.register("post", postBlock);
|
||||
const layout = new TerminalLayout();
|
||||
const block = new TerminalBlock();
|
||||
const titleBlock = new TerminalBlock();
|
||||
const postBlock = new TerminalBlock();
|
||||
InputManager.addEventListener("exit", () => layout.clearAll());
|
||||
titleBlock.setLines(["An incredible fruit menu!"]);
|
||||
postBlock.setLines(["I'm here too!"]);
|
||||
titleBlock.setFixedHeight(1);
|
||||
postBlock.setFixedHeight(1);
|
||||
layout.register("title", titleBlock);
|
||||
layout.register("block", block);
|
||||
|
||||
// const val = await selectMenuInteractive("choose a fruit", [
|
||||
// "apple",
|
||||
@@ -276,8 +298,8 @@ if (import.meta.main) {
|
||||
"ximenia",
|
||||
"yuzu",
|
||||
"zucchini",
|
||||
]);
|
||||
console.log(val);
|
||||
], { terminalBlock: block });
|
||||
// console.log(val);
|
||||
|
||||
// Deno.stdout.writeSync(new TextEncoder().encode("\x07"));
|
||||
}
|
||||
|
Reference in New Issue
Block a user