feat(ui): add file icon resolver with Nerd/ASCII sets, env override, and breadcrumb display

- Introduce `IconMode` in core config (default Auto) and bump schema version to 1.4.0.
- Add `FileIconSet`, `IconDetection`, and `FileIconResolver` to resolve per‑file icons with configurable fallbacks and environment variable `OWLEN_TUI_ICONS`.
- Export resolver types from `owlen-tui::state::file_icons`.
- Extend `ChatApp` with `file_icons` field, initialize it from config, and expose via `file_icons()` accessor.
- Append system status line showing selected icon set and detection source.
- Implement breadcrumb construction (`repo > path > file`) and display in code pane headers.
- Render icons in file tree, handle unsaved file markers, hidden files, and Git decorations with proper styling.
- Add helper `collect_unsaved_relative_paths` and tree line computation for visual guides.
- Provide `Workspace::panes()` iterator for unsaved tracking.
- Update UI imports and tests to cover new breadcrumb feature.
This commit is contained in:
2025-10-13 00:25:30 +02:00
parent 15f81d9728
commit 0da8a3f193
7 changed files with 604 additions and 40 deletions

View File

@@ -28,10 +28,10 @@ use crate::config;
use crate::events::Event;
use crate::model_info_panel::ModelInfoPanel;
use crate::state::{
CodeWorkspace, CommandPalette, FileFilterMode, FileNode, FileTreeState, ModelPaletteEntry,
PaletteSuggestion, PaneDirection, PaneRestoreRequest, RepoSearchMessage, RepoSearchState,
SplitAxis, SymbolSearchMessage, SymbolSearchState, WorkspaceSnapshot, spawn_repo_search_task,
spawn_symbol_search_task,
CodeWorkspace, CommandPalette, FileFilterMode, FileIconResolver, FileNode, FileTreeState,
ModelPaletteEntry, PaletteSuggestion, PaneDirection, PaneRestoreRequest, RepoSearchMessage,
RepoSearchState, SplitAxis, SymbolSearchMessage, SymbolSearchState, WorkspaceSnapshot,
spawn_repo_search_task, spawn_symbol_search_task,
};
use crate::ui::format_tool_output;
// Agent executor moved to separate binary `owlen-agent`. The TUI no longer directly
@@ -224,6 +224,7 @@ pub struct ChatApp {
resize_snap_index: usize, // Cycles through 25/50/75 snaps
last_snap_direction: Option<PaneDirection>,
file_tree: FileTreeState, // Workspace file tree state
file_icons: FileIconResolver, // Icon resolver with Nerd/ASCII fallback
file_panel_collapsed: bool, // Whether the file panel is collapsed
file_panel_width: u16, // Cached file panel width
saved_sessions: Vec<SessionMeta>, // Cached list of saved sessions
@@ -384,6 +385,7 @@ impl ChatApp {
let show_cursor_outside_insert = config_guard.ui.show_cursor_outside_insert;
let syntax_highlighting = config_guard.ui.syntax_highlighting;
let show_timestamps = config_guard.ui.show_timestamps;
let icon_mode = config_guard.ui.icon_mode;
drop(config_guard);
let theme = owlen_core::theme::get_theme(&theme_name).unwrap_or_else(|| {
eprintln!("Warning: Theme '{}' not found, using default", theme_name);
@@ -392,6 +394,7 @@ impl ChatApp {
let workspace_root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let file_tree = FileTreeState::new(workspace_root);
let file_icons = FileIconResolver::from_mode(icon_mode);
let mut app = Self {
controller,
@@ -454,6 +457,7 @@ impl ChatApp {
resize_snap_index: 0,
last_snap_direction: None,
file_tree,
file_icons,
file_panel_collapsed: true,
file_panel_width: 32,
saved_sessions: Vec::new(),
@@ -479,6 +483,12 @@ impl ChatApp {
show_message_timestamps: show_timestamps,
};
app.append_system_status(&format!(
"Icons: {} ({})",
app.file_icons.status_label(),
app.file_icons.detection_label()
));
app.update_command_palette_catalog();
if let Err(err) = app.restore_workspace_layout().await {
@@ -973,6 +983,10 @@ impl ChatApp {
&mut self.file_tree
}
pub fn file_icons(&self) -> &FileIconResolver {
&self.file_icons
}
pub fn workspace(&self) -> &CodeWorkspace {
&self.code_workspace
}