mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-22 10:06:49 +08:00
Render control characters as Unicode Control Pictures
Inserting Tab or Backspace characters causes weird glitches. Sometimes it's useful to paste tabs as part of a code block. Render tabs as "␉" and so on for other ASCII control characters, see https://unicode-table.com/en/blocks/control-pictures/. This fixes the width-related glitches. You can see it in action by inserting some control characters into the command line: set chars for x in (seq 1 0x1F) set -a chars (printf "%02x\\\\x%02x" $x $x) end eval set chars $chars commandline -i "echo '" $chars Fixes #6923 Fixes #5274 Closes #7295 We could extend this approach to display a fallback symbol for every unknown nonprintable character, not just ASCII control characters. In future we might want to support tab properly.
This commit is contained in:
parent
d3b700f98c
commit
0627c9d9af
|
@ -49,6 +49,7 @@ Interactive improvements
|
|||
- When the cursor is on a command that resolves to a script, :kbd:`Alt-O` will now open that script in your editor.
|
||||
- :kbd:`Alt-E` now passes the cursor position to the external editor also if the editor aliases a supported editor (via ``complete --wraps``).
|
||||
- Option completion now uses fuzzy subsequence filtering as well. This means that ``--fb`` may be completed to ``--foobar`` if there is no better match.
|
||||
- ASCII control characters are now rendered using symbols from Unicode's Control Pictures block.
|
||||
|
||||
New or improved bindings
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -153,6 +153,11 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
|
|||
bind --preset -M paste \e\[201~ __fish_stop_bracketed_paste
|
||||
# In paste-mode, everything self-inserts except for the sequence to get out of it
|
||||
bind --preset -M paste "" self-insert
|
||||
# Pass through formatting control characters else they may be dropped
|
||||
# on some terminals.
|
||||
bind --preset -M paste \b 'commandline -i \b'
|
||||
bind --preset -M paste \t 'commandline -i \t'
|
||||
bind --preset -M paste \v 'commandline -i \v'
|
||||
# Without this, a \r will overwrite the other text, rendering it invisible - which makes the exercise kinda pointless.
|
||||
bind --preset -M paste \r "commandline -i \n"
|
||||
|
||||
|
|
|
@ -29,11 +29,11 @@ use crate::flog::FLOGF;
|
|||
use crate::future::IsSomeAnd;
|
||||
use crate::global_safety::RelaxedAtomicBool;
|
||||
use crate::highlight::HighlightColorResolver;
|
||||
use crate::highlight::HighlightSpec;
|
||||
use crate::output::Outputter;
|
||||
use crate::termsize::{termsize_last, Termsize};
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wcstringutil::string_prefixes_string;
|
||||
use crate::{highlight::HighlightSpec, wcstringutil::fish_wcwidth_visible};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct HighlightedChar {
|
||||
|
@ -64,7 +64,7 @@ impl Line {
|
|||
pub fn append(&mut self, character: char, highlight: HighlightSpec) {
|
||||
self.text.push(HighlightedChar {
|
||||
highlight,
|
||||
character,
|
||||
character: rendered_character(character),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -101,11 +101,7 @@ impl Line {
|
|||
pub fn wcswidth_min_0(&self, max: usize /* = usize::MAX */) -> usize {
|
||||
let mut result: usize = 0;
|
||||
for c in &self.text[..max.min(self.text.len())] {
|
||||
let w = fish_wcwidth_visible(c.character);
|
||||
// A backspace at the start of the line does nothing.
|
||||
if w > 0 || result > 0 {
|
||||
result = result.checked_add_signed(w).unwrap();
|
||||
}
|
||||
result += wcwidth_rendered(c.character);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
@ -329,10 +325,7 @@ impl Screen {
|
|||
colors[i],
|
||||
usize::try_from(indent[i]).unwrap(),
|
||||
first_line_prompt_space,
|
||||
usize::try_from(fish_wcwidth_visible(
|
||||
effective_commandline.as_char_slice()[i],
|
||||
))
|
||||
.unwrap(),
|
||||
wcwidth_rendered(effective_commandline.as_char_slice()[i]),
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
|
@ -933,8 +926,7 @@ impl Screen {
|
|||
// Skip over skip_remaining width worth of characters.
|
||||
let mut j = 0;
|
||||
while j < o_line(&zelf, i).len() {
|
||||
let width =
|
||||
usize::try_from(fish_wcwidth_visible(o_line(&zelf, i).char_at(j))).unwrap();
|
||||
let width = wcwidth_rendered(o_line(&zelf, i).char_at(j));
|
||||
if skip_remaining < width {
|
||||
break;
|
||||
}
|
||||
|
@ -945,7 +937,7 @@ impl Screen {
|
|||
|
||||
// Skip over zero-width characters (e.g. combining marks at the end of the prompt).
|
||||
while j < o_line(&zelf, i).len() {
|
||||
let width = fish_wcwidth_visible(o_line(&zelf, i).char_at(j));
|
||||
let width = wcwidth_rendered(o_line(&zelf, i).char_at(j));
|
||||
if width > 0 {
|
||||
break;
|
||||
}
|
||||
|
@ -978,7 +970,7 @@ impl Screen {
|
|||
let color = o_line(&zelf, i).color_at(j);
|
||||
set_color(&mut zelf, color);
|
||||
let ch = o_line(&zelf, i).char_at(j);
|
||||
let width = usize::try_from(fish_wcwidth_visible(ch)).unwrap();
|
||||
let width = wcwidth_rendered(ch);
|
||||
zelf.write_char(ch, isize::try_from(width).unwrap());
|
||||
current_width += width;
|
||||
j += 1;
|
||||
|
@ -1525,11 +1517,7 @@ fn measure_run_from(
|
|||
width = next_tab_stop(width);
|
||||
} else {
|
||||
// Ordinary char. Add its width with care to ignore control chars which have width -1.
|
||||
let w = fish_wcwidth_visible(input.char_at(idx));
|
||||
// A backspace at the start of the line does nothing.
|
||||
if w != -1 || width > 0 {
|
||||
width = width.checked_add_signed(w).unwrap();
|
||||
}
|
||||
width += wcwidth_rendered(input.char_at(idx));
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
@ -1575,7 +1563,7 @@ fn truncate_run(
|
|||
curr_width = measure_run_from(run, 0, None, cache);
|
||||
idx = 0;
|
||||
} else {
|
||||
let char_width = fish_wcwidth_visible(c) as usize;
|
||||
let char_width = wcwidth_rendered(c);
|
||||
curr_width -= std::cmp::min(curr_width, char_width);
|
||||
run.remove(idx);
|
||||
}
|
||||
|
@ -1724,7 +1712,7 @@ fn compute_layout(
|
|||
multiline = true;
|
||||
break;
|
||||
} else {
|
||||
first_line_width += usize::try_from(fish_wcwidth_visible(c)).unwrap();
|
||||
first_line_width += wcwidth_rendered(c);
|
||||
}
|
||||
}
|
||||
let first_command_line_width = first_line_width;
|
||||
|
@ -1739,7 +1727,7 @@ fn compute_layout(
|
|||
autosuggest_truncated_widths.reserve(1 + autosuggestion_str.len());
|
||||
for c in autosuggestion.chars() {
|
||||
autosuggest_truncated_widths.push(autosuggest_total_width);
|
||||
autosuggest_total_width += usize::try_from(fish_wcwidth_visible(c)).unwrap();
|
||||
autosuggest_total_width += wcwidth_rendered(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1830,3 +1818,20 @@ fn compute_layout(
|
|||
result.autosuggestion = autosuggestion.to_owned();
|
||||
result
|
||||
}
|
||||
|
||||
// Display non-printable control characters as a graphic symbol.
|
||||
// This is to prevent control characters like \t and \v from moving the
|
||||
// cursor in a way we don't handle. The ones we do handle are \r and
|
||||
// \n.
|
||||
// See https://unicode-table.com/en/blocks/control-pictures/
|
||||
fn rendered_character(c: char) -> char {
|
||||
if c <= '\x1F' {
|
||||
char::from_u32(u32::from(c) + 0x2400).unwrap()
|
||||
} else {
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
fn wcwidth_rendered(c: char) -> usize {
|
||||
usize::try_from(fish_wcwidth(rendered_character(c))).unwrap_or_default()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user