Skip to content

feat(ui): add optional Nerd Font file icons#474

Open
eduwass wants to merge 6 commits into
modem-dev:mainfrom
eduwass:feat/optional-nerd-font-icons
Open

feat(ui): add optional Nerd Font file icons#474
eduwass wants to merge 6 commits into
modem-dev:mainfrom
eduwass:feat/optional-nerd-font-icons

Conversation

@eduwass

@eduwass eduwass commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

This adds optional Nerd Font file and folder icons to Hunk's review UI. When enabled, the sidebar, folder groups, and diff file headers show familiar file-type icons next to paths, making large reviews easier to scan at a glance.

The feature is intentionally off by default so existing terminals and layouts keep rendering exactly as they do today. Users who have a Nerd Font installed can enable it from config, from the CLI, or temporarily while Hunk is running.

How to enable it

  • Add file_icons = true or nerd_font_icons = true to Hunk config
  • Pass --file-icons / --nerd-font-icons on the command line
  • Toggle View > File icons from the menu bar during a session

Behavior

Git status markers stay unchanged. A modified TypeScript file still starts with M; the Nerd Font icon is rendered as a separate visual cue between the status marker and the filename.

Folder groups use folder icons, while files use lazygit's MIT-licensed Nerd Font icon definitions and lookup order: exact filename first, extension second, then the generic folder/file fallback.

Demo

2026 06 22 21 52 45@2x
2026.06.22.22.11.20.mp4

Testing

  • bun run format:check
  • bun run typecheck
  • bun test src/core/cli.test.ts src/core/config.test.ts src/ui/components/ui-components.test.tsx
  • bun test src/ui/AppHost.interactions.test.tsx -t "View menu can toggle Nerd Font file icons"
  • bun test src/ui/lib/ui-lib.test.ts -t "iconForFile resolves exact basename"
  • bun run test:tty-smoke
  • Real source TTY launch with bun run src/main.tsx -- diff <before> <after>

@eduwass eduwass marked this pull request as ready for review June 22, 2026 20:37
@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds optional Nerd Font file and folder icons (disabled by default) behind a file_icons config key, --file-icons CLI flag, and a runtime View menu toggle. Icon definitions (~750 entries) are adapted from lazygit's MIT-licensed Go implementation, and the lookup order mirrors lazygit: exact basename first, then file extension, then a default icon.

  • Threads a new nerdFontIcons boolean through the full config → CLI → bootstrap → App → component prop chain, keeping it consistent with how other view preferences like wrapLines and hunkHeaders are handled.
  • Adds icons to both the SidebarPane (FileListItem, FileGroupHeader) and the DiffPane (DiffFileHeaderRow), with icon width accounted for in the available text budget so filenames are not clipped.
  • Includes tests covering the View menu toggle interaction, sidebar rendering, and unit tests for basename-based icon lookup.

Confidence Score: 4/5

Safe to merge; the feature is disabled by default so no existing behaviour changes without opt-in.

The implementation is clean and well-tested. The only notable issues are a dead .d.ts entry in the extension table (mirroring the upstream lazygit source) and a hardcoded navigation count in one interaction test that could silently break if the View menu grows. Neither affects runtime correctness for users.

src/ui/lib/fileIcons.ts (dead .d.ts entry) and src/ui/AppHost.interactions.test.tsx (hardcoded menu navigation count).

Important Files Changed

