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 { 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); }