Optimize completion doc position. (#691)

* optimize completion doc's render

* optimize completion doc's render

* optimize completion doc position

* cargo fmt

* fix panic

* use saturating_sub

* fixs

* fix clippy

* limit completion doc max width 120
pull/732/head
CossonLeo 3 years ago committed by GitHub
parent 2ce87968cd
commit 011f9aa47f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -262,8 +262,7 @@ impl Component for Completion {
.cursor(doc.text().slice(..)); .cursor(doc.text().slice(..));
let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
- view.offset.row) as u16; - view.offset.row) as u16;
let mut markdown_doc = match &option.documentation {
let mut doc = match &option.documentation {
Some(lsp::Documentation::String(contents)) Some(lsp::Documentation::String(contents))
| Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
kind: lsp::MarkupKind::PlainText, kind: lsp::MarkupKind::PlainText,
@ -311,24 +310,42 @@ impl Component for Completion {
None => return, None => return,
}; };
let half = area.height / 2; let (popup_x, popup_y) = self.popup.get_rel_position(area, cx);
let height = 15.min(half); let (popup_width, _popup_height) = self.popup.get_size();
// we want to make sure the cursor is visible (not hidden behind the documentation) let mut width = area
let y = if cursor_pos + area.y .width
>= (cx.editor.tree.area().height - height - 2/* statusline + commandline */) .saturating_sub(popup_x)
{ .saturating_sub(popup_width);
0 let area = if width > 30 {
let mut height = area.height.saturating_sub(popup_y);
let x = popup_x + popup_width;
let y = popup_y;
if let Some((rel_width, rel_height)) = markdown_doc.required_size((width, height)) {
width = rel_width;
height = rel_height;
}
Rect::new(x, y, width, height)
} else { } else {
// -2 to subtract command line + statusline. a bit of a hack, because of splits. let half = area.height / 2;
area.height.saturating_sub(height).saturating_sub(2) let height = 15.min(half);
}; // we want to make sure the cursor is visible (not hidden behind the documentation)
let y = if cursor_pos + area.y
>= (cx.editor.tree.area().height - height - 2/* statusline + commandline */)
{
0
} else {
// -2 to subtract command line + statusline. a bit of a hack, because of splits.
area.height.saturating_sub(height).saturating_sub(2)
};
let area = Rect::new(0, y, area.width, height); Rect::new(0, y, area.width, height)
};
// clear area // clear area
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);
doc.render(area, surface, cx); markdown_doc.render(area, surface, cx);
} }
} }
} }

@ -215,10 +215,30 @@ impl Component for Markdown {
} }
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
let contents = parse(&self.contents, None, &self.config_loader);
let padding = 2; let padding = 2;
let width = std::cmp::min(contents.width() as u16 + padding, viewport.0); if padding >= viewport.1 || padding >= viewport.0 {
let height = std::cmp::min(contents.height() as u16 + padding, viewport.1); return None;
Some((width, height)) }
let contents = parse(&self.contents, None, &self.config_loader);
let max_text_width = (viewport.0 - padding).min(120);
let mut text_width = 0;
let mut height = padding;
for content in contents {
height += 1;
let content_width = content.width() as u16;
if content_width > max_text_width {
text_width = max_text_width;
height += content_width / max_text_width;
} else if content_width > text_width {
text_width = content_width;
}
if height >= viewport.1 {
height = viewport.1;
break;
}
}
Some((text_width + padding, height))
} }
} }

@ -31,6 +31,33 @@ impl<T: Component> Popup<T> {
self.position = pos; self.position = pos;
} }
pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) {
let position = self
.position
.get_or_insert_with(|| cx.editor.cursor().0.unwrap_or_default());
let (width, height) = self.size;
// -- make sure frame doesn't stick out of bounds
let mut rel_x = position.col as u16;
let rel_y = position.row as u16;
if viewport.width <= rel_x + width {
rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
};
// TODO: be able to specify orientation preference. We want above for most popups, below
// for menus/autocomplete.
if height <= rel_y {
(rel_x, rel_y.saturating_sub(height)) // position above point
} else {
(rel_x, rel_y + 1) // position below point
}
}
pub fn get_size(&self) -> (u16, u16) {
(self.size.0, self.size.1)
}
pub fn scroll(&mut self, offset: usize, direction: bool) { pub fn scroll(&mut self, offset: usize, direction: bool) {
if direction { if direction {
self.scroll += offset; self.scroll += offset;
@ -108,29 +135,10 @@ impl<T: Component> Component for Popup<T> {
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
cx.scroll = Some(self.scroll); cx.scroll = Some(self.scroll);
let position = self let (rel_x, rel_y) = self.get_rel_position(viewport, cx);
.position
.get_or_insert_with(|| cx.editor.cursor().0.unwrap_or_default());
let (width, height) = self.size;
// -- make sure frame doesn't stick out of bounds
let mut rel_x = position.col as u16;
let mut rel_y = position.row as u16;
if viewport.width <= rel_x + width {
rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
};
// TODO: be able to specify orientation preference. We want above for most popups, below
// for menus/autocomplete.
if height <= rel_y {
rel_y = rel_y.saturating_sub(height) // position above point
} else {
rel_y += 1 // position below point
}
// clip to viewport // clip to viewport
let area = viewport.intersection(Rect::new(rel_x, rel_y, width, height)); let area = viewport.intersection(Rect::new(rel_x, rel_y, self.size.0, self.size.1));
// clear area // clear area
let background = cx.editor.theme.get("ui.popup"); let background = cx.editor.theme.get("ui.popup");

Loading…
Cancel
Save