mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com> Co-authored-by: Daniel <57051444+daniel-lxs@users.noreply.github.com>
174 lines
5.1 KiB
TypeScript
174 lines
5.1 KiB
TypeScript
import { Box, Text } from "ink"
|
|
import type { TextProps } from "ink"
|
|
|
|
/**
|
|
* Icon names supported by the Icon component.
|
|
* Each icon has a Nerd Font glyph and an ASCII fallback.
|
|
*/
|
|
export type IconName =
|
|
| "folder"
|
|
| "file"
|
|
| "file-edit"
|
|
| "check"
|
|
| "cross"
|
|
| "arrow-right"
|
|
| "bullet"
|
|
| "spinner"
|
|
// Tool-related icons
|
|
| "search"
|
|
| "terminal"
|
|
| "browser"
|
|
| "switch"
|
|
| "question"
|
|
| "gear"
|
|
| "diff"
|
|
// TODO-related icons
|
|
| "checkbox"
|
|
| "checkbox-checked"
|
|
| "checkbox-progress"
|
|
| "todo-list"
|
|
|
|
/**
|
|
* Icon definitions with Nerd Font glyph and ASCII fallback.
|
|
* Nerd Font glyphs are surrogate pairs (2 JS chars, 1 visual char).
|
|
*/
|
|
const ICONS: Record<IconName, { nerd: string; fallback: string }> = {
|
|
folder: { nerd: "\uf413", fallback: "▼" },
|
|
file: { nerd: "\uf4a5", fallback: "●" },
|
|
"file-edit": { nerd: "\uf4d2", fallback: "✎" },
|
|
check: { nerd: "\uf42e", fallback: "✓" },
|
|
cross: { nerd: "\uf517", fallback: "✗" },
|
|
"arrow-right": { nerd: "\uf432", fallback: "→" },
|
|
bullet: { nerd: "\uf444", fallback: "•" },
|
|
spinner: { nerd: "\uf4e3", fallback: "*" },
|
|
// Tool-related icons
|
|
search: { nerd: "\uf422", fallback: "🔍" },
|
|
terminal: { nerd: "\uf489", fallback: "$" },
|
|
browser: { nerd: "\uf488", fallback: "🌐" },
|
|
switch: { nerd: "\uf443", fallback: "⇄" },
|
|
question: { nerd: "\uf420", fallback: "?" },
|
|
gear: { nerd: "\uf423", fallback: "⚙" },
|
|
diff: { nerd: "\uf4d2", fallback: "±" },
|
|
// TODO-related icons
|
|
checkbox: { nerd: "\uf4aa", fallback: "○" }, // Empty checkbox
|
|
"checkbox-checked": { nerd: "\uf4a4", fallback: "✓" }, // Checked checkbox
|
|
"checkbox-progress": { nerd: "\uf4aa", fallback: "→" }, // In progress (dot circle)
|
|
"todo-list": { nerd: "\uf45e", fallback: "☑" }, // List icon for TODO header
|
|
}
|
|
|
|
/**
|
|
* Check if a string contains surrogate pairs (characters outside BMP).
|
|
* Surrogate pairs have .length of 2 but render as 1 visual character.
|
|
*/
|
|
function containsSurrogatePair(str: string): boolean {
|
|
// Surrogate pairs are in the range U+D800 to U+DFFF
|
|
return /[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(str)
|
|
}
|
|
|
|
/**
|
|
* Detect if Nerd Font icons are likely supported.
|
|
*
|
|
* Users can override this with the ROOCODE_NERD_FONT environment variable:
|
|
* - ROOCODE_NERD_FONT=0 to force ASCII fallbacks (if icons don't render correctly)
|
|
* - ROOCODE_NERD_FONT=1 to force Nerd Font icons
|
|
*
|
|
* Defaults to true because:
|
|
* 1. Nerd Fonts are common in developer terminal setups
|
|
* 2. Modern terminals handle missing glyphs gracefully
|
|
* 3. Users can easily disable if icons don't render correctly
|
|
*/
|
|
function detectNerdFontSupport(): boolean {
|
|
// Allow explicit override via environment variable
|
|
const envOverride = process.env.ROOCODE_NERD_FONT
|
|
if (envOverride === "0" || envOverride === "false") return false
|
|
if (envOverride === "1" || envOverride === "true") return true
|
|
|
|
// Default to Nerd Font icons - they're common in developer setups
|
|
// and users can set ROOCODE_NERD_FONT=0 if needed
|
|
return true
|
|
}
|
|
|
|
// Cache the detection result
|
|
let nerdFontSupported: boolean | null = null
|
|
|
|
/**
|
|
* Get whether Nerd Font icons are supported (cached).
|
|
*/
|
|
export function isNerdFontSupported(): boolean {
|
|
if (nerdFontSupported === null) {
|
|
nerdFontSupported = detectNerdFontSupport()
|
|
}
|
|
return nerdFontSupported
|
|
}
|
|
|
|
/**
|
|
* Reset the Nerd Font detection cache (useful for testing).
|
|
*/
|
|
export function resetNerdFontCache(): void {
|
|
nerdFontSupported = null
|
|
}
|
|
|
|
export interface IconProps extends Omit<TextProps, "children"> {
|
|
/** The icon to display */
|
|
name: IconName
|
|
/** Override the automatic Nerd Font detection */
|
|
useNerdFont?: boolean
|
|
/** Custom width for the icon container (default: 2) */
|
|
width?: number
|
|
}
|
|
|
|
/**
|
|
* Icon component that renders Nerd Font icons with ASCII fallbacks.
|
|
*
|
|
* Renders icons in a fixed-width Box to handle surrogate pair width
|
|
* calculation issues in Ink. Surrogate pairs (like Nerd Font glyphs)
|
|
* have .length of 2 in JavaScript but render as 1 visual character.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <Icon name="folder" color="blue" />
|
|
* <Icon name="file" />
|
|
* <Icon name="check" color="green" useNerdFont={false} />
|
|
* ```
|
|
*/
|
|
export function Icon({ name, useNerdFont, width = 2, color, ...textProps }: IconProps) {
|
|
const iconDef = ICONS[name]
|
|
if (!iconDef) {
|
|
return null
|
|
}
|
|
|
|
const shouldUseNerdFont = useNerdFont ?? isNerdFontSupported()
|
|
const icon = shouldUseNerdFont ? iconDef.nerd : iconDef.fallback
|
|
|
|
// Use fixed-width Box to isolate surrogate pair width calculation
|
|
// from surrounding text. This prevents the off-by-one truncation bug.
|
|
const needsWidthFix = containsSurrogatePair(icon)
|
|
|
|
if (needsWidthFix) {
|
|
return (
|
|
<Box width={width}>
|
|
<Text color={color} {...textProps}>
|
|
{icon}
|
|
</Text>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
// For BMP characters (no surrogate pairs), render directly
|
|
return (
|
|
<Text color={color} {...textProps}>
|
|
{icon}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Get the raw icon character (useful for string concatenation).
|
|
*/
|
|
export function getIconChar(name: IconName, useNerdFont?: boolean): string {
|
|
const iconDef = ICONS[name]
|
|
if (!iconDef) return ""
|
|
|
|
const shouldUseNerdFont = useNerdFont ?? isNerdFontSupported()
|
|
return shouldUseNerdFont ? iconDef.nerd : iconDef.fallback
|
|
}
|