mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-24 05:41:14 -03:00
Show soft-wrapped portions in autosuggestions
Co-authored-by: Johannes Altmanninger <aclopte@gmail.com> Closes #12153
This commit is contained in:
committed by
Johannes Altmanninger
parent
92c814841a
commit
f417cbc981
@@ -37,6 +37,7 @@ Interactive improvements
|
||||
- Prefix-matching completions are now shown even if they don't have the case typed by the user (:issue:`7944`).
|
||||
- On Cygwin/MSYS, command name completion will favor the non-exe name (``foo``) unless the user started typing the extension.
|
||||
- When using the exe name (``foo.exe``), fish will use to the description and completions for ``foo`` if there are none for ``foo.exe``.
|
||||
- Autosuggestions now also show soft-wrapped portions (:issue:`12045`).
|
||||
|
||||
Improved terminal support
|
||||
-------------------------
|
||||
|
||||
147
src/screen.rs
147
src/screen.rs
@@ -10,7 +10,6 @@
|
||||
use crate::editable_line::line_at_cursor;
|
||||
use crate::key::ViewportPosition;
|
||||
use crate::pager::{PAGER_MIN_HEIGHT, PageRendering, Pager};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::LinkedList;
|
||||
use std::io::Write;
|
||||
@@ -1953,21 +1952,6 @@ struct ScreenLayout {
|
||||
pub(crate) autosuggestion: WString,
|
||||
}
|
||||
|
||||
// Given a vector whose indexes are offsets and whose values are the widths of the string if
|
||||
// truncated at that offset, return the offset that fits in the given width. Returns
|
||||
// width_by_offset.size() - 1 if they all fit. The first value in width_by_offset is assumed to be
|
||||
// 0.
|
||||
fn truncation_offset_for_width(str: &wstr, max_width: usize) -> usize {
|
||||
let mut i = 0;
|
||||
let mut width = 0;
|
||||
while i < str.len() && width <= max_width {
|
||||
width += wcwidth_rendered_min_0(str.char_at(i));
|
||||
i += 1;
|
||||
}
|
||||
// i is the first index that did not fit; i - 1 is therefore the last that did.
|
||||
i - 1
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_layout(
|
||||
ellipsis_char: char,
|
||||
@@ -2043,8 +2027,9 @@ fn compute_layout(
|
||||
// Now we should definitely fit.
|
||||
assert!(left_prompt_width + right_prompt_width <= screen_width);
|
||||
|
||||
// Truncate each logical line from the autosuggestion to fit on a single screen line.
|
||||
// In future, we should truncate only once, at the end (#12004).
|
||||
// Track each logical line from the autosuggestion so we can determine how much of it fits
|
||||
// on screen. We allow the lines to soft wrap naturally and we only truncate vertically if
|
||||
// we would exceed the screen height.
|
||||
let cursor_y = left_prompt_layout.line_starts.len() - 1
|
||||
+ commandline_before_suggestion
|
||||
.chars()
|
||||
@@ -2052,9 +2037,7 @@ fn compute_layout(
|
||||
.count();
|
||||
|
||||
struct SuggestionLine<'a> {
|
||||
available_autosuggest_space: usize,
|
||||
autosuggestion_line: &'a wstr,
|
||||
autosuggest_total_width: usize,
|
||||
}
|
||||
let mut suggestion_lines = vec![];
|
||||
|
||||
@@ -2070,10 +2053,6 @@ struct SuggestionLine<'a> {
|
||||
.enumerate()
|
||||
{
|
||||
let autosuggestion_line = wstr::from_char_slice(autosuggestion_line);
|
||||
let autosuggest_total_width = autosuggestion_line
|
||||
.chars()
|
||||
.map(wcwidth_rendered_min_0)
|
||||
.sum();
|
||||
|
||||
// Calculate space available for autosuggestion.
|
||||
let indent_width = |pos| usize::try_from(indent[pos]).unwrap() * INDENT_STEP;
|
||||
@@ -2087,17 +2066,27 @@ struct SuggestionLine<'a> {
|
||||
} else {
|
||||
indent_width(suggestion_start - "\n".len())
|
||||
};
|
||||
let available_horizontal_space = screen_width - (width % screen_width);
|
||||
|
||||
let suggestion_line_height =
|
||||
if width >= screen_width || autosuggest_total_width >= available_horizontal_space {
|
||||
// As per the comment above, we truncate and autosuggestion lines that would wrap.
|
||||
// We truncate them at the very end of the screen, so they (barely) soft wrap,
|
||||
// and take up two screen lines.
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let column = width % screen_width;
|
||||
let suggestion_line_height = {
|
||||
let mut column = column;
|
||||
let mut lines = 1;
|
||||
for ch in autosuggestion_line.chars() {
|
||||
let ch_width = wcwidth_rendered_min_0(ch);
|
||||
let new_column = column + ch_width;
|
||||
if new_column > screen_width {
|
||||
column = 0;
|
||||
}
|
||||
if column == 0 && ch_width != 0 {
|
||||
lines += 1;
|
||||
}
|
||||
column = if new_column == screen_width {
|
||||
0
|
||||
} else {
|
||||
column + ch_width
|
||||
};
|
||||
}
|
||||
lines
|
||||
};
|
||||
match available_vertical_space.checked_sub(suggestion_line_height) {
|
||||
Some(lines) => available_vertical_space = lines,
|
||||
None => {
|
||||
@@ -2107,60 +2096,40 @@ struct SuggestionLine<'a> {
|
||||
};
|
||||
|
||||
suggestion_lines.push(SuggestionLine {
|
||||
available_autosuggest_space: available_horizontal_space,
|
||||
autosuggestion_line,
|
||||
autosuggest_total_width,
|
||||
});
|
||||
|
||||
suggestion_start += autosuggestion_line.len() + "\n".len();
|
||||
}
|
||||
|
||||
let mut autosuggestion = WString::new();
|
||||
let mut erased = 0;
|
||||
let mut suggestion_start = commandline_before_suggestion.len();
|
||||
let mut displayed_len = 0;
|
||||
for (
|
||||
line,
|
||||
line_idx,
|
||||
&SuggestionLine {
|
||||
available_autosuggest_space,
|
||||
autosuggestion_line,
|
||||
autosuggest_total_width,
|
||||
},
|
||||
) in suggestion_lines.iter().enumerate()
|
||||
{
|
||||
let truncated_suggestion_line;
|
||||
let mut vertical_truncation_marker = None;
|
||||
if autosuggest_total_width > 0 && autosuggest_total_width >= available_autosuggest_space {
|
||||
// horizontal truncation
|
||||
let truncation_offset =
|
||||
truncation_offset_for_width(autosuggestion_line, available_autosuggest_space - 1);
|
||||
truncated_suggestion_line = Cow::Owned(
|
||||
autosuggestion_line[..truncation_offset].to_owned()
|
||||
+ wstr::from_char_slice(&[ellipsis_char]),
|
||||
);
|
||||
} else if truncated_vertically && line == suggestion_lines.len() - 1 {
|
||||
// vertical truncation
|
||||
truncated_suggestion_line = Cow::Borrowed(autosuggestion_line);
|
||||
vertical_truncation_marker = Some(ellipsis_char);
|
||||
} else {
|
||||
// no truncation
|
||||
assert!(available_autosuggest_space >= autosuggest_total_width);
|
||||
truncated_suggestion_line = Cow::Borrowed(autosuggestion_line);
|
||||
}
|
||||
|
||||
let truncation_range = suggestion_start - erased + truncated_suggestion_line.len()
|
||||
..suggestion_start - erased + autosuggestion_line.len();
|
||||
colors.drain(truncation_range.clone());
|
||||
indent.drain(truncation_range.clone());
|
||||
erased += truncation_range.len();
|
||||
suggestion_start += autosuggestion_line.len() + "\n".len();
|
||||
if line != 0 {
|
||||
if line_idx != 0 {
|
||||
autosuggestion.push('\n');
|
||||
displayed_len += "\n".len();
|
||||
}
|
||||
autosuggestion.push_utfstr(&truncated_suggestion_line);
|
||||
if let Some(extra) = vertical_truncation_marker {
|
||||
autosuggestion.push(extra);
|
||||
colors.insert(truncation_range.end, colors[truncation_range.end - 1]);
|
||||
indent.insert(truncation_range.end, indent[truncation_range.end - 1]);
|
||||
autosuggestion.push_utfstr(autosuggestion_line);
|
||||
displayed_len += autosuggestion_line.len();
|
||||
}
|
||||
|
||||
let total_autosuggestion_len = autosuggestion_str.len();
|
||||
let truncated_chars = total_autosuggestion_len.saturating_sub(displayed_len);
|
||||
if truncated_chars > 0 {
|
||||
let suggestion_end = commandline_before_suggestion.len() + displayed_len;
|
||||
colors.drain(suggestion_end..suggestion_end + truncated_chars);
|
||||
indent.drain(suggestion_end..suggestion_end + truncated_chars);
|
||||
if truncated_vertically && displayed_len > 0 {
|
||||
let suggestion_last = suggestion_end - 1;
|
||||
autosuggestion.push(ellipsis_char);
|
||||
colors.insert(suggestion_end, colors[suggestion_last]);
|
||||
indent.insert(suggestion_end, indent[suggestion_last]);
|
||||
}
|
||||
}
|
||||
result.autosuggestion = autosuggestion;
|
||||
@@ -2502,7 +2471,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"<right",
|
||||
" autosugges…",
|
||||
" autosuggesTION",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2522,7 +2491,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"<right",
|
||||
" autosuggestion t…",
|
||||
" autosuggestion tRUNCATED",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2532,7 +2501,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"<right",
|
||||
" autosuggesti…",
|
||||
" autosuggestiON TRUNCATED",
|
||||
)
|
||||
);
|
||||
let indent = validate!(
|
||||
@@ -2542,10 +2511,10 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"<right",
|
||||
" autosuggesti…",
|
||||
" autosuggestiON TRUNCATED",
|
||||
)
|
||||
);
|
||||
assert_eq!(indent["if :\ncommand autosuggesti…\n".len()], 1);
|
||||
assert_eq!(indent["if :\ncommand autosuggestiON TRUNCATED\n".len()], 1);
|
||||
|
||||
validate!(
|
||||
(
|
||||
@@ -2554,7 +2523,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"",
|
||||
" auto…",
|
||||
" autoSUGGESTION",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2564,7 +2533,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"",
|
||||
"…",
|
||||
"s",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2574,7 +2543,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"",
|
||||
"…",
|
||||
"SUGGESTION",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2584,7 +2553,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"",
|
||||
"uggestion long so…",
|
||||
"uggestion long soFT WRAP",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2594,7 +2563,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"<right",
|
||||
"and …",
|
||||
"and AUTOSUGGESTION",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2604,7 +2573,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"<right",
|
||||
"…",
|
||||
"AUTOSUGGESTION",
|
||||
)
|
||||
);
|
||||
validate!( //
|
||||
@@ -2614,7 +2583,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"<right",
|
||||
"utosuggestion sof…",
|
||||
"utosuggestion sofT WRAP",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2624,7 +2593,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"",
|
||||
"and …",
|
||||
"and AUTOSUGGESTION",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2634,7 +2603,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"",
|
||||
"…",
|
||||
"AUTOSUGGESTION",
|
||||
)
|
||||
);
|
||||
validate!(
|
||||
@@ -2644,7 +2613,7 @@ macro_rules! validate {
|
||||
"left>",
|
||||
5,
|
||||
"",
|
||||
"utosuggestion sof…",
|
||||
"utosuggestion sofT WRAP",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ tmux-sleep
|
||||
isolated-tmux capture-pane -p | sed /if/,/end/s/^/^/
|
||||
# CHECK: ^prompt> if true
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000000000000000…
|
||||
# CHECK: ^
|
||||
# CHECK: ^ echo 000000000000000000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^0000000000000000000000000000000000000
|
||||
# CHECK: ^ end
|
||||
|
||||
# Enter does not invalidate autosuggestion.
|
||||
@@ -26,8 +26,8 @@ tmux-sleep
|
||||
isolated-tmux capture-pane -p | sed /if/,/end/s/^/^/
|
||||
# CHECK: ^prompt> if true
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000000000000000…
|
||||
# CHECK: ^
|
||||
# CHECK: ^ echo 000000000000000000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^0000000000000000000000000000000000000
|
||||
# CHECK: ^ end
|
||||
|
||||
# Autosuggestion is also computed after Enter.
|
||||
@@ -36,8 +36,8 @@ tmux-sleep
|
||||
isolated-tmux capture-pane -p \; send-keys C-u C-u C-u C-l | sed /if/,/end/s/^/^/
|
||||
# CHECK: ^prompt> if true
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000000000000000…
|
||||
# CHECK: ^
|
||||
# CHECK: ^ echo 000000000000000000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^0000000000000000000000000000000000000
|
||||
# CHECK: ^ end
|
||||
|
||||
# Test smaller windows; only the lines that fit will be shown.
|
||||
@@ -46,8 +46,8 @@ tmux-sleep
|
||||
isolated-tmux capture-pane -p | sed s/^/^/
|
||||
# CHECK: ^prompt> if true
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000000000000000…
|
||||
# CHECK: ^
|
||||
# CHECK: ^ echo 000000000000000000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^0000000000000000000000000000000000000…
|
||||
|
||||
# Currently, we take either all or nothing from soft-wrapped suggestion-lines.
|
||||
# The ellipsis means that we'll get more lines.
|
||||
@@ -73,8 +73,8 @@ isolated-tmux capture-pane -p | sed s/^/^/
|
||||
# CHECK: ^prompt>
|
||||
# CHECK: ^prompt> if true
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000000000000000…
|
||||
# CHECK: ^
|
||||
# CHECK: ^ echo 000000000000000000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^0000000000000000000000000000000000000…
|
||||
|
||||
# Again, we take all or nothing from a soft-wrapped line.
|
||||
isolated-tmux send-keys C-u Enter if
|
||||
@@ -114,8 +114,8 @@ isolated-tmux capture-pane -p | sed s/^/^/
|
||||
# CHECK: ^prompt-line1/2>
|
||||
# CHECK: ^prompt-line2/2> if true
|
||||
# CHECK: ^ echo 00000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^ echo 000000000000000000000000000000000000000000000000000000…
|
||||
# CHECK: ^
|
||||
# CHECK: ^ echo 0000000000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^000000000000000000000000000000000000000000000…
|
||||
|
||||
# Autosuggestion with a line that barely wraps.
|
||||
isolated-tmux resize-window -x 80 -y 4 \; send-keys C-u \
|
||||
@@ -131,7 +131,7 @@ tmux-sleep
|
||||
isolated-tmux capture-pane -p | sed s/^/^/
|
||||
# CHECK: ^prompt-line1
|
||||
# CHECK: ^> begin
|
||||
# CHECK: ^ : 00000000000000000000000000000000000000000000000000000000000000000000000…
|
||||
# CHECK: ^ : 000000000000000000000000000000000000000000000000000000000000000000000000
|
||||
# CHECK: ^
|
||||
|
||||
# Autosuggestions on a soft-wrapped commandline don't push the prompt.
|
||||
@@ -142,8 +142,8 @@ isolated-tmux resize-window -x 6 -y 4 \; send-keys C-u \
|
||||
tmux-sleep
|
||||
isolated-tmux capture-pane -p | sed s/^/^/
|
||||
# CHECK: ^>
|
||||
# CHECK: ^> ech…
|
||||
# CHECK: ^
|
||||
# CHECK: ^> echo
|
||||
# CHECK: ^ l1 \…
|
||||
# CHECK: ^
|
||||
|
||||
isolated-tmux resize-window -x 6 -y 4 \; send-keys C-u \
|
||||
@@ -159,4 +159,4 @@ isolated-tmux capture-pane -p | sed s/^/^/
|
||||
# CHECK: ^>
|
||||
# CHECK: ^>
|
||||
# CHECK: ^> echo
|
||||
# CHECK: ^ wrap…
|
||||
# CHECK: ^
|
||||
|
||||
@@ -11,7 +11,8 @@ isolated-tmux-start -C '
|
||||
isolated-tmux send-keys : Space Tab Tab
|
||||
tmux-sleep
|
||||
isolated-tmux capture-pane -p
|
||||
# CHECK: prompt 0> : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…
|
||||
# CHECK: prompt 0> : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
# CHECK: AAAAAAAAAAAA
|
||||
# CHECK: 1 135 269 403 537 671
|
||||
# CHECK: 2 136 270 404 538 672
|
||||
# CHECK: 3 137 271 405 539 673
|
||||
@@ -19,10 +20,9 @@ isolated-tmux capture-pane -p
|
||||
# CHECK: 5 139 273 407 541 675
|
||||
# CHECK: 6 140 274 408 542 676
|
||||
# CHECK: 7 141 275 409 543 677
|
||||
# CHECK: 8 142 276 410 544 678
|
||||
# CHECK: rows 1 to 8 of 134
|
||||
# CHECK: rows 1 to 7 of 134
|
||||
|
||||
# Check that completions don't break than called on empty line
|
||||
# Check that completions don't break when called on empty line
|
||||
isolated-tmux send-keys C-u 'set fish_autosuggestion_enabled 0; function fish_prompt; string repeat (math $COLUMNS - 2) 0; end' Enter
|
||||
isolated-tmux send-keys C-l : Space Tab Tab
|
||||
tmux-sleep
|
||||
|
||||
Reference in New Issue
Block a user