initial cli api, some movement on tool selection
This commit is contained in:
186
cli/selectMenu.ts
Normal file
186
cli/selectMenu.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { colorize } from "./colorize.ts";
|
||||
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
|
||||
|
||||
interface ISelectMenuConfig {
|
||||
multiSelect?: boolean;
|
||||
terminalBlock?: TerminalBlock;
|
||||
}
|
||||
|
||||
export function selectMenu(items: string[]) {
|
||||
const menu = items.map((i, index) => `${index + 1}. ${i}`).join("\n");
|
||||
console.log(menu);
|
||||
const index = parseInt(prompt("Please select an option:") || "1") - 1;
|
||||
return items[index];
|
||||
}
|
||||
|
||||
export async function selectMenuInteractive(
|
||||
q: string,
|
||||
options: string[],
|
||||
config?: ISelectMenuConfig,
|
||||
): Promise<string | null> {
|
||||
Deno.stdin.setRaw(true);
|
||||
let selected = 0;
|
||||
|
||||
if (config?.multiSelect) {
|
||||
console.warn("Multi-select not implemented yet");
|
||||
return null;
|
||||
}
|
||||
|
||||
const terminalBlock = config?.terminalBlock || new TerminalBlock();
|
||||
if (!config?.terminalBlock) {
|
||||
terminalBlock.setRenderHeight(Deno.consoleSize().rows);
|
||||
}
|
||||
|
||||
console.log(terminalBlock.getRenderHeight());
|
||||
|
||||
function renderMenu() {
|
||||
const { rows } = Deno.consoleSize();
|
||||
const terminalHeight = terminalBlock.getRenderHeight() || rows;
|
||||
const maxHeight = Math.min(terminalHeight - 1, options.length);
|
||||
let startPoint = Math.max(0, selected - Math.floor(maxHeight / 2));
|
||||
const endPoint = Math.min(options.length, startPoint + maxHeight);
|
||||
if (endPoint - startPoint < maxHeight) {
|
||||
startPoint = Math.max(0, options.length - maxHeight);
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(colorize(q, "green"));
|
||||
for (let i = startPoint; i < endPoint; i++) {
|
||||
const option = options[i];
|
||||
if (i === selected) {
|
||||
lines.push(`${numberAndPadding(i, ">")}${colorize(option, "porple")}`);
|
||||
} else {
|
||||
lines.push(`${numberAndPadding(i)}${option}`);
|
||||
}
|
||||
}
|
||||
|
||||
terminalBlock.setLines(lines);
|
||||
}
|
||||
|
||||
function numberAndPadding(i: number, prefix?: string) {
|
||||
const padded = `${i + 1}. `.padStart(
|
||||
options.length.toString().length + 4,
|
||||
);
|
||||
return prefix ? padded.replace(" ", prefix.substring(0, 1)) : padded;
|
||||
}
|
||||
|
||||
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 [a, b, c] = buf;
|
||||
|
||||
if (a === 3) {
|
||||
Deno.stdin.setRaw(false);
|
||||
console.log("\nInterrupted\n");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
terminalBlock.clear();
|
||||
Deno.stdin.setRaw(false);
|
||||
return options[selected];
|
||||
}
|
||||
return await handleInput();
|
||||
}
|
||||
|
||||
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 val = await selectMenuInteractive("choose a fruit", [
|
||||
// "apple",
|
||||
// "banana",
|
||||
// "cherry",
|
||||
// "date",
|
||||
// "elderberry",
|
||||
// "fig",
|
||||
// "grape",
|
||||
// "honeydew",
|
||||
// "ilama",
|
||||
// "jackfruit",
|
||||
// "kiwi",
|
||||
// "lemon",
|
||||
// "mango",
|
||||
// "nectarine",
|
||||
// "orange",
|
||||
// "papaya",
|
||||
// "peach",
|
||||
// "pineapple",
|
||||
// "pomegranate",
|
||||
// "quince",
|
||||
// "raspberry",
|
||||
// "strawberry",
|
||||
// "tangerine",
|
||||
// "watermelon",
|
||||
// ], { terminalBlock: block });
|
||||
// layout.clearAll();
|
||||
// console.log(val);
|
||||
|
||||
const val = await selectMenuInteractive("choose a fruit", [
|
||||
"apple",
|
||||
"banana",
|
||||
"cherry",
|
||||
"date",
|
||||
"elderberry",
|
||||
"fig",
|
||||
"grape",
|
||||
"honeydew",
|
||||
"ilama",
|
||||
"jackfruit",
|
||||
"kiwi",
|
||||
"lemon",
|
||||
"mango",
|
||||
"nectarine",
|
||||
"orange",
|
||||
"papaya",
|
||||
"quince",
|
||||
"raspberry",
|
||||
"strawberry",
|
||||
"tangerine",
|
||||
"udara",
|
||||
"vogelbeere",
|
||||
"watermelon",
|
||||
"ximenia",
|
||||
"yuzu",
|
||||
"zucchini",
|
||||
]);
|
||||
console.log(val);
|
||||
}
|
Reference in New Issue
Block a user