mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
Support drawing popup frame (#4313)
Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
This commit is contained in:
parent
06d7dc628e
commit
9ba691cd3a
@ -65,6 +65,7 @@ ### `[editor]` Section
|
|||||||
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
|
| `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` |
|
| `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` |
|
| `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`
|
| `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
|
### `[editor.statusline]` Section
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
|
use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
|
||||||
use helix_dap::{self as dap, Client};
|
use helix_dap::{self as dap, Client};
|
||||||
use helix_lsp::block_on;
|
use helix_lsp::block_on;
|
||||||
use helix_view::editor::Breakpoint;
|
use helix_view::{editor::Breakpoint, graphics::Margin};
|
||||||
|
|
||||||
use serde_json::{to_value, Value};
|
use serde_json::{to_value, Value};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
@ -581,7 +581,12 @@ pub fn dap_variables(cx: &mut Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let contents = Text::from(tui::text::Text::from(variables));
|
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);
|
cx.replace_or_push_layer("dap-variables", popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
|
document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
|
||||||
editor::Action,
|
editor::Action,
|
||||||
|
graphics::Margin,
|
||||||
theme::Style,
|
theme::Style,
|
||||||
Document, View,
|
Document, View,
|
||||||
};
|
};
|
||||||
@ -744,7 +745,16 @@ pub fn code_action(cx: &mut Context) {
|
|||||||
});
|
});
|
||||||
picker.move_down(); // pre-select the first item
|
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);
|
compositor.replace_or_push("code-action", popup);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -574,7 +574,6 @@ fn set_line_ending(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn earlier(
|
fn earlier(
|
||||||
cx: &mut compositor::Context,
|
cx: &mut compositor::Context,
|
||||||
args: &[Cow<str>],
|
args: &[Cow<str>],
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::SavePoint,
|
document::SavePoint,
|
||||||
editor::CompleteAction,
|
editor::CompleteAction,
|
||||||
|
graphics::Margin,
|
||||||
theme::{Modifier, Style},
|
theme::{Modifier, Style},
|
||||||
ViewId,
|
ViewId,
|
||||||
};
|
};
|
||||||
@ -326,9 +327,18 @@ macro_rules! language_server {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let margin = if editor.menu_border() {
|
||||||
|
Margin::vertical(1)
|
||||||
|
} else {
|
||||||
|
Margin::none()
|
||||||
|
};
|
||||||
|
|
||||||
let popup = Popup::new(Self::ID, menu)
|
let popup = Popup::new(Self::ID, menu)
|
||||||
.with_scrollbar(false)
|
.with_scrollbar(false)
|
||||||
.ignore_escape_key(true);
|
.ignore_escape_key(true)
|
||||||
|
.margin(margin);
|
||||||
|
|
||||||
let mut completion = Self {
|
let mut completion = Self {
|
||||||
popup,
|
popup,
|
||||||
start_offset,
|
start_offset,
|
||||||
@ -569,6 +579,12 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
// clear area
|
// clear area
|
||||||
let background = cx.editor.theme.get("ui.popup");
|
let background = cx.editor.theme.get("ui.popup");
|
||||||
surface.clear_with(doc_area, background);
|
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);
|
markdown_doc.render(doc_area, surface, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,9 @@ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
|||||||
Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
|
Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
|
||||||
};
|
};
|
||||||
let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
|
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)
|
let sig_doc_para = Paragraph::new(sig_doc)
|
||||||
.wrap(Wrap { trim: false })
|
.wrap(Wrap { trim: false })
|
||||||
.scroll((cx.scroll.unwrap_or_default() as u16, 0));
|
.scroll((cx.scroll.unwrap_or_default() as u16, 0));
|
||||||
|
@ -7,11 +7,18 @@
|
|||||||
use helix_core::fuzzy::MATCHER;
|
use helix_core::fuzzy::MATCHER;
|
||||||
use nucleo::pattern::{Atom, AtomKind, CaseMatching};
|
use nucleo::pattern::{Atom, AtomKind, CaseMatching};
|
||||||
use nucleo::{Config, Utf32Str};
|
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};
|
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;
|
use tui::layout::Constraint;
|
||||||
|
|
||||||
pub trait Item: Sync + Send + 'static {
|
pub trait Item: Sync + Send + 'static {
|
||||||
@ -322,6 +329,15 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
let selected = theme.get("ui.menu.selected");
|
let selected = theme.get("ui.menu.selected");
|
||||||
surface.clear_with(area, style);
|
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 scroll = self.scroll;
|
||||||
|
|
||||||
let options: Vec<_> = self
|
let options: Vec<_> = self
|
||||||
@ -362,15 +378,19 @@ const fn div_ceil(a: usize, b: usize) -> usize {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(cursor) = self.cursor {
|
let render_borders = cx.editor.menu_border();
|
||||||
let offset_from_top = cursor - scroll;
|
|
||||||
let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
|
if !render_borders {
|
||||||
left.set_style(selected);
|
if let Some(cursor) = self.cursor {
|
||||||
let right = &mut surface[(
|
let offset_from_top = cursor - scroll;
|
||||||
area.right().saturating_sub(1),
|
let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
|
||||||
area.y + offset_from_top as u16,
|
left.set_style(selected);
|
||||||
)];
|
let right = &mut surface[(
|
||||||
right.set_style(selected);
|
area.right().saturating_sub(1),
|
||||||
|
area.y + offset_from_top as u16,
|
||||||
|
)];
|
||||||
|
right.set_style(selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let fits = len <= win_height;
|
let fits = len <= win_height;
|
||||||
@ -385,12 +405,13 @@ const fn div_ceil(a: usize, b: usize) -> usize {
|
|||||||
for i in 0..win_height {
|
for i in 0..win_height {
|
||||||
cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
|
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 {
|
if scroll_line <= i && i < scroll_line + scroll_height {
|
||||||
// Draw scroll thumb
|
// Draw scroll thumb
|
||||||
|
cell.set_symbol(half_block);
|
||||||
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
|
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
|
||||||
} else {
|
} else if !render_borders {
|
||||||
// Draw scroll track
|
// Draw scroll track
|
||||||
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
|
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
compositor::{Callback, Component, Context, Event, EventResult},
|
compositor::{Callback, Component, Context, Event, EventResult},
|
||||||
ctrl, key,
|
ctrl, key,
|
||||||
};
|
};
|
||||||
use tui::buffer::Buffer as Surface;
|
use tui::{
|
||||||
|
buffer::Buffer as Surface,
|
||||||
|
widgets::{Block, Borders, Widget},
|
||||||
|
};
|
||||||
|
|
||||||
use helix_core::Position;
|
use helix_core::Position;
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
@ -252,13 +255,29 @@ fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
let background = cx.editor.theme.get("ui.popup");
|
let background = cx.editor.theme.get("ui.popup");
|
||||||
surface.clear_with(area, background);
|
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(render_borders);
|
||||||
|
if render_borders {
|
||||||
|
Widget::render(Block::default().borders(Borders::ALL), area, surface);
|
||||||
|
}
|
||||||
|
|
||||||
self.contents.render(inner, surface, cx);
|
self.contents.render(inner, surface, cx);
|
||||||
|
|
||||||
// render scrollbar if contents do not fit
|
// render scrollbar if contents do not fit
|
||||||
if self.has_scrollbar {
|
if self.has_scrollbar {
|
||||||
let win_height = inner.height as usize;
|
let win_height = (inner.height as usize).saturating_sub(2 * border);
|
||||||
let len = self.child_size.1 as usize;
|
let len = (self.child_size.1 as usize).saturating_sub(2 * border);
|
||||||
let fits = len <= win_height;
|
let fits = len <= win_height;
|
||||||
let scroll = self.scroll;
|
let scroll = self.scroll;
|
||||||
let scroll_style = cx.editor.theme.get("ui.menu.scroll");
|
let scroll_style = cx.editor.theme.get("ui.menu.scroll");
|
||||||
@ -274,14 +293,15 @@ const fn div_ceil(a: usize, b: usize) -> usize {
|
|||||||
|
|
||||||
let mut cell;
|
let mut cell;
|
||||||
for i in 0..win_height {
|
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 {
|
if scroll_line <= i && i < scroll_line + scroll_height {
|
||||||
// Draw scroll thumb
|
// Draw scroll thumb
|
||||||
|
cell.set_symbol(half_block);
|
||||||
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
|
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
|
||||||
} else {
|
} else if !render_borders {
|
||||||
// Draw scroll track
|
// Draw scroll track
|
||||||
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
|
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,8 @@
|
|||||||
use helix_core::{
|
use helix_core::{
|
||||||
auto_pairs::AutoPairs,
|
auto_pairs::AutoPairs,
|
||||||
syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
|
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_dap as dap;
|
||||||
use helix_lsp::lsp;
|
use helix_lsp::lsp;
|
||||||
|
|
||||||
@ -291,6 +290,8 @@ pub struct Config {
|
|||||||
pub insert_final_newline: bool,
|
pub insert_final_newline: bool,
|
||||||
/// Enables smart tab
|
/// Enables smart tab
|
||||||
pub smart_tab: Option<SmartTabConfig>,
|
pub smart_tab: Option<SmartTabConfig>,
|
||||||
|
/// Draw border around popups.
|
||||||
|
pub popup_border: PopupBorderConfig,
|
||||||
/// Which indent heuristic to use when a new line is inserted
|
/// Which indent heuristic to use when a new line is inserted
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub indent_heuristic: IndentationHeuristic,
|
pub indent_heuristic: IndentationHeuristic,
|
||||||
@ -797,6 +798,15 @@ fn from(line_ending: LineEndingConfig) -> Self {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum PopupBorderConfig {
|
||||||
|
None,
|
||||||
|
All,
|
||||||
|
Popup,
|
||||||
|
Menu,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -844,6 +854,7 @@ fn default() -> Self {
|
|||||||
default_line_ending: LineEndingConfig::default(),
|
default_line_ending: LineEndingConfig::default(),
|
||||||
insert_final_newline: true,
|
insert_final_newline: true,
|
||||||
smart_tab: Some(SmartTabConfig::default()),
|
smart_tab: Some(SmartTabConfig::default()),
|
||||||
|
popup_border: PopupBorderConfig::None,
|
||||||
indent_heuristic: IndentationHeuristic::default(),
|
indent_heuristic: IndentationHeuristic::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1064,6 +1075,16 @@ pub fn new(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
pub fn apply_motion<F: Fn(&mut Self) + 'static>(&mut self, motion: F) {
|
||||||
motion(self);
|
motion(self);
|
||||||
self.last_motion = Some(Box::new(motion));
|
self.last_motion = Some(Box::new(motion));
|
||||||
|
Loading…
Reference in New Issue
Block a user