@heyhuynhgiabuu/pi-pretty 0.1.5 → 0.1.7
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 +91 -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.7",
|
|
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,39 +156,107 @@ 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
|
+
// Handles tmux passthrough for image protocols.
|
|
160
161
|
// ---------------------------------------------------------------------------
|
|
161
162
|
|
|
162
|
-
|
|
163
|
+
type ImageProtocol = "iterm2" | "kitty" | "none";
|
|
164
|
+
|
|
165
|
+
const IS_TMUX = !!process.env.TMUX;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Detect the outer terminal when running inside tmux.
|
|
169
|
+
* tmux sets TERM_PROGRAM=tmux, but the real terminal is often in
|
|
170
|
+
* the environment of the tmux server or can be inferred.
|
|
171
|
+
*/
|
|
172
|
+
function getOuterTerminal(): string {
|
|
173
|
+
// Direct terminal (not in tmux)
|
|
163
174
|
const term = process.env.TERM_PROGRAM ?? "";
|
|
175
|
+
if (term !== "tmux" && term !== "screen") return term;
|
|
176
|
+
|
|
177
|
+
// Inside tmux: check common env vars that leak through
|
|
178
|
+
// Ghostty sets this; iTerm2 sets LC_TERMINAL
|
|
179
|
+
if (process.env.LC_TERMINAL === "iTerm2") return "iTerm.app";
|
|
180
|
+
|
|
181
|
+
// TERM_PROGRAM_VERSION sometimes survives into tmux
|
|
182
|
+
// Try to detect via COLORTERM or other hints
|
|
183
|
+
if (process.env.GHOSTTY_RESOURCES_DIR) return "ghostty";
|
|
184
|
+
|
|
185
|
+
// Default: assume modern terminal if truecolor is supported
|
|
186
|
+
if (process.env.COLORTERM === "truecolor" || process.env.COLORTERM === "24bit") {
|
|
187
|
+
// Can't determine exact terminal, but likely modern
|
|
188
|
+
return "unknown-modern";
|
|
189
|
+
}
|
|
190
|
+
return term;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function detectImageProtocol(): ImageProtocol {
|
|
194
|
+
const term = getOuterTerminal();
|
|
195
|
+
// Ghostty and Kitty use the Kitty graphics protocol
|
|
196
|
+
if (term === "ghostty" || term === "kitty") return "kitty";
|
|
164
197
|
// iTerm2, WezTerm, Mintty support the iTerm2 protocol
|
|
165
|
-
if (["iTerm.app", "WezTerm", "mintty"].includes(term)) return
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
if (["iTerm.app", "WezTerm", "mintty"].includes(term)) return "iterm2";
|
|
199
|
+
if (process.env.LC_TERMINAL === "iTerm2") return "iterm2";
|
|
200
|
+
return "none";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Wrap escape sequence for tmux passthrough.
|
|
205
|
+
* tmux requires: ESC Ptmux; <escaped-sequence> ESC \
|
|
206
|
+
* Inner ESC chars must be doubled.
|
|
207
|
+
*/
|
|
208
|
+
function tmuxWrap(seq: string): string {
|
|
209
|
+
if (!IS_TMUX) return seq;
|
|
210
|
+
// Double all ESC chars inside the sequence
|
|
211
|
+
const escaped = seq.replace(/\x1b/g, "\x1b\x1b");
|
|
212
|
+
return `\x1bPtmux;${escaped}\x1b\\`;
|
|
171
213
|
}
|
|
172
214
|
|
|
173
215
|
/**
|
|
174
216
|
* Render base64 image inline using iTerm2 inline image protocol.
|
|
175
217
|
* Protocol: ESC ] 1337 ; File=[args] : base64data BEL
|
|
176
|
-
* Supported by: iTerm2, WezTerm, Mintty, Kitty (compat mode)
|
|
177
218
|
*/
|
|
178
|
-
function
|
|
219
|
+
function renderIterm2Image(
|
|
179
220
|
base64Data: string,
|
|
180
|
-
opts: { width?: string;
|
|
221
|
+
opts: { width?: string; name?: string } = {},
|
|
181
222
|
): string {
|
|
182
|
-
const args: string[] = ["inline=1"];
|
|
223
|
+
const args: string[] = ["inline=1", "preserveAspectRatio=1"];
|
|
183
224
|
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
225
|
if (opts.name) args.push(`name=${Buffer.from(opts.name).toString("base64")}`);
|
|
187
|
-
// Calculate size for the size= param
|
|
188
226
|
const byteSize = Math.ceil(base64Data.length * 3 / 4);
|
|
189
227
|
args.push(`size=${byteSize}`);
|
|
228
|
+
const seq = `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
|
|
229
|
+
return tmuxWrap(seq);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Render base64 image inline using Kitty graphics protocol.
|
|
234
|
+
* Protocol: ESC _G <key>=<value>,...; <base64data> ESC \
|
|
235
|
+
* Chunked in 4096-byte pieces as required by protocol.
|
|
236
|
+
* Supported by: Kitty, Ghostty
|
|
237
|
+
*/
|
|
238
|
+
function renderKittyImage(
|
|
239
|
+
base64Data: string,
|
|
240
|
+
opts: { cols?: number } = {},
|
|
241
|
+
): string {
|
|
242
|
+
const chunks: string[] = [];
|
|
243
|
+
const CHUNK_SIZE = 4096;
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i < base64Data.length; i += CHUNK_SIZE) {
|
|
246
|
+
const chunk = base64Data.slice(i, i + CHUNK_SIZE);
|
|
247
|
+
const isFirst = i === 0;
|
|
248
|
+
const isLast = i + CHUNK_SIZE >= base64Data.length;
|
|
249
|
+
const more = isLast ? 0 : 1;
|
|
250
|
+
|
|
251
|
+
if (isFirst) {
|
|
252
|
+
const colPart = opts.cols ? `,c=${opts.cols}` : "";
|
|
253
|
+
chunks.push(tmuxWrap(`\x1b_Ga=T,f=100,t=d,m=${more}${colPart};${chunk}\x1b\\`));
|
|
254
|
+
} else {
|
|
255
|
+
chunks.push(tmuxWrap(`\x1b_Gm=${more};${chunk}\x1b\\`));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
190
258
|
|
|
191
|
-
return
|
|
259
|
+
return chunks.join("");
|
|
192
260
|
}
|
|
193
261
|
|
|
194
262
|
/**
|
|
@@ -690,16 +758,18 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
690
758
|
out.push(` ${fileIcon(d.filePath)}${FG_DIM}${mimeStr} · ${sizeStr}${RST}`);
|
|
691
759
|
out.push(rule(tw));
|
|
692
760
|
|
|
693
|
-
|
|
694
|
-
|
|
761
|
+
const protocol = detectImageProtocol();
|
|
762
|
+
if (protocol === "kitty") {
|
|
763
|
+
const imgCols = Math.min(tw - 4, 80);
|
|
764
|
+
out.push(renderKittyImage(d.data, { cols: imgCols }));
|
|
765
|
+
} else if (protocol === "iterm2") {
|
|
695
766
|
const imgWidth = Math.min(tw - 4, 80);
|
|
696
|
-
out.push(
|
|
767
|
+
out.push(renderIterm2Image(d.data, {
|
|
697
768
|
width: `${imgWidth}`,
|
|
698
769
|
name: fname,
|
|
699
770
|
}));
|
|
700
771
|
} else {
|
|
701
|
-
|
|
702
|
-
out.push(` ${FG_DIM}(Inline image preview requires iTerm2, WezTerm, or Kitty)${RST}`);
|
|
772
|
+
out.push(` ${FG_DIM}(Inline image preview requires Ghostty, iTerm2, WezTerm, or Kitty)${RST}`);
|
|
703
773
|
}
|
|
704
774
|
|
|
705
775
|
out.push(rule(tw));
|