@heyhuynhgiabuu/pi-pretty 0.1.7 → 0.1.8
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/README.md +28 -150
- package/biome.json +20 -15
- package/media/bash-and-read.png +0 -0
- package/media/icons-and-grep.png +0 -0
- package/media/inline-image.png +0 -0
- package/package.json +48 -48
- package/src/index.ts +213 -199
package/README.md
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
# pi-pretty
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@heyhuynhgiabuu/pi-pretty)
|
|
4
|
+
[](https://github.com/heyhuynhgiabuu/pi-pretty/releases/latest)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
A [pi](https://pi.dev) extension that upgrades built-in tool output in the terminal without changing tool behavior.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
It currently enhances:
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
- **`read`**: syntax-highlighted text previews with line numbers, plus inline image rendering when the terminal supports it
|
|
11
|
+
- **`bash`**: colored exit summary (`exit 0`/`exit 1`) with a preview body of command output
|
|
12
|
+
- **`ls` / `find` / `grep`**: Nerd Font file icons with tree/grouped layouts and clearer match rendering
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|------|-------------|
|
|
13
|
-
| **read** | Syntax-highlighted file content with line numbers (190+ languages via Shiki) |
|
|
14
|
-
| **bash** | Colored exit status (`✓ exit 0` / `✗ exit 1`), line count |
|
|
15
|
-
| **ls** | Tree-view directory listing with file-type icons (📁🟦🐍🦀…) |
|
|
16
|
-
| **find** | Grouped results by directory with file-type icons |
|
|
17
|
-
| **grep** | Highlighted pattern matches with file headers and line numbers |
|
|
14
|
+
> Companion to [@heyhuynhgiabuu/pi-diff](https://github.com/heyhuynhgiabuu/pi-diff) for `write`/`edit` diff rendering.
|
|
18
15
|
|
|
19
16
|
## Install
|
|
20
17
|
|
|
@@ -22,168 +19,49 @@ A [pi](https://pi.dev) extension that enhances built-in tool output with **synta
|
|
|
22
19
|
pi install npm:@heyhuynhgiabuu/pi-pretty
|
|
23
20
|
```
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
Latest release: https://github.com/heyhuynhgiabuu/pi-pretty/releases/latest
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
pi -e ./src/index.ts
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## How It Works
|
|
32
|
-
|
|
33
|
-
pi-pretty wraps the built-in SDK tools (`createReadTool`, `createBashTool`, `createLsTool`, `createFindTool`, `createGrepTool`). For each tool:
|
|
34
|
-
|
|
35
|
-
1. **Delegates** to the original `execute()` — no behavior changes
|
|
36
|
-
2. **Attaches metadata** to `result.details` for custom rendering
|
|
37
|
-
3. **Renders** enhanced output via `renderCall` / `renderResult`
|
|
38
|
-
|
|
39
|
-
The agent sees the same tool results. Only the TUI display changes.
|
|
40
|
-
|
|
41
|
-
## Configuration
|
|
42
|
-
|
|
43
|
-
All settings via environment variables. Add to your shell profile or `.envrc`:
|
|
44
|
-
|
|
45
|
-
### Theme
|
|
46
|
-
|
|
47
|
-
| Variable | Default | Description |
|
|
48
|
-
|----------|---------|-------------|
|
|
49
|
-
| `PRETTY_THEME` | `github-dark` | Shiki theme for syntax highlighting |
|
|
50
|
-
|
|
51
|
-
### Limits
|
|
52
|
-
|
|
53
|
-
| Variable | Default | Description |
|
|
54
|
-
|----------|---------|-------------|
|
|
55
|
-
| `PRETTY_MAX_HL_CHARS` | `80000` | Skip syntax highlighting above this |
|
|
56
|
-
| `PRETTY_MAX_PREVIEW_LINES` | `80` | Max lines shown in rendered output |
|
|
57
|
-
| `PRETTY_CACHE_LIMIT` | `128` | LRU cache entries for highlighted blocks |
|
|
58
|
-
|
|
59
|
-
### Example `.envrc`
|
|
24
|
+
Or load locally:
|
|
60
25
|
|
|
61
26
|
```bash
|
|
62
|
-
|
|
63
|
-
export PRETTY_MAX_PREVIEW_LINES=120
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Tool Details
|
|
67
|
-
|
|
68
|
-
### `read` — Syntax Highlighting
|
|
69
|
-
|
|
70
|
-
When the agent reads a file, pi-pretty renders it with:
|
|
71
|
-
- Shiki syntax highlighting (190+ languages, auto-detected from extension)
|
|
72
|
-
- Line numbers in a left gutter
|
|
73
|
-
- Long lines truncated with `›` indicator
|
|
74
|
-
- Respects `offset` and `limit` parameters
|
|
75
|
-
|
|
76
|
-
### `bash` — Exit Status
|
|
77
|
-
|
|
78
|
-
Bash command results show:
|
|
79
|
-
- `✓ exit 0` in green for success
|
|
80
|
-
- `✗ exit 1` in red for failure
|
|
81
|
-
- `⚡ killed` in yellow for terminated processes
|
|
82
|
-
- Line count for multi-line output
|
|
83
|
-
|
|
84
|
-
### `ls` — Tree View
|
|
85
|
-
|
|
86
|
-
Directory listings rendered as:
|
|
87
|
-
```
|
|
88
|
-
3 entries
|
|
89
|
-
├── 📁 src
|
|
90
|
-
├── 📦 package.json
|
|
91
|
-
└── 📖 README.md
|
|
27
|
+
pi -e ./src/index.ts
|
|
92
28
|
```
|
|
93
29
|
|
|
94
|
-
|
|
30
|
+
## Screenshots
|
|
95
31
|
|
|
96
|
-
|
|
32
|
+

|
|
33
|
+
*`bash` exit summary + output preview, and syntax-highlighted `read` text output.*
|
|
97
34
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
5 files
|
|
101
|
-
📁 src/
|
|
102
|
-
├── 🟦 index.ts
|
|
103
|
-
└── 🟦 utils.ts
|
|
104
|
-
📁 test/
|
|
105
|
-
├── 🟦 index.test.ts
|
|
106
|
-
└── 🟦 utils.test.ts
|
|
107
|
-
```
|
|
35
|
+

|
|
36
|
+
*`ls`/`find`/`grep` with Nerd Font icons and grouped/tree-oriented rendering.*
|
|
108
37
|
|
|
109
|
-
|
|
38
|
+

|
|
39
|
+
*`read` rendering an image inline in supported terminals.*
|
|
110
40
|
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
3 matches
|
|
114
|
-
🟦 src/index.ts
|
|
115
|
-
12 │ const result = await createReadTool(cwd);
|
|
116
|
-
45 │ export function createReadTool(path: string) {
|
|
117
|
-
|
|
118
|
-
🟦 src/utils.ts
|
|
119
|
-
8 │ import { createReadTool } from "./index";
|
|
120
|
-
```
|
|
41
|
+
## Terminal support for inline images
|
|
121
42
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
| Icon | Extensions |
|
|
125
|
-
|------|-----------|
|
|
126
|
-
| 🟦 | `.ts`, `.tsx`, `tsconfig.json` |
|
|
127
|
-
| 🟨 | `.js`, `.jsx`, `.mjs`, `.cjs` |
|
|
128
|
-
| 🐍 | `.py`, `pyproject.toml` |
|
|
129
|
-
| 🦀 | `.rs`, `Cargo.toml` |
|
|
130
|
-
| 🔵 | `.go`, `go.mod` |
|
|
131
|
-
| ☕ | `.java` |
|
|
132
|
-
| 🍊 | `.swift` |
|
|
133
|
-
| 💎 | `.rb` |
|
|
134
|
-
| 🌐 | `.html` |
|
|
135
|
-
| 🎨 | `.css`, `.scss`, `.less` |
|
|
136
|
-
| 📋 | `.json`, `.yaml`, `.toml` |
|
|
137
|
-
| 📝 | `.md`, `.mdx` |
|
|
138
|
-
| 🐚 | `.sh`, `.bash`, `.zsh` |
|
|
139
|
-
| 🖼️ | `.png`, `.jpg`, `.svg`, `.webp` |
|
|
140
|
-
| 📦 | `package.json` |
|
|
141
|
-
| 🐳 | `Dockerfile` |
|
|
142
|
-
| 🔐 | `.env`, `.envrc` |
|
|
143
|
-
| 📖 | `README.md` |
|
|
144
|
-
| ⚖️ | `LICENSE` |
|
|
145
|
-
|
|
146
|
-
## Architecture
|
|
43
|
+
Inline image previews are supported in **Ghostty**, **Kitty**, **iTerm2**, and **WezTerm**.
|
|
44
|
+
When running in **tmux**, pi-pretty uses passthrough escape sequences so inline image protocols still work.
|
|
147
45
|
|
|
148
|
-
|
|
149
|
-
src/
|
|
150
|
-
└── index.ts # Extension entry — wraps read/bash/ls/find/grep with pretty rendering
|
|
151
|
-
```
|
|
46
|
+
## Configuration
|
|
152
47
|
|
|
153
|
-
|
|
48
|
+
Optional environment variables:
|
|
154
49
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
| `renderTree()` | Tree-view with connectors and file icons |
|
|
161
|
-
| `renderFindResults()` | Directory-grouped file list with icons |
|
|
162
|
-
| `renderGrepResults()` | Pattern-highlighted matches with file headers |
|
|
163
|
-
| `fileIcon()` | Extension → emoji icon mapper |
|
|
164
|
-
| `lang()` | Extension → Shiki language mapper |
|
|
50
|
+
- `PRETTY_THEME` (default: `github-dark`)
|
|
51
|
+
- `PRETTY_MAX_HL_CHARS` (default: `80000`)
|
|
52
|
+
- `PRETTY_MAX_PREVIEW_LINES` (default: `80`)
|
|
53
|
+
- `PRETTY_CACHE_LIMIT` (default: `128`)
|
|
54
|
+
- `PRETTY_ICONS` (`nerd` by default, set to `none` to disable icons)
|
|
165
55
|
|
|
166
56
|
## Development
|
|
167
57
|
|
|
168
58
|
```bash
|
|
169
|
-
git clone https://github.com/heyhuynhgiabuu/pi-pretty.git
|
|
170
|
-
cd pi-pretty
|
|
171
59
|
npm install
|
|
172
60
|
npm run typecheck
|
|
173
61
|
npm run lint
|
|
174
62
|
npm test
|
|
175
63
|
```
|
|
176
64
|
|
|
177
|
-
### Load in pi for testing
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
pi -e ./src/index.ts
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
## Related
|
|
184
|
-
|
|
185
|
-
- [@heyhuynhgiabuu/pi-diff](https://github.com/heyhuynhgiabuu/pi-diff) — Syntax-highlighted diffs for `write`/`edit` tools
|
|
186
|
-
|
|
187
65
|
## License
|
|
188
66
|
|
|
189
67
|
MIT — [huynhgiabuu](https://github.com/heyhuynhgiabuu)
|
package/biome.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
|
|
3
|
+
"assist": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"actions": {
|
|
6
|
+
"source": {
|
|
7
|
+
"organizeImports": "on"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"linter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"rules": {
|
|
14
|
+
"recommended": true
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"formatter": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"indentStyle": "tab",
|
|
20
|
+
"lineWidth": 120
|
|
21
|
+
}
|
|
17
22
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
2
|
+
"name": "@heyhuynhgiabuu/pi-pretty",
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"description": "Pretty terminal output for pi — syntax-highlighted file reads, colored bash output, tree-view directory listings, and more.",
|
|
5
|
+
"author": "huynhgiabuu",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/heyhuynhgiabuu/pi-pretty.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/heyhuynhgiabuu/pi-pretty#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/heyhuynhgiabuu/pi-pretty/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"pi",
|
|
18
|
+
"pi-extension",
|
|
19
|
+
"syntax-highlighting",
|
|
20
|
+
"shiki",
|
|
21
|
+
"terminal",
|
|
22
|
+
"pretty-print"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@shikijs/cli": "^4.0.2"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
29
|
+
"@mariozechner/pi-tui": "*"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
33
|
+
"typescript": "^5.0.0",
|
|
34
|
+
"@biomejs/biome": "^2.3.5",
|
|
35
|
+
"vitest": "^4.0.18"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"lint": "biome check src/",
|
|
41
|
+
"lint:fix": "biome check --fix src/",
|
|
42
|
+
"test": "vitest run --passWithNoTests",
|
|
43
|
+
"test:watch": "vitest --passWithNoTests"
|
|
44
|
+
},
|
|
45
|
+
"pi": {
|
|
46
|
+
"extensions": [
|
|
47
|
+
"./src/index.ts"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
50
|
}
|
package/src/index.ts
CHANGED
|
@@ -33,8 +33,7 @@ import type { BundledLanguage, BundledTheme } from "shiki";
|
|
|
33
33
|
// Config
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
35
|
|
|
36
|
-
const THEME: BundledTheme =
|
|
37
|
-
(process.env.PRETTY_THEME as BundledTheme | undefined) ?? "github-dark";
|
|
36
|
+
const THEME: BundledTheme = (process.env.PRETTY_THEME as BundledTheme | undefined) ?? "github-dark";
|
|
38
37
|
|
|
39
38
|
function envInt(name: string, fallback: number): number {
|
|
40
39
|
const v = Number.parseInt(process.env[name] ?? "", 10);
|
|
@@ -49,7 +48,7 @@ const CACHE_LIMIT = envInt("PRETTY_CACHE_LIMIT", 128);
|
|
|
49
48
|
// ANSI
|
|
50
49
|
// ---------------------------------------------------------------------------
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
let RST = "\x1b[0m";
|
|
53
52
|
const BOLD = "\x1b[1m";
|
|
54
53
|
const DIM = "\x1b[2m";
|
|
55
54
|
const ITALIC = "\x1b[3m";
|
|
@@ -68,7 +67,34 @@ const FG_PURPLE = "\x1b[38;2;170;120;200m";
|
|
|
68
67
|
|
|
69
68
|
const BG_STDERR = "\x1b[48;2;40;25;25m";
|
|
70
69
|
|
|
71
|
-
const
|
|
70
|
+
const BG_DEFAULT = "\x1b[49m";
|
|
71
|
+
let BG_BASE = BG_DEFAULT; // tool box base bg — updated from theme's toolSuccessBg
|
|
72
|
+
|
|
73
|
+
/** Parse an ANSI 24-bit color escape into { r, g, b }. Handles both fg (38;2) and bg (48;2). */
|
|
74
|
+
function parseAnsiRgb(ansi: string): { r: number; g: number; b: number } | null {
|
|
75
|
+
const m = ansi.match(/\x1b\[(?:38|48);2;(\d+);(\d+);(\d+)m/);
|
|
76
|
+
return m ? { r: +m[1], g: +m[2], b: +m[3] } : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Read toolSuccessBg from the pi theme and update BG_BASE + RST.
|
|
80
|
+
* Call once when theme is first available. Idempotent. */
|
|
81
|
+
let _bgBaseResolved = false;
|
|
82
|
+
function resolveBaseBackground(theme: any): void {
|
|
83
|
+
if (_bgBaseResolved || !theme?.getBgAnsi) return;
|
|
84
|
+
_bgBaseResolved = true;
|
|
85
|
+
try {
|
|
86
|
+
const bgAnsi = theme.getBgAnsi("toolSuccessBg");
|
|
87
|
+
const parsed = parseAnsiRgb(bgAnsi);
|
|
88
|
+
if (parsed) {
|
|
89
|
+
BG_BASE = bgAnsi;
|
|
90
|
+
RST = `\x1b[0m${BG_BASE}`;
|
|
91
|
+
}
|
|
92
|
+
} catch { /* ignore — keep defaults */ }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ESC_RE = "\u001b";
|
|
96
|
+
const ANSI_RE = new RegExp(`${ESC_RE}\\[[0-9;]*m`, "g");
|
|
97
|
+
const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([0-9;]*)m`, "g");
|
|
72
98
|
|
|
73
99
|
// ---------------------------------------------------------------------------
|
|
74
100
|
// Low-contrast fix (same as pi-diff)
|
|
@@ -79,19 +105,14 @@ function isLowContrastShikiFg(params: string): boolean {
|
|
|
79
105
|
if (params === "38;5;0" || params === "38;5;8") return true;
|
|
80
106
|
if (!params.startsWith("38;2;")) return false;
|
|
81
107
|
const parts = params.split(";").map(Number);
|
|
82
|
-
if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n)))
|
|
83
|
-
return false;
|
|
108
|
+
if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n))) return false;
|
|
84
109
|
const [, , r, g, b] = parts;
|
|
85
110
|
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
86
111
|
return luminance < 72;
|
|
87
112
|
}
|
|
88
113
|
|
|
89
114
|
function normalizeShikiContrast(ansi: string): string {
|
|
90
|
-
return ansi.replace(
|
|
91
|
-
/\x1b\[([0-9;]*)m/g,
|
|
92
|
-
(seq, params: string) =>
|
|
93
|
-
isLowContrastShikiFg(params) ? FG_MUTED : seq,
|
|
94
|
-
);
|
|
115
|
+
return ansi.replace(ANSI_CAPTURE_RE, (seq, params: string) => (isLowContrastShikiFg(params) ? FG_MUTED : seq));
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
// ---------------------------------------------------------------------------
|
|
@@ -104,10 +125,7 @@ function strip(s: string): string {
|
|
|
104
125
|
|
|
105
126
|
function termW(): number {
|
|
106
127
|
const raw =
|
|
107
|
-
process.stdout.columns ||
|
|
108
|
-
(process.stderr as any).columns ||
|
|
109
|
-
Number.parseInt(process.env.COLUMNS ?? "", 10) ||
|
|
110
|
-
200;
|
|
128
|
+
process.stdout.columns || (process.stderr as any).columns || Number.parseInt(process.env.COLUMNS ?? "", 10) || 200;
|
|
111
129
|
return Math.max(80, Math.min(raw - 4, 210));
|
|
112
130
|
}
|
|
113
131
|
|
|
@@ -132,19 +150,54 @@ function lnum(n: number, w: number): string {
|
|
|
132
150
|
// ---------------------------------------------------------------------------
|
|
133
151
|
|
|
134
152
|
const EXT_LANG: Record<string, BundledLanguage> = {
|
|
135
|
-
ts: "typescript",
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
ts: "typescript",
|
|
154
|
+
tsx: "tsx",
|
|
155
|
+
js: "javascript",
|
|
156
|
+
jsx: "jsx",
|
|
157
|
+
mjs: "javascript",
|
|
158
|
+
cjs: "javascript",
|
|
159
|
+
py: "python",
|
|
160
|
+
rb: "ruby",
|
|
161
|
+
rs: "rust",
|
|
162
|
+
go: "go",
|
|
163
|
+
java: "java",
|
|
164
|
+
c: "c",
|
|
165
|
+
cpp: "cpp",
|
|
166
|
+
h: "c",
|
|
167
|
+
hpp: "cpp",
|
|
168
|
+
cs: "csharp",
|
|
169
|
+
swift: "swift",
|
|
170
|
+
kt: "kotlin",
|
|
171
|
+
html: "html",
|
|
172
|
+
css: "css",
|
|
173
|
+
scss: "scss",
|
|
174
|
+
less: "css",
|
|
175
|
+
json: "json",
|
|
176
|
+
jsonc: "jsonc",
|
|
177
|
+
yaml: "yaml",
|
|
178
|
+
yml: "yaml",
|
|
179
|
+
toml: "toml",
|
|
180
|
+
md: "markdown",
|
|
181
|
+
mdx: "mdx",
|
|
182
|
+
sql: "sql",
|
|
183
|
+
sh: "bash",
|
|
184
|
+
bash: "bash",
|
|
185
|
+
zsh: "bash",
|
|
186
|
+
lua: "lua",
|
|
187
|
+
php: "php",
|
|
188
|
+
dart: "dart",
|
|
189
|
+
xml: "xml",
|
|
190
|
+
graphql: "graphql",
|
|
191
|
+
svelte: "svelte",
|
|
192
|
+
vue: "vue",
|
|
193
|
+
dockerfile: "dockerfile",
|
|
194
|
+
makefile: "make",
|
|
195
|
+
zig: "zig",
|
|
196
|
+
nim: "nim",
|
|
197
|
+
elixir: "elixir",
|
|
198
|
+
ex: "elixir",
|
|
199
|
+
erb: "erb",
|
|
200
|
+
hbs: "handlebars",
|
|
148
201
|
};
|
|
149
202
|
|
|
150
203
|
function lang(fp: string): BundledLanguage | undefined {
|
|
@@ -208,7 +261,7 @@ function detectImageProtocol(): ImageProtocol {
|
|
|
208
261
|
function tmuxWrap(seq: string): string {
|
|
209
262
|
if (!IS_TMUX) return seq;
|
|
210
263
|
// Double all ESC chars inside the sequence
|
|
211
|
-
const escaped = seq.
|
|
264
|
+
const escaped = seq.split("\x1b").join("\x1b\x1b");
|
|
212
265
|
return `\x1bPtmux;${escaped}\x1b\\`;
|
|
213
266
|
}
|
|
214
267
|
|
|
@@ -216,14 +269,11 @@ function tmuxWrap(seq: string): string {
|
|
|
216
269
|
* Render base64 image inline using iTerm2 inline image protocol.
|
|
217
270
|
* Protocol: ESC ] 1337 ; File=[args] : base64data BEL
|
|
218
271
|
*/
|
|
219
|
-
function renderIterm2Image(
|
|
220
|
-
base64Data: string,
|
|
221
|
-
opts: { width?: string; name?: string } = {},
|
|
222
|
-
): string {
|
|
272
|
+
function renderIterm2Image(base64Data: string, opts: { width?: string; name?: string } = {}): string {
|
|
223
273
|
const args: string[] = ["inline=1", "preserveAspectRatio=1"];
|
|
224
274
|
if (opts.width) args.push(`width=${opts.width}`);
|
|
225
275
|
if (opts.name) args.push(`name=${Buffer.from(opts.name).toString("base64")}`);
|
|
226
|
-
const byteSize = Math.ceil(base64Data.length * 3 / 4);
|
|
276
|
+
const byteSize = Math.ceil((base64Data.length * 3) / 4);
|
|
227
277
|
args.push(`size=${byteSize}`);
|
|
228
278
|
const seq = `\x1b]1337;File=${args.join(";")}:${base64Data}\x07`;
|
|
229
279
|
return tmuxWrap(seq);
|
|
@@ -235,10 +285,7 @@ function renderIterm2Image(
|
|
|
235
285
|
* Chunked in 4096-byte pieces as required by protocol.
|
|
236
286
|
* Supported by: Kitty, Ghostty
|
|
237
287
|
*/
|
|
238
|
-
function renderKittyImage(
|
|
239
|
-
base64Data: string,
|
|
240
|
-
opts: { cols?: number } = {},
|
|
241
|
-
): string {
|
|
288
|
+
function renderKittyImage(base64Data: string, opts: { cols?: number } = {}): string {
|
|
242
289
|
const chunks: string[] = [];
|
|
243
290
|
const CHUNK_SIZE = 4096;
|
|
244
291
|
|
|
@@ -279,96 +326,96 @@ const ICONS_MODE = (process.env.PRETTY_ICONS ?? "nerd").toLowerCase();
|
|
|
279
326
|
const USE_ICONS = ICONS_MODE !== "none" && ICONS_MODE !== "off";
|
|
280
327
|
|
|
281
328
|
// Nerd Font codepoints + ANSI color per file type
|
|
282
|
-
const NF_DIR
|
|
283
|
-
const NF_DIR_OPEN = `${FG_BLUE}\ue5fe${RST}`;
|
|
284
|
-
const NF_DEFAULT
|
|
329
|
+
const NF_DIR = `${FG_BLUE}\ue5ff${RST}`; // folder
|
|
330
|
+
const NF_DIR_OPEN = `${FG_BLUE}\ue5fe${RST}`; // folder open
|
|
331
|
+
const NF_DEFAULT = `${FG_DIM}\uf15b${RST}`; // generic file
|
|
285
332
|
|
|
286
333
|
const EXT_ICON: Record<string, string> = {
|
|
287
334
|
// TypeScript / JavaScript
|
|
288
|
-
ts:
|
|
289
|
-
tsx: `\x1b[38;2;49;120;198m\ue7ba${RST}`,
|
|
290
|
-
js:
|
|
291
|
-
jsx: `\x1b[38;2;97;218;251m\ue7ba${RST}`,
|
|
335
|
+
ts: `\x1b[38;2;49;120;198m\ue628${RST}`, // blue
|
|
336
|
+
tsx: `\x1b[38;2;49;120;198m\ue7ba${RST}`, // react blue
|
|
337
|
+
js: `\x1b[38;2;241;224;90m\ue74e${RST}`, // yellow
|
|
338
|
+
jsx: `\x1b[38;2;97;218;251m\ue7ba${RST}`, // react cyan
|
|
292
339
|
mjs: `\x1b[38;2;241;224;90m\ue74e${RST}`,
|
|
293
340
|
cjs: `\x1b[38;2;241;224;90m\ue74e${RST}`,
|
|
294
341
|
|
|
295
342
|
// Systems / Backend
|
|
296
|
-
py:
|
|
297
|
-
rs:
|
|
298
|
-
go:
|
|
299
|
-
java:
|
|
300
|
-
swift: `\x1b[38;2;255;172;77m\ue755${RST}`,
|
|
301
|
-
rb:
|
|
302
|
-
kt:
|
|
303
|
-
c:
|
|
304
|
-
cpp:
|
|
305
|
-
h:
|
|
306
|
-
hpp:
|
|
307
|
-
cs:
|
|
343
|
+
py: `\x1b[38;2;55;118;171m\ue73c${RST}`, // python blue
|
|
344
|
+
rs: `\x1b[38;2;222;165;132m\ue7a8${RST}`, // rust orange
|
|
345
|
+
go: `\x1b[38;2;0;173;216m\ue724${RST}`, // go cyan
|
|
346
|
+
java: `\x1b[38;2;204;62;68m\ue738${RST}`, // java red
|
|
347
|
+
swift: `\x1b[38;2;255;172;77m\ue755${RST}`, // swift orange
|
|
348
|
+
rb: `\x1b[38;2;204;52;45m\ue739${RST}`, // ruby red
|
|
349
|
+
kt: `\x1b[38;2;126;103;200m\ue634${RST}`, // kotlin purple
|
|
350
|
+
c: `\x1b[38;2;85;154;211m\ue61e${RST}`, // c blue
|
|
351
|
+
cpp: `\x1b[38;2;85;154;211m\ue61d${RST}`, // cpp blue
|
|
352
|
+
h: `\x1b[38;2;140;160;185m\ue61e${RST}`, // header muted
|
|
353
|
+
hpp: `\x1b[38;2;140;160;185m\ue61d${RST}`,
|
|
354
|
+
cs: `\x1b[38;2;104;33;122m\ue648${RST}`, // c# purple
|
|
308
355
|
|
|
309
356
|
// Web
|
|
310
|
-
html:
|
|
311
|
-
css:
|
|
312
|
-
scss:
|
|
313
|
-
less:
|
|
314
|
-
vue:
|
|
315
|
-
svelte: `\x1b[38;2;255;62;0m\ue697${RST}`,
|
|
357
|
+
html: `\x1b[38;2;228;77;38m\ue736${RST}`, // html orange
|
|
358
|
+
css: `\x1b[38;2;66;165;245m\ue749${RST}`, // css blue
|
|
359
|
+
scss: `\x1b[38;2;207;100;154m\ue749${RST}`, // scss pink
|
|
360
|
+
less: `\x1b[38;2;66;165;245m\ue749${RST}`,
|
|
361
|
+
vue: `\x1b[38;2;65;184;131m\ue6a0${RST}`, // vue green
|
|
362
|
+
svelte: `\x1b[38;2;255;62;0m\ue697${RST}`, // svelte red-orange
|
|
316
363
|
|
|
317
364
|
// Config / Data
|
|
318
|
-
json:
|
|
365
|
+
json: `\x1b[38;2;241;224;90m\ue60b${RST}`, // json yellow
|
|
319
366
|
jsonc: `\x1b[38;2;241;224;90m\ue60b${RST}`,
|
|
320
|
-
yaml:
|
|
321
|
-
yml:
|
|
322
|
-
toml:
|
|
323
|
-
xml:
|
|
324
|
-
sql:
|
|
367
|
+
yaml: `\x1b[38;2;160;116;196m\ue6a8${RST}`, // yaml purple
|
|
368
|
+
yml: `\x1b[38;2;160;116;196m\ue6a8${RST}`,
|
|
369
|
+
toml: `\x1b[38;2;160;116;196m\ue6b2${RST}`, // toml purple
|
|
370
|
+
xml: `\x1b[38;2;228;77;38m\ue619${RST}`, // xml orange
|
|
371
|
+
sql: `\x1b[38;2;218;218;218m\ue706${RST}`, // sql gray
|
|
325
372
|
|
|
326
373
|
// Markdown / Docs
|
|
327
|
-
md:
|
|
374
|
+
md: `\x1b[38;2;66;165;245m\ue73e${RST}`, // markdown blue
|
|
328
375
|
mdx: `\x1b[38;2;66;165;245m\ue73e${RST}`,
|
|
329
376
|
|
|
330
377
|
// Shell / Scripts
|
|
331
|
-
sh:
|
|
378
|
+
sh: `\x1b[38;2;137;180;130m\ue795${RST}`, // shell green
|
|
332
379
|
bash: `\x1b[38;2;137;180;130m\ue795${RST}`,
|
|
333
|
-
zsh:
|
|
380
|
+
zsh: `\x1b[38;2;137;180;130m\ue795${RST}`,
|
|
334
381
|
fish: `\x1b[38;2;137;180;130m\ue795${RST}`,
|
|
335
|
-
lua:
|
|
336
|
-
php:
|
|
337
|
-
dart: `\x1b[38;2;87;182;240m\ue798${RST}`,
|
|
382
|
+
lua: `\x1b[38;2;81;160;207m\ue620${RST}`, // lua blue
|
|
383
|
+
php: `\x1b[38;2;137;147;186m\ue73d${RST}`, // php purple
|
|
384
|
+
dart: `\x1b[38;2;87;182;240m\ue798${RST}`, // dart blue
|
|
338
385
|
|
|
339
386
|
// Images
|
|
340
|
-
png:
|
|
341
|
-
jpg:
|
|
387
|
+
png: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
|
|
388
|
+
jpg: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
|
|
342
389
|
jpeg: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
|
|
343
|
-
gif:
|
|
344
|
-
svg:
|
|
390
|
+
gif: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
|
|
391
|
+
svg: `\x1b[38;2;255;180;50m\uf1c5${RST}`,
|
|
345
392
|
webp: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
|
|
346
|
-
ico:
|
|
393
|
+
ico: `\x1b[38;2;160;116;196m\uf1c5${RST}`,
|
|
347
394
|
|
|
348
395
|
// Misc
|
|
349
|
-
lock: `\x1b[38;2;130;130;130m\uf023${RST}`,
|
|
350
|
-
env:
|
|
351
|
-
graphql: `\x1b[38;2;224;51;144m\ue662${RST}`,
|
|
396
|
+
lock: `\x1b[38;2;130;130;130m\uf023${RST}`, // lock gray
|
|
397
|
+
env: `\x1b[38;2;241;224;90m\ue615${RST}`, // env yellow
|
|
398
|
+
graphql: `\x1b[38;2;224;51;144m\ue662${RST}`, // graphql pink
|
|
352
399
|
dockerfile: `\x1b[38;2;56;152;236m\ue7b0${RST}`,
|
|
353
400
|
};
|
|
354
401
|
|
|
355
402
|
const NAME_ICON: Record<string, string> = {
|
|
356
|
-
"package.json":
|
|
357
|
-
"package-lock.json": `\x1b[38;2;130;130;130m\ue71e${RST}`,
|
|
358
|
-
"tsconfig.json":
|
|
359
|
-
"biome.json":
|
|
360
|
-
".gitignore":
|
|
361
|
-
".git":
|
|
362
|
-
".env":
|
|
363
|
-
".envrc":
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
"readme.md":
|
|
368
|
-
|
|
369
|
-
"cargo.toml":
|
|
370
|
-
"go.mod":
|
|
371
|
-
"pyproject.toml":
|
|
403
|
+
"package.json": `\x1b[38;2;137;180;130m\ue71e${RST}`, // npm green
|
|
404
|
+
"package-lock.json": `\x1b[38;2;130;130;130m\ue71e${RST}`, // npm gray
|
|
405
|
+
"tsconfig.json": `\x1b[38;2;49;120;198m\ue628${RST}`, // ts blue
|
|
406
|
+
"biome.json": `\x1b[38;2;96;165;250m\ue615${RST}`, // config blue
|
|
407
|
+
".gitignore": `\x1b[38;2;222;165;132m\ue702${RST}`, // git orange
|
|
408
|
+
".git": `\x1b[38;2;222;165;132m\ue702${RST}`,
|
|
409
|
+
".env": `\x1b[38;2;241;224;90m\ue615${RST}`, // env yellow
|
|
410
|
+
".envrc": `\x1b[38;2;241;224;90m\ue615${RST}`,
|
|
411
|
+
dockerfile: `\x1b[38;2;56;152;236m\ue7b0${RST}`, // docker blue
|
|
412
|
+
makefile: `\x1b[38;2;130;130;130m\ue615${RST}`, // make gray
|
|
413
|
+
gnumakefile: `\x1b[38;2;130;130;130m\ue615${RST}`,
|
|
414
|
+
"readme.md": `\x1b[38;2;66;165;245m\ue73e${RST}`, // readme blue
|
|
415
|
+
license: `\x1b[38;2;218;218;218m\ue60a${RST}`, // license white
|
|
416
|
+
"cargo.toml": `\x1b[38;2;222;165;132m\ue7a8${RST}`, // rust
|
|
417
|
+
"go.mod": `\x1b[38;2;0;173;216m\ue724${RST}`, // go
|
|
418
|
+
"pyproject.toml": `\x1b[38;2;55;118;171m\ue73c${RST}`, // python
|
|
372
419
|
};
|
|
373
420
|
|
|
374
421
|
function fileIcon(fp: string): string {
|
|
@@ -403,10 +450,7 @@ function _touch(k: string, v: string[]): string[] {
|
|
|
403
450
|
return v;
|
|
404
451
|
}
|
|
405
452
|
|
|
406
|
-
async function hlBlock(
|
|
407
|
-
code: string,
|
|
408
|
-
language: BundledLanguage | undefined,
|
|
409
|
-
): Promise<string[]> {
|
|
453
|
+
async function hlBlock(code: string, language: BundledLanguage | undefined): Promise<string[]> {
|
|
410
454
|
if (!code) return [""];
|
|
411
455
|
if (!language || code.length > MAX_HL_CHARS) return code.split("\n");
|
|
412
456
|
|
|
@@ -470,32 +514,24 @@ async function renderFileContent(
|
|
|
470
514
|
vis++;
|
|
471
515
|
j++;
|
|
472
516
|
}
|
|
473
|
-
display = code.slice(0, j)
|
|
517
|
+
display = `${code.slice(0, j)}${RST}${FG_DIM}›${RST}`;
|
|
474
518
|
}
|
|
475
519
|
out.push(`${lnum(ln, nw)} ${FG_RULE}│${RST} ${display}${RST}`);
|
|
476
520
|
}
|
|
477
521
|
|
|
478
522
|
out.push(rule(tw));
|
|
479
523
|
if (total > maxLines) {
|
|
480
|
-
out.push(
|
|
481
|
-
`${FG_DIM} … ${total - maxLines} more lines (${total} total)${RST}`,
|
|
482
|
-
);
|
|
524
|
+
out.push(`${FG_DIM} … ${total - maxLines} more lines (${total} total)${RST}`);
|
|
483
525
|
}
|
|
484
526
|
return out.join("\n");
|
|
485
527
|
}
|
|
486
528
|
|
|
487
529
|
/** Render bash output with colored exit code and stderr highlighting. */
|
|
488
|
-
function renderBashOutput(
|
|
489
|
-
text: string,
|
|
490
|
-
exitCode: number | null,
|
|
491
|
-
): { summary: string; body: string } {
|
|
530
|
+
function renderBashOutput(text: string, exitCode: number | null): { summary: string; body: string } {
|
|
492
531
|
const isOk = exitCode === 0;
|
|
493
532
|
const statusFg = isOk ? FG_GREEN : FG_RED;
|
|
494
533
|
const statusIcon = isOk ? "✓" : "✗";
|
|
495
|
-
const codeStr =
|
|
496
|
-
exitCode !== null
|
|
497
|
-
? `${statusFg}${statusIcon} exit ${exitCode}${RST}`
|
|
498
|
-
: `${FG_YELLOW}⚡ killed${RST}`;
|
|
534
|
+
const codeStr = exitCode !== null ? `${statusFg}${statusIcon} exit ${exitCode}${RST}` : `${FG_YELLOW}⚡ killed${RST}`;
|
|
499
535
|
|
|
500
536
|
const lines = text.split("\n");
|
|
501
537
|
const maxShow = MAX_PREVIEW_LINES;
|
|
@@ -536,9 +572,7 @@ function renderTree(text: string, basePath: string): string {
|
|
|
536
572
|
}
|
|
537
573
|
|
|
538
574
|
if (total > MAX_PREVIEW_LINES) {
|
|
539
|
-
out.push(
|
|
540
|
-
`${FG_RULE}└── ${RST}${FG_DIM}… ${total - MAX_PREVIEW_LINES} more entries${RST}`,
|
|
541
|
-
);
|
|
575
|
+
out.push(`${FG_RULE}└── ${RST}${FG_DIM}… ${total - MAX_PREVIEW_LINES} more entries${RST}`);
|
|
542
576
|
}
|
|
543
577
|
|
|
544
578
|
return out.join("\n");
|
|
@@ -567,9 +601,7 @@ function renderFindResults(text: string): string {
|
|
|
567
601
|
out.push(`${dirIcon()}${FG_BLUE}${BOLD}${dir}/${RST}`);
|
|
568
602
|
for (let i = 0; i < files.length; i++) {
|
|
569
603
|
if (count >= MAX_PREVIEW_LINES) {
|
|
570
|
-
out.push(
|
|
571
|
-
` ${FG_DIM}… ${lines.length - count} more files${RST}`,
|
|
572
|
-
);
|
|
604
|
+
out.push(` ${FG_DIM}… ${lines.length - count} more files${RST}`);
|
|
573
605
|
return out.join("\n");
|
|
574
606
|
}
|
|
575
607
|
const isLast = i === files.length - 1;
|
|
@@ -584,13 +616,9 @@ function renderFindResults(text: string): string {
|
|
|
584
616
|
}
|
|
585
617
|
|
|
586
618
|
/** Render grep results with highlighted matches and line numbers. */
|
|
587
|
-
async function renderGrepResults(
|
|
588
|
-
text: string,
|
|
589
|
-
pattern: string,
|
|
590
|
-
): Promise<string> {
|
|
619
|
+
async function renderGrepResults(text: string, pattern: string): Promise<string> {
|
|
591
620
|
const lines = text.split("\n");
|
|
592
|
-
if (!lines.length || (lines.length === 1 && !lines[0].trim()))
|
|
593
|
-
return `${FG_DIM}(no matches)${RST}`;
|
|
621
|
+
if (!lines.length || (lines.length === 1 && !lines[0].trim())) return `${FG_DIM}(no matches)${RST}`;
|
|
594
622
|
|
|
595
623
|
const tw = termW();
|
|
596
624
|
const out: string[] = [];
|
|
@@ -612,7 +640,7 @@ async function renderGrepResults(
|
|
|
612
640
|
}
|
|
613
641
|
|
|
614
642
|
// ripgrep-style: "file:line:content" or "file-line-content" or just "file"
|
|
615
|
-
const fileMatch = line.match(/^(.+?)[
|
|
643
|
+
const fileMatch = line.match(/^(.+?)[:-](\d+)[:-](.*)$/);
|
|
616
644
|
if (fileMatch) {
|
|
617
645
|
const [, file, lineNo, content] = fileMatch;
|
|
618
646
|
if (file !== currentFile) {
|
|
@@ -625,10 +653,7 @@ async function renderGrepResults(
|
|
|
625
653
|
const nw = Math.max(3, lineNo.length);
|
|
626
654
|
let display = content;
|
|
627
655
|
if (re) {
|
|
628
|
-
display = content.replace(
|
|
629
|
-
re,
|
|
630
|
-
`${RST}${FG_YELLOW}${BOLD}$1${RST}`,
|
|
631
|
-
);
|
|
656
|
+
display = content.replace(re, `${RST}${FG_YELLOW}${BOLD}$1${RST}`);
|
|
632
657
|
}
|
|
633
658
|
out.push(` ${lnum(Number(lineNo), nw)} ${FG_RULE}│${RST} ${display}${RST}`);
|
|
634
659
|
count++;
|
|
@@ -722,24 +747,25 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
722
747
|
},
|
|
723
748
|
|
|
724
749
|
renderCall(args: any, theme: any, ctx: any) {
|
|
750
|
+
resolveBaseBackground(theme);
|
|
725
751
|
const fp = args?.path ?? "";
|
|
726
752
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
727
753
|
const offset = args?.offset ? ` ${theme.fg("muted", `from line ${args.offset}`)}` : "";
|
|
728
754
|
const limit = args?.limit ? ` ${theme.fg("muted", `(${args.limit} lines)`)}` : "";
|
|
729
|
-
text.setText(
|
|
730
|
-
`${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", sp(fp))}${offset}${limit}`,
|
|
731
|
-
);
|
|
755
|
+
text.setText(`${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", sp(fp))}${offset}${limit}`);
|
|
732
756
|
return text;
|
|
733
757
|
},
|
|
734
758
|
|
|
735
759
|
renderResult(result: any, _opt: any, theme: any, ctx: any) {
|
|
760
|
+
resolveBaseBackground(theme);
|
|
736
761
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
737
762
|
|
|
738
763
|
if (ctx.isError) {
|
|
739
|
-
const e =
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
764
|
+
const e =
|
|
765
|
+
result.content
|
|
766
|
+
?.filter((c: any) => c.type === "text")
|
|
767
|
+
.map((c: any) => c.text || "")
|
|
768
|
+
.join("\n") ?? "Error";
|
|
743
769
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
744
770
|
return text;
|
|
745
771
|
}
|
|
@@ -751,7 +777,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
751
777
|
const tw = termW();
|
|
752
778
|
const out: string[] = [];
|
|
753
779
|
const fname = basename(d.filePath);
|
|
754
|
-
const byteSize = Math.ceil((d.data as string).length * 3 / 4);
|
|
780
|
+
const byteSize = Math.ceil(((d.data as string).length * 3) / 4);
|
|
755
781
|
const sizeStr = humanSize(byteSize);
|
|
756
782
|
const mimeStr = d.mimeType ?? "image";
|
|
757
783
|
|
|
@@ -764,10 +790,12 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
764
790
|
out.push(renderKittyImage(d.data, { cols: imgCols }));
|
|
765
791
|
} else if (protocol === "iterm2") {
|
|
766
792
|
const imgWidth = Math.min(tw - 4, 80);
|
|
767
|
-
out.push(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
793
|
+
out.push(
|
|
794
|
+
renderIterm2Image(d.data, {
|
|
795
|
+
width: `${imgWidth}`,
|
|
796
|
+
name: fname,
|
|
797
|
+
}),
|
|
798
|
+
);
|
|
771
799
|
} else {
|
|
772
800
|
out.push(` ${FG_DIM}(Inline image preview requires Ghostty, iTerm2, WezTerm, or Kitty)${RST}`);
|
|
773
801
|
}
|
|
@@ -826,15 +854,10 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
826
854
|
// Try to extract exit code from the output
|
|
827
855
|
let exitCode: number | null = 0;
|
|
828
856
|
if (textContent) {
|
|
829
|
-
const exitMatch = textContent.match(
|
|
830
|
-
/(?:exit code|exited with|exit status)[:\s]*(\d+)/i,
|
|
831
|
-
);
|
|
857
|
+
const exitMatch = textContent.match(/(?:exit code|exited with|exit status)[:\s]*(\d+)/i);
|
|
832
858
|
if (exitMatch) exitCode = Number(exitMatch[1]);
|
|
833
859
|
// Check for common error indicators
|
|
834
|
-
if (
|
|
835
|
-
textContent.includes("command not found") ||
|
|
836
|
-
textContent.includes("No such file")
|
|
837
|
-
) {
|
|
860
|
+
if (textContent.includes("command not found") || textContent.includes("No such file")) {
|
|
838
861
|
exitCode = 1;
|
|
839
862
|
}
|
|
840
863
|
}
|
|
@@ -850,11 +873,10 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
850
873
|
},
|
|
851
874
|
|
|
852
875
|
renderCall(args: any, theme: any, ctx: any) {
|
|
876
|
+
resolveBaseBackground(theme);
|
|
853
877
|
const cmd = args?.command ?? "";
|
|
854
878
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
855
|
-
const timeout = args?.timeout
|
|
856
|
-
? ` ${theme.fg("muted", `(${args.timeout}s timeout)`)}`
|
|
857
|
-
: "";
|
|
879
|
+
const timeout = args?.timeout ? ` ${theme.fg("muted", `(${args.timeout}s timeout)`)}` : "";
|
|
858
880
|
text.setText(
|
|
859
881
|
`${theme.fg("toolTitle", theme.bold("bash"))} ${theme.fg("accent", cmd.length > 80 ? cmd.slice(0, 77) + "…" : cmd)}${timeout}`,
|
|
860
882
|
);
|
|
@@ -862,13 +884,15 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
862
884
|
},
|
|
863
885
|
|
|
864
886
|
renderResult(result: any, _opt: any, theme: any, ctx: any) {
|
|
887
|
+
resolveBaseBackground(theme);
|
|
865
888
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
866
889
|
|
|
867
890
|
if (ctx.isError) {
|
|
868
|
-
const e =
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
891
|
+
const e =
|
|
892
|
+
result.content
|
|
893
|
+
?.filter((c: any) => c.type === "text")
|
|
894
|
+
.map((c: any) => c.text || "")
|
|
895
|
+
.join("\n") ?? "Error";
|
|
872
896
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
873
897
|
return text;
|
|
874
898
|
}
|
|
@@ -902,9 +926,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
902
926
|
}
|
|
903
927
|
|
|
904
928
|
const fallback = result.content?.[0]?.text ?? "done";
|
|
905
|
-
text.setText(
|
|
906
|
-
` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
|
|
907
|
-
);
|
|
929
|
+
text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
|
|
908
930
|
return text;
|
|
909
931
|
},
|
|
910
932
|
});
|
|
@@ -930,9 +952,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
930
952
|
.join("\n");
|
|
931
953
|
|
|
932
954
|
const fp = params.path ?? cwd;
|
|
933
|
-
const entryCount = textContent
|
|
934
|
-
? textContent.trim().split("\n").filter(Boolean).length
|
|
935
|
-
: 0;
|
|
955
|
+
const entryCount = textContent ? textContent.trim().split("\n").filter(Boolean).length : 0;
|
|
936
956
|
|
|
937
957
|
(result as any).details = {
|
|
938
958
|
_type: "lsResult",
|
|
@@ -945,22 +965,23 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
945
965
|
},
|
|
946
966
|
|
|
947
967
|
renderCall(args: any, theme: any, ctx: any) {
|
|
968
|
+
resolveBaseBackground(theme);
|
|
948
969
|
const fp = args?.path ?? ".";
|
|
949
970
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
950
|
-
text.setText(
|
|
951
|
-
`${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", sp(fp))}`,
|
|
952
|
-
);
|
|
971
|
+
text.setText(`${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", sp(fp))}`);
|
|
953
972
|
return text;
|
|
954
973
|
},
|
|
955
974
|
|
|
956
975
|
renderResult(result: any, _opt: any, theme: any, ctx: any) {
|
|
976
|
+
resolveBaseBackground(theme);
|
|
957
977
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
958
978
|
|
|
959
979
|
if (ctx.isError) {
|
|
960
|
-
const e =
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
980
|
+
const e =
|
|
981
|
+
result.content
|
|
982
|
+
?.filter((c: any) => c.type === "text")
|
|
983
|
+
.map((c: any) => c.text || "")
|
|
984
|
+
.join("\n") ?? "Error";
|
|
964
985
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
965
986
|
return text;
|
|
966
987
|
}
|
|
@@ -974,9 +995,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
974
995
|
}
|
|
975
996
|
|
|
976
997
|
const fallback = result.content?.[0]?.text ?? "listed";
|
|
977
|
-
text.setText(
|
|
978
|
-
` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
|
|
979
|
-
);
|
|
998
|
+
text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
|
|
980
999
|
return text;
|
|
981
1000
|
},
|
|
982
1001
|
});
|
|
@@ -1001,9 +1020,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
1001
1020
|
.map((c: any) => c.text || "")
|
|
1002
1021
|
.join("\n");
|
|
1003
1022
|
|
|
1004
|
-
const matchCount = textContent
|
|
1005
|
-
? textContent.trim().split("\n").filter(Boolean).length
|
|
1006
|
-
: 0;
|
|
1023
|
+
const matchCount = textContent ? textContent.trim().split("\n").filter(Boolean).length : 0;
|
|
1007
1024
|
|
|
1008
1025
|
(result as any).details = {
|
|
1009
1026
|
_type: "findResult",
|
|
@@ -1016,23 +1033,24 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
1016
1033
|
},
|
|
1017
1034
|
|
|
1018
1035
|
renderCall(args: any, theme: any, ctx: any) {
|
|
1036
|
+
resolveBaseBackground(theme);
|
|
1019
1037
|
const pattern = args?.pattern ?? "";
|
|
1020
1038
|
const path = args?.path ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}` : "";
|
|
1021
1039
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1022
|
-
text.setText(
|
|
1023
|
-
`${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}${path}`,
|
|
1024
|
-
);
|
|
1040
|
+
text.setText(`${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}${path}`);
|
|
1025
1041
|
return text;
|
|
1026
1042
|
},
|
|
1027
1043
|
|
|
1028
1044
|
renderResult(result: any, _opt: any, theme: any, ctx: any) {
|
|
1045
|
+
resolveBaseBackground(theme);
|
|
1029
1046
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1030
1047
|
|
|
1031
1048
|
if (ctx.isError) {
|
|
1032
|
-
const e =
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1049
|
+
const e =
|
|
1050
|
+
result.content
|
|
1051
|
+
?.filter((c: any) => c.type === "text")
|
|
1052
|
+
.map((c: any) => c.text || "")
|
|
1053
|
+
.join("\n") ?? "Error";
|
|
1036
1054
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
1037
1055
|
return text;
|
|
1038
1056
|
}
|
|
@@ -1046,9 +1064,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
1046
1064
|
}
|
|
1047
1065
|
|
|
1048
1066
|
const fallback = result.content?.[0]?.text ?? "found";
|
|
1049
|
-
text.setText(
|
|
1050
|
-
` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
|
|
1051
|
-
);
|
|
1067
|
+
text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
|
|
1052
1068
|
return text;
|
|
1053
1069
|
},
|
|
1054
1070
|
});
|
|
@@ -1077,8 +1093,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
1077
1093
|
? textContent
|
|
1078
1094
|
.trim()
|
|
1079
1095
|
.split("\n")
|
|
1080
|
-
.filter((l: string) => l.match(/^.+?[:\-]\d+[:\-]/))
|
|
1081
|
-
.length
|
|
1096
|
+
.filter((l: string) => l.match(/^.+?[:\-]\d+[:\-]/)).length
|
|
1082
1097
|
: 0;
|
|
1083
1098
|
|
|
1084
1099
|
(result as any).details = {
|
|
@@ -1092,24 +1107,25 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
1092
1107
|
},
|
|
1093
1108
|
|
|
1094
1109
|
renderCall(args: any, theme: any, ctx: any) {
|
|
1110
|
+
resolveBaseBackground(theme);
|
|
1095
1111
|
const pattern = args?.pattern ?? "";
|
|
1096
1112
|
const path = args?.path ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}` : "";
|
|
1097
1113
|
const glob = args?.glob ? ` ${theme.fg("muted", `(${args.glob})`)}` : "";
|
|
1098
1114
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1099
|
-
text.setText(
|
|
1100
|
-
`${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", pattern)}${path}${glob}`,
|
|
1101
|
-
);
|
|
1115
|
+
text.setText(`${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", pattern)}${path}${glob}`);
|
|
1102
1116
|
return text;
|
|
1103
1117
|
},
|
|
1104
1118
|
|
|
1105
1119
|
renderResult(result: any, _opt: any, theme: any, ctx: any) {
|
|
1120
|
+
resolveBaseBackground(theme);
|
|
1106
1121
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1107
1122
|
|
|
1108
1123
|
if (ctx.isError) {
|
|
1109
|
-
const e =
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1124
|
+
const e =
|
|
1125
|
+
result.content
|
|
1126
|
+
?.filter((c: any) => c.type === "text")
|
|
1127
|
+
.map((c: any) => c.text || "")
|
|
1128
|
+
.join("\n") ?? "Error";
|
|
1113
1129
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
1114
1130
|
return text;
|
|
1115
1131
|
}
|
|
@@ -1135,9 +1151,7 @@ export default function piPrettyExtension(pi: any): void {
|
|
|
1135
1151
|
}
|
|
1136
1152
|
|
|
1137
1153
|
const fallback = result.content?.[0]?.text ?? "searched";
|
|
1138
|
-
text.setText(
|
|
1139
|
-
` ${theme.fg("dim", String(fallback).slice(0, 120))}`,
|
|
1140
|
-
);
|
|
1154
|
+
text.setText(` ${theme.fg("dim", String(fallback).slice(0, 120))}`);
|
|
1141
1155
|
return text;
|
|
1142
1156
|
},
|
|
1143
1157
|
});
|