From badbff3f9b63c554fe9993fc09609cbbefd588ba Mon Sep 17 00:00:00 2001 From: ath3 Date: Tue, 14 Mar 2023 12:18:36 +0100 Subject: [PATCH] Support drawing popup frame --- book/src/configuration.md | 1 + helix-term/src/commands/lsp.rs | 18 ++++++++-- helix-term/src/ui/completion.rs | 26 +++++++++++++-- helix-term/src/ui/lsp.rs | 9 ++++- helix-term/src/ui/menu.rs | 58 +++++++++++++++++++++++---------- helix-term/src/ui/popup.rs | 39 +++++++++++++++++----- helix-view/src/editor.rs | 15 +++++++-- 7 files changed, 133 insertions(+), 33 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index bf31499374e82..31778cfa5779f 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -58,6 +58,7 @@ signal to the Helix process on Unix operating systems, such as by using the comm | `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | | `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap_at_text_width` is set | `80` | +| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` | ### `[editor.statusline]` Section diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 13d62b5db2179..a23ad04b25031 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -18,7 +18,8 @@ use super::{align_view, push_jump, Align, Context, Editor, Open}; use helix_core::{path, text_annotations::InlineAnnotation, Selection}; use helix_view::{ document::{DocumentInlayHints, DocumentInlayHintsId, Mode}, - editor::Action, + editor::{Action, PopupBorderConfig}, + graphics::Margin, theme::Style, Document, View, }; @@ -664,7 +665,20 @@ pub fn code_action(cx: &mut Context) { }); picker.move_down(); // pre-select the first item - let popup = Popup::new("code-action", picker).with_scrollbar(false); + let border_config = &editor.config().popup_border; + + let margin = if border_config == &PopupBorderConfig::All + || border_config == &PopupBorderConfig::Menu + { + Margin::vertical(1) + } else { + Margin::none() + }; + + let popup = Popup::new("code-action", picker) + .with_scrollbar(false) + .margin(margin); + compositor.replace_or_push("code-action", popup); }, ) diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index da6b5ddcbc1fb..26db9f07b2e12 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,7 +1,8 @@ use crate::compositor::{Component, Context, Event, EventResult}; use helix_view::{ document::SavePoint, - editor::CompleteAction, + editor::{CompleteAction, PopupBorderConfig}, + graphics::Margin, theme::{Modifier, Style}, ViewId, }; @@ -292,9 +293,22 @@ impl Completion { } }; }); + + let border_config = &editor.config().popup_border; + + let margin = if border_config == &PopupBorderConfig::All + || border_config == &PopupBorderConfig::Menu + { + Margin::vertical(1) + } else { + Margin::none() + }; + let popup = Popup::new(Self::ID, menu) .with_scrollbar(false) - .ignore_escape_key(true); + .ignore_escape_key(true) + .margin(margin); + let mut completion = Self { popup, start_offset, @@ -525,6 +539,14 @@ impl Component for Completion { // clear area let background = cx.editor.theme.get("ui.popup"); surface.clear_with(doc_area, background); + + let border_config = &cx.editor.config().popup_border; + + if border_config == &PopupBorderConfig::All || border_config == &PopupBorderConfig::Popup { + use tui::widgets::{Block, Borders, Widget}; + Widget::render(Block::default().borders(Borders::ALL), doc_area, surface); + } + markdown_doc.render(doc_area, surface, cx); } } diff --git a/helix-term/src/ui/lsp.rs b/helix-term/src/ui/lsp.rs index 44050aa129695..980d2e51d0c89 100644 --- a/helix-term/src/ui/lsp.rs +++ b/helix-term/src/ui/lsp.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use helix_core::syntax; +use helix_view::editor::PopupBorderConfig; use helix_view::graphics::{Margin, Rect, Style}; use tui::buffer::Buffer; use tui::widgets::{BorderType, Paragraph, Widget, Wrap}; @@ -92,7 +93,13 @@ impl Component for SignatureHelp { Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)), }; let sig_doc = sig_doc.parse(Some(&cx.editor.theme)); - let sig_doc_area = area.clip_top(sig_text_area.height + 2); + let mut sig_doc_area = area.clip_top(sig_text_area.height + 2); + + let border_config = &cx.editor.config().popup_border; + + if border_config == &PopupBorderConfig::All || border_config == &PopupBorderConfig::Popup { + sig_doc_area = sig_doc_area.clip_bottom(1); + } let sig_doc_para = Paragraph::new(sig_doc) .wrap(Wrap { trim: false }) .scroll((cx.scroll.unwrap_or_default() as u16, 0)); diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 30625acee60cb..f0dfd44487efb 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -4,14 +4,21 @@ use crate::{ compositor::{Callback, Component, Compositor, Context, Event, EventResult}, ctrl, key, shift, }; -use tui::{buffer::Buffer as Surface, widgets::Table}; +use tui::{ + buffer::Buffer as Surface, + widgets::{Block, Borders, Table, Widget}, +}; pub use tui::widgets::{Cell, Row}; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; -use helix_view::{graphics::Rect, Editor}; +use helix_view::{ + editor::PopupBorderConfig, + graphics::{Margin, Rect}, + Editor, +}; use tui::layout::Constraint; pub trait Item { @@ -310,6 +317,18 @@ impl Component for Menu { let selected = theme.get("ui.menu.selected"); surface.clear_with(area, style); + let border_config = &cx.editor.config().popup_border; + + let render_borders = + border_config == &PopupBorderConfig::All || border_config == &PopupBorderConfig::Menu; + + let inner = if render_borders { + Widget::render(Block::default().borders(Borders::ALL), area, surface); + area.inner(&Margin::vertical(1)) + } else { + area + }; + let scroll = self.scroll; let options: Vec<_> = self @@ -323,7 +342,7 @@ impl Component for Menu { let len = options.len(); - let win_height = area.height as usize; + let win_height = inner.height as usize; const fn div_ceil(a: usize, b: usize) -> usize { (a + b - 1) / b @@ -341,7 +360,7 @@ impl Component for Menu { use tui::widgets::TableState; table.render_table( - area.clip_left(Self::LEFT_PADDING as u16).clip_right(1), + inner.clip_left(Self::LEFT_PADDING as u16).clip_right(1), surface, &mut TableState { offset: scroll, @@ -349,15 +368,17 @@ impl Component for Menu { }, ); - if let Some(cursor) = self.cursor { - let offset_from_top = cursor - scroll; - let left = &mut surface[(area.left(), area.y + offset_from_top as u16)]; - left.set_style(selected); - let right = &mut surface[( - area.right().saturating_sub(1), - area.y + offset_from_top as u16, - )]; - right.set_style(selected); + if !render_borders { + if let Some(cursor) = self.cursor { + let offset_from_top = cursor - scroll; + let left = &mut surface[(inner.left(), inner.y + offset_from_top as u16)]; + left.set_style(selected); + let right = &mut surface[( + inner.right().saturating_sub(1), + inner.y + offset_from_top as u16, + )]; + right.set_style(selected); + } } let fits = len <= win_height; @@ -370,14 +391,17 @@ impl Component for Menu { let mut cell; for i in 0..win_height { - cell = &mut surface[(area.right() - 1, area.top() + i as u16)]; - - cell.set_symbol("▐"); // right half block + cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)]; if scroll_line <= i && i < scroll_line + scroll_height { // Draw scroll thumb + if render_borders { + cell.set_symbol("▌"); // left half block + } else { + cell.set_symbol("▐"); // right half block + } cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset)); - } else { + } else if !render_borders { // Draw scroll track cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset)); } diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index 5a95c1bbae797..ef0eac793496b 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -3,10 +3,16 @@ use crate::{ compositor::{Callback, Component, Context, Event, EventResult}, ctrl, key, }; -use tui::buffer::Buffer as Surface; +use tui::{ + buffer::Buffer as Surface, + widgets::{Block, Borders, Widget}, +}; use helix_core::Position; -use helix_view::graphics::{Margin, Rect}; +use helix_view::{ + editor::PopupBorderConfig, + graphics::{Margin, Rect}, +}; // TODO: share logic with Menu, it's essentially Popup(render_fn), but render fn needs to return // a width/height hint. maybe Popup(Box) @@ -246,13 +252,25 @@ impl Component for Popup { let background = cx.editor.theme.get("ui.popup"); surface.clear_with(area, background); - let inner = area.inner(&self.margin); + let border_config = &cx.editor.config().popup_border; + + let render_borders = + border_config == &PopupBorderConfig::All || border_config == &PopupBorderConfig::Popup; + + let (inner, border) = if render_borders { + Widget::render(Block::default().borders(Borders::ALL), area, surface); + (area, 1) + } else { + let inner = area.inner(&self.margin); + (inner, 0) + }; + self.contents.render(inner, surface, cx); // render scrollbar if contents do not fit if self.has_scrollbar { - let win_height = inner.height as usize; - let len = self.child_size.1 as usize; + let win_height = inner.height as usize - 2 * border; + let len = self.child_size.1 as usize - 2 * border; let fits = len <= win_height; let scroll = self.scroll; let scroll_style = cx.editor.theme.get("ui.menu.scroll"); @@ -268,14 +286,17 @@ impl Component for Popup { let mut cell; for i in 0..win_height { - cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)]; - - cell.set_symbol("▐"); // right half block + cell = &mut surface[(inner.right() - 1, inner.top() + (border + i) as u16)]; if scroll_line <= i && i < scroll_line + scroll_height { // Draw scroll thumb + if render_borders { + cell.set_symbol("▌"); // left half block + } else { + cell.set_symbol("▐"); // right half block + } cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset)); - } else { + } else if !render_borders { // Draw scroll track cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset)); } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7207baf38a2e0..8760b42ea0a92 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -43,9 +43,8 @@ pub use helix_core::register::Registers; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig, SoftWrap}, - Change, + Change, Position, Selection, }; -use helix_core::{Position, Selection}; use helix_dap as dap; use helix_lsp::lsp; @@ -281,6 +280,8 @@ pub struct Config { /// Whether to color modes with different colors. Defaults to `false`. pub color_modes: bool, pub soft_wrap: SoftWrap, + /// Draw border around popups. + pub popup_border: PopupBorderConfig, } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -712,6 +713,15 @@ impl Default for IndentGuidesConfig { } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum PopupBorderConfig { + None, + All, + Popup, + Menu, +} + impl Default for Config { fn default() -> Self { Self { @@ -751,6 +761,7 @@ impl Default for Config { soft_wrap: SoftWrap::default(), text_width: 80, completion_replace: false, + popup_border: PopupBorderConfig::None, } } }