Skip to content

Commit

Permalink
introduce the notion of anchor in the prompt
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yo-main committed Nov 8, 2024
1 parent 307e89c commit eed56b5
Show file tree
Hide file tree
Showing 7 changed files with 924 additions and 20 deletions.
10 changes: 9 additions & 1 deletion helix-term/src/ui/picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,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)
}
Expand Down
51 changes: 32 additions & 19 deletions helix-term/src/ui/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct Prompt {
prompt: Cow<'static, str>,
line: String,
cursor: usize,
anchor: usize,
completion: Vec<Completion>,
selection: Option<usize>,
history_register: Option<char>,
Expand Down Expand Up @@ -80,6 +81,7 @@ impl Prompt {
prompt,
line: String::new(),
cursor: 0,
anchor: 0,
completion: Vec::new(),
selection: None,
history_register,
Expand Down Expand Up @@ -142,7 +144,7 @@ impl Prompt {

pub fn recalculate_completion(&mut self, editor: &Editor) {
self.exit_selection();
self.completion = (self.completion_fn)(editor, &self.line);
self.completion = (self.completion_fn)(editor, self.line.as_str());
}

/// Compute the cursor position after applying movement
Expand Down Expand Up @@ -268,7 +270,7 @@ impl Prompt {
self.recalculate_completion(editor);
}

pub fn move_cursor(&mut self, movement: Movement) {
fn move_cursor(&mut self, movement: Movement) {
let pos = self.eval_movement(movement);
self.cursor = pos
}
Expand Down Expand Up @@ -329,6 +331,7 @@ impl Prompt {
pub fn clear(&mut self, editor: &Editor) {
self.line.clear();
self.cursor = 0;

self.recalculate_completion(editor);
}

Expand All @@ -355,13 +358,12 @@ impl Prompt {
}
.min(end);

self.line = values.nth(index).unwrap().to_string();
self.set_line(values.nth(index).unwrap().to_string(), cx.editor);
// Appease the borrow checker.
drop(values);

self.history_pos = Some(index);

self.move_end();
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
self.recalculate_completion(cx.editor);
}
Expand Down Expand Up @@ -395,13 +397,14 @@ 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");
let selected_color = theme.get("ui.menu.selected");
let suggestion_color = theme.get("ui.text.inactive");
let background = theme.get("ui.background");

// completion

let max_len = self
Expand Down Expand Up @@ -500,7 +503,10 @@ 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);
let 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.
Expand All @@ -518,14 +524,21 @@ impl Prompt {
.into();
text.render(line_area, surface, cx);
} else {
surface.set_string_truncated(
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,
true,
true,
);
}
}
Expand Down Expand Up @@ -696,18 +709,18 @@ 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 area = area.clip_left(self.prompt.len() as u16);

let line_size = UnicodeWidthStr::width(&self.line[..]);
let mut upbound = area.left() as usize
+ UnicodeWidthStr::width(&self.line[self.anchor..self.cursor.max(self.anchor)]);

let upbound: usize = if line_size > (area.width - area.x) as usize {
let cursor_relative_position = (line_size - self.cursor).min(area.width as usize);
(area.width as usize - cursor_relative_position).max(area.left() as usize + 1)
} else {
area.left() as usize + UnicodeWidthStr::width(&self.line[..self.cursor])
};
if self.anchor > 0 {
upbound += 1;
}

if self.anchor > 0 && self.cursor > self.anchor && self.line.len() > self.cursor as usize {
upbound -= 1;
}

let line = area.height as usize - 1;
(
Expand Down
66 changes: 66 additions & 0 deletions helix-tui/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,72 @@ impl Buffer {
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
/// 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
Expand Down
Loading

0 comments on commit eed56b5

Please sign in to comment.