From 9bd9e4cc427be33c5ea33c1b86318cd21e8a80c6 Mon Sep 17 00:00:00 2001 From: Igor Cohanovschi Date: Sun, 17 Jul 2022 13:17:52 +0200 Subject: [PATCH] Added icons and light styling to tree_explorer --- helix-term/src/ui/explore.rs | 131 ++++++++++++++++++++++++++++++----- helix-term/src/ui/tree.rs | 32 +++++++-- 2 files changed, 140 insertions(+), 23 deletions(-) diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index 4d255ece..d0739ca0 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -26,6 +26,27 @@ macro_rules! get_theme { }; } +const ICONS: &'static [&'static str] = + &["", "", "", "", "", "ﰟ", "", "", "", "ﯤ", "", "ﬥ"]; + +const ICONS_EXT: &'static [&'static str] = &[ + ".rs", ".md", ".js", ".c", ".png", ".svg", ".css", ".html", ".lua", ".ts", ".py", ".json", +]; + +const ICONS_COLORS: &'static [helix_view::theme::Color] = &[ + helix_view::theme::Color::Rgb(227, 134, 84), + helix_view::theme::Color::LightCyan, + helix_view::theme::Color::Yellow, + helix_view::theme::Color::Blue, + helix_view::theme::Color::Yellow, + helix_view::theme::Color::Yellow, + helix_view::theme::Color::Green, + helix_view::theme::Color::Blue, + helix_view::theme::Color::Red, + helix_view::theme::Color::Blue, + helix_view::theme::Color::Red, +]; + #[derive(Debug, Clone, Copy, PartialEq)] enum FileType { File, @@ -39,18 +60,24 @@ enum FileType { #[derive(Debug, Clone)] struct FileInfo { file_type: FileType, + expanded: bool, path: PathBuf, } impl FileInfo { fn new(path: PathBuf, file_type: FileType) -> Self { - Self { path, file_type } + Self { + path, + file_type, + expanded: false, + } } fn root(path: PathBuf) -> Self { Self { file_type: FileType::Root, path, + expanded: true, } } @@ -59,6 +86,7 @@ impl FileInfo { Self { file_type: FileType::Parent, path: p.to_path_buf(), + expanded: false, } } @@ -159,6 +187,7 @@ impl TreeItem for FileInfo { Self { file_type, path: self.path.join(entry.file_name()), + expanded: false, } }) }) @@ -167,6 +196,7 @@ impl TreeItem for FileInfo { ret.push(Self { path: self.path.clone(), file_type: FileType::Placeholder, + expanded: false, }) } Ok(ret) @@ -179,6 +209,33 @@ impl TreeItem for FileInfo { self.get_text().contains(s) } } + + fn icon(&self) -> Option<(&'static str, &'static helix_view::theme::Color)> { + return match self.file_type { + FileType::Dir => { + if self.expanded { + //Some(("", &helix_view::theme::Color::Yellow)) + Some(("", &helix_view::theme::Color::Yellow)) + } else { + // Some(("", &helix_view::theme::Color::Yellow)) + Some(("", &helix_view::theme::Color::Yellow)) + } + } + FileType::File => { + for (i, ext) in ICONS_EXT.iter().enumerate() { + if self.get_text().ends_with(ext) { + let color = ICONS_COLORS + .iter() + .nth(i) + .unwrap_or(&helix_view::theme::Color::Blue); + return ICONS.iter().nth(i).map(|c| (*c, color)); + } + } + return Some(("", &helix_view::theme::Color::LightBlue)); + } + _ => None, + }; + } } // #[derive(Default, Debug, Clone)] @@ -254,7 +311,9 @@ impl Explorer { let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into()); let items = Self::get_items(current_root.clone(), cx)?; Ok(Self { - tree: Tree::build_tree(items).with_enter_fn(Self::toggle_current), + tree: Tree::build_tree(items) + .with_enter_fn(Self::toggle_current) + .with_folded_fn(Self::fold_current), state: State::new(true, current_root), repeat_motion: None, prompt: None, @@ -266,8 +325,9 @@ impl Explorer { let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into()); let parent = FileInfo::parent(¤t_root); let root = FileInfo::root(current_root.clone()); - let mut tree = - Tree::build_from_root(root, usize::MAX / 2)?.with_enter_fn(Self::toggle_current); + let mut tree = Tree::build_from_root(root, usize::MAX / 2)? + .with_enter_fn(Self::toggle_current) + .with_folded_fn(Self::fold_current); tree.insert_current_level(parent); Ok(Self { tree, @@ -319,7 +379,12 @@ impl Explorer { if item.file_type == FileType::Placeholder { return; } - let head_area = render_block(area.clip_bottom(area.height - 2), surface, Borders::BOTTOM); + let head_area = render_block( + area.clip_bottom(area.height - 2), + surface, + Borders::BOTTOM, + None, + ); let path_str = format!("{}", item.path.display()); surface.set_stringn( head_area.x, @@ -434,6 +499,12 @@ impl Explorer { )); } + fn fold_current(item: &mut FileInfo, cx: &mut Context, state: &mut State) { + if item.path.is_dir() { + item.expanded = false; + } + } + fn toggle_current( item: &mut FileInfo, cx: &mut Context, @@ -461,6 +532,7 @@ impl Explorer { } if item.path.is_dir() { + item.expanded = true; if cx.editor.config().explorer.is_list() || item.file_type == FileType::Parent { match Self::get_items(item.path.clone(), cx) { Ok(items) => { @@ -481,19 +553,28 @@ impl Explorer { let background = cx.editor.theme.get("ui.background"); let column_width = cx.editor.config().explorer.column_width as u16; surface.clear_with(area, background); - let area = render_block(area, surface, Borders::ALL); + let area = render_block(area, surface, Borders::ALL, None); let mut preview_area = area.clip_left(column_width + 1); if let Some((_, prompt)) = self.prompt.as_mut() { let area = preview_area.clip_bottom(2); - let promp_area = - render_block(preview_area.clip_top(area.height), surface, Borders::TOP); + let promp_area = render_block( + preview_area.clip_top(area.height), + surface, + Borders::TOP, + None, + ); prompt.render(promp_area, surface, cx); preview_area = area; } self.render_preview(preview_area, surface, cx.editor); - let list_area = render_block(area.clip_right(preview_area.width), surface, Borders::RIGHT); + let list_area = render_block( + area.clip_right(preview_area.width), + surface, + Borders::RIGHT, + None, + ); self.tree.render(list_area, surface, cx, &mut self.state); } @@ -502,14 +583,21 @@ impl Explorer { let side_area = area .with_width(area.width.min(config.column_width as u16 + 2)) .clip_bottom(1); - let background = cx.editor.theme.get("ui.background"); + + let background = cx.editor.theme.get("ui.statusline"); surface.clear_with(side_area, background); let preview_area = area.clip_left(side_area.width).clip_bottom(2); let prompt_area = area.clip_top(side_area.height); - let list_area = - render_block(side_area.clip_left(1), surface, Borders::RIGHT).clip_bottom(1); + let border_style = cx.editor.theme.get("ui.explore.border"); + let list_area = render_block( + side_area.clip_left(1), + surface, + Borders::RIGHT, + Some(border_style), + ) + .clip_bottom(1); self.tree.render(list_area, surface, cx, &mut self.state); { @@ -543,7 +631,7 @@ impl Explorer { } let area = Rect::new(preview_area.x, y, width, height); surface.clear_with(area, background); - let area = render_block(area, surface, Borders::all()); + let area = render_block(area, surface, Borders::all(), None); self.render_preview(area, surface, cx.editor); } @@ -757,7 +845,9 @@ impl Component for Explorer { match Self::get_items(p.to_path_buf(), cx) { Ok(items) => { self.state.current_root = p.to_path_buf(); - self.tree = Tree::build_tree(items).with_enter_fn(Self::toggle_current); + self.tree = Tree::build_tree(items) + .with_enter_fn(Self::toggle_current) + .with_folded_fn(Self::fold_current); } Err(e) => cx.editor.set_error(format!("{e}")), } @@ -859,8 +949,17 @@ fn get_preview(p: impl AsRef, max_line: usize) -> Result> { .collect()) } -fn render_block(area: Rect, surface: &mut Surface, borders: Borders) -> Rect { - let block = Block::default().borders(borders); +fn render_block( + area: Rect, + surface: &mut Surface, + borders: Borders, + border_style: Option, +) -> Rect { + let mut block = Block::default().borders(borders); + if let Some(style) = border_style { + block = block.border_style(style); + } + //let block = Block::default(); let inner = block.inner(area); block.render(area, surface); inner diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index 4da57ad5..6945bb38 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -18,6 +18,7 @@ pub trait TreeItem: Sized { fn text(&self, cx: &mut Context, selected: bool, params: &mut Self::Params) -> Spans; fn is_child(&self, other: &Self) -> bool; fn cmp(&self, other: &Self) -> Ordering; + fn icon(&self) -> Option<(&'static str, &'static helix_view::theme::Color)>; fn filter(&self, cx: &mut Context, s: &str, params: &mut Self::Params) -> bool { self.text(cx, false, params) @@ -117,12 +118,14 @@ pub enum TreeOp { pub struct Elem { item: T, level: usize, + expanded: bool, folded: Vec, } impl Clone for Elem { fn clone(&self) -> Self { Self { + expanded: false, item: self.item.clone(), level: self.level, folded: self.folded.clone(), @@ -135,6 +138,7 @@ impl Elem { Self { item, level, + expanded: false, folded: vec![], } } @@ -176,7 +180,7 @@ impl Tree { col: 0, max_len: 0, count: 0, - tree_symbol_style: "ui.text".into(), + tree_symbol_style: "ui.explore.guide".into(), pre_render: None, on_opened_fn: None, on_folded_fn: None, @@ -264,6 +268,7 @@ impl Tree { if next_level > current_level { if let Some(mut on_folded_fn) = self.on_folded_fn.take() { on_folded_fn(&mut current.item, cx, params); + self.on_folded_fn = Some(on_folded_fn); } self.fold_current_child(); @@ -469,6 +474,8 @@ impl Tree { self.max_len = 0; self.row = std::cmp::min(self.row, area.height.saturating_sub(1) as usize); let style = cx.editor.theme.get(&self.tree_symbol_style); + let line_style = cx.editor.theme.get("ui.virtual.ruler"); + let folder_style = cx.editor.theme.get("special"); let last_item_index = self.items.len().saturating_sub(1); let skip = self.selected.saturating_sub(self.row); let iter = self @@ -477,14 +484,17 @@ impl Tree { .skip(skip) .take(area.height as usize) .enumerate(); + for (index, elem) in iter { let row = index as u16; let mut area = Rect::new(area.x, area.y + row, area.width, 1); let indent = if elem.level > 0 { if index + skip != last_item_index { - format!("{}├─", "│ ".repeat(elem.level - 1)) + //format!("{}├─", "│ ".repeat(elem.level - 1)) + format!("{}", "│ ".repeat(elem.level - 1)) } else { - format!("└─{}", "┴─".repeat(elem.level - 1)) + //format!("└─{}", "┴─".repeat(elem.level - 1)) + format!("{}", "".repeat(elem.level - 1)) } } else { "".to_string() @@ -500,14 +510,19 @@ impl Tree { }; let mut start_index = self.col.saturating_sub(indent_len); let mut text = elem.item.text(cx, skip + index == self.selected, params); - self.max_len = self.max_len.max(text.width() + indent.len()); + self.max_len = self.max_len.max(text.width() + indent.len() - 2); for span in text.0.iter_mut() { if area.width == 0 { return; } if start_index == 0 { - surface.set_span(area.x, area.y, span, area.width); - area = area.clip_left(span.width() as u16); + //let expanded = elem.folded.len() > 0; + if let Some((icon, color)) = elem.item.icon() { + let style = folder_style.fg(*color); + surface.set_string(area.x, area.y, icon, style); + } + surface.set_span(area.x + 2, area.y, span, area.width - 2); + area = area.clip_left((span.width() - 2) as u16); } else { let span_width = span.width(); if start_index > span_width { @@ -525,10 +540,13 @@ impl Tree { } }) .collect(); + let mut cont = String::new(); + cont.push_str(""); + cont.push_str(&content); surface.set_string_truncated( area.x, area.y, - &content, + &cont, area.width as usize, |_| span.style, false,