mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-24 18:36:18 +04:00
Fix: scope prompt line to its are
This introduces the notion of anchor in the prompt. The prompt anchor represents the position of the left-side of the input. This anchor is needed so that we are able to correctly display the prompt input when its length is longer than the one of the prompt area itself
This commit is contained in:
parent
101a74bf6e
commit
1bdecd360d
@ -649,10 +649,6 @@ fn render_picker(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
|||||||
|
|
||||||
// -- Render the input bar:
|
// -- 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!(
|
let count = format!(
|
||||||
"{}{}/{}",
|
"{}{}/{}",
|
||||||
if status.running || self.matcher.active_injectors() > 0 {
|
if status.running || self.matcher.active_injectors() > 0 {
|
||||||
@ -663,6 +659,13 @@ fn render_picker(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
|||||||
snapshot.matched_item_count(),
|
snapshot.matched_item_count(),
|
||||||
snapshot.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(
|
surface.set_stringn(
|
||||||
(area.x + area.width).saturating_sub(count.len() as u16 + 1),
|
(area.x + area.width).saturating_sub(count.len() as u16 + 1),
|
||||||
area.y,
|
area.y,
|
||||||
@ -1073,7 +1076,15 @@ fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind)
|
|||||||
let inner = block.inner(area);
|
let inner = block.inner(area);
|
||||||
|
|
||||||
// prompt 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)
|
self.prompt.cursor(area, editor)
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ pub struct Prompt {
|
|||||||
prompt: Cow<'static, str>,
|
prompt: Cow<'static, str>,
|
||||||
line: String,
|
line: String,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
|
anchor: usize,
|
||||||
completion: Vec<Completion>,
|
completion: Vec<Completion>,
|
||||||
selection: Option<usize>,
|
selection: Option<usize>,
|
||||||
history_register: Option<char>,
|
history_register: Option<char>,
|
||||||
@ -80,6 +81,7 @@ pub fn new(
|
|||||||
prompt,
|
prompt,
|
||||||
line: String::new(),
|
line: String::new(),
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
|
anchor: 0,
|
||||||
completion: Vec::new(),
|
completion: Vec::new(),
|
||||||
selection: None,
|
selection: None,
|
||||||
history_register,
|
history_register,
|
||||||
@ -329,6 +331,7 @@ pub fn kill_to_end_of_line(&mut self, editor: &Editor) {
|
|||||||
pub fn clear(&mut self, editor: &Editor) {
|
pub fn clear(&mut self, editor: &Editor) {
|
||||||
self.line.clear();
|
self.line.clear();
|
||||||
self.cursor = 0;
|
self.cursor = 0;
|
||||||
|
|
||||||
self.recalculate_completion(editor);
|
self.recalculate_completion(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,13 +398,14 @@ pub fn exit_selection(&mut self) {
|
|||||||
const BASE_WIDTH: u16 = 30;
|
const BASE_WIDTH: u16 = 30;
|
||||||
|
|
||||||
impl Prompt {
|
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 theme = &cx.editor.theme;
|
||||||
let prompt_color = theme.get("ui.text");
|
let prompt_color = theme.get("ui.text");
|
||||||
let completion_color = theme.get("ui.menu");
|
let completion_color = theme.get("ui.menu");
|
||||||
let selected_color = theme.get("ui.menu.selected");
|
let selected_color = theme.get("ui.menu.selected");
|
||||||
let suggestion_color = theme.get("ui.text.inactive");
|
let suggestion_color = theme.get("ui.text.inactive");
|
||||||
let background = theme.get("ui.background");
|
let background = theme.get("ui.background");
|
||||||
|
|
||||||
// completion
|
// completion
|
||||||
|
|
||||||
let max_len = self
|
let max_len = self
|
||||||
@ -500,7 +504,11 @@ pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
|||||||
// render buffer text
|
// render buffer text
|
||||||
surface.set_string(area.x, area.y + line, &self.prompt, prompt_color);
|
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);
|
let line_area = area
|
||||||
|
.clip_left(self.prompt.len() as u16)
|
||||||
|
.clip_top(line)
|
||||||
|
.clip_right(2);
|
||||||
|
|
||||||
if self.line.is_empty() {
|
if self.line.is_empty() {
|
||||||
// Show the most recently entered value as a suggestion.
|
// Show the most recently entered value as a suggestion.
|
||||||
if let Some(suggestion) = self.first_history_completion(cx.editor) {
|
if let Some(suggestion) = self.first_history_completion(cx.editor) {
|
||||||
@ -517,7 +525,22 @@ pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
|||||||
.into();
|
.into();
|
||||||
text.render(line_area, surface, cx);
|
text.render(line_area, surface, cx);
|
||||||
} else {
|
} else {
|
||||||
surface.set_string(line_area.x, line_area.y, self.line.clone(), prompt_color);
|
if self.line.len() < line_area.width as usize {
|
||||||
|
self.anchor = 0;
|
||||||
|
} else if self.cursor < self.anchor {
|
||||||
|
self.anchor = self.cursor;
|
||||||
|
} else if self.cursor - self.anchor > line_area.width as usize {
|
||||||
|
self.anchor = self.cursor - line_area.width as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
surface.set_string_anchored(
|
||||||
|
line_area.x,
|
||||||
|
line_area.y,
|
||||||
|
self.anchor,
|
||||||
|
self.line.as_str(),
|
||||||
|
line_area.width as usize,
|
||||||
|
|_| prompt_color,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -687,14 +710,24 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
|
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 upbound = area.left() as usize
|
||||||
|
+ UnicodeWidthStr::width(&self.line[self.anchor..self.cursor.max(self.anchor)]);
|
||||||
|
|
||||||
|
if self.anchor > 0 {
|
||||||
|
upbound += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.anchor > 0 && self.cursor > self.anchor && self.line.len() > self.cursor {
|
||||||
|
upbound -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
let line = area.height as usize - 1;
|
let line = area.height as usize - 1;
|
||||||
(
|
(
|
||||||
Some(Position::new(
|
Some(Position::new(area.y as usize + line, upbound)),
|
||||||
area.y as usize + line,
|
|
||||||
area.x as usize
|
|
||||||
+ self.prompt.len()
|
|
||||||
+ UnicodeWidthStr::width(&self.line[..self.cursor]),
|
|
||||||
)),
|
|
||||||
editor.config().cursor_shape.from_mode(Mode::Insert),
|
editor.config().cursor_shape.from_mode(Mode::Insert),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -306,6 +306,72 @@ pub fn set_stringn<S>(
|
|||||||
self.set_string_truncated_at_end(x, y, string.as_ref(), width, style)
|
self.set_string_truncated_at_end(x, y, string.as_ref(), width, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allow to display a big string in a reduce area.
|
||||||
|
/// A long string will be truncated, with the first character to display starting at the `anchor`.
|
||||||
|
/// The start and end of the string are going to be replaced with an ellipsis (`…`), if needed.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn set_string_anchored(
|
||||||
|
&mut self,
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
anchor: usize,
|
||||||
|
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
|
||||||
|
let mut anchor = anchor;
|
||||||
|
if !self.in_bounds(x, y) || width == 0 {
|
||||||
|
return (x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ellipsis_start, ellipsis_end) = if string.len() <= width {
|
||||||
|
(false, false)
|
||||||
|
} else {
|
||||||
|
(anchor > 0, string.len() - anchor > width)
|
||||||
|
};
|
||||||
|
|
||||||
|
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 ellipsis_end {
|
||||||
|
self.content[end_index].set_symbol("…");
|
||||||
|
end_index -= 1;
|
||||||
|
|
||||||
|
if ellipsis_start {
|
||||||
|
anchor += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ellipsis_start {
|
||||||
|
self.content[start_index].set_symbol("…");
|
||||||
|
start_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let graphemes = string.grapheme_indices(true);
|
||||||
|
|
||||||
|
for (byte_offset, s) in graphemes.skip(anchor) {
|
||||||
|
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
|
/// 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
|
/// 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
|
/// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string
|
||||||
|
Loading…
Reference in New Issue
Block a user