use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::StatefulWidget,
};
use crate::primitives::tree_view::{matches_filter, TreeViewRef, TreeViewState};
use crate::widgets::file_system_tree::{FileSystemEntry, FileSystemTree};
use crate::widgets::file_system_tree::traits::get_ayu_dark_color::get_ayu_dark_color;
use crate::widgets::file_system_tree::traits::get_custom_icon::get_custom_icon;
use devicons::{icon_for_file, Theme as DevIconTheme};
fn entry_matches_filter(entry: &FileSystemEntry, filter: &Option<String>) -> bool {
matches_filter(&entry.name, filter)
}
fn render_filter_line(filter_text: Option<&str>, filter_mode: bool, area: Rect, buf: &mut Buffer) {
if area.height == 0 {
return;
}
let y = area.y + area.height - 1;
let filter_str = filter_text.unwrap_or("");
let cursor = if filter_mode { "_" } else { "" };
let line = Line::from(vec![
Span::styled(
"/ ",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::styled(filter_str, Style::default().fg(Color::White)),
Span::styled(
cursor,
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::SLOW_BLINK),
),
]);
let bg_style = Style::default().bg(Color::Rgb(15, 25, 40));
for x in area.x..(area.x + area.width) {
buf[(x, y)].set_style(bg_style);
}
buf.set_line(area.x, y, &line, area.width);
}
impl<'a> StatefulWidget for FileSystemTree<'a> {
type State = TreeViewState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let config = self.config;
let block = self.block;
let filter_mode = state.filter_mode;
let has_filter = state.filter.as_ref().is_some_and(|f| !f.is_empty());
let show_filter_line = filter_mode || has_filter;
let tree_area = if show_filter_line && area.height > 1 {
Rect {
height: area.height - 1,
..area
}
} else {
area
};
let tree_view = TreeViewRef::new(&self.nodes)
.icons("", "")
.render_fn(move |entry, node_state| {
let (icon_glyph, icon_color) = if entry.is_dir {
if node_state.is_expanded {
('\u{f07c}', Color::Rgb(31, 111, 136))
} else {
('\u{f07b}', Color::Rgb(31, 111, 136))
}
} else {
let theme = if config.use_dark_theme {
DevIconTheme::Dark
} else {
DevIconTheme::Light
};
let icon_char = if let Some((custom_icon, _)) = get_custom_icon(&entry.name) {
custom_icon
} else {
let file_icon = icon_for_file(&entry.name, &Some(theme));
file_icon.icon
};
let color = get_ayu_dark_color(&entry.name);
(icon_char, color)
};
let style = ratatui::style::Style::default().fg(icon_color);
Line::from(vec![
Span::styled(
format!("{} ", icon_glyph),
ratatui::style::Style::default().fg(icon_color),
),
Span::styled(entry.name.clone(), style),
])
})
.filter_fn(entry_matches_filter)
.highlight_style(Style::default().bg(Color::Rgb(15, 25, 40)));
let tree_view = if let Some(block) = block {
tree_view.block(block)
} else {
tree_view
};
tree_view.render(tree_area, buf, state);
if show_filter_line && area.height > 1 {
render_filter_line(state.filter.as_deref(), filter_mode, area, buf);
}
}
}