Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
343c36a9f0 | |||
123bf51001 | |||
252863c813 | |||
a858ea4b60 | |||
cca6de1877 | |||
b43a837c6a | |||
c0ce69af6f | |||
041129dc83 | |||
89a3df17e6 |
@ -44,7 +44,9 @@ export class TerminalLayout {
|
|||||||
clearTimeout(this.debounceTimer);
|
clearTimeout(this.debounceTimer);
|
||||||
}
|
}
|
||||||
this.debounceTimer = setTimeout(
|
this.debounceTimer = setTimeout(
|
||||||
() => this.renderLayout(),
|
() => {
|
||||||
|
this.renderLayout();
|
||||||
|
},
|
||||||
this.debounceDelay,
|
this.debounceDelay,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -76,6 +78,10 @@ export class TerminalLayout {
|
|||||||
block.renderInternal(usedLines + 1);
|
block.renderInternal(usedLines + 1);
|
||||||
usedLines += lines.length;
|
usedLines += lines.length;
|
||||||
}
|
}
|
||||||
|
for (const name of this.layoutOrder) {
|
||||||
|
const block = this.blocks[name];
|
||||||
|
block.runPostRenderAction?.();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAll() {
|
clearAll() {
|
||||||
@ -217,7 +223,9 @@ export class TerminalBlock {
|
|||||||
const baseRow = startRow ?? this.lastRenderRow;
|
const baseRow = startRow ?? this.lastRenderRow;
|
||||||
const excessLines = this.renderHeight - this.renderLines.length;
|
const excessLines = this.renderHeight - this.renderLines.length;
|
||||||
for (let i = 0; i < excessLines; i++) {
|
for (let i = 0; i < excessLines; i++) {
|
||||||
const moveToLine = `[${baseRow + this.renderLines.length + i};1H[2K`;
|
const moveToLine = `\x1b[${
|
||||||
|
baseRow + this.renderLines.length + i
|
||||||
|
};1H\x1b[2K`;
|
||||||
Deno.stdout.writeSync(new TextEncoder().encode(moveToLine));
|
Deno.stdout.writeSync(new TextEncoder().encode(moveToLine));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,6 +262,17 @@ export class TerminalBlock {
|
|||||||
return this.fixedHeight ?? 0;
|
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() {
|
get lineCount() {
|
||||||
return this.renderLines.length;
|
return this.renderLines.length;
|
||||||
}
|
}
|
||||||
|
100
cli/prompts.ts
100
cli/prompts.ts
@ -1,4 +1,5 @@
|
|||||||
// deno-lint-disable-must-await-calls
|
// deno-lint-disable-must-await-calls
|
||||||
|
import { log } from "util/logfile.ts";
|
||||||
import { Cursor } from "./cursor.ts";
|
import { Cursor } from "./cursor.ts";
|
||||||
import { colorize } from "./style.ts";
|
import { colorize } from "./style.ts";
|
||||||
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
|
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
|
||||||
@ -9,6 +10,7 @@ export async function cliPrompt(
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const input: string[] = [];
|
const input: string[] = [];
|
||||||
|
let cursorPos = 0;
|
||||||
|
|
||||||
await Deno.stdin.setRaw(true);
|
await Deno.stdin.setRaw(true);
|
||||||
|
|
||||||
@ -22,33 +24,84 @@ export async function cliPrompt(
|
|||||||
Deno.stdout.writeSync(encoder.encode(message + " "));
|
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) {
|
while (true) {
|
||||||
const n = await Deno.stdin.read(buf);
|
const n = await Deno.stdin.read(buf);
|
||||||
if (n === null) break;
|
if (n === null) break;
|
||||||
const byte = buf[0];
|
|
||||||
|
|
||||||
if (byte === 3) { // Ctrl+C
|
for (let i = 0; i < n; i++) {
|
||||||
Deno.stdin.setRaw(false);
|
const byte = buf[i];
|
||||||
block?.["layout"]?.clearAll();
|
|
||||||
block?.clear();
|
// Ctrl+C
|
||||||
Deno.exit(130);
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if (byte === 13) { // Enter
|
render();
|
||||||
break;
|
|
||||||
} else if (byte === 127 || byte === 8) { // Backspace
|
|
||||||
input.pop();
|
|
||||||
} else if (byte >= 32 && byte <= 126) { // Printable chars
|
|
||||||
input.push(String.fromCharCode(byte));
|
|
||||||
}
|
|
||||||
|
|
||||||
const line = message + " " + input.join("");
|
|
||||||
if (block) {
|
|
||||||
range = block.setLines([line], range);
|
|
||||||
} else {
|
|
||||||
Deno.stdout.writeSync(encoder.encode("\r\x1b[K" + line));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Deno.stdin.setRaw(false);
|
await Deno.stdin.setRaw(false);
|
||||||
@ -92,13 +145,18 @@ if (import.meta.main) {
|
|||||||
const layout = new TerminalLayout();
|
const layout = new TerminalLayout();
|
||||||
const title = new TerminalBlock();
|
const title = new TerminalBlock();
|
||||||
const block = new TerminalBlock();
|
const block = new TerminalBlock();
|
||||||
|
const footer = new TerminalBlock();
|
||||||
block.setPreserveHistory(true);
|
block.setPreserveHistory(true);
|
||||||
// ScrollManager.enable(block);
|
// ScrollManager.enable(block);
|
||||||
title.setLines(["Hello, World!"]);
|
title.setLines(["Hello, World!"]);
|
||||||
title.setFixedHeight(1);
|
title.setFixedHeight(1);
|
||||||
|
|
||||||
|
footer.setLines(["Press Ctrl+C to exit"]);
|
||||||
|
footer.setFixedHeight(1);
|
||||||
|
|
||||||
layout.register("title", title);
|
layout.register("title", title);
|
||||||
layout.register("block", block);
|
layout.register("block", block);
|
||||||
|
layout.register("footer", footer);
|
||||||
|
|
||||||
Deno.addSignalListener("SIGINT", () => {
|
Deno.addSignalListener("SIGINT", () => {
|
||||||
layout.clearAll();
|
layout.clearAll();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bearmetal/pdf-tools",
|
"name": "@bearmetal/pdf-tools",
|
||||||
"version": "1.0.3",
|
"version": "1.0.7",
|
||||||
"license": "GPL 3.0",
|
"license": "GPL 3.0",
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run -A --env-file=.env main.ts",
|
"dev": "deno run -A --env-file=.env main.ts",
|
||||||
|
@ -153,6 +153,7 @@ class RenameFields implements ITool {
|
|||||||
const fields = form.getFields();
|
const fields = form.getFields();
|
||||||
|
|
||||||
const foundUpdates: [string, callback][] = [];
|
const foundUpdates: [string, callback][] = [];
|
||||||
|
let changesMade = false;
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
const name = field.getName();
|
const name = field.getName();
|
||||||
@ -164,6 +165,7 @@ class RenameFields implements ITool {
|
|||||||
`${colorize(name, "red")} -> ${colorize(preview, "green")}`,
|
`${colorize(name, "red")} -> ${colorize(preview, "green")}`,
|
||||||
() => {
|
() => {
|
||||||
applyRename(field, name, patternRegex, toChange);
|
applyRename(field, name, patternRegex, toChange);
|
||||||
|
changesMade = true;
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -178,11 +180,15 @@ class RenameFields implements ITool {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = await cliPrompt(
|
if (changesMade) {
|
||||||
"Save to path (or hit enter to keep current):",
|
const path = await cliPrompt(
|
||||||
this.block,
|
"Save to path (or hit enter to keep current):",
|
||||||
);
|
this.block,
|
||||||
await savePdf(pdf, path || pdfPath);
|
);
|
||||||
|
await savePdf(pdf, path || pdfPath);
|
||||||
|
} else {
|
||||||
|
cliLog("No changes made, skipping", this.block);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user