Skip to content

Commit 18d5a56

Browse files
feat: implement terminal emoji detection and display adaptation
• Add emoji detection utility with industry-standard heuristics • Implement automatic emoji stripping for non-emoji terminals • Always store Unicode emojis in Git commits regardless of terminal • Update all command displays to adapt based on terminal capabilities • Ensure GitHub and emoji terminals show emojis correctly • Improve UX by cleaning broken emoji symbols on non-emoji terminals
1 parent b178505 commit 18d5a56

File tree

3 files changed

+167
-8
lines changed

3 files changed

+167
-8
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@labcatr/labcommitr": minor
3+
---
4+
5+
feat: implement terminal emoji detection and display adaptation
6+
7+
- Add emoji detection utility with industry-standard heuristics (CI, TERM, NO_COLOR, Windows Terminal)
8+
- Implement automatic emoji stripping for non-emoji terminals in Labcommitr UI
9+
- Always store Unicode emojis in Git commits regardless of terminal support
10+
- Update commit, preview, and revert commands to adapt display based on terminal capabilities
11+
- Ensure GitHub and emoji-capable terminals always show emojis correctly
12+
- Improve user experience by cleaning up broken emoji symbols on non-emoji terminals
13+

src/lib/config/loader.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
import { ConfigError } from "./types.js";
2121
import { mergeWithDefaults, createFallbackConfig } from "./defaults.js";
2222
import { ConfigValidator } from "./validator.js";
23+
import { detectEmojiSupport } from "../util/emoji.js";
2324