Filename Overview
src/ui/lib/fileIcons.ts New 759-line file with icon lookup tables (ICON_BY_NAME, ICON_BY_EXT) and iconForFile function adapted from lazygit; one dead-code entry (.d.ts) that extname() can never return.
src/core/config.ts Adds nerdFontIcons to option merging and resolution, with file_icons as canonical key and nerd_font_icons as alias; consistent with existing patterns.
src/core/cli.ts Adds --file-icons/--no-file-icons (canonical) and --nerd-font-icons/--no-nerd-font-icons (alias) with ?? fallback chaining; mirrors config.ts alias approach correctly.
src/ui/App.tsx Threads nerdFontIcons state and toggleNerdFontIcons down to SidebarPane and DiffPane; follows same pattern as other view toggles.
src/ui/components/panes/FileListItem.tsx Renders git status icon then optional type icon; icon widths are accounted for in the nameWidth budget; correctly separates git status from file-type icons.
src/ui/components/panes/DiffFileHeaderRow.tsx Renders optional type icon before filename; iconWidth (2) subtracted from the fitText budget to prevent truncation; clean integration.
src/ui/AppHost.interactions.test.tsx Adds View menu toggle test using hardcoded 4 down-arrow presses to reach the File icons item; fragile if View menu items are added above it in the future.
src/ui/lib/appMenus.ts Adds File icons toggle item to View menu after Sidebar; interface and implementation correctly extended.
src/core/types.ts Adds nerdFontIcons to CommonOptions, PersistedViewPreferences, and AppBootstrap interfaces; all three addition points are correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Config file\n(file_icons / nerd_font_icons)"] --> C[resolveConfiguredCliInput]
    B["CLI flags\n(--file-icons / --nerd-font-icons)"] --> C
    C --> D[loadAppBootstrap\ninitialNerdFontIcons]
    D --> E["App.tsx\nuseState(nerdFontIcons)"]
    E --> F[toggleNerdFontIcons]
    F --> E
    E --> G[buildAppMenus\n'File icons' toggle]
    E --> H[SidebarPane\nnerdFontIcons prop]
    E --> I[DiffPane\nnerdFontIcons prop]
    H --> J[FileGroupHeader\niconForFile label, isDir=true]
    H --> K[FileListItem\niconForFile entry.name]
    I --> L[DiffSection\nnerdFontIcons prop]
    L --> M[DiffFileHeaderRow\niconForFile file.path]
    J & K & M --> N["fileIcons.ts\niconForFile()\nICON_BY_NAME → ICON_BY_EXT → default"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["Config file\n(file_icons / nerd_font_icons)"] --> C[resolveConfiguredCliInput]
    B["CLI flags\n(--file-icons / --nerd-font-icons)"] --> C
    C --> D[loadAppBootstrap\ninitialNerdFontIcons]
    D --> E["App.tsx\nuseState(nerdFontIcons)"]
    E --> F[toggleNerdFontIcons]
    F --> E
    E --> G[buildAppMenus\n'File icons' toggle]
    E --> H[SidebarPane\nnerdFontIcons prop]
    E --> I[DiffPane\nnerdFontIcons prop]
    H --> J[FileGroupHeader\niconForFile label, isDir=true]
    H --> K[FileListItem\niconForFile entry.name]
    I --> L[DiffSection\nnerdFontIcons prop]
    L --> M[DiffFileHeaderRow\niconForFile file.path]
    J & K & M --> N["fileIcons.ts\niconForFile()\nICON_BY_NAME → ICON_BY_EXT → default"]
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/ui/lib/fileIcons.ts:308
The `.d.ts` entry in `ICON_BY_EXT` is unreachable. Node's `extname("foo.d.ts")` returns `.ts` (only the last segment), so this key will never be matched — `.d.ts` declaration files fall through to the `.ts` entry instead, which uses a different colour (`#3178c6` vs the intended `#0188d1`). The same limitation exists in the upstream lazygit source, so this is an accepted trade-off, but the dead entry is misleading. Removing it would make the table self-consistent, or a comment could document why it can't be reached via `extname`.

```suggestion
  // NOTE: extname("foo.d.ts") returns ".ts", so this key is unreachable via the
  // iconForFile extension lookup; .d.ts files fall through to the ".ts" entry.
  // ".d.ts": { icon: "\ue628", color: "#0188d1" },
```

### Issue 2 of 2
src/ui/AppHost.interactions.test.tsx:1435-1440
**Hardcoded navigation count is fragile.** The loop presses ↓ exactly 4 times to land the cursor on "File icons". Because `pressEnter` activates whichever item is highlighted, adding any checkable item above "File icons" in the View menu would silently toggle the wrong item and cause the final `iconForFile("alpha.ts").icon` assertion to fail with a confusing message. Consider asserting the highlighted label immediately before pressing Enter so the failure site is clear.

Reviews (1): Last reviewed commit: "fix(ui): resolve Gemfile file icon names" | Re-trigger Greptile

Comment thread src/ui/lib/fileIcons.ts Outdated
Comment thread src/ui/AppHost.interactions.test.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant