use iced::widget::column;
use super::*;
use crate::features::icons::icon_handle;
impl App {
pub(super) fn view_fuzzy_finder_overlay(&self) -> Element<'_, Message> {
use iced::widget::{center, opaque, stack, Space};
use syntect::{
highlighting::{HighlightIterator, HighlightState, Highlighter as SyntectHighlighter},
parsing::{ParseState, ScopeStack, SyntaxSet},
};
let input = text_input("Search files...", &self.fuzzy_finder.input)
.id(self.fuzzy_finder.input_id.clone())
.on_input(Message::FuzzyFinderQueryChanged)
.size(15)
.padding(iced::Padding {
top: 16.0,
right: 18.0,
bottom: 16.0,
left: 18.0,
})
.style(search_input_style)
.width(Length::Fill);
let folder_label: Element<'_, Message> =
if let Some(folder) = &self.fuzzy_finder.current_folder {
container(text(format!("{}", folder.display())).size(10).color(theme().text_dim))
.padding(iced::Padding {
top: 0.0,
right: 18.0,
bottom: 0.0,
left: 18.0,
})
.into()
} else {
container(text("")).into()
};
let mut items: Vec<Element<'_, Message>> = Vec::new();
if self.fuzzy_finder.filtered_files.is_empty() {
items.push(
container(text("No files found").size(13).color(theme().text_dim))
.padding(20)
.width(Length::Fill)
.center_x(Length::Fill)
.into(),
);
} else {
for (idx, file) in self.fuzzy_finder.filtered_files.iter().enumerate() {
let is_selected = idx == self.fuzzy_finder.selected_index;
let path = file.path.clone();
let icon_asset = crate::features::icons::get_file_icon(
file.path.file_name().unwrap_or_default().to_str().unwrap_or(""),
);
let icon: Element<'_, Message> =
iced::widget::image::Image::new(icon_handle(icon_asset, 14))
.width(Length::Fixed(14.0))
.height(Length::Fixed(14.0))
.into();
items.push(
button(
row![
icon,
text(&file.display_name).size(13).color(if is_selected {
theme().text_primary
} else {
theme().text_muted
}),
]
.spacing(8)
.align_y(iced::Alignment::Center),
)
.style(file_finder_item_style(is_selected))
.on_press(Message::FileClicked(path))
.padding(iced::Padding {
top: 6.0,
right: 10.0,
bottom: 6.0,
left: 10.0,
})
.width(Length::Fill)
.into(),
);
}
}
let file_list = scrollable(column(items).spacing(2).padding(iced::Padding {
top: 4.0,
right: 4.0,
bottom: 4.0,
left: 4.0,
}))
.height(Length::Fill);
let separator_v = container(Space::new())
.width(Length::Fixed(1.0))
.height(Length::Fill)
.style(|_theme| container::Style {
background: Some(Background::Color(SURFACE_2)),
..Default::default()
});
let separator_h = container(Space::new())
.width(Length::Fill)
.height(Length::Fixed(1.0))
.style(|_theme| container::Style {
background: Some(Background::Color(SURFACE_2)),
..Default::default()
});
let preview: Element<'_, Message> = if let Some((preview_path, content)) =
&self.fuzzy_finder.preview_cache
{
let ext = preview_path.extension().and_then(|e| e.to_str()).unwrap_or("");
let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set
.find_syntax_by_extension(ext)
.unwrap_or_else(|| syntax_set.find_syntax_plain_text());
let current_theme = theme();
let highlighter = SyntectHighlighter::new(¤t_theme.syntax_theme);
let mut parse_state = ParseState::new(syntax);
let mut highlight_state = HighlightState::new(&highlighter, ScopeStack::new());
let mut line_elements: Vec<Element<'_, Message>> = Vec::new();
for (line_idx, line) in content.lines().enumerate().take(100) {
let line_with_newline = format!("{}\n", line);
let ops =
parse_state.parse_line(&line_with_newline, &syntax_set).unwrap_or_default();
let ranges: Vec<_> = HighlightIterator::new(
&mut highlight_state,
&ops,
&line_with_newline,
&highlighter,
)
.collect();
let line_num: Element<'_, Message> =
container(text(format!("{}", line_idx + 1)).size(11).color(OVERLAY_2))
.width(Length::Fixed(36.0))
.align_right(Length::Fixed(36.0))
.into();
let mut spans: Vec<iced::widget::text::Span<'_, iced::Font>> = Vec::new();
for (style, fragment) in &ranges {
let txt = fragment.strip_suffix('\n').unwrap_or(fragment);
if txt.is_empty() {
continue;
}
spans.push(
iced::widget::text::Span::new(txt.to_string())
.color(iced::Color::from_rgba8(
style.foreground.r,
style.foreground.g,
style.foreground.b,
style.foreground.a as f32 / 255.0,
))
.size(11.0),
);
}
line_elements
.push(row![line_num, iced::widget::rich_text(spans)].spacing(8).into());
}
let preview_header = container(
text(preview_path.file_name().unwrap_or_default().to_string_lossy().to_string())
.size(11)
.color(theme().text_dim),
)
.padding(iced::Padding {
top: 8.0,
right: 12.0,
bottom: 6.0,
left: 12.0,
});
let preview_sep = container(Space::new())
.width(Length::Fill)
.height(Length::Fixed(1.0))
.style(|_theme| container::Style {
background: Some(Background::Color(SURFACE_2)),
..Default::default()
});
let preview_content =
scrollable(column(line_elements).spacing(0).padding(iced::Padding {
top: 4.0,
right: 8.0,
bottom: 8.0,
left: 8.0,
}))
.height(Length::Fill);
column![preview_header, preview_sep, preview_content]
.width(Length::Fill)
.height(Length::Fill)
.into()
} else {
container(text("No preview available").size(13).color(theme().text_dim))
.width(Length::Fill)
.height(Length::Fill)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into()
};
let left_panel = column![input, folder_label, separator_h, file_list]
.width(Length::FillPortion(2))
.height(Length::Fill);
let right_panel = container(preview)
.width(Length::FillPortion(3))
.height(Length::Fill)
.style(|_theme| container::Style {
background: Some(Background::Color(BG_BASE)),
..Default::default()
});
let overlay_box =
container(row![left_panel, separator_v, right_panel].height(Length::Fill))
.width(Length::Fixed(900.0))
.height(Length::Fixed(520.0))
.style(file_finder_panel_style);
let backdrop = mouse_area(
container(Space::new())
.width(Length::Fill)
.height(Length::Fill)
.style(|_theme| container::Style {
background: Some(Background::Color(Color::from_rgba(0.0, 0.0, 0.0, 0.5))),
..Default::default()
}),
)
.on_press(Message::ToggleFuzzyFinder);
stack![backdrop, center(opaque(overlay_box))].into()
}
pub(super) fn view_file_finder_overlay(&self) -> Element<'_, Message> {
use iced::widget::{center, opaque, stack, Space};
let input = text_input("Go to file...", &self.file_finder_query)
.id(self.file_finder_input_id.clone())
.on_input(Message::FileFinderQueryChanged)
.size(15)
.padding(iced::Padding {
top: 16.0,
right: 18.0,
bottom: 16.0,
left: 18.0,
})
.style(search_input_style)
.width(Length::Fill);
let mut items: Vec<Element<'_, Message>> = Vec::new();
if self.file_finder_query.is_empty() {
if !self.recent_files.is_empty() {
items.push(
container(text("Recent Files").size(10).color(theme().text_dim))
.padding(iced::Padding {
top: 8.0,
right: 8.0,
bottom: 4.0,
left: 14.0,
})
.into(),
);
}
for (idx, path) in self.recent_files.iter().enumerate() {
let is_selected = idx == self.file_finder_selected;
let display = path.file_name().unwrap_or_default().to_string_lossy().to_string();
let parent = path
.parent()
.and_then(|p| {
self.file_tree.as_ref().map(|t| {
p.strip_prefix(&t.root).unwrap_or(p).to_string_lossy().to_string()
})
})
.unwrap_or_default();
let file_path = path.clone();
items.push(
button(
row![
text(display).size(13).color(if is_selected {
theme().text_primary
} else {
theme().text_muted
}),
text(parent).size(11).color(theme().text_dim),
]
.spacing(10)
.align_y(iced::Alignment::Center),
)
.style(file_finder_item_style(is_selected))
.on_press(Message::FileClicked(file_path))
.padding(iced::Padding {
top: 7.0,
right: 10.0,
bottom: 7.0,
left: 10.0,
})
.width(Length::Fill)
.into(),
);
}
} else {
for (idx, (_score, display, abs_path)) in self.file_finder_results.iter().enumerate() {
let is_selected = idx == self.file_finder_selected;
let path = abs_path.clone();
items.push(
button(text(display).size(13).color(if is_selected {
theme().text_primary
} else {
theme().text_muted
}))
.style(file_finder_item_style(is_selected))
.on_press(Message::FileClicked(path))
.padding(iced::Padding {
top: 7.0,
right: 10.0,
bottom: 7.0,
left: 10.0,
})
.width(Length::Fill)
.into(),
);
}
}
let has_results = !items.is_empty();
let separator = container(Space::new())
.width(Length::Fill)
.height(Length::Fixed(1.0))
.style(|_theme| container::Style {
background: Some(Background::Color(Color::from_rgba(1.0, 1.0, 1.0, 0.07))),
..Default::default()
});
let inner: Element<'_, Message> = if has_results {
let results_column = scrollable(column(items).spacing(2).padding(iced::Padding {
top: 6.0,
right: 6.0,
bottom: 6.0,
left: 6.0,
}))
.height(Length::Shrink);
column![input, separator, results_column].spacing(0).into()
} else {
input.into()
};
let overlay_box = container(inner)
.width(Length::Fixed(520.0))
.max_height(440.0)
.style(file_finder_panel_style);
let backdrop = mouse_area(
container(Space::new())
.width(Length::Fill)
.height(Length::Fill)
.style(|_theme| container::Style {
background: Some(Background::Color(Color::from_rgba(0.0, 0.0, 0.0, 0.45))),
..Default::default()
}),
)
.on_press(Message::ToggleFileFinder);
stack![backdrop, center(opaque(overlay_box))].into()
}
}