2425
/**
2526
* Configuration file names to search for (in priority order)
@@ -231,7 +232,7 @@ export class ConfigLoader {
231232
config: fallbackConfig,
232233
source: "defaults",
233234
loadedAt: Date.now(),
234-
emojiModeActive: this.detectEmojiSupport(), // TODO: Implement emoji detection
235+
emojiModeActive: this.detectEmojiSupport(fallbackConfig),
235236
};
236237
}
237238

@@ -309,7 +310,7 @@ export class ConfigLoader {
309310
source: "project",
310311
path: configPath,
311312
loadedAt: Date.now(),
312-
emojiModeActive: this.detectEmojiSupport(), // TODO: Implement emoji detection
313+
emojiModeActive: this.detectEmojiSupport(processedConfig),
313314
};
314315
}
315316

@@ -346,14 +347,20 @@ export class ConfigLoader {
346347
/**
347348
* Detects whether the current terminal supports emoji display
348349
*
349-
* TODO: Implement proper emoji detection logic
350-
* For now, returns true as a placeholder
350+
* Combines user preference (force_emoji_detection) with terminal capability detection.
351+
* User preference takes precedence over automatic detection.
351352
*
352-
* @returns Whether emojis should be displayed
353+
* @param config - The loaded configuration (may contain force_emoji_detection override)
354+
* @returns Whether emojis should be displayed in the terminal
353355
*/
354-
private detectEmojiSupport(): boolean {
355-
// Placeholder implementation - will be enhanced later
356-
return true;
356+
private detectEmojiSupport(config?: LabcommitrConfig): boolean {
357+
// User override takes highest priority
358+
if (config?.config.force_emoji_detection !== null && config?.config.force_emoji_detection !== undefined) {
359+
return config.config.force_emoji_detection;
360+
}
361+
362+
// Automatic terminal detection
363+
return detectEmojiSupport();
357364
}
358365

359366
/**

src/lib/util/emoji.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Emoji Detection and Display Utilities
3+
*
4+
* Provides terminal emoji support detection and emoji stripping
5+
* functionality for clean display on non-emoji terminals.
6+
*
7+
* Industry Standard Approach:
8+
* - Always store Unicode emojis in Git commits
9+
* - Strip emojis from Labcommitr's UI display when terminal doesn't support them
10+
* - This ensures GitHub and emoji-capable terminals show emojis correctly
11+
*/
12+
13+
import { platform } from "os";
14+
15+
/**
16+
* Detects whether the current terminal supports emoji display
17+
*
18+
* Uses industry-standard heuristics:
19+
* - Disable in CI environments (CI=true)
20+
* - Disable for dumb terminals (TERM=dumb)
21+
* - Disable on older Windows terminals
22+
* - Check for NO_COLOR environment variable
23+
* - Allow user override via FORCE_EMOJI_DETECTION
24+
*
25+
* @returns Whether emojis should be displayed in the terminal
26+
*/
27+
export function detectEmojiSupport(): boolean {
28+
// User override (highest priority)
29+
const forceDetection = process.env.FORCE_EMOJI_DETECTION;
30+
if (forceDetection !== undefined) {
31+
return forceDetection.toLowerCase() === "true" || forceDetection === "1";
32+
}
33+
34+
// NO_COLOR standard (https://no-color.org/)
35+
if (process.env.NO_COLOR) {
36+
return false;
37+
}
38+
39+
// CI environments typically don't support emojis well
40+
if (process.env.CI === "true" || process.env.CI === "1") {
41+
return false;
42+
}
43+
44+
// Dumb terminals don't support emojis
45+
const term = process.env.TERM;
46+
if (term === "dumb" || term === "unknown") {
47+
return false;
48+
}
49+
50+
// Windows terminal detection
51+
const isWindows = platform() === "win32";
52+
if (isWindows) {
53+
// Modern Windows Terminal (10+) supports emojis
54+
// Older cmd.exe and PowerShell may not
55+
// Check for Windows Terminal specific environment variables
56+
const wtSession = process.env.WT_SESSION;
57+
if (wtSession) {
58+
// Windows Terminal detected - supports emojis
59+
return true;
60+
}
61+
62+
// Check for ConEmu or other modern terminals
63+
const conEmu = process.env.CONEMUANSI;
64+
if (conEmu === "ON") {
65+
return true;
66+
}
67+
68+
// For older Windows terminals, be conservative
69+
// Check if we're in a TTY (interactive terminal)
70+
if (!process.stdout.isTTY) {
71+
return false;
72+
}
73+
74+
// Default to false for older Windows (can be overridden by FORCE_EMOJI_DETECTION)
75+
return false;
76+
}
77+
78+
// Unix-like systems: check TERM variable
79+
// Most modern terminals support emojis
80+
if (term) {
81+
// Known non-emoji terminals
82+
const nonEmojiTerms = ["linux", "vt100", "vt220", "xterm-mono"];
83+
if (nonEmojiTerms.includes(term.toLowerCase())) {
84+
return false;
85+
}
86+
87+
// Modern terminals typically support emojis
88+
// xterm-256color, screen-256color, tmux-256color, etc.
89+
return true;
90+
}
91+
92+
// Default: assume emoji support if we have a TTY
93+
return process.stdout.isTTY === true;
94+
}
95+
96+
/**
97+
* Strips Unicode emojis from a string for display on non-emoji terminals
98+
*
99+
* Uses Unicode emoji pattern matching to remove emoji characters
100+
* while preserving the rest of the text.
101+
*
102+
* @param text - Text that may contain emojis
103+
* @returns Text with emojis removed
104+
*/
105+
export function stripEmojis(text: string): string {
106+
// Unicode emoji pattern matching
107+
// Matches emoji characters including:
108+
// - Emoticons (😀-🙏)
109+
// - Symbols & Pictographs (🌀-🗿)
110+
// - Transport & Map Symbols (🚀-🛿)
111+
// - Flags (country flags)
112+
// - Regional indicators
113+
// - Variation selectors
114+
const emojiPattern =
115+
/[\p{Emoji}\p{Emoji_Presentation}\p{Emoji_Modifier_Base}\p{Emoji_Modifier}\p{Emoji_Component}]/gu;
116+
117+
return text.replace(emojiPattern, "").trim();
118+
}
119+
120+
/**
121+
* Conditionally strips emojis from text based on terminal support
122+
*
123+
* If terminal supports emojis, returns original text.
124+
* If terminal doesn't support emojis, returns text with emojis stripped.
125+
*
126+
* @param text - Text that may contain emojis
127+
* @param terminalSupportsEmojis - Whether terminal supports emoji display
128+
* @returns Text with emojis conditionally stripped
129+
*/
130+
export function formatForDisplay(
131+
text: string,
132+
terminalSupportsEmojis: boolean,
133+
): string {
134+
if (terminalSupportsEmojis) {
135+
return text;
136+
}
137+
return stripEmojis(text);
138+
}
139+

0 commit comments

Comments
 (0)