Compare commits

..

3 Commits

Author SHA1 Message Date
c0ce69af6f Merge pull request 'fix: arrow keys in prompts now move cursor, also implements delete key' (#11) from 1.0.2 into main
All checks were successful
Create Version Tag / version-check (push) Successful in 23s
Create Version Tag / build-release (push) Successful in 1m51s
Create Version Tag / publish-release (push) Successful in 31s
Reviewed-on: #11
2025-05-21 10:50:55 -07:00
041129dc83 fix: arrow keys in prompts now move cursor, also implements delete key 2025-05-21 11:46:51 -06:00
89a3df17e6 fix: field rename skips saves for unmodified files 2025-05-21 11:40:17 -06:00
4 changed files with 84 additions and 24 deletions

View File

@ -44,7 +44,9 @@ export class TerminalLayout {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(
() => this.renderLayout(),
() => {
this.renderLayout();
},
this.debounceDelay,
);
}
@ -76,6 +78,10 @@ export class TerminalLayout {
block.renderInternal(usedLines + 1);
usedLines += lines.length;
}
for (const name of this.layoutOrder) {
const block = this.blocks[name];
block.runPostRenderAction?.();
}
}
clearAll() {
@ -217,7 +223,9 @@ export class TerminalBlock {
const baseRow = startRow ?? this.lastRenderRow;
const excessLines = this.renderHeight - this.renderLines.length;
for (let i = 0; i < excessLines; i++) {
const moveToLine = `[${baseRow + this.renderLines.length + i};1H`;
const moveToLine = `\x1b[${
baseRow + this.renderLines.length + i
};1H\x1b[2K`;
Deno.stdout.writeSync(new TextEncoder().encode(moveToLine));
}
@ -254,6 +262,17 @@ export class TerminalBlock {
return this.fixedHeight ?? 0;
}
private _postRenderAction?: () => void;
setPostRenderAction(action: (this: TerminalBlock) => void) {
this._postRenderAction = action;
}
runPostRenderAction() {
if (this._postRenderAction) {
this._postRenderAction.call(this);
this._postRenderAction = undefined;
}
}
get lineCount() {
return this.renderLines.length;
}

View File

@ -1,4 +1,5 @@
// deno-lint-disable-must-await-calls
import { log } from "util/logfile.ts";
import { Cursor } from "./cursor.ts";
import { colorize } from "./style.ts";
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
@ -9,6 +10,7 @@ export async function cliPrompt(
): Promise<string> {
const encoder = new TextEncoder();
const input: string[] = [];
let cursorPos = 0;
await Deno.stdin.setRaw(true);
@ -22,33 +24,61 @@ export async function cliPrompt(
Deno.stdout.writeSync(encoder.encode(message + " "));
}
const buf = new Uint8Array(1);
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(6); // 6 bytes is enough for all the keys
while (true) {
const n = await Deno.stdin.read(buf);
if (n === null) break;
const byte = buf[0];
const [a, b, c] = buf;
if (byte === 3) { // Ctrl+C
Deno.stdin.setRaw(false);
block?.["layout"]?.clearAll();
if (a === 3) { // Ctrl+C
block?.clear();
block?.["layout"]?.clearAll();
Deno.stdin.setRaw(false);
Deno.exit(130);
}
if (byte === 13) { // Enter
if (a === 13) { // Enter
break;
} else if (byte === 127 || byte === 8) { // Backspace
input.pop();
} else if (byte >= 32 && byte <= 126) { // Printable chars
input.push(String.fromCharCode(byte));
} else if (a === 127 || a === 8) { // Backspace
if (cursorPos > 0) {
input.splice(cursorPos - 1, 1);
cursorPos--;
}
} else if (a === 46) { // Delete
if (cursorPos < input.length) {
input.splice(cursorPos, 1);
}
} else if (a === 27 && b === 91) { // Arrow keys
if (c === 51 && cursorPos < input.length) { // delete
input.splice(cursorPos, 1);
}
if (c === 68 && cursorPos > 0) cursorPos--; // Left
if (c === 67 && cursorPos < input.length) cursorPos++; // Right
} else if (a >= 32 && a <= 126) { // Printable ASCII
input.splice(cursorPos, 0, String.fromCharCode(a));
cursorPos++;
}
const line = message + " " + input.join("");
if (block) {
range = block.setLines([line], range);
} else {
Deno.stdout.writeSync(encoder.encode("\r\x1b[K" + line));
}
render();
}
await Deno.stdin.setRaw(false);
@ -92,13 +122,18 @@ if (import.meta.main) {
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);
Deno.addSignalListener("SIGINT", () => {
layout.clearAll();

View File

@ -1,6 +1,6 @@
{
"name": "@bearmetal/pdf-tools",
"version": "1.0.3",
"version": "1.0.4",
"license": "GPL 3.0",
"tasks": {
"dev": "deno run -A --env-file=.env main.ts",

View File

@ -153,6 +153,7 @@ class RenameFields implements ITool {
const fields = form.getFields();
const foundUpdates: [string, callback][] = [];
let changesMade = false;
for (const field of fields) {
const name = field.getName();
@ -164,6 +165,7 @@ class RenameFields implements ITool {
`${colorize(name, "red")} -> ${colorize(preview, "green")}`,
() => {
applyRename(field, name, patternRegex, toChange);
changesMade = true;
},
]);
}
@ -178,11 +180,15 @@ class RenameFields implements ITool {
);
}
const path = await cliPrompt(
"Save to path (or hit enter to keep current):",
this.block,
);
await savePdf(pdf, path || pdfPath);
if (changesMade) {
const path = await cliPrompt(
"Save to path (or hit enter to keep current):",
this.block,
);
await savePdf(pdf, path || pdfPath);
} else {
cliLog("No changes made, skipping", this.block);
}
}
}
}