claude-statusline 2.1.2
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/LICENSE +203 -0
- package/README.md +362 -0
- package/bin/claude-statusline +22 -0
- package/dist/core/cache.d.ts +67 -0
- package/dist/core/cache.js +223 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/config.d.ts +190 -0
- package/dist/core/config.js +192 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/security.d.ts +27 -0
- package/dist/core/security.js +154 -0
- package/dist/core/security.js.map +1 -0
- package/dist/env/context.d.ts +92 -0
- package/dist/env/context.js +295 -0
- package/dist/env/context.js.map +1 -0
- package/dist/git/native.d.ts +35 -0
- package/dist/git/native.js +141 -0
- package/dist/git/native.js.map +1 -0
- package/dist/git/status.d.ts +65 -0
- package/dist/git/status.js +256 -0
- package/dist/git/status.js.map +1 -0
- package/dist/index.bundle.js +11 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +396 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile.prod.json +473 -0
- package/dist/ui/symbols.d.ts +31 -0
- package/dist/ui/symbols.js +308 -0
- package/dist/ui/symbols.js.map +1 -0
- package/dist/ui/width.d.ts +29 -0
- package/dist/ui/width.js +261 -0
- package/dist/ui/width.js.map +1 -0
- package/dist/utils/runtime.d.ts +31 -0
- package/dist/utils/runtime.js +82 -0
- package/dist/utils/runtime.js.map +1 -0
- package/docs/ARCHITECTURE.md +336 -0
- package/docs/FEATURE_COMPARISON.md +178 -0
- package/docs/MIGRATION.md +354 -0
- package/docs/README.md +101 -0
- package/docs/eval-01-terminal-widths.md +519 -0
- package/docs/guide-01-configuration.md +277 -0
- package/docs/guide-02-troubleshooting.md +416 -0
- package/docs/guide-03-performance.md +183 -0
- package/docs/prd-01-typescript-perf-optimization.md +480 -0
- package/docs/research-01-sandbox-detection.md +169 -0
- package/docs/research-02-competitive-analysis.md +226 -0
- package/docs/research-03-platform-analysis.md +142 -0
- package/package.json +89 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol detection cache - symbols don't change during runtime
|
|
3
|
+
* Added cache version to handle potential stale data issues
|
|
4
|
+
*/
|
|
5
|
+
const symbolCache = new Map();
|
|
6
|
+
const CACHE_VERSION = 1; // Increment to invalidate all caches
|
|
7
|
+
/**
|
|
8
|
+
* ASCII symbol set (fallback)
|
|
9
|
+
*/
|
|
10
|
+
const ASCII_SYMBOLS = {
|
|
11
|
+
git: '@',
|
|
12
|
+
model: '*',
|
|
13
|
+
contextWindow: '#',
|
|
14
|
+
staged: '+',
|
|
15
|
+
conflict: 'C',
|
|
16
|
+
stashed: '$',
|
|
17
|
+
ahead: 'A',
|
|
18
|
+
behind: 'B',
|
|
19
|
+
diverged: 'D',
|
|
20
|
+
renamed: '>',
|
|
21
|
+
deleted: 'X',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Nerd Font symbol set (enhanced)
|
|
25
|
+
*/
|
|
26
|
+
const NERD_FONT_SYMBOLS = {
|
|
27
|
+
git: '',
|
|
28
|
+
model: '',
|
|
29
|
+
contextWindow: '⚡︎',
|
|
30
|
+
staged: '+',
|
|
31
|
+
conflict: '×',
|
|
32
|
+
stashed: '⚑',
|
|
33
|
+
ahead: '⇡',
|
|
34
|
+
behind: '⇣',
|
|
35
|
+
diverged: '⇕',
|
|
36
|
+
renamed: '»',
|
|
37
|
+
deleted: '✘',
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Detect Nerd Font support and return appropriate symbols (with caching)
|
|
41
|
+
*/
|
|
42
|
+
export async function detectSymbols(config) {
|
|
43
|
+
// Create cache key based on config and environment
|
|
44
|
+
const envFingerprint = process.env.NERD_FONT + '|' + process.env.TERM_PROGRAM + '|' + process.env.TERM;
|
|
45
|
+
const cacheKey = `${CACHE_VERSION}:${config.noEmoji ? 'ascii' : 'nerd'}:${envFingerprint}`;
|
|
46
|
+
// Check cache first with timestamp validation
|
|
47
|
+
const cached = symbolCache.get(cacheKey);
|
|
48
|
+
if (cached && Date.now() - cached.timestamp < 60000) { // 1 minute cache TTL
|
|
49
|
+
return cached.symbols;
|
|
50
|
+
}
|
|
51
|
+
let symbols;
|
|
52
|
+
// If emoji/nerd font is explicitly disabled, use ASCII
|
|
53
|
+
if (config.noEmoji) {
|
|
54
|
+
symbols = { ...ASCII_SYMBOLS, ...config.symbols, ...config.asciiSymbols };
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Try to detect Nerd Font support
|
|
58
|
+
const detection = await detectNerdFontSupport();
|
|
59
|
+
if (detection.hasNerdFont) {
|
|
60
|
+
// Merge user's custom symbols with Nerd Font defaults
|
|
61
|
+
symbols = { ...NERD_FONT_SYMBOLS, ...config.symbols };
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Merge user's custom ASCII symbols with ASCII defaults
|
|
65
|
+
symbols = { ...ASCII_SYMBOLS, ...config.asciiSymbols };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Cache the result with timestamp
|
|
69
|
+
symbolCache.set(cacheKey, { symbols, timestamp: Date.now() });
|
|
70
|
+
return symbols;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Comprehensive Nerd Font support detection
|
|
74
|
+
*/
|
|
75
|
+
async function detectNerdFontSupport() {
|
|
76
|
+
const terminalInfo = {
|
|
77
|
+
hasNerdFont: false,
|
|
78
|
+
terminal: '',
|
|
79
|
+
font: '',
|
|
80
|
+
method: '',
|
|
81
|
+
};
|
|
82
|
+
// Method 1: Environment variable NERD_FONT=1
|
|
83
|
+
if (process.env.NERD_FONT === '1') {
|
|
84
|
+
terminalInfo.hasNerdFont = true;
|
|
85
|
+
terminalInfo.method = 'NERD_FONT env var';
|
|
86
|
+
return terminalInfo;
|
|
87
|
+
}
|
|
88
|
+
// Method 2: Terminal program detection
|
|
89
|
+
const termProgram = process.env.TERM_PROGRAM;
|
|
90
|
+
const term = process.env.TERM;
|
|
91
|
+
if (termProgram) {
|
|
92
|
+
terminalInfo.terminal = termProgram;
|
|
93
|
+
terminalInfo.method = 'TERM_PROGRAM detection';
|
|
94
|
+
// These terminals commonly have Nerd Font support
|
|
95
|
+
const nerdFontTerminals = ['vscode', 'ghostty', 'wezterm', 'iterm'];
|
|
96
|
+
if (nerdFontTerminals.includes(termProgram)) {
|
|
97
|
+
terminalInfo.hasNerdFont = true;
|
|
98
|
+
return terminalInfo;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (term) {
|
|
102
|
+
terminalInfo.terminal = term;
|
|
103
|
+
terminalInfo.method = 'TERM detection';
|
|
104
|
+
// These terminal types commonly support Nerd Fonts
|
|
105
|
+
const nerdFontTerms = ['alacritty', 'kitty', 'wezterm', 'ghostty', 'xterm-256color'];
|
|
106
|
+
if (nerdFontTerms.includes(term)) {
|
|
107
|
+
terminalInfo.hasNerdFont = true;
|
|
108
|
+
return terminalInfo;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Method 3: Try to detect via font list (Unix/Linux/macOS)
|
|
112
|
+
const fontDetection = await detectViaFontList();
|
|
113
|
+
if (fontDetection.hasNerdFont) {
|
|
114
|
+
terminalInfo.hasNerdFont = true;
|
|
115
|
+
terminalInfo.font = fontDetection.font;
|
|
116
|
+
terminalInfo.method = 'font list detection';
|
|
117
|
+
return terminalInfo;
|
|
118
|
+
}
|
|
119
|
+
// Method 4: Check for common Nerd Font installation patterns
|
|
120
|
+
const installDetection = await detectNerdFontInstallation();
|
|
121
|
+
if (installDetection.hasNerdFont) {
|
|
122
|
+
terminalInfo.hasNerdFont = true;
|
|
123
|
+
terminalInfo.font = installDetection.font;
|
|
124
|
+
terminalInfo.method = 'installation detection';
|
|
125
|
+
return terminalInfo;
|
|
126
|
+
}
|
|
127
|
+
// Method 5: Check environment variables that might indicate font support
|
|
128
|
+
const envDetection = detectFromEnvironment();
|
|
129
|
+
if (envDetection.hasNerdFont) {
|
|
130
|
+
terminalInfo.hasNerdFont = true;
|
|
131
|
+
terminalInfo.method = 'environment detection';
|
|
132
|
+
return terminalInfo;
|
|
133
|
+
}
|
|
134
|
+
// Method 6: Platform-specific detection
|
|
135
|
+
const platformDetection = await detectPlatformSpecific();
|
|
136
|
+
if (platformDetection.hasNerdFont) {
|
|
137
|
+
terminalInfo.hasNerdFont = true;
|
|
138
|
+
terminalInfo.method = 'platform-specific detection';
|
|
139
|
+
return terminalInfo;
|
|
140
|
+
}
|
|
141
|
+
// Default: no Nerd Font detected
|
|
142
|
+
return terminalInfo;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Detect Nerd Font via font list command
|
|
146
|
+
*/
|
|
147
|
+
async function detectViaFontList() {
|
|
148
|
+
try {
|
|
149
|
+
const { exec } = await import('child_process');
|
|
150
|
+
const { promisify } = await import('util');
|
|
151
|
+
const execAsync = promisify(exec);
|
|
152
|
+
// Try fc-list (Linux) or system_profiler (macOS)
|
|
153
|
+
let fontListCommand = '';
|
|
154
|
+
const platform = process.platform;
|
|
155
|
+
if (platform === 'linux') {
|
|
156
|
+
fontListCommand = 'fc-list';
|
|
157
|
+
}
|
|
158
|
+
else if (platform === 'darwin') {
|
|
159
|
+
fontListCommand = 'system_profiler SPFontsDataType 2>/dev/null || system_profiler SPFontsDataType';
|
|
160
|
+
}
|
|
161
|
+
if (fontListCommand) {
|
|
162
|
+
const { stdout } = await execAsync(fontListCommand, {
|
|
163
|
+
timeout: 3000,
|
|
164
|
+
encoding: 'utf-8',
|
|
165
|
+
});
|
|
166
|
+
const nerdFontPatterns = [
|
|
167
|
+
/nerd font/i,
|
|
168
|
+
/symbols only/i,
|
|
169
|
+
/jetbrains mono.*nerd/i,
|
|
170
|
+
/fira code.*nerd/i,
|
|
171
|
+
/hack.*nerd/i,
|
|
172
|
+
/source code pro.*nerd/i,
|
|
173
|
+
/ubuntu mono.*nerd/i,
|
|
174
|
+
/anonymous pro.*nerd/i,
|
|
175
|
+
];
|
|
176
|
+
for (const pattern of nerdFontPatterns) {
|
|
177
|
+
if (pattern.test(stdout)) {
|
|
178
|
+
// Try to extract font name
|
|
179
|
+
const match = stdout.match(/([^:\n]*)(?=\s*(nerd|symbols))/i);
|
|
180
|
+
const fontName = match ? match[1] : 'Nerd Font';
|
|
181
|
+
return { hasNerdFont: true, font: fontName?.trim() || 'Nerd Font' };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Font detection failed
|
|
188
|
+
}
|
|
189
|
+
return { hasNerdFont: false, font: '' };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Detect Nerd Font installation in common locations
|
|
193
|
+
*/
|
|
194
|
+
async function detectNerdFontInstallation() {
|
|
195
|
+
try {
|
|
196
|
+
const { access, readdir } = await import('fs/promises');
|
|
197
|
+
const { homedir } = await import('os');
|
|
198
|
+
const platform = process.platform;
|
|
199
|
+
const fontPaths = [];
|
|
200
|
+
if (platform === 'darwin') {
|
|
201
|
+
fontPaths.push(`${homedir()}/Library/Fonts`, '/System/Library/Fonts', '/Library/Fonts');
|
|
202
|
+
}
|
|
203
|
+
else if (platform === 'linux') {
|
|
204
|
+
fontPaths.push(`${homedir()}/.local/share/fonts`, `${homedir()}/.fonts`, '/usr/share/fonts', '/usr/local/share/fonts');
|
|
205
|
+
}
|
|
206
|
+
const nerdFontNames = [
|
|
207
|
+
'jetbrains-mono-nerd-font',
|
|
208
|
+
'fira-code-nerd-font',
|
|
209
|
+
'hack-nerd-font',
|
|
210
|
+
'source-code-pro-nerd-font',
|
|
211
|
+
'ubuntu-mono-nerd-font',
|
|
212
|
+
'anonymous-pro-nerd-font',
|
|
213
|
+
];
|
|
214
|
+
for (const fontPath of fontPaths) {
|
|
215
|
+
try {
|
|
216
|
+
await access(fontPath);
|
|
217
|
+
const files = await readdir(fontPath);
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
const fileName = file.toLowerCase();
|
|
220
|
+
for (const nerdFontName of nerdFontNames) {
|
|
221
|
+
if (fileName.includes(nerdFontName)) {
|
|
222
|
+
return { hasNerdFont: true, font: file };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Can't access this font directory
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// Font detection failed
|
|
234
|
+
}
|
|
235
|
+
return { hasNerdFont: false, font: '' };
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Detect Nerd Font support from environment variables
|
|
239
|
+
*/
|
|
240
|
+
function detectFromEnvironment() {
|
|
241
|
+
// Check for various environment variables that might indicate Nerd Font support
|
|
242
|
+
const nerdFontEnvVars = [
|
|
243
|
+
'POWERLINE_COMMAND', // Often used with Nerd Fonts
|
|
244
|
+
'NERDFONTS', // Some terminals set this
|
|
245
|
+
'FONT_FAMILY', // Some terminals expose the current font
|
|
246
|
+
];
|
|
247
|
+
for (const envVar of nerdFontEnvVars) {
|
|
248
|
+
const value = process.env[envVar];
|
|
249
|
+
if (value && value.toLowerCase().includes('nerd')) {
|
|
250
|
+
return { hasNerdFont: true };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Check if we're in a development environment that likely has Nerd Fonts
|
|
254
|
+
if (process.env.VSCODE_PID ||
|
|
255
|
+
process.env.TERM_PROGRAM === 'vscode' ||
|
|
256
|
+
process.env.TERM_PROGRAM === 'ghostty' ||
|
|
257
|
+
process.env.TERM_PROGRAM === 'wezterm') {
|
|
258
|
+
return { hasNerdFont: true };
|
|
259
|
+
}
|
|
260
|
+
return { hasNerdFont: false };
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Platform-specific Nerd Font detection
|
|
264
|
+
*/
|
|
265
|
+
async function detectPlatformSpecific() {
|
|
266
|
+
const platform = process.platform;
|
|
267
|
+
if (platform === 'darwin') {
|
|
268
|
+
// macOS: check if we're in a common development environment
|
|
269
|
+
try {
|
|
270
|
+
const { exec } = await import('child_process');
|
|
271
|
+
const { promisify } = await import('util');
|
|
272
|
+
const execAsync = promisify(exec);
|
|
273
|
+
// Check for Homebrew-installed Nerd Fonts
|
|
274
|
+
const { stdout } = await execAsync('brew list | grep -i font', {
|
|
275
|
+
timeout: 2000,
|
|
276
|
+
encoding: 'utf-8',
|
|
277
|
+
});
|
|
278
|
+
if (stdout.includes('nerd')) {
|
|
279
|
+
return { hasNerdFont: true };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// brew command failed or not available
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return { hasNerdFont: false };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get environment symbols with version information
|
|
290
|
+
*/
|
|
291
|
+
export function getEnvironmentSymbols(symbolSet) {
|
|
292
|
+
return {
|
|
293
|
+
node: '', // Node.js
|
|
294
|
+
python: '', // Python
|
|
295
|
+
docker: '', // Docker
|
|
296
|
+
git: symbolSet.git,
|
|
297
|
+
model: symbolSet.model,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Test if symbols can be displayed properly
|
|
302
|
+
*/
|
|
303
|
+
export async function testSymbolDisplay(_symbols) {
|
|
304
|
+
// In a terminal environment, we can't easily test if symbols display correctly
|
|
305
|
+
// For now, we'll assume that if we detected Nerd Font support, they can be displayed
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=symbols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/ui/symbols.ts"],"names":[],"mappings":"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","sourcesContent":["import { Config } from '../core/config.js';\n\n/**\n * Symbol detection cache - symbols don't change during runtime\n * Added cache version to handle potential stale data issues\n */\nconst symbolCache = new Map<string, { symbols: SymbolSet; timestamp: number }>();\nconst CACHE_VERSION = 1; // Increment to invalidate all caches\n\n/**\n * Symbol configuration interface\n */\nexport interface SymbolSet {\n git: string;\n model: string;\n contextWindow: string;\n staged: string;\n conflict: string;\n stashed: string;\n ahead: string;\n behind: string;\n diverged: string;\n renamed: string;\n deleted: string;\n}\n\n/**\n * ASCII symbol set (fallback)\n */\nconst ASCII_SYMBOLS: SymbolSet = {\n git: '@',\n model: '*',\n contextWindow: '#',\n staged: '+',\n conflict: 'C',\n stashed: '$',\n ahead: 'A',\n behind: 'B',\n diverged: 'D',\n renamed: '>',\n deleted: 'X',\n};\n\n/**\n * Nerd Font symbol set (enhanced)\n */\nconst NERD_FONT_SYMBOLS: SymbolSet = {\n git: '',\n model: '',\n contextWindow: '⚡︎',\n staged: '+',\n conflict: '×',\n stashed: '⚑',\n ahead: '⇡',\n behind: '⇣',\n diverged: '⇕',\n renamed: '»',\n deleted: '✘',\n};\n\n/**\n * Terminal detection results\n */\ninterface TerminalInfo {\n hasNerdFont: boolean;\n terminal: string;\n font: string;\n method: string;\n}\n\n/**\n * Detect Nerd Font support and return appropriate symbols (with caching)\n */\nexport async function detectSymbols(config: Config): Promise<SymbolSet> {\n // Create cache key based on config and environment\n const envFingerprint = process.env.NERD_FONT + '|' + process.env.TERM_PROGRAM + '|' + process.env.TERM;\n const cacheKey = `${CACHE_VERSION}:${config.noEmoji ? 'ascii' : 'nerd'}:${envFingerprint}`;\n\n // Check cache first with timestamp validation\n const cached = symbolCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < 60000) { // 1 minute cache TTL\n return cached.symbols;\n }\n\n let symbols: SymbolSet;\n\n // If emoji/nerd font is explicitly disabled, use ASCII\n if (config.noEmoji) {\n symbols = { ...ASCII_SYMBOLS, ...config.symbols, ...config.asciiSymbols };\n } else {\n // Try to detect Nerd Font support\n const detection = await detectNerdFontSupport();\n\n if (detection.hasNerdFont) {\n // Merge user's custom symbols with Nerd Font defaults\n symbols = { ...NERD_FONT_SYMBOLS, ...config.symbols };\n } else {\n // Merge user's custom ASCII symbols with ASCII defaults\n symbols = { ...ASCII_SYMBOLS, ...config.asciiSymbols };\n }\n }\n\n // Cache the result with timestamp\n symbolCache.set(cacheKey, { symbols, timestamp: Date.now() });\n return symbols;\n}\n\n/**\n * Comprehensive Nerd Font support detection\n */\nasync function detectNerdFontSupport(): Promise<TerminalInfo> {\n const terminalInfo: TerminalInfo = {\n hasNerdFont: false,\n terminal: '',\n font: '',\n method: '',\n };\n\n // Method 1: Environment variable NERD_FONT=1\n if (process.env.NERD_FONT === '1') {\n terminalInfo.hasNerdFont = true;\n terminalInfo.method = 'NERD_FONT env var';\n return terminalInfo;\n }\n\n // Method 2: Terminal program detection\n const termProgram = process.env.TERM_PROGRAM;\n const term = process.env.TERM;\n\n if (termProgram) {\n terminalInfo.terminal = termProgram;\n terminalInfo.method = 'TERM_PROGRAM detection';\n\n // These terminals commonly have Nerd Font support\n const nerdFontTerminals = ['vscode', 'ghostty', 'wezterm', 'iterm'];\n if (nerdFontTerminals.includes(termProgram)) {\n terminalInfo.hasNerdFont = true;\n return terminalInfo;\n }\n }\n\n if (term) {\n terminalInfo.terminal = term;\n terminalInfo.method = 'TERM detection';\n\n // These terminal types commonly support Nerd Fonts\n const nerdFontTerms = ['alacritty', 'kitty', 'wezterm', 'ghostty', 'xterm-256color'];\n if (nerdFontTerms.includes(term)) {\n terminalInfo.hasNerdFont = true;\n return terminalInfo;\n }\n }\n\n // Method 3: Try to detect via font list (Unix/Linux/macOS)\n const fontDetection = await detectViaFontList();\n if (fontDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.font = fontDetection.font;\n terminalInfo.method = 'font list detection';\n return terminalInfo;\n }\n\n // Method 4: Check for common Nerd Font installation patterns\n const installDetection = await detectNerdFontInstallation();\n if (installDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.font = installDetection.font;\n terminalInfo.method = 'installation detection';\n return terminalInfo;\n }\n\n // Method 5: Check environment variables that might indicate font support\n const envDetection = detectFromEnvironment();\n if (envDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.method = 'environment detection';\n return terminalInfo;\n }\n\n // Method 6: Platform-specific detection\n const platformDetection = await detectPlatformSpecific();\n if (platformDetection.hasNerdFont) {\n terminalInfo.hasNerdFont = true;\n terminalInfo.method = 'platform-specific detection';\n return terminalInfo;\n }\n\n // Default: no Nerd Font detected\n return terminalInfo;\n}\n\n/**\n * Detect Nerd Font via font list command\n */\nasync function detectViaFontList(): Promise<{ hasNerdFont: boolean; font: string }> {\n try {\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n // Try fc-list (Linux) or system_profiler (macOS)\n let fontListCommand = '';\n const platform = process.platform;\n\n if (platform === 'linux') {\n fontListCommand = 'fc-list';\n } else if (platform === 'darwin') {\n fontListCommand = 'system_profiler SPFontsDataType 2>/dev/null || system_profiler SPFontsDataType';\n }\n\n if (fontListCommand) {\n const { stdout } = await execAsync(fontListCommand, {\n timeout: 3000,\n encoding: 'utf-8',\n });\n\n const nerdFontPatterns = [\n /nerd font/i,\n /symbols only/i,\n /jetbrains mono.*nerd/i,\n /fira code.*nerd/i,\n /hack.*nerd/i,\n /source code pro.*nerd/i,\n /ubuntu mono.*nerd/i,\n /anonymous pro.*nerd/i,\n ];\n\n for (const pattern of nerdFontPatterns) {\n if (pattern.test(stdout)) {\n // Try to extract font name\n const match = stdout.match(/([^:\\n]*)(?=\\s*(nerd|symbols))/i);\n const fontName = match ? match[1] : 'Nerd Font';\n return { hasNerdFont: true, font: fontName?.trim() || 'Nerd Font' };\n }\n }\n }\n } catch {\n // Font detection failed\n }\n\n return { hasNerdFont: false, font: '' };\n}\n\n/**\n * Detect Nerd Font installation in common locations\n */\nasync function detectNerdFontInstallation(): Promise<{ hasNerdFont: boolean; font: string }> {\n try {\n const { access, readdir } = await import('fs/promises');\n const { homedir } = await import('os');\n\n const platform = process.platform;\n const fontPaths: string[] = [];\n\n if (platform === 'darwin') {\n fontPaths.push(\n `${homedir()}/Library/Fonts`,\n '/System/Library/Fonts',\n '/Library/Fonts'\n );\n } else if (platform === 'linux') {\n fontPaths.push(\n `${homedir()}/.local/share/fonts`,\n `${homedir()}/.fonts`,\n '/usr/share/fonts',\n '/usr/local/share/fonts'\n );\n }\n\n const nerdFontNames = [\n 'jetbrains-mono-nerd-font',\n 'fira-code-nerd-font',\n 'hack-nerd-font',\n 'source-code-pro-nerd-font',\n 'ubuntu-mono-nerd-font',\n 'anonymous-pro-nerd-font',\n ];\n\n for (const fontPath of fontPaths) {\n try {\n await access(fontPath);\n const files = await readdir(fontPath);\n\n for (const file of files) {\n const fileName = file.toLowerCase();\n for (const nerdFontName of nerdFontNames) {\n if (fileName.includes(nerdFontName)) {\n return { hasNerdFont: true, font: file };\n }\n }\n }\n } catch {\n // Can't access this font directory\n }\n }\n } catch {\n // Font detection failed\n }\n\n return { hasNerdFont: false, font: '' };\n}\n\n/**\n * Detect Nerd Font support from environment variables\n */\nfunction detectFromEnvironment(): { hasNerdFont: boolean } {\n // Check for various environment variables that might indicate Nerd Font support\n const nerdFontEnvVars = [\n 'POWERLINE_COMMAND', // Often used with Nerd Fonts\n 'NERDFONTS', // Some terminals set this\n 'FONT_FAMILY', // Some terminals expose the current font\n ];\n\n for (const envVar of nerdFontEnvVars) {\n const value = process.env[envVar];\n if (value && value.toLowerCase().includes('nerd')) {\n return { hasNerdFont: true };\n }\n }\n\n // Check if we're in a development environment that likely has Nerd Fonts\n if (\n process.env.VSCODE_PID ||\n process.env.TERM_PROGRAM === 'vscode' ||\n process.env.TERM_PROGRAM === 'ghostty' ||\n process.env.TERM_PROGRAM === 'wezterm'\n ) {\n return { hasNerdFont: true };\n }\n\n return { hasNerdFont: false };\n}\n\n/**\n * Platform-specific Nerd Font detection\n */\nasync function detectPlatformSpecific(): Promise<{ hasNerdFont: boolean }> {\n const platform = process.platform;\n\n if (platform === 'darwin') {\n // macOS: check if we're in a common development environment\n try {\n const { exec } = await import('child_process');\n const { promisify } = await import('util');\n const execAsync = promisify(exec);\n\n // Check for Homebrew-installed Nerd Fonts\n const { stdout } = await execAsync('brew list | grep -i font', {\n timeout: 2000,\n encoding: 'utf-8',\n });\n\n if (stdout.includes('nerd')) {\n return { hasNerdFont: true };\n }\n } catch {\n // brew command failed or not available\n }\n }\n\n return { hasNerdFont: false };\n}\n\n/**\n * Get environment symbols with version information\n */\nexport function getEnvironmentSymbols(symbolSet: SymbolSet): { [key: string]: string } {\n return {\n node: '', // Node.js\n python: '', // Python\n docker: '', // Docker\n git: symbolSet.git,\n model: symbolSet.model,\n };\n}\n\n/**\n * Test if symbols can be displayed properly\n */\nexport async function testSymbolDisplay(_symbols: SymbolSet): Promise<boolean> {\n // In a terminal environment, we can't easily test if symbols display correctly\n // For now, we'll assume that if we detected Nerd Font support, they can be displayed\n return true;\n}"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Config } from '../core/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Terminal width detection utilities
|
|
4
|
+
* Ported from bash implementation with cross-platform Node.js support
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Get terminal width using multiple detection methods
|
|
8
|
+
* Ordered from most reliable to fallback methods
|
|
9
|
+
*/
|
|
10
|
+
export declare function getTerminalWidth(config: Config): Promise<number>;
|
|
11
|
+
/**
|
|
12
|
+
* Debug width detection (matches bash implementation)
|
|
13
|
+
*/
|
|
14
|
+
export declare function debugWidthDetection(config: Config): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Text truncation utilities
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Simple text truncation with ellipsis
|
|
20
|
+
*/
|
|
21
|
+
export declare function truncateText(text: string, maxLength: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* Smart truncation with branch prioritization (matches bash implementation)
|
|
24
|
+
*/
|
|
25
|
+
export declare function smartTruncate(project: string, gitInfo: string, maxLen: number, _config: Config): string;
|
|
26
|
+
/**
|
|
27
|
+
* Soft wrapping function (experimental)
|
|
28
|
+
*/
|
|
29
|
+
export declare function softWrapText(text: string, maxLength: number, wrapChar?: 'newline' | 'space', modelPrefix?: string): string;
|
package/dist/ui/width.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal width detection utilities
|
|
3
|
+
* Ported from bash implementation with cross-platform Node.js support
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Get terminal width using multiple detection methods
|
|
7
|
+
* Ordered from most reliable to fallback methods
|
|
8
|
+
*/
|
|
9
|
+
export async function getTerminalWidth(config) {
|
|
10
|
+
// Method 0: Respect manual width override first (for testing)
|
|
11
|
+
if (config.forceWidth && config.forceWidth > 0) {
|
|
12
|
+
return config.forceWidth;
|
|
13
|
+
}
|
|
14
|
+
// Method 1: Try COLUMNS environment variable
|
|
15
|
+
const columnsEnv = process.env.COLUMNS;
|
|
16
|
+
if (columnsEnv) {
|
|
17
|
+
const columns = parseInt(columnsEnv, 10);
|
|
18
|
+
if (!isNaN(columns) && columns > 0) {
|
|
19
|
+
return columns;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Method 2: Try Node.js process.stdout.columns
|
|
23
|
+
if (process.stdout.columns && process.stdout.columns > 0) {
|
|
24
|
+
return process.stdout.columns;
|
|
25
|
+
}
|
|
26
|
+
// Method 3: Try tput command (Unix/Linux/macOS)
|
|
27
|
+
const tputWidth = await tryCommand('tput', ['cols']);
|
|
28
|
+
if (tputWidth) {
|
|
29
|
+
return tputWidth;
|
|
30
|
+
}
|
|
31
|
+
// Method 4: Try stty command (Unix/Linux/macOS)
|
|
32
|
+
const sttyWidth = await tryStty();
|
|
33
|
+
if (sttyWidth) {
|
|
34
|
+
return sttyWidth;
|
|
35
|
+
}
|
|
36
|
+
// Method 5: Check Claude Code specific environment
|
|
37
|
+
const claudeWidth = process.env.CLAUDE_CODE_TERMINAL_WIDTH;
|
|
38
|
+
if (claudeWidth) {
|
|
39
|
+
const width = parseInt(claudeWidth, 10);
|
|
40
|
+
if (!isNaN(width) && width > 0) {
|
|
41
|
+
return width;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Method 6: Terminal-specific defaults
|
|
45
|
+
const termProgram = process.env.TERM_PROGRAM;
|
|
46
|
+
const term = process.env.TERM;
|
|
47
|
+
if (termProgram === 'vscode' && process.env.VSCODE_PID) {
|
|
48
|
+
return 120; // VS Code default
|
|
49
|
+
}
|
|
50
|
+
if (['ghostty', 'wezterm', 'iterm'].includes(termProgram || '')) {
|
|
51
|
+
return 120; // Modern terminals default to wider
|
|
52
|
+
}
|
|
53
|
+
if (term && ['alacritty', 'kitty', 'wezterm', 'ghostty', 'xterm-256color'].includes(term)) {
|
|
54
|
+
return 120; // Modern terminals
|
|
55
|
+
}
|
|
56
|
+
// Method 7: Check for Windows Terminal
|
|
57
|
+
if (process.env.WT_SESSION || process.env.WT_PROFILE_ID) {
|
|
58
|
+
return 120; // Windows Terminal
|
|
59
|
+
}
|
|
60
|
+
// Final fallback: conservative 80-column default
|
|
61
|
+
return 80;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Execute a command and parse numeric output
|
|
65
|
+
*/
|
|
66
|
+
async function tryCommand(command, args) {
|
|
67
|
+
try {
|
|
68
|
+
const { exec } = await import('child_process');
|
|
69
|
+
const { promisify } = await import('util');
|
|
70
|
+
const execAsync = promisify(exec);
|
|
71
|
+
const { stdout } = await execAsync(`${command} ${args.join(' ')}`, {
|
|
72
|
+
timeout: 1000, // 1 second timeout
|
|
73
|
+
encoding: 'utf-8',
|
|
74
|
+
});
|
|
75
|
+
const width = parseInt(stdout.trim(), 10);
|
|
76
|
+
if (!isNaN(width) && width > 0) {
|
|
77
|
+
return width;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Command failed or not available
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Try stty size command
|
|
87
|
+
*/
|
|
88
|
+
async function tryStty() {
|
|
89
|
+
try {
|
|
90
|
+
const { exec } = await import('child_process');
|
|
91
|
+
const { promisify } = await import('util');
|
|
92
|
+
const execAsync = promisify(exec);
|
|
93
|
+
const { stdout } = await execAsync('stty size', {
|
|
94
|
+
timeout: 1000,
|
|
95
|
+
encoding: 'utf-8',
|
|
96
|
+
});
|
|
97
|
+
// stty size returns: "rows cols"
|
|
98
|
+
const parts = stdout.trim().split(' ');
|
|
99
|
+
if (parts.length === 2) {
|
|
100
|
+
const width = parseInt(parts[1] || '0', 10);
|
|
101
|
+
if (!isNaN(width) && width > 0) {
|
|
102
|
+
return width;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// stty failed or not available
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Debug width detection (matches bash implementation)
|
|
113
|
+
*/
|
|
114
|
+
export async function debugWidthDetection(config) {
|
|
115
|
+
if (!config.debugWidth) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.error('[WIDTH DEBUG] Debug mode enabled');
|
|
119
|
+
console.error('[WIDTH DEBUG] Methods tried:');
|
|
120
|
+
// Test process.stdout.columns
|
|
121
|
+
if (process.stdout.columns) {
|
|
122
|
+
console.error(`[WIDTH DEBUG] process.stdout.columns: ${process.stdout.columns}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.error('[WIDTH DEBUG] process.stdout.columns: not available');
|
|
126
|
+
}
|
|
127
|
+
// Test COLUMNS variable
|
|
128
|
+
const columnsEnv = process.env.COLUMNS;
|
|
129
|
+
if (columnsEnv) {
|
|
130
|
+
console.error(`[WIDTH DEBUG] COLUMNS variable: ${columnsEnv}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.error('[WIDTH DEBUG] COLUMNS variable: not set');
|
|
134
|
+
}
|
|
135
|
+
// Test tput
|
|
136
|
+
const tputWidth = await tryCommand('tput', ['cols']);
|
|
137
|
+
if (tputWidth) {
|
|
138
|
+
console.error(`[WIDTH DEBUG] tput cols: ${tputWidth}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.error('[WIDTH DEBUG] tput: not available or failed');
|
|
142
|
+
}
|
|
143
|
+
// Test stty
|
|
144
|
+
const sttyWidth = await tryStty();
|
|
145
|
+
if (sttyWidth) {
|
|
146
|
+
console.error(`[WIDTH DEBUG] stty size: ${sttyWidth}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.error('[WIDTH DEBUG] stty: not available or failed');
|
|
150
|
+
}
|
|
151
|
+
// Test environment variables
|
|
152
|
+
console.error(`[WIDTH DEBUG] CLAUDE_CODE_STATUSLINE_FORCE_WIDTH: ${config.forceWidth || 'not set'}`);
|
|
153
|
+
console.error(`[WIDTH DEBUG] COLUMNS variable: ${columnsEnv || 'not set'}`);
|
|
154
|
+
console.error(`[WIDTH DEBUG] CLAUDE_CODE_TERMINAL_WIDTH: ${process.env.CLAUDE_CODE_TERMINAL_WIDTH || 'not set'}`);
|
|
155
|
+
console.error(`[WIDTH DEBUG] TERM_PROGRAM: ${process.env.TERM_PROGRAM || 'not set'}`);
|
|
156
|
+
console.error(`[WIDTH DEBUG] TERM: ${process.env.TERM || 'not set'}`);
|
|
157
|
+
// Show final result
|
|
158
|
+
const finalWidth = await getTerminalWidth(config);
|
|
159
|
+
console.error(`[WIDTH DEBUG] Final detected width: ${finalWidth}`);
|
|
160
|
+
console.error(`[WIDTH DEBUG] Statusline will use: ${finalWidth - config.rightMargin} columns max`);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Text truncation utilities
|
|
164
|
+
*/
|
|
165
|
+
/**
|
|
166
|
+
* Simple text truncation with ellipsis
|
|
167
|
+
*/
|
|
168
|
+
export function truncateText(text, maxLength) {
|
|
169
|
+
if (text.length <= maxLength) {
|
|
170
|
+
return text;
|
|
171
|
+
}
|
|
172
|
+
// Edge case: if maxLength is too small, return minimal result
|
|
173
|
+
if (maxLength < 4) {
|
|
174
|
+
return '..';
|
|
175
|
+
}
|
|
176
|
+
return `${text.substring(0, maxLength - 2)}..`;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Smart truncation with branch prioritization (matches bash implementation)
|
|
180
|
+
*/
|
|
181
|
+
export function smartTruncate(project, gitInfo, maxLen, _config) {
|
|
182
|
+
// Step 1: Check if everything fits
|
|
183
|
+
if (project.length + gitInfo.length <= maxLen) {
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
// Step 2: Truncate project only (preserve branch)
|
|
187
|
+
const projLen = maxLen - gitInfo.length - 2;
|
|
188
|
+
if (projLen >= 5) {
|
|
189
|
+
return `${project.substring(0, projLen)}..${gitInfo}`;
|
|
190
|
+
}
|
|
191
|
+
// Step 3: Truncate project + branch (preserve indicators)
|
|
192
|
+
let indicators = '';
|
|
193
|
+
const bracketMatch = gitInfo.match(/\[([^\]]+)\]/);
|
|
194
|
+
if (bracketMatch) {
|
|
195
|
+
indicators = bracketMatch[1] || '';
|
|
196
|
+
}
|
|
197
|
+
const branchLen = maxLen - indicators.length - 8;
|
|
198
|
+
if (branchLen >= 8) {
|
|
199
|
+
const gitPrefix = gitInfo.substring(0, Math.min(gitInfo.length, branchLen));
|
|
200
|
+
return `${project.substring(0, 4)}..${gitPrefix}..${indicators ? ` [${indicators}]` : ''}`;
|
|
201
|
+
}
|
|
202
|
+
// Step 4: Basic fallback
|
|
203
|
+
return `${project.substring(0, maxLen)}..`;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Soft wrapping function (experimental)
|
|
207
|
+
*/
|
|
208
|
+
export function softWrapText(text, maxLength, wrapChar = 'newline', modelPrefix = '*') {
|
|
209
|
+
if (text.length <= maxLength) {
|
|
210
|
+
return text;
|
|
211
|
+
}
|
|
212
|
+
if (wrapChar === 'newline') {
|
|
213
|
+
// Smart wrap: try to break at space or safe character
|
|
214
|
+
let breakPos = maxLength;
|
|
215
|
+
let foundBreak = false;
|
|
216
|
+
// Look for safe break points (spaces)
|
|
217
|
+
for (let i = Math.min(maxLength - 1, text.length - 1); i > Math.max(maxLength - 20, 0) && i >= 0; i--) {
|
|
218
|
+
const char = text[i];
|
|
219
|
+
if (char === ' ') {
|
|
220
|
+
breakPos = i;
|
|
221
|
+
foundBreak = true;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// If no safe break found and we're very close to max_length, just fit without wrapping
|
|
226
|
+
if (!foundBreak && maxLength - text.length > -3) {
|
|
227
|
+
return text;
|
|
228
|
+
}
|
|
229
|
+
// Adjust break position to avoid splitting multi-byte UTF-8 characters
|
|
230
|
+
// UTF-8 continuation bytes have their two most significant bits set to 10 (0x80 to 0xBF)
|
|
231
|
+
while (breakPos > 0 && (text.charCodeAt(breakPos) & 0xC0) === 0x80) {
|
|
232
|
+
breakPos--;
|
|
233
|
+
}
|
|
234
|
+
const firstLine = text.substring(0, breakPos);
|
|
235
|
+
let secondLine = text.substring(breakPos);
|
|
236
|
+
// Remove leading space from second line if we broke at space
|
|
237
|
+
if (secondLine.startsWith(' ')) {
|
|
238
|
+
secondLine = secondLine.substring(1);
|
|
239
|
+
}
|
|
240
|
+
// Only wrap if second line has meaningful content
|
|
241
|
+
if (secondLine) {
|
|
242
|
+
return `${firstLine}\n${modelPrefix}${secondLine}`;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
return firstLine;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Use space separator for wrapping
|
|
250
|
+
let breakPos = maxLength;
|
|
251
|
+
// Adjust break position to avoid splitting multi-byte UTF-8 characters
|
|
252
|
+
// UTF-8 continuation bytes have their two most significant bits set to 10 (0x80 to 0xBF)
|
|
253
|
+
while (breakPos > 0 && (text.charCodeAt(breakPos) & 0xC0) === 0x80) {
|
|
254
|
+
breakPos--;
|
|
255
|
+
}
|
|
256
|
+
const firstLine = text.substring(0, breakPos);
|
|
257
|
+
const secondLine = text.substring(breakPos);
|
|
258
|
+
return `${firstLine} ${secondLine}`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=width.js.map
|