on master help sign in
Optimize completion doc position. (#691)
011f9aaCossonLeo
helix-term/src/ui/completion.rs+31−14
@@ -262,8 +262,7 @@ impl Component for Completion {
262 .cursor(doc.text().slice(..));
262 .cursor(doc.text().slice(..));
263 let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
263 let cursor_pos = (helix_core::coords_at_pos(doc.text().slice(..), cursor_pos).row
264 - view.offset.row) as u16;
264 - view.offset.row) as u16;
265−
266− let mut doc = match &option.documentation {
265+ let mut markdown_doc = match &option.documentation {
267 Some(lsp::Documentation::String(contents))
266 Some(lsp::Documentation::String(contents))
268 | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
267 | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
269 kind: lsp::MarkupKind::PlainText,
268 kind: lsp::MarkupKind::PlainText,
@@ -311,24 +310,42 @@ impl Component for Completion {
311 None => return,
310 None => return,
312 };
311 };
313
312
314− let half = area.height / 2;
313+ let (popup_x, popup_y) = self.popup.get_rel_position(area, cx);
315− let height = 15.min(half);
314+ let (popup_width, _popup_height) = self.popup.get_size();
316− // we want to make sure the cursor is visible (not hidden behind the documentation)
315+ let mut width = area
317− let y = if cursor_pos + area.y
316+ .width
318− >= (cx.editor.tree.area().height - height - 2/* statusline + commandline */)
317+ .saturating_sub(popup_x)
319− {
318+ .saturating_sub(popup_width);
320− 0
319+ let area = if width > 30 {
320+ let mut height = area.height.saturating_sub(popup_y);
321+ let x = popup_x + popup_width;
322+ let y = popup_y;
323+
324+ if let Some((rel_width, rel_height)) = markdown_doc.required_size((width, height)) {
325+ width = rel_width;
326+ height = rel_height;
327+ }
328+ Rect::new(x, y, width, height)
321 } else {
329 } else {
330+ let half = area.height / 2;
331+ let height = 15.min(half);
332+ // we want to make sure the cursor is visible (not hidden behind the documentation)
333+ let y = if cursor_pos + area.y
334+ >= (cx.editor.tree.area().height - height - 2/* statusline + commandline */)
335+ {
336+ 0
337+ } else {
322− // -2 to subtract command line + statusline. a bit of a hack, because of splits.
338+ // -2 to subtract command line + statusline. a bit of a hack, because of splits.
323− area.height.saturating_sub(height).saturating_sub(2)
339+ area.height.saturating_sub(height).saturating_sub(2)
324− };
340+ };
325
341
326− let area = Rect::new(0, y, area.width, height);
342+ Rect::new(0, y, area.width, height)
343+ };
327
344
328 // clear area
345 // clear area
329 let background = cx.editor.theme.get("ui.popup");
346 let background = cx.editor.theme.get("ui.popup");
330 surface.clear_with(area, background);
347 surface.clear_with(area, background);
331− doc.render(area, surface, cx);
348+ markdown_doc.render(area, surface, cx);
332 }
349 }
333 }
350 }
334}
351}
helix-term/src/ui/markdown.rs+24−4
@@ -215,10 +215,30 @@ impl Component for Markdown {
215 }
215 }
216
216
217 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
217 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
218− let contents = parse(&self.contents, None, &self.config_loader);
219 let padding = 2;
218 let padding = 2;
220− let width = std::cmp::min(contents.width() as u16 + padding, viewport.0);
219+ if padding >= viewport.1 || padding >= viewport.0 {
221− let height = std::cmp::min(contents.height() as u16 + padding, viewport.1);
220+ return None;
221+ }
222+ let contents = parse(&self.contents, None, &self.config_loader);
223+ let max_text_width = (viewport.0 - padding).min(120);
224+ let mut text_width = 0;
225+ let mut height = padding;
226+ for content in contents {
227+ height += 1;
228+ let content_width = content.width() as u16;
229+ if content_width > max_text_width {
230+ text_width = max_text_width;
231+ height += content_width / max_text_width;
232+ } else if content_width > text_width {
233+ text_width = content_width;
234+ }
235+
236+ if height >= viewport.1 {
237+ height = viewport.1;
238+ break;
239+ }
240+ }
241+
222− Some((width, height))
242+ Some((text_width + padding, height))
223 }
243 }
224}
244}
helix-term/src/ui/popup.rs+29−21
@@ -31,6 +31,33 @@ impl<T: Component> Popup<T> {
31 self.position = pos;
31 self.position = pos;
32 }
32 }
33
33
34+ pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) {
35+ let position = self
36+ .position
37+ .get_or_insert_with(|| cx.editor.cursor().0.unwrap_or_default());
38+
39+ let (width, height) = self.size;
40+
41+ // -- make sure frame doesn't stick out of bounds
42+ let mut rel_x = position.col as u16;
43+ let rel_y = position.row as u16;
44+ if viewport.width <= rel_x + width {
45+ rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
46+ };
47+
48+ // TODO: be able to specify orientation preference. We want above for most popups, below
49+ // for menus/autocomplete.
50+ if height <= rel_y {
51+ (rel_x, rel_y.saturating_sub(height)) // position above point
52+ } else {
53+ (rel_x, rel_y + 1) // position below point
54+ }
55+ }
56+
57+ pub fn get_size(&self) -> (u16, u16) {
58+ (self.size.0, self.size.1)
59+ }
60+
34 pub fn scroll(&mut self, offset: usize, direction: bool) {
61 pub fn scroll(&mut self, offset: usize, direction: bool) {
35 if direction {
62 if direction {
36 self.scroll += offset;
63 self.scroll += offset;
@@ -108,29 +135,10 @@ impl<T: Component> Component for Popup<T> {
108 fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
135 fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
109 cx.scroll = Some(self.scroll);
136 cx.scroll = Some(self.scroll);
110
137
111− let position = self
138+ let (rel_x, rel_y) = self.get_rel_position(viewport, cx);
112− .position
113− .get_or_insert_with(|| cx.editor.cursor().0.unwrap_or_default());
114−
115− let (width, height) = self.size;
116−
117− // -- make sure frame doesn't stick out of bounds
118− let mut rel_x = position.col as u16;
119− let mut rel_y = position.row as u16;
120− if viewport.width <= rel_x + width {
121− rel_x = rel_x.saturating_sub((rel_x + width).saturating_sub(viewport.width));
122− };
123−
124− // TODO: be able to specify orientation preference. We want above for most popups, below
125− // for menus/autocomplete.
126− if height <= rel_y {
127− rel_y = rel_y.saturating_sub(height) // position above point
128− } else {
129− rel_y += 1 // position below point
130− }
131
139
132 // clip to viewport
140 // clip to viewport
133− let area = viewport.intersection(Rect::new(rel_x, rel_y, width, height));
141+ let area = viewport.intersection(Rect::new(rel_x, rel_y, self.size.0, self.size.1));
134
142
135 // clear area
143 // clear area
136 let background = cx.editor.theme.get("ui.popup");
144 let background = cx.editor.theme.get("ui.popup");