Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support drawing popup frame #4313

Merged
merged 2 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`

### `[editor.statusline]` Section
Expand Down
9 changes: 7 additions & 2 deletions helix-term/src/commands/dap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use dap::{StackFrame, Thread, ThreadStates};
use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
use helix_dap::{self as dap, Client};
use helix_lsp::block_on;
use helix_view::editor::Breakpoint;
use helix_view::{editor::Breakpoint, graphics::Margin};

use serde_json::{to_value, Value};
use tokio_stream::wrappers::UnboundedReceiverStream;
Expand Down Expand Up @@ -581,7 +581,12 @@ pub fn dap_variables(cx: &mut Context) {
}

let contents = Text::from(tui::text::Text::from(variables));
let popup = Popup::new("dap-variables", contents);
let margin = if cx.editor.popup_border() {
Margin::all(1)
} else {
Margin::none()
};
let popup = Popup::new("dap-variables", contents).margin(margin);
cx.replace_or_push_layer("dap-variables", popup);
}

Expand Down
12 changes: 11 additions & 1 deletion helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use helix_core::{
use helix_view::{
document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
editor::Action,
graphics::Margin,
theme::Style,
Document, View,
};
Expand Down Expand Up @@ -744,7 +745,16 @@ 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 margin = if editor.menu_border() {
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);
};

Expand Down
1 change: 0 additions & 1 deletion helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,6 @@ fn set_line_ending(

Ok(())
}

fn earlier(
cx: &mut compositor::Context,
args: &[Cow<str>],
Expand Down
18 changes: 17 additions & 1 deletion helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::compositor::{Component, Context, Event, EventResult};
use helix_view::{
document::SavePoint,
editor::CompleteAction,
graphics::Margin,
theme::{Modifier, Style},
ViewId,
};
Expand Down Expand Up @@ -326,9 +327,18 @@ impl Completion {
}
};
});

let margin = if editor.menu_border() {
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,
Expand Down Expand Up @@ -569,6 +579,12 @@ impl Component for Completion {
// clear area
let background = cx.editor.theme.get("ui.popup");
surface.clear_with(doc_area, background);

if cx.editor.popup_border() {
use tui::widgets::{Block, Borders, Widget};
Widget::render(Block::default().borders(Borders::ALL), doc_area, surface);
}

markdown_doc.render(doc_area, surface, cx);
}
}
4 changes: 3 additions & 1 deletion helix-term/src/ui/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ 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 sig_doc_area = area
.clip_top(sig_text_area.height + 2)
.clip_bottom(u16::from(cx.editor.popup_border()));
let sig_doc_para = Paragraph::new(sig_doc)
.wrap(Wrap { trim: false })
.scroll((cx.scroll.unwrap_or_default() as u16, 0));
Expand Down
47 changes: 34 additions & 13 deletions helix-term/src/ui/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ use crate::{
use helix_core::fuzzy::MATCHER;
use nucleo::pattern::{Atom, AtomKind, CaseMatching};
use nucleo::{Config, Utf32Str};
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 helix_view::{editor::SmartTabConfig, graphics::Rect, Editor};
use helix_view::{
editor::SmartTabConfig,
graphics::{Margin, Rect},
Editor,
};
use tui::layout::Constraint;

pub trait Item: Sync + Send + 'static {
Expand Down Expand Up @@ -322,6 +329,15 @@ impl<T: Item + 'static> Component for Menu<T> {
let selected = theme.get("ui.menu.selected");
surface.clear_with(area, style);

let render_borders = cx.editor.menu_border();

let area = 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
Expand Down Expand Up @@ -362,15 +378,19 @@ impl<T: Item + 'static> Component for Menu<T> {
false,
);

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);
let render_borders = cx.editor.menu_border();

if !render_borders {
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);
}
}

let fits = len <= win_height;
Expand All @@ -385,12 +405,13 @@ impl<T: Item + 'static> Component for Menu<T> {
for i in 0..win_height {
cell = &mut surface[(area.right() - 1, area.top() + i as u16)];

cell.set_symbol("▐"); // right half block
let half_block = if render_borders { "▌" } else { "▐" };

if scroll_line <= i && i < scroll_line + scroll_height {
// Draw scroll thumb
cell.set_symbol(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));
}
Expand Down
34 changes: 27 additions & 7 deletions helix-term/src/ui/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ 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::{
Expand Down Expand Up @@ -252,13 +255,29 @@ impl<T: Component> Component for Popup<T> {
let background = cx.editor.theme.get("ui.popup");
surface.clear_with(area, background);

let inner = area.inner(&self.margin);
let render_borders = cx.editor.popup_border();

let inner = if self
.contents
.type_name()
.starts_with("helix_term::ui::menu::Menu")
Comment on lines +260 to +263
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems very hacky to me but I don't see a good way around it - there isn't a way to compare the type to Menu<_> without dealing with strings like this. I think I'm fine with this for now but it's something we should try to eliminate in the future (though it may take a large refactor to the compositor system to do it)

{
area
} else {
area.inner(&self.margin)
};

let border = usize::from(render_borders);
if render_borders {
Widget::render(Block::default().borders(Borders::ALL), area, surface);
}

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).saturating_sub(2 * border);
let len = (self.child_size.1 as usize).saturating_sub(2 * border);
let fits = len <= win_height;
let scroll = self.scroll;
let scroll_style = cx.editor.theme.get("ui.menu.scroll");
Expand All @@ -274,14 +293,15 @@ impl<T: Component> Component for Popup<T> {

let mut cell;
for i in 0..win_height {
cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)];
cell = &mut surface[(inner.right() - 1, inner.top() + (border + i) as u16)];

cell.set_symbol("▐"); // right half block
let half_block = if render_borders { "▌" } else { "▐" };

if scroll_line <= i && i < scroll_line + scroll_height {
// Draw scroll thumb
cell.set_symbol(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));
}
Expand Down
25 changes: 23 additions & 2 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ pub use helix_core::diagnostic::Severity;
use helix_core::{
auto_pairs::AutoPairs,
syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
Change, LineEnding, NATIVE_LINE_ENDING,
Change, LineEnding, Position, Selection, NATIVE_LINE_ENDING,
};
use helix_core::{Position, Selection};
use helix_dap as dap;
use helix_lsp::lsp;

Expand Down Expand Up @@ -291,6 +290,8 @@ pub struct Config {
pub insert_final_newline: bool,
/// Enables smart tab
pub smart_tab: Option<SmartTabConfig>,
/// Draw border around popups.
pub popup_border: PopupBorderConfig,
/// Which indent heuristic to use when a new line is inserted
#[serde(default)]
pub indent_heuristic: IndentationHeuristic,
Expand Down Expand Up @@ -797,6 +798,15 @@ impl From<LineEndingConfig> for LineEnding {
}
}

#[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 {
Expand Down Expand Up @@ -844,6 +854,7 @@ impl Default for Config {
default_line_ending: LineEndingConfig::default(),
insert_final_newline: true,
smart_tab: Some(SmartTabConfig::default()),
popup_border: PopupBorderConfig::None,
indent_heuristic: IndentationHeuristic::default(),
}
}
Expand Down Expand Up @@ -1064,6 +1075,16 @@ impl Editor {
}
}

pub fn popup_border(&self) -> bool {
self.config().popup_border == PopupBorderConfig::All
|| self.config().popup_border == PopupBorderConfig::Popup
}

pub fn menu_border(&self) -> bool {
self.config().popup_border == PopupBorderConfig::All
|| self.config().popup_border == PopupBorderConfig::Menu
}

pub fn apply_motion<F: Fn(&mut Self) + 'static>(&mut self, motion: F) {
motion(self);
self.last_motion = Some(Box::new(motion));
Expand Down
Loading