mirror of
https://github.com/helix-editor/helix.git
synced 2025-04-03 02:47:45 +03:00
fix: better display of prompts on long inputs (#12036)
This commit is contained in:
parent
1afa63d457
commit
8af33108f6
3 changed files with 140 additions and 16 deletions
|
@ -649,10 +649,6 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
|||
|
||||
// -- Render the input bar:
|
||||
|
||||
let area = inner.clip_left(1).with_height(1);
|
||||
// render the prompt first since it will clear its background
|
||||
self.prompt.render(area, surface, cx);
|
||||
|
||||
let count = format!(
|
||||
"{}{}/{}",
|
||||
if status.running || self.matcher.active_injectors() > 0 {
|
||||
|
@ -663,6 +659,13 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
|||
snapshot.matched_item_count(),
|
||||
snapshot.item_count(),
|
||||
);
|
||||
|
||||
let area = inner.clip_left(1).with_height(1);
|
||||
let line_area = area.clip_right(count.len() as u16 + 1);
|
||||
|
||||
// render the prompt first since it will clear its background
|
||||
self.prompt.render(line_area, surface, cx);
|
||||
|
||||
surface.set_stringn(
|
||||
(area.x + area.width).saturating_sub(count.len() as u16 + 1),
|
||||
area.y,
|
||||
|
@ -1073,7 +1076,15 @@ impl<I: 'static + Send + Sync, D: 'static + Send + Sync> Component for Picker<I,
|
|||
let inner = block.inner(area);
|
||||
|
||||
// prompt area
|
||||
let area = inner.clip_left(1).with_height(1);
|
||||
let render_preview =
|
||||
self.show_preview && self.file_fn.is_some() && area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
|
||||
|
||||
let picker_width = if render_preview {
|
||||
area.width / 2
|
||||
} else {
|
||||
area.width
|
||||
};
|
||||
let area = inner.clip_left(1).with_height(1).with_width(picker_width);
|
||||
|
||||
self.prompt.cursor(area, editor)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,12 @@ pub struct Prompt {
|
|||
prompt: Cow<'static, str>,
|
||||
line: String,
|
||||
cursor: usize,
|
||||
// Fields used for Component callbacks and rendering:
|
||||
line_area: Rect,
|
||||
anchor: usize,
|
||||
truncate_start: bool,
|
||||
truncate_end: bool,
|
||||
// ---
|
||||
completion: Vec<Completion>,
|
||||
selection: Option<usize>,
|
||||
history_register: Option<char>,
|
||||
|
@ -82,6 +88,10 @@ impl Prompt {
|
|||
prompt,
|
||||
line: String::new(),
|
||||
cursor: 0,
|
||||
line_area: Rect::default(),
|
||||
anchor: 0,
|
||||
truncate_start: false,
|
||||
truncate_end: false,
|
||||
completion: Vec::new(),
|
||||
selection: None,
|
||||
history_register,
|
||||
|
@ -389,7 +399,7 @@ impl Prompt {
|
|||
const BASE_WIDTH: u16 = 30;
|
||||
|
||||
impl Prompt {
|
||||
pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||
pub fn render_prompt(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||
let theme = &cx.editor.theme;
|
||||
let prompt_color = theme.get("ui.text");
|
||||
let completion_color = theme.get("ui.menu");
|
||||
|
@ -499,11 +509,20 @@ impl Prompt {
|
|||
// render buffer text
|
||||
surface.set_string(area.x, area.y + line, &self.prompt, prompt_color);
|
||||
|
||||
let line_area = area.clip_left(self.prompt.len() as u16).clip_top(line);
|
||||
self.line_area = area
|
||||
.clip_left(self.prompt.len() as u16)
|
||||
.clip_top(line)
|
||||
.clip_right(2);
|
||||
|
||||
if self.line.is_empty() {
|
||||
// Show the most recently entered value as a suggestion.
|
||||
if let Some(suggestion) = self.first_history_completion(cx.editor) {
|
||||
surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
|
||||
surface.set_string(
|
||||
self.line_area.x,
|
||||
self.line_area.y,
|
||||
suggestion,
|
||||
suggestion_color,
|
||||
);
|
||||
}
|
||||
} else if let Some((language, loader)) = self.language.as_ref() {
|
||||
let mut text: ui::text::Text = crate::ui::markdown::highlighted_code_block(
|
||||
|
@ -514,9 +533,34 @@ impl Prompt {
|
|||
None,
|
||||
)
|
||||
.into();
|
||||
text.render(line_area, surface, cx);
|
||||
text.render(self.line_area, surface, cx);
|
||||
} else {
|
||||
surface.set_string(line_area.x, line_area.y, self.line.clone(), prompt_color);
|
||||
if self.line.len() < self.line_area.width as usize {
|
||||
self.anchor = 0;
|
||||
} else if self.cursor < self.anchor {
|
||||
self.anchor = self.cursor;
|
||||
} else if self.cursor - self.anchor > self.line_area.width as usize {
|
||||
self.anchor = self.cursor - self.line_area.width as usize;
|
||||
}
|
||||
|
||||
self.truncate_start = self.anchor > 0;
|
||||
self.truncate_end = self.line.len() - self.anchor > self.line_area.width as usize;
|
||||
|
||||
// if we keep inserting characters just before the end elipsis, we move the anchor
|
||||
// so that those new characters are displayed
|
||||
if self.truncate_end && self.cursor - self.anchor >= self.line_area.width as usize {
|
||||
self.anchor += 1;
|
||||
}
|
||||
|
||||
surface.set_string_anchored(
|
||||
self.line_area.x,
|
||||
self.line_area.y,
|
||||
self.truncate_start,
|
||||
self.truncate_end,
|
||||
&self.line.as_str()[self.anchor..],
|
||||
self.line_area.width as usize - self.truncate_end as usize,
|
||||
|_| prompt_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -686,14 +730,26 @@ impl Component for Prompt {
|
|||
}
|
||||
|
||||
fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
|
||||
let area = area
|
||||
.clip_left(self.prompt.len() as u16)
|
||||
.clip_right(if self.prompt.len() > 0 { 0 } else { 2 });
|
||||
|
||||
let mut col = area.left() as usize
|
||||
+ UnicodeWidthStr::width(&self.line[self.anchor..self.cursor.max(self.anchor)]);
|
||||
|
||||
// ensure the cursor does not go beyond elipses
|
||||
if self.truncate_end && self.cursor - self.anchor >= self.line_area.width as usize {
|
||||
col -= 1;
|
||||
}
|
||||
|
||||
if self.truncate_start && self.cursor == self.anchor {
|
||||
col += 1;
|
||||
}
|
||||
|
||||
let line = area.height as usize - 1;
|
||||
|
||||
(
|
||||
Some(Position::new(
|
||||
area.y as usize + line,
|
||||
area.x as usize
|
||||
+ self.prompt.len()
|
||||
+ UnicodeWidthStr::width(&self.line[..self.cursor]),
|
||||
)),
|
||||
Some(Position::new(area.y as usize + line, col)),
|
||||
editor.config().cursor_shape.from_mode(Mode::Insert),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -306,6 +306,63 @@ impl Buffer {
|
|||
self.set_string_truncated_at_end(x, y, string.as_ref(), width, style)
|
||||
}
|
||||
|
||||
/// Print at most the first `width` characters of a string if enough space is available
|
||||
/// until the end of the line.
|
||||
/// If `ellipsis` is true appends a `…` at the end of truncated lines.
|
||||
/// If `truncate_start` is `true`, adds a `…` at the beginning of truncated lines.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn set_string_anchored(
|
||||
&mut self,
|
||||
x: u16,
|
||||
y: u16,
|
||||
truncate_start: bool,
|
||||
truncate_end: bool,
|
||||
string: &str,
|
||||
width: usize,
|
||||
style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style
|
||||
) -> (u16, u16) {
|
||||
// prevent panic if out of range
|
||||
if !self.in_bounds(x, y) || width == 0 {
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
let max_offset = min(
|
||||
self.area.right() as usize - 1,
|
||||
width.saturating_add(x as usize),
|
||||
);
|
||||
let mut start_index = self.index_of(x, y);
|
||||
let mut end_index = self.index_of(max_offset as u16, y);
|
||||
|
||||
if truncate_end {
|
||||
self.content[end_index].set_symbol("…");
|
||||
end_index -= 1;
|
||||
}
|
||||
|
||||
if truncate_start {
|
||||
self.content[start_index].set_symbol("…");
|
||||
start_index += 1;
|
||||
}
|
||||
|
||||
let graphemes = string.grapheme_indices(true);
|
||||
|
||||
for (byte_offset, s) in graphemes.skip(truncate_start as usize) {
|
||||
if start_index > end_index {
|
||||
break;
|
||||
}
|
||||
|
||||
self.content[start_index].set_symbol(s);
|
||||
self.content[start_index].set_style(style(byte_offset));
|
||||
|
||||
for i in start_index + 1..end_index {
|
||||
self.content[i].reset();
|
||||
}
|
||||
|
||||
start_index += s.width();
|
||||
}
|
||||
|
||||
(x, y)
|
||||
}
|
||||
|
||||
/// Print at most the first `width` characters of a string if enough space is available
|
||||
/// until the end of the line. If `ellipsis` is true appends a `…` at the end of
|
||||
/// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue