Compare commits

...

9 Commits
v1.0.3 ... main

Author SHA1 Message Date
343c36a9f0 Merge pull request 'fix: I really am thick' (#14) from 1.0.2 into main
All checks were successful
Create Version Tag / version-check (push) Successful in 21s
Create Version Tag / build-release (push) Successful in 1m58s
Create Version Tag / publish-release (push) Successful in 31s
Reviewed-on: #14
2025-05-21 11:56:09 -07:00
123bf51001 fix: I really am thick 2025-05-21 12:55:45 -06:00
252863c813 Merge pull request 'fix: I am stupid and forgot to press enter' (#13) 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 1m53s
Create Version Tag / publish-release (push) Successful in 30s
Reviewed-on: #13
2025-05-21 11:41:45 -07:00
a858ea4b60 fix: I am stupid and forgot to press enter 2025-05-21 12:26:40 -06:00
cca6de1877 Merge pull request 'fix: pasting in prompt no worky' (#12) from 1.0.2 into main
All checks were successful
Create Version Tag / version-check (push) Successful in 22s
Create Version Tag / build-release (push) Successful in 3m54s
Create Version Tag / publish-release (push) Successful in 36s
Reviewed-on: #12
2025-05-21 11:13:06 -07:00
b43a837c6a fix: pasting in prompt no worky 2025-05-21 12:12:37 -06:00
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 112 additions and 29 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,84 @@ 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(64); // large enough for most pastes
inputLoop:
while (true) {
const n = await Deno.stdin.read(buf);
if (n === null) break;
const byte = buf[0];
if (byte === 3) { // Ctrl+C
Deno.stdin.setRaw(false);
block?.["layout"]?.clearAll();
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;
} else if (byte === 127 || byte === 8) { // Backspace
input.pop();
} else if (byte >= 32 && byte <= 126) { // Printable chars
input.push(String.fromCharCode(byte));
break inputLoop;
}
const line = message + " " + input.join("");
if (block) {
range = block.setLines([line], range);
} else {
Deno.stdout.writeSync(encoder.encode("\r\x1b[K" + line));
// 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);
@ -92,13 +145,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.7",
"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 {
);
}
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);
}
}
}
}