@smoose/pi-beautify 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +62 -4
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CustomEditor, type ExtensionAPI, type KeybindingsManager, type Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { ImageContent } from "@earendil-works/pi-ai";
|
|
3
|
-
import { truncateToWidth, type EditorTheme, type TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import { getKeybindings, matchesKey, truncateToWidth, type EditorTheme, type TUI } from "@earendil-works/pi-tui";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { extname } from "node:path";
|
|
6
6
|
|
|
@@ -12,6 +12,7 @@ interface Attachment {
|
|
|
12
12
|
|
|
13
13
|
const CLIPBOARD_PATH_RE = /(?:[^\s"'`<>]+[\\/])?pi-clipboard-[0-9a-f-]+\.(?:png|jpe?g|webp|gif)/gi;
|
|
14
14
|
const TOKEN_RE = /\[image(\d+)\]/g;
|
|
15
|
+
const TOKEN_LINE_RE = /\[image\d+\]/g;
|
|
15
16
|
|
|
16
17
|
function mimeTypeForPath(path: string): string {
|
|
17
18
|
const ext = extname(path).toLowerCase();
|
|
@@ -45,10 +46,15 @@ class BeautifyEditor extends CustomEditor {
|
|
|
45
46
|
|
|
46
47
|
handleInput(data: string): void {
|
|
47
48
|
const isImagePaste = this.appKeybindings.matches(data, "app.clipboard.pasteImage");
|
|
49
|
+
if (this.deleteImageTokenAtCursor(data)) return;
|
|
48
50
|
super.handleInput(data);
|
|
49
51
|
if (isImagePaste) this.scheduleClipboardPathScan();
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
insertTextAtCursor(text: string): void {
|
|
55
|
+
super.insertTextAtCursor(this.replaceClipboardPathsInText(text));
|
|
56
|
+
}
|
|
57
|
+
|
|
52
58
|
render(width: number): string[] {
|
|
53
59
|
let lines = super.render(width);
|
|
54
60
|
const currentTheme = this.getTheme();
|
|
@@ -67,20 +73,72 @@ class BeautifyEditor extends CustomEditor {
|
|
|
67
73
|
);
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
private deleteImageTokenAtCursor(data: string): boolean {
|
|
77
|
+
const keybindings = getKeybindings();
|
|
78
|
+
const backward = keybindings.matches(data, "tui.editor.deleteCharBackward") || matchesKey(data, "shift+backspace");
|
|
79
|
+
const forward = keybindings.matches(data, "tui.editor.deleteCharForward") || matchesKey(data, "shift+delete");
|
|
80
|
+
if (!backward && !forward) return false;
|
|
81
|
+
|
|
82
|
+
const editor = this as unknown as {
|
|
83
|
+
state: { lines: string[]; cursorLine: number; cursorCol: number };
|
|
84
|
+
historyIndex: number;
|
|
85
|
+
lastAction: string | null;
|
|
86
|
+
pushUndoSnapshot: () => void;
|
|
87
|
+
setCursorCol: (col: number) => void;
|
|
88
|
+
};
|
|
89
|
+
const line = editor.state.lines[editor.state.cursorLine] || "";
|
|
90
|
+
const range = this.findImageTokenDeleteRange(line, editor.state.cursorCol, backward);
|
|
91
|
+
if (!range) return false;
|
|
92
|
+
|
|
93
|
+
editor.historyIndex = -1;
|
|
94
|
+
editor.lastAction = null;
|
|
95
|
+
editor.pushUndoSnapshot();
|
|
96
|
+
editor.state.lines[editor.state.cursorLine] = line.slice(0, range.start) + line.slice(range.end);
|
|
97
|
+
editor.setCursorCol(range.start);
|
|
98
|
+
this.attachments.delete(range.token);
|
|
99
|
+
if (this.onChange) this.onChange(this.getText());
|
|
100
|
+
this.tui.requestRender();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private findImageTokenDeleteRange(line: string, cursorCol: number, backward: boolean): { start: number; end: number; token: string } | undefined {
|
|
105
|
+
for (const match of line.matchAll(TOKEN_LINE_RE)) {
|
|
106
|
+
const token = match[0];
|
|
107
|
+
const start = match.index;
|
|
108
|
+
let end = start + token.length;
|
|
109
|
+
if (backward) {
|
|
110
|
+
if (start < cursorCol && cursorCol <= end) return { start, end, token };
|
|
111
|
+
if (cursorCol === end + 1 && line[end] === " ") return { start, end: end + 1, token };
|
|
112
|
+
} else if (start <= cursorCol && cursorCol < end) {
|
|
113
|
+
if (line[end] === " ") end += 1;
|
|
114
|
+
return { start, end, token };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
70
120
|
private replaceClipboardPaths(): void {
|
|
71
121
|
const current = this.getText();
|
|
72
122
|
let changed = false;
|
|
73
123
|
const next = current.replace(CLIPBOARD_PATH_RE, (path) => {
|
|
74
|
-
const token = imageChip(this.nextId++);
|
|
75
|
-
this.attachments.set(token, { token, path, mimeType: mimeTypeForPath(path) });
|
|
76
124
|
changed = true;
|
|
77
|
-
return
|
|
125
|
+
return this.createImageToken(path);
|
|
78
126
|
});
|
|
79
127
|
if (changed) {
|
|
80
128
|
this.setText(next);
|
|
81
129
|
this.tui.requestRender();
|
|
82
130
|
}
|
|
83
131
|
}
|
|
132
|
+
|
|
133
|
+
private replaceClipboardPathsInText(text: string): string {
|
|
134
|
+
return text.replace(CLIPBOARD_PATH_RE, (path) => this.createImageToken(path));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private createImageToken(path: string): string {
|
|
138
|
+
const token = imageChip(this.nextId++);
|
|
139
|
+
this.attachments.set(token, { token, path, mimeType: mimeTypeForPath(path) });
|
|
140
|
+
return `${token} `;
|
|
141
|
+
}
|
|
84
142
|
}
|
|
85
143
|
|
|
86
144
|
function collectImageAttachments(text: string, attachments: Map<string, Attachment>): Attachment[] {
|