@heyhuynhgiabuu/pi-pretty 0.1.4 → 0.1.5
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 +87 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyhuynhgiabuu/pi-pretty",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Pretty terminal output for pi — syntax-highlighted file reads, colored bash output, tree-view directory listings, and more.",
|
|
5
5
|
"author": "huynhgiabuu",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -155,6 +155,51 @@ function lang(fp: string): BundledLanguage | undefined {
|
|
|
155
155
|
return EXT_LANG[extname(fp).slice(1).toLowerCase()];
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Terminal image rendering (iTerm2 / Kitty / WezTerm inline image protocol)
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
function supportsInlineImages(): boolean {
|
|
163
|
+
const term = process.env.TERM_PROGRAM ?? "";
|
|
164
|
+
// iTerm2, WezTerm, Mintty support the iTerm2 protocol
|
|
165
|
+
if (["iTerm.app", "WezTerm", "mintty"].includes(term)) return true;
|
|
166
|
+
// Kitty has its own protocol but also supports iTerm2 compat in newer versions
|
|
167
|
+
if (term === "kitty") return true;
|
|
168
|
+
// TERM_PROGRAM_VERSION for iTerm2 check
|
|
169
|
+
if (process.env.LC_TERMINAL === "iTerm2") return true;
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Render base64 image inline using iTerm2 inline image protocol.
|
|
175
|
+
* Protocol: ESC ] 1337 ; File=[args] : base64data BEL
|
|
176
|
+
* Supported by: iTerm2, WezTerm, Mintty, Kitty (compat mode)
|
|
177
|
+
*/
|
|
178
|
+
function renderInlineImage(
|
|
179
|
+
base64Data: string,
|
|
180
|
+
opts: { width?: string; height?: string; preserveAspect?: boolean; name?: string } = {},
|
|
181
|
+
): string {
|
|
182
|
+
const args: string[] = ["inline=1"];
|
|
183
|
+
if (opts.width) args.push(`width=${opts.width}`);
|
|
184
|
+
if (opts.height) args.push(`height=${opts.height}`);
|
|
185
|
+
if (opts.preserveAspect !== false) args.push("preserveAspectRatio=1");
|
|
186
|
+
if (opts.name) args.push(`name=${Buffer.from(opts.name).toString("base64")}`);
|
|
187
|
+
// Calculate size for the size= param
|
|
188
|
+
const byteSize = Math.ceil(base64Data.length * 3 / 4);
|
|
189
|
+
args.push(`size=${byteSize}`);
|
|
190
|
+
|
|
191
|
+
return `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get human-readable file size
|
|
196
|
+
*/
|
|
197
|
+
function humanSize(bytes: number): string {
|
|
198
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
199
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
200
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
201
|
+
}
|
|
202
|
+
|
|
158
203
|
// ---------------------------------------------------------------------------
|
|
159
204
|
// File-type icons — Nerd Font glyphs (Seti-UI + Devicons, stable in NF v3+)
|
|
160
205
|
//
|
|
@@ -576,6 +621,18 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
576
621
|
const fp = params.path ?? "";
|
|
577
622
|
const offset = params.offset ?? 1;
|
|
578
623
|
|
|
624
|
+
// Check for image content
|
|
625
|
+
const imageBlock = result.content?.find((c: any) => c.type === "image");
|
|
626
|
+
if (imageBlock) {
|
|
627
|
+
(result as any).details = {
|
|
628
|
+
_type: "readImage",
|
|
629
|
+
filePath: fp,
|
|
630
|
+
data: imageBlock.data,
|
|
631
|
+
mimeType: imageBlock.mimeType ?? "image/png",
|
|
632
|
+
};
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
|
|
579
636
|
// Extract text content for rendering
|
|
580
637
|
const textContent = result.content
|
|
581
638
|
?.filter((c: any) => c.type === "text")
|
|
@@ -620,6 +677,36 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
620
677
|
}
|
|
621
678
|
|
|
622
679
|
const d = result.details;
|
|
680
|
+
|
|
681
|
+
// Image rendering
|
|
682
|
+
if (d?._type === "readImage") {
|
|
683
|
+
const tw = termW();
|
|
684
|
+
const out: string[] = [];
|
|
685
|
+
const fname = basename(d.filePath);
|
|
686
|
+
const byteSize = Math.ceil((d.data as string).length * 3 / 4);
|
|
687
|
+
const sizeStr = humanSize(byteSize);
|
|
688
|
+
const mimeStr = d.mimeType ?? "image";
|
|
689
|
+
|
|
690
|
+
out.push(` ${fileIcon(d.filePath)}${FG_DIM}${mimeStr} · ${sizeStr}${RST}`);
|
|
691
|
+
out.push(rule(tw));
|
|
692
|
+
|
|
693
|
+
if (supportsInlineImages()) {
|
|
694
|
+
// Max width: use ~80 columns or terminal width, capped for readability
|
|
695
|
+
const imgWidth = Math.min(tw - 4, 80);
|
|
696
|
+
out.push(renderInlineImage(d.data, {
|
|
697
|
+
width: `${imgWidth}`,
|
|
698
|
+
name: fname,
|
|
699
|
+
}));
|
|
700
|
+
} else {
|
|
701
|
+
// Fallback: show metadata only
|
|
702
|
+
out.push(` ${FG_DIM}(Inline image preview requires iTerm2, WezTerm, or Kitty)${RST}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
out.push(rule(tw));
|
|
706
|
+
text.setText(out.join("\n"));
|
|
707
|
+
return text;
|
|
708
|
+
}
|
|
709
|
+
|
|
623
710
|
if (d?._type === "readFile" && d.content) {
|
|
624
711
|
const key = `read:${d.filePath}:${d.offset}:${d.lineCount}:${termW()}`;
|
|
625
712
|
if (ctx.state._rk !== key) {
|