diff --git a/README.md b/README.md index aab3fb84..bffedff4 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ # Merged PRs - [File explorer and tree helper](https://github.com/helix-editor/helix/pull/2377) + - [with Icons](https://github.com/r0l1/helix/tree/tree_explorer_icons) - [Add LSP workspace command picker](https://github.com/helix-editor/helix/pull/3140) And others I forgot about... diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index 18cc88fa..a6e6be2f 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -338,33 +338,6 @@ impl Registry { .map(|(_, client)| client.as_ref()) } - pub fn restart( - &mut self, - language_config: &LanguageConfiguration, - ) -> Result>> { - let config = match &language_config.language_server { - Some(config) => config, - None => return Ok(None), - }; - - let scope = language_config.scope.clone(); - - match self.inner.entry(scope) { - Entry::Vacant(_) => Ok(None), - Entry::Occupied(mut entry) => { - // initialize a new client - let id = self.counter.fetch_add(1, Ordering::Relaxed); - - let NewClientResult(client, incoming) = start_client(id, language_config, config)?; - self.incoming.push(UnboundedReceiverStream::new(incoming)); - - entry.insert((id, client.clone())); - - Ok(Some(client)) - } - } - } - pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result>> { let config = match &language_config.language_server { Some(config) => config, @@ -385,6 +358,7 @@ impl Registry { } } } + pub fn restart(&mut self, language_config: &LanguageConfiguration) -> Result> { let config = language_config .language_server diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 35f94c87..3ee5324d 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1559,16 +1559,6 @@ fn run_shell_command( Ok(()) } -fn lsp_restart( - cx: &mut compositor::Context, - _args: &[Cow], - _event: PromptEvent, -) -> anyhow::Result<()> { - let current_document = doc!(cx.editor).id(); - cx.editor.restart_language_server(current_document); - Ok(()) -} - pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index 2f32adcd..951a2ebc 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, @@ -453,7 +524,7 @@ impl Explorer { } }; if meta.is_file() { - if let Err(e) = cx.editor.open(&item.path, Action::Replace) { + if let Err(e) = cx.editor.open(&item.path.clone(), Action::Replace) { cx.editor.set_error(format!("{e}")); } state.focus = false; @@ -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.explorer.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); } @@ -569,9 +657,7 @@ impl Explorer { } key!(Esc) | ctrl!('c') => self.tree.restore_recycle(), _ => { - if let EventResult::Consumed(_) = - prompt.handle_event(&Event::Key(event.clone()), cx) - { + if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) { self.tree.filter(prompt.line(), cx, &mut self.state); } self.prompt = Some((action, prompt)); @@ -618,9 +704,7 @@ impl Explorer { } key!(Esc) | ctrl!('c') => self.tree.restore_view(), _ => { - if let EventResult::Consumed(_) = - prompt.handle_event(&Event::Key(event.clone()), cx) - { + if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) { if search_next { self.tree.search_next(cx, prompt.line(), &mut self.state); } else { @@ -676,7 +760,7 @@ impl Explorer { } (_, key!(Esc) | ctrl!('c')) => {} _ => { - prompt.handle_event(&Event::Key(event.clone()), cx); + prompt.handle_event(&Event::Key(*event), cx); self.prompt = Some((action, prompt)); } } @@ -720,7 +804,7 @@ impl Component for Explorer { /// Process input events, return true if handled. fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let key_event = match event { - Event::Key(event) => event, + Event::Key(event) => *event, Event::Resize(..) => return EventResult::Consumed(None), _ => return EventResult::Ignored(None), }; @@ -728,7 +812,7 @@ impl Component for Explorer { return EventResult::Ignored(None); } if let Some(mut on_next_key) = self.on_next_key.take() { - return on_next_key(cx, self, key_event); + return on_next_key(cx, self, &key_event); } if let EventResult::Consumed(c) = self.handle_prompt_event(&key_event, cx) { @@ -761,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}")), } @@ -863,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 fd2af8c6..1a3ec01d 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -20,6 +20,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) @@ -178,7 +179,7 @@ impl Tree { col: 0, max_len: 0, count: 0, - tree_symbol_style: "ui.text".into(), + tree_symbol_style: "ui.explorer.guide".into(), pre_render: None, on_opened_fn: None, on_folded_fn: None, @@ -471,6 +472,7 @@ 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 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 @@ -484,9 +486,9 @@ impl Tree { 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)) } else { - format!("└─{}", "┴─".repeat(elem.level - 1)) + format!("{}", "".repeat(elem.level - 1)) } } else { "".to_string() @@ -502,14 +504,20 @@ 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 mut icon_offset = 0; + if let Some((icon, color)) = elem.item.icon() { + let style = folder_style.fg(*color); + surface.set_string(area.x, area.y, icon, style); + icon_offset = 2; + } + surface.set_span(area.x + icon_offset, area.y, span, area.width - icon_offset); + area = area.clip_left((span.width() - icon_offset as usize) as u16); } else { let span_width = span.width(); if start_index > span_width { @@ -527,10 +535,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, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 92cb6a80..c38733d0 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -134,31 +134,19 @@ pub struct ExplorerConfig { impl ExplorerConfig { pub fn is_embed(&self) -> bool { - match self.position { - ExplorerPosition::Embed => true, - ExplorerPosition::Overlay => false, - } + return self.position == ExplorerPosition::Embed; } pub fn is_overlay(&self) -> bool { - match self.position { - ExplorerPosition::Embed => false, - ExplorerPosition::Overlay => true, - } + return self.position == ExplorerPosition::Overlay; } pub fn is_list(&self) -> bool { - match self.style { - ExplorerStyle::List => true, - ExplorerStyle::Tree => false, - } + return self.style == ExplorerStyle::List; } pub fn is_tree(&self) -> bool { - match self.style { - ExplorerStyle::List => false, - ExplorerStyle::Tree => true, - } + return self.style == ExplorerStyle::Tree; } }