@heyhuynhgiabuu/pi-pretty 0.1.6 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +46 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyhuynhgiabuu/pi-pretty",
3
- "version": "0.1.6",
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
@@ -157,12 +157,41 @@ function lang(fp: string): BundledLanguage | undefined {
157
157
 
158
158
  // ---------------------------------------------------------------------------
159
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";
163
164
 
164
- function detectImageProtocol(): ImageProtocol {
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)
165
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();
166
195
  // Ghostty and Kitty use the Kitty graphics protocol
167
196
  if (term === "ghostty" || term === "kitty") return "kitty";
168
197
  // iTerm2, WezTerm, Mintty support the iTerm2 protocol
@@ -171,6 +200,18 @@ function detectImageProtocol(): ImageProtocol {
171
200
  return "none";
172
201
  }
173
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\\`;
213
+ }
214
+
174
215
  /**
175
216
  * Render base64 image inline using iTerm2 inline image protocol.
176
217
  * Protocol: ESC ] 1337 ; File=[args] : base64data BEL
@@ -184,7 +225,8 @@ function renderIterm2Image(
184
225
  if (opts.name) args.push(`name=${Buffer.from(opts.name).toString("base64")}`);
185
226
  const byteSize = Math.ceil(base64Data.length * 3 / 4);
186
227
  args.push(`size=${byteSize}`);
187
- return `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
228
+ const seq = `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
229
+ return tmuxWrap(seq);
188
230
  }
189
231
 
190
232
  /**
@@ -207,13 +249,10 @@ function renderKittyImage(
207
249
  const more = isLast ? 0 : 1;
208
250
 
209
251
  if (isFirst) {
210
- // First chunk: include all metadata
211
- // a=T (transmit+display), f=100 (PNG), t=d (direct data)
212
252
  const colPart = opts.cols ? `,c=${opts.cols}` : "";
213
- chunks.push(`\x1b_Ga=T,f=100,t=d,m=${more}${colPart};${chunk}\x1b\\`);
253
+ chunks.push(tmuxWrap(`\x1b_Ga=T,f=100,t=d,m=${more}${colPart};${chunk}\x1b\\`));
214
254
  } else {
215
- // Continuation chunks
216
- chunks.push(`\x1b_Gm=${more};${chunk}\x1b\\`);
255
+ chunks.push(tmuxWrap(`\x1b_Gm=${more};${chunk}\x1b\\`));
217
256
  }
218
257
  }
219
258