Deduplicate layout computation logic

This commit is contained in:
Johannes Altmanninger 2024-12-25 14:32:17 +01:00
parent 8ae12973df
commit 8bb6597b9b
3 changed files with 59 additions and 74 deletions

View file

@ -1,3 +1,5 @@
use std::ops::Range;
#[allow(unused_imports)]
use crate::future::IsSomeAnd;
use crate::highlight::HighlightSpec;
@ -142,21 +144,8 @@ impl EditableLine {
self.text.char_at(idx)
}
pub fn line_at_cursor(&self) -> &wstr {
let start = self.text[0..self.position()]
.as_char_slice()
.iter()
.rposition(|&c| c == '\n')
.map(|newline| newline + 1)
.unwrap_or(0);
let end = self.text[self.position()..]
.as_char_slice()
.iter()
.position(|&c| c == '\n')
.map(|pos| self.position() + pos)
.unwrap_or(self.len());
// Remove any traililng newline
self.text[start..end].trim_matches('\n')
pub fn offset_to_line(&self, offset: usize) -> usize {
self.text[0..offset].chars().filter(|&c| c == '\n').count()
}
/// Modify the commandline according to @edit. Most modifications to the
@ -352,3 +341,27 @@ fn cursor_position_after_edit(edit: &Edit) -> usize {
let removed = chars_deleted_left_of_cursor(edit);
cursor.saturating_sub(removed)
}
fn range_of_line_at_cursor(buffer: &wstr, cursor: usize) -> Range<usize> {
let start = buffer[0..cursor]
.as_char_slice()
.iter()
.rposition(|&c| c == '\n')
.map(|newline| newline + 1)
.unwrap_or(0);
let mut end = buffer[cursor..]
.as_char_slice()
.iter()
.position(|&c| c == '\n')
.map(|pos| cursor + pos)
.unwrap_or(buffer.len());
// Remove any trailing newline
if end != start && buffer.char_at(end - 1) == '\n' {
end -= 1;
}
start..end
}
pub fn line_at_cursor(buffer: &wstr, cursor: usize) -> &wstr {
&buffer[range_of_line_at_cursor(buffer, cursor)]
}

View file

@ -58,6 +58,7 @@ use crate::complete::{
complete, complete_load, sort_and_prioritize, CompleteFlags, Completion, CompletionList,
CompletionRequestOptions,
};
use crate::editable_line::line_at_cursor;
use crate::editable_line::{Edit, EditableLine};
use crate::env::{EnvMode, Environment, Statuses};
use crate::exec::exec_subshell;
@ -2791,7 +2792,7 @@ impl<'a> Reader<'a> {
let needle = if !cmdsub.contains('\n') {
cmdsub
} else {
self.command_line.line_at_cursor()
line_at_cursor(self.command_line.text(), self.command_line.position())
};
parse_util_escape_wildcards(needle)
} else {

View file

@ -7,6 +7,7 @@
//! The current implementation is less smart than ncurses allows and can not for example move blocks
//! of text around to handle text insertion.
use crate::editable_line::line_at_cursor;
use crate::key::ViewportPosition;
use crate::pager::{PageRendering, Pager, PAGER_MIN_HEIGHT};
use crate::FLOG;
@ -1902,17 +1903,11 @@ fn compute_layout(
assert!(left_prompt_width + right_prompt_width <= screen_width);
// Get the width of the first line, and if there is more than one line.
let mut multiline = false;
let mut first_line_width = 0;
for c in commandline.chars() {
if c == '\n' {
multiline = true;
break;
} else {
first_line_width += wcwidth_rendered_min_0(c);
}
}
let first_command_line_width = first_line_width;
let multiline = commandline.contains('\n');
let first_command_line_width: usize = line_at_cursor(commandline, 0)
.chars()
.map(wcwidth_rendered_min_0)
.sum();
// If we have more than one line, ensure we have no autosuggestion.
let mut autosuggestion = autosuggestion_str;
@ -1922,7 +1917,7 @@ fn compute_layout(
autosuggestion = L!("");
} else {
autosuggest_truncated_widths.reserve(1 + autosuggestion_str.len());
for c in autosuggestion.chars() {
for c in autosuggestion_str.chars() {
autosuggest_truncated_widths.push(autosuggest_total_width);
autosuggest_total_width += wcwidth_rendered_min_0(c);
}
@ -1947,65 +1942,41 @@ fn compute_layout(
// prompt will wrap to the next line. This means that we can't go back to the line that we were
// on, and things turn to chaos very quickly.
// Case 1
let calculated_width =
left_prompt_width + right_prompt_width + first_command_line_width + autosuggest_total_width;
if calculated_width <= screen_width {
result.left_prompt = left_prompt;
result.left_prompt_space = left_prompt_width;
result.right_prompt = right_prompt;
result.autosuggestion = autosuggestion.to_owned();
return result;
}
let truncated_autosuggestion = |right_prompt_width: usize| {
let width = left_prompt_width + right_prompt_width + first_command_line_width;
// Need at least two characters to show an autosuggestion.
let available_autosuggest_space = screen_width - width;
let mut result = WString::new();
if available_autosuggest_space > autosuggest_total_width {
result = autosuggestion.to_owned();
} else if autosuggest_total_width > 0 && available_autosuggest_space > 2 {
let truncation_offset = truncation_offset_for_width(
&autosuggest_truncated_widths,
available_autosuggest_space - 2,
);
result = autosuggestion[..truncation_offset].to_owned();
result.push(get_ellipsis_char());
}
result
};
// Case 2. Note that we require strict inequality so that there's always at least one space
// between the left edge and the rprompt.
// Case 1 and 2. Note that we require strict inequality so that there's always at least
// one space between the left edge and the rprompt.
let calculated_width = left_prompt_width + right_prompt_width + first_command_line_width;
if calculated_width <= screen_width {
result.left_prompt = left_prompt;
result.left_prompt_space = left_prompt_width;
result.right_prompt = right_prompt;
// Need at least two characters to show an autosuggestion.
let available_autosuggest_space =
screen_width - (left_prompt_width + right_prompt_width + first_command_line_width);
if autosuggest_total_width > 0 && available_autosuggest_space > 2 {
let truncation_offset = truncation_offset_for_width(
&autosuggest_truncated_widths,
available_autosuggest_space - 2,
);
result.autosuggestion = autosuggestion[..truncation_offset].to_owned();
result.autosuggestion.push(get_ellipsis_char());
}
result.autosuggestion = truncated_autosuggestion(right_prompt_width);
return result;
}
// Case 3
let calculated_width = left_prompt_width + first_command_line_width + autosuggest_total_width;
if calculated_width <= screen_width {
result.left_prompt = left_prompt;
result.left_prompt_space = left_prompt_width;
result.autosuggestion = autosuggestion.to_owned();
return result;
}
// Case 4
// Case 3 and 4
let calculated_width = left_prompt_width + first_command_line_width;
if calculated_width <= screen_width {
result.left_prompt = left_prompt;
result.left_prompt_space = left_prompt_width;
// Need at least two characters to show an autosuggestion.
let available_autosuggest_space =
screen_width - (left_prompt_width + first_command_line_width);
if autosuggest_total_width > 0 && available_autosuggest_space > 2 {
let truncation_offset = truncation_offset_for_width(
&autosuggest_truncated_widths,
available_autosuggest_space - 2,
);
result.autosuggestion = autosuggestion[..truncation_offset].to_owned();
result.autosuggestion.push(get_ellipsis_char());
}
result.autosuggestion = truncated_autosuggestion(0);
return result;
}