Skip to content

Commit

Permalink
Support drawing popup frame
Browse files Browse the repository at this point in the history
  • Loading branch information
ath3 committed Oct 16, 2023
1 parent d9d7f67 commit bf72b1d
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 27 deletions.
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` |

### `[editor.statusline]` Section

Expand Down
1 change: 1 addition & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,4 @@
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
| `:redraw` | Clear and re-render the whole UI |
| `:popup-border` | Set the borders for popups and menus (none, popup, menu, all) |
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
47 changes: 47 additions & 0 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,46 @@ fn set_line_ending(
Ok(())
}

fn set_borders(
cx: &mut compositor::Context,
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}

if args.is_empty() {
let borders = (cx.editor.popup_border, cx.editor.menu_border);
cx.editor.set_status(match borders {
(true, true) => "all",
(true, false) => "popup",
(false, true) => "menu",
(false, false) => "none",
});

return Ok(());
}

let arg = args.get(0).context("argument missing")?.to_lowercase();

if let Some(borders) = match arg {
arg if arg.starts_with("all") => Some((true, true)),
arg if arg.starts_with("popup") => Some((true, false)),
arg if arg.starts_with("menu") => Some((false, true)),
arg if arg.starts_with("none") => Some((false, false)),
_ => None,
} {
cx.editor.popup_border = borders.0;
cx.editor.menu_border = borders.1;
} else {
cx.editor
.set_status("Valid options are: none, popup, menu, all");
};

Ok(())
}

fn earlier(
cx: &mut compositor::Context,
args: &[Cow<str>],
Expand Down Expand Up @@ -3008,6 +3048,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: redraw,
signature: CommandSignature::none(),
},
TypableCommand {
name: "popup-border",
aliases: &[],
doc: "Set the borders for popups and menus (none, popup, menu, all)",
fun: set_borders,
signature: CommandSignature::none(),
},
];

pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
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")
{
area
} else {
area.inner(&self.margin)
};

let border = usize::from(cx.editor.popup_border);
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 - 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");
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, 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,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -799,6 +800,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 @@ -846,6 +856,7 @@ impl Default for Config {
default_line_ending: LineEndingConfig::default(),
insert_final_newline: true,
smart_tab: Some(SmartTabConfig::default()),
popup_border: PopupBorderConfig::None,
}
}
}
Expand Down Expand Up @@ -950,6 +961,8 @@ pub struct Editor {
/// field is set and any old requests are automatically
/// canceled as a result
pub completion_request_handle: Option<oneshot::Sender<()>>,
pub popup_border: bool,
pub menu_border: bool,
}

pub type Motion = Box<dyn Fn(&mut Editor)>;
Expand Down Expand Up @@ -1062,6 +1075,10 @@ impl Editor {
needs_redraw: false,
cursor_cache: Cell::new(None),
completion_request_handle: None,
popup_border: conf.popup_border == PopupBorderConfig::All
|| conf.popup_border == PopupBorderConfig::Popup,
menu_border: conf.popup_border == PopupBorderConfig::All
|| conf.popup_border == PopupBorderConfig::Menu,
}
}

Expand Down Expand Up @@ -1092,6 +1109,10 @@ impl Editor {
pub fn refresh_config(&mut self) {
let config = self.config();
self.auto_pairs = (&config.auto_pairs).into();
self.popup_border = config.popup_border == PopupBorderConfig::All
|| config.popup_border == PopupBorderConfig::Popup;
self.menu_border = config.popup_border == PopupBorderConfig::All
|| config.popup_border == PopupBorderConfig::Menu;
self.reset_idle_timer();
self._refresh();
}
Expand Down

0 comments on commit bf72b1d

Please sign in to comment.