@heyhuynhgiabuu/pi-pretty 0.1.5 → 0.1.6
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 +52 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyhuynhgiabuu/pi-pretty",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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
|
@@ -156,41 +156,70 @@ function lang(fp: string): BundledLanguage | undefined {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// ---------------------------------------------------------------------------
|
|
159
|
-
// Terminal image rendering (iTerm2 / Kitty /
|
|
159
|
+
// Terminal image rendering (iTerm2 / Kitty / Ghostty inline image protocols)
|
|
160
160
|
// ---------------------------------------------------------------------------
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
type ImageProtocol = "iterm2" | "kitty" | "none";
|
|
163
|
+
|
|
164
|
+
function detectImageProtocol(): ImageProtocol {
|
|
163
165
|
const term = process.env.TERM_PROGRAM ?? "";
|
|
166
|
+
// Ghostty and Kitty use the Kitty graphics protocol
|
|
167
|
+
if (term === "ghostty" || term === "kitty") return "kitty";
|
|
164
168
|
// iTerm2, WezTerm, Mintty support the iTerm2 protocol
|
|
165
|
-
if (["iTerm.app", "WezTerm", "mintty"].includes(term)) return
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// TERM_PROGRAM_VERSION for iTerm2 check
|
|
169
|
-
if (process.env.LC_TERMINAL === "iTerm2") return true;
|
|
170
|
-
return false;
|
|
169
|
+
if (["iTerm.app", "WezTerm", "mintty"].includes(term)) return "iterm2";
|
|
170
|
+
if (process.env.LC_TERMINAL === "iTerm2") return "iterm2";
|
|
171
|
+
return "none";
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
/**
|
|
174
175
|
* Render base64 image inline using iTerm2 inline image protocol.
|
|
175
176
|
* Protocol: ESC ] 1337 ; File=[args] : base64data BEL
|
|
176
|
-
* Supported by: iTerm2, WezTerm, Mintty, Kitty (compat mode)
|
|
177
177
|
*/
|
|
178
|
-
function
|
|
178
|
+
function renderIterm2Image(
|
|
179
179
|
base64Data: string,
|
|
180
|
-
opts: { width?: string;
|
|
180
|
+
opts: { width?: string; name?: string } = {},
|
|
181
181
|
): string {
|
|
182
|
-
const args: string[] = ["inline=1"];
|
|
182
|
+
const args: string[] = ["inline=1", "preserveAspectRatio=1"];
|
|
183
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
184
|
if (opts.name) args.push(`name=${Buffer.from(opts.name).toString("base64")}`);
|
|
187
|
-
// Calculate size for the size= param
|
|
188
185
|
const byteSize = Math.ceil(base64Data.length * 3 / 4);
|
|
189
186
|
args.push(`size=${byteSize}`);
|
|
190
|
-
|
|
191
187
|
return `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
|
|
192
188
|
}
|
|
193
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Render base64 image inline using Kitty graphics protocol.
|
|
192
|
+
* Protocol: ESC _G <key>=<value>,...; <base64data> ESC \
|
|
193
|
+
* Chunked in 4096-byte pieces as required by protocol.
|
|
194
|
+
* Supported by: Kitty, Ghostty
|
|
195
|
+
*/
|
|
196
|
+
function renderKittyImage(
|
|
197
|
+
base64Data: string,
|
|
198
|
+
opts: { cols?: number } = {},
|
|
199
|
+
): string {
|
|
200
|
+
const chunks: string[] = [];
|
|
201
|
+
const CHUNK_SIZE = 4096;
|
|
202
|
+
|
|
203
|
+
for (let i = 0; i < base64Data.length; i += CHUNK_SIZE) {
|
|
204
|
+
const chunk = base64Data.slice(i, i + CHUNK_SIZE);
|
|
205
|
+
const isFirst = i === 0;
|
|
206
|
+
const isLast = i + CHUNK_SIZE >= base64Data.length;
|
|
207
|
+
const more = isLast ? 0 : 1;
|
|
208
|
+
|
|
209
|
+
if (isFirst) {
|
|
210
|
+
// First chunk: include all metadata
|
|
211
|
+
// a=T (transmit+display), f=100 (PNG), t=d (direct data)
|
|
212
|
+
const colPart = opts.cols ? `,c=${opts.cols}` : "";
|
|
213
|
+
chunks.push(`\x1b_Ga=T,f=100,t=d,m=${more}${colPart};${chunk}\x1b\\`);
|
|
214
|
+
} else {
|
|
215
|
+
// Continuation chunks
|
|
216
|
+
chunks.push(`\x1b_Gm=${more};${chunk}\x1b\\`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return chunks.join("");
|
|
221
|
+
}
|
|
222
|
+
|
|
194
223
|
/**
|
|
195
224
|
* Get human-readable file size
|
|
196
225
|
*/
|
|
@@ -690,16 +719,18 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
690
719
|
out.push(` ${fileIcon(d.filePath)}${FG_DIM}${mimeStr} · ${sizeStr}${RST}`);
|
|
691
720
|
out.push(rule(tw));
|
|
692
721
|
|
|
693
|
-
|
|
694
|
-
|
|
722
|
+
const protocol = detectImageProtocol();
|
|
723
|
+
if (protocol === "kitty") {
|
|
724
|
+
const imgCols = Math.min(tw - 4, 80);
|
|
725
|
+
out.push(renderKittyImage(d.data, { cols: imgCols }));
|
|
726
|
+
} else if (protocol === "iterm2") {
|
|
695
727
|
const imgWidth = Math.min(tw - 4, 80);
|
|
696
|
-
out.push(
|
|
728
|
+
out.push(renderIterm2Image(d.data, {
|
|
697
729
|
width: `${imgWidth}`,
|
|
698
730
|
name: fname,
|
|
699
731
|
}));
|
|
700
732
|
} else {
|
|
701
|
-
|
|
702
|
-
out.push(` ${FG_DIM}(Inline image preview requires iTerm2, WezTerm, or Kitty)${RST}`);
|
|
733
|
+
out.push(` ${FG_DIM}(Inline image preview requires Ghostty, iTerm2, WezTerm, or Kitty)${RST}`);
|
|
703
734
|
}
|
|
704
735
|
|
|
705
736
|
out.push(rule(tw));
|