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

Optimize completion doc position. #691

Merged
merged 15 commits into from
Sep 8, 2021
Merged
45 changes: 31 additions & 14 deletions helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,7 @@ impl Component for Completion {
.cursor(doc.text().slice(..));
let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
- view.offset.row) as u16;

let mut doc = match &option.documentation {
let mut markdown_doc = match &option.documentation {
Some(lsp::Documentation::String(contents))
| Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
kind: lsp::MarkupKind::PlainText,
Expand Down Expand Up @@ -311,24 +310,42 @@ impl Component for Completion {
None => return,
};

let half = area.height / 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
let (popup_x, popup_y) = self.popup.get_rel_position(area, cx);
let (popup_width, _popup_height) = self.popup.get_size();
let mut width = area
.width
.saturating_sub(popup_x)
.saturating_sub(popup_width);
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 {
// -2 to subtract command line + statusline. a bit of a hack, because of splits.
area.height.saturating_sub(height).saturating_sub(2)
};
let half = area.height / 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
let background = cx.editor.theme.get("ui.popup");
surface.clear_with(area, background);
doc.render(area, surface, cx);
markdown_doc.render(area, surface, cx);
}
}
}
23 changes: 21 additions & 2 deletions helix-term/src/ui/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,27 @@ impl Component for Markdown {
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
let contents = parse(&self.contents, None, &self.config_loader);
let padding = 2;
let width = std::cmp::min(contents.width() as u16 + padding, viewport.0);
let height = std::cmp::min(contents.height() as u16 + padding, viewport.1);
let area_width = viewport.0 - padding;
let mut width = 0;
let mut height = padding;
for content in contents {
if height >= viewport.1 {
height = viewport.1;
break;
}
let mut content_width = content.width() as u16;
height += 1;
if content_width > area_width {
width = viewport.0;
height += content_width / area_width;
continue;
}
content_width += padding;
if content_width > width {
width = content_width
}
}

Some((width, height))
}
}
50 changes: 29 additions & 21 deletions helix-term/src/ui/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,33 @@ impl<T: Component> Popup<T> {
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) {
if direction {
self.scroll += offset;
Expand Down Expand Up @@ -108,29 +135,10 @@ impl<T: Component> Component for Popup<T> {
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
cx.scroll = Some(self.scroll);

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 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
}
let (rel_x, rel_y) = self.get_rel_position(viewport, cx);

// 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
let background = cx.editor.theme.get("ui.popup");
Expand Down