fix: bad exit logic feat: field rename now supports renaming things with multiple widgets
322 lines
8.1 KiB
TypeScript
322 lines
8.1 KiB
TypeScript
// deno-lint-disable-must-await-calls
|
|
import { Cursor } from "./cursor.ts";
|
|
import { colorize } from "./style.ts";
|
|
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
|
|
import { type CLICharEvent, InputManager } from "./InputManager.ts";
|
|
|
|
// export async function cliPrompt(
|
|
// message: string,
|
|
// block?: TerminalBlock,
|
|
// ): Promise<string> {
|
|
// const encoder = new TextEncoder();
|
|
// const input: string[] = [];
|
|
// let cursorPos = 0;
|
|
|
|
// await Deno.stdin.setRaw(true);
|
|
|
|
// const cursorVisible = Cursor["visible"];
|
|
// Cursor.show();
|
|
|
|
// let range: [number, number] = [0, 1];
|
|
// if (block) {
|
|
// range = block.setLines([message + " "]);
|
|
// } else {
|
|
// Deno.stdout.writeSync(encoder.encode(message + " "));
|
|
// }
|
|
|
|
// const render = () => {
|
|
// const line = message + " " + input.join("");
|
|
// const moveTo = `\x1b[${message.length + 2 + cursorPos}G`;
|
|
|
|
// if (block) {
|
|
// block.setPostRenderAction(function () {
|
|
// Deno.stdout.writeSync(
|
|
// encoder.encode(`\x1b[${this["lastRenderRow"]};1H`),
|
|
// );
|
|
// Deno.stdout.writeSync(encoder.encode(moveTo));
|
|
// });
|
|
// range = block.setLines([line], range);
|
|
// } else {
|
|
// Deno.stdout.writeSync(encoder.encode("\x1b[K" + line + moveTo));
|
|
// }
|
|
// };
|
|
|
|
// render();
|
|
|
|
// const buf = new Uint8Array(64); // large enough for most pastes
|
|
// inputLoop:
|
|
// while (true) {
|
|
// const n = await Deno.stdin.read(buf);
|
|
// if (n === null) break;
|
|
|
|
// for (let i = 0; i < n; i++) {
|
|
// const byte = buf[i];
|
|
|
|
// // Ctrl+C
|
|
// if (byte === 3) {
|
|
// block?.clear();
|
|
// block?.["layout"]?.clearAll();
|
|
// await Deno.stdin.setRaw(false);
|
|
// Deno.exit(130);
|
|
// }
|
|
|
|
// if (byte === 13) { // Enter
|
|
// break inputLoop;
|
|
// }
|
|
|
|
// // Escape sequence?
|
|
// if (byte === 27 && i + 1 < n && buf[i + 1] === 91) {
|
|
// const third = buf[i + 2];
|
|
// if (third === 68 && cursorPos > 0) cursorPos--; // Left
|
|
// else if (third === 67 && cursorPos < input.length) cursorPos++; // Right
|
|
// else if (third === 51 && i + 3 < n && buf[i + 3] === 126) { // Delete
|
|
// if (cursorPos < input.length) input.splice(cursorPos, 1);
|
|
// i += 1; // consume tilde
|
|
// }
|
|
// i += 2; // consume ESC [ X
|
|
// continue;
|
|
// }
|
|
|
|
// // Backspace
|
|
// if (byte === 127 || byte === 8) {
|
|
// if (cursorPos > 0) {
|
|
// input.splice(cursorPos - 1, 1);
|
|
// cursorPos--;
|
|
// }
|
|
// continue;
|
|
// }
|
|
|
|
// // Delete (ASCII 46)
|
|
// if (byte === 46 && cursorPos < input.length) {
|
|
// input.splice(cursorPos, 1);
|
|
// continue;
|
|
// }
|
|
|
|
// // Printable
|
|
// if (byte >= 32 && byte <= 126) {
|
|
// input.splice(cursorPos, 0, String.fromCharCode(byte));
|
|
// cursorPos++;
|
|
// }
|
|
|
|
// // Other cases: ignore
|
|
// }
|
|
|
|
// render();
|
|
// }
|
|
|
|
// await Deno.stdin.setRaw(false);
|
|
// if (!cursorVisible) {
|
|
// Cursor.hide();
|
|
// }
|
|
// Deno.stdout.writeSync(encoder.encode("\n"));
|
|
|
|
// return input.join("");
|
|
// }
|
|
|
|
export async function cliPrompt(
|
|
message: string,
|
|
block?: TerminalBlock,
|
|
): Promise<string> {
|
|
const encoder = new TextEncoder();
|
|
const input: string[] = [];
|
|
let cursorPos = 0;
|
|
let range: [number, number] | undefined;
|
|
|
|
Cursor.show();
|
|
|
|
const im = InputManager.getInstance();
|
|
im.activate();
|
|
|
|
const render = () => {
|
|
const line = message + " " + input.join("");
|
|
const moveTo = `\x1b[${message.length + 2 + cursorPos}G`;
|
|
|
|
if (block) {
|
|
block.setPostRenderAction(() => {
|
|
Deno.stdout.writeSync(encoder.encode(moveTo));
|
|
});
|
|
range = block.setLines([line], range);
|
|
} else {
|
|
Deno.stdout.writeSync(encoder.encode("\r\x1b[K" + line + moveTo));
|
|
}
|
|
};
|
|
|
|
const exit = () => {
|
|
im.removeEventListener("enter", onEnter);
|
|
im.removeEventListener("backspace", onBackspace);
|
|
im.removeEventListener("delete", onDelete);
|
|
im.removeEventListener("arrow-left", onLeft);
|
|
im.removeEventListener("arrow-right", onRight);
|
|
im.removeEventListener("char", onKey);
|
|
Cursor.hide();
|
|
};
|
|
|
|
let resolve: null | ((value: string) => void) = null;
|
|
|
|
const onEnter = () => {
|
|
exit();
|
|
resolve?.(input.join(""));
|
|
};
|
|
|
|
const onBackspace = () => {
|
|
if (cursorPos > 0) {
|
|
input.splice(cursorPos - 1, 1);
|
|
cursorPos--;
|
|
render();
|
|
}
|
|
};
|
|
|
|
const onDelete = () => {
|
|
if (cursorPos < input.length) {
|
|
input.splice(cursorPos, 1);
|
|
render();
|
|
}
|
|
};
|
|
|
|
const onLeft = () => {
|
|
if (cursorPos > 0) {
|
|
cursorPos--;
|
|
render();
|
|
}
|
|
};
|
|
|
|
const onRight = () => {
|
|
if (cursorPos < input.length) {
|
|
cursorPos++;
|
|
render();
|
|
}
|
|
};
|
|
|
|
const onKey = (e: Event) => {
|
|
const ke = (e as CLICharEvent).detail;
|
|
input.splice(cursorPos, 0, ke.char);
|
|
cursorPos++;
|
|
render();
|
|
};
|
|
|
|
render();
|
|
|
|
return await new Promise<string>((res) => {
|
|
resolve = res;
|
|
im.addEventListener("enter", onEnter);
|
|
im.addEventListener("backspace", onBackspace);
|
|
im.addEventListener("delete", onDelete);
|
|
im.addEventListener("arrow-left", onLeft);
|
|
im.addEventListener("arrow-right", onRight);
|
|
im.addEventListener("char", onKey);
|
|
});
|
|
}
|
|
|
|
export async function cliConfirm(message: string, block?: TerminalBlock) {
|
|
const im = InputManager.getInstance();
|
|
let inpout = "";
|
|
function isValidInput(input: string) {
|
|
switch (input) {
|
|
case "y":
|
|
case "n":
|
|
return inpout.length === 0;
|
|
case "e":
|
|
return inpout === "y";
|
|
case "s":
|
|
return inpout === "ye";
|
|
case "o":
|
|
return inpout === "n";
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function onKey(e: CLICharEvent) {
|
|
const ke = e.detail;
|
|
const char = String.fromCharCode(ke.key);
|
|
if (isValidInput(char)) {
|
|
inpout += char;
|
|
} else {
|
|
e.stopImmediatePropagation();
|
|
}
|
|
}
|
|
im.addEventListener("char", onKey);
|
|
const value = await cliPrompt(message + " (y/n)", block).then((v) =>
|
|
v.charAt(0).toLowerCase() === "y"
|
|
);
|
|
im.removeEventListener("char", onKey);
|
|
return value;
|
|
}
|
|
|
|
export async function cliAlert(message: string, block?: TerminalBlock) {
|
|
const im = InputManager.getInstance();
|
|
const onKey = (e: CLICharEvent) => {
|
|
e.stopImmediatePropagation();
|
|
};
|
|
im.addEventListener("char", onKey);
|
|
await cliPrompt(
|
|
message + colorize(" Press Enter to continue", "gray"),
|
|
block,
|
|
);
|
|
im.removeEventListener("char", onKey);
|
|
}
|
|
|
|
export function cliLog(
|
|
message: string | object | Array<unknown>,
|
|
block?: TerminalBlock,
|
|
) {
|
|
if (!block) {
|
|
console.log(message);
|
|
} else {
|
|
if (typeof message === "object") message = Deno.inspect(message);
|
|
block.setLines(message.split("\n"));
|
|
}
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
Cursor.hide();
|
|
const layout = new TerminalLayout();
|
|
const title = new TerminalBlock();
|
|
const block = new TerminalBlock();
|
|
const footer = new TerminalBlock();
|
|
block.setPreserveHistory(true);
|
|
// ScrollManager.enable(block);
|
|
title.setLines(["Hello, World!"]);
|
|
title.setFixedHeight(1);
|
|
|
|
footer.setLines(["Press Ctrl+C to exit"]);
|
|
footer.setFixedHeight(1);
|
|
|
|
layout.register("title", title);
|
|
layout.register("block", block);
|
|
layout.register("footer", footer);
|
|
|
|
InputManager.addEventListener("exit", () => {
|
|
layout.clearAll();
|
|
// console.clear();
|
|
Deno.exit(0);
|
|
});
|
|
|
|
Deno.addSignalListener("SIGINT", () => {
|
|
layout.clearAll();
|
|
// console.clear();
|
|
// Deno.exit(0);
|
|
});
|
|
const name = await cliPrompt("Enter your name:", block);
|
|
cliLog(`Hello, ${name}!`, block);
|
|
const single = await cliConfirm("Are you single?", block);
|
|
cliLog(single ? "Do you want to go out with me?" : "Okay", block);
|
|
// ScrollManager.enable(block);
|
|
const loopingConvo = [
|
|
"No response?",
|
|
"I guess that's okay",
|
|
"Maybe I'll see you next week?",
|
|
"Wow, really not going to say anything to me?",
|
|
"Well, if that's how you feel",
|
|
];
|
|
let convo = 0;
|
|
setInterval(() => {
|
|
cliLog(loopingConvo[convo % loopingConvo.length], block);
|
|
convo++;
|
|
}, 2000);
|
|
// setTimeout(async () => {
|
|
// await cliAlert("Well, if that's that...", block);
|
|
// Deno.exit(0);
|
|
// }, 10000);
|
|
}
|