From 89a3df17e68c37d7e42710ce8332c3f8da03a2f6 Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 21 May 2025 11:40:17 -0600 Subject: [PATCH 1/2] fix: field rename skips saves for unmodified files --- tools/fieldRename.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tools/fieldRename.ts b/tools/fieldRename.ts index 9fb6868..00fa603 100644 --- a/tools/fieldRename.ts +++ b/tools/fieldRename.ts @@ -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); + } } } } From 041129dc8316e6c0141457699bb4681b2a98d3e1 Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 21 May 2025 11:46:51 -0600 Subject: [PATCH 2/2] fix: arrow keys in prompts now move cursor, also implements delete key --- cli/TerminalLayout.ts | 23 +++++++++++++-- cli/prompts.ts | 67 ++++++++++++++++++++++++++++++++----------- deno.json | 2 +- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/cli/TerminalLayout.ts b/cli/TerminalLayout.ts index 4866444..83ed712 100644 --- a/cli/TerminalLayout.ts +++ b/cli/TerminalLayout.ts @@ -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; } diff --git a/cli/prompts.ts b/cli/prompts.ts index e183ef5..0d23d65 100644 --- a/cli/prompts.ts +++ b/cli/prompts.ts @@ -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 { 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(); diff --git a/deno.json b/deno.json index fc0d08d..cbd45d6 100644 --- a/deno.json +++ b/deno.json @@ -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",