mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-28 04:35:09 +00:00
Handle backspaces for visible width
This makes it so we treat backspaces as width -1, but never go below a 0 total width when talking about *lines*, like in screen or string length --visible. Fixes #8277.
This commit is contained in:
parent
85ea9bf781
commit
bb115c847e
5 changed files with 66 additions and 18 deletions
|
@ -203,11 +203,13 @@ struct options_t { //!OCLINT(too many fields)
|
||||||
|
|
||||||
static size_t width_without_escapes(const wcstring &ins) {
|
static size_t width_without_escapes(const wcstring &ins) {
|
||||||
ssize_t width = 0;
|
ssize_t width = 0;
|
||||||
// TODO: this is the same as fish_wcwidth_min_0 from screen.cpp
|
|
||||||
// Make that reusable (and add a wcswidth version).
|
|
||||||
for (auto c : ins) {
|
for (auto c : ins) {
|
||||||
auto w = fish_wcwidth(c);
|
auto w = fish_wcwidth_visible(c);
|
||||||
if (w > 0) width += w;
|
// We assume that this string is on its own line,
|
||||||
|
// in which case a backslash can't bring us below 0.
|
||||||
|
if (w > 0 || width > 0) {
|
||||||
|
width += w;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANSI escape sequences like \e\[31m contain printable characters. Subtract their width
|
// ANSI escape sequences like \e\[31m contain printable characters. Subtract their width
|
||||||
|
@ -218,8 +220,8 @@ static size_t width_without_escapes(const wcstring &ins) {
|
||||||
if (len) {
|
if (len) {
|
||||||
auto sub = ins.substr(pos, *len);
|
auto sub = ins.substr(pos, *len);
|
||||||
for (auto c : sub) {
|
for (auto c : sub) {
|
||||||
auto w = fish_wcwidth(c);
|
auto w = fish_wcwidth_visible(c);
|
||||||
if (w > 0) width -= w;
|
width -= w;
|
||||||
}
|
}
|
||||||
// Move us forward behind the escape code,
|
// Move us forward behind the escape code,
|
||||||
// it might include a second escape!
|
// it might include a second escape!
|
||||||
|
|
|
@ -81,13 +81,14 @@ static size_t next_tab_stop(size_t current_line_width) {
|
||||||
return ((current_line_width / tab_width) + 1) * tab_width;
|
return ((current_line_width / tab_width) + 1) * tab_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like fish_wcwidth, but returns 0 for control characters instead of -1.
|
|
||||||
static int fish_wcwidth_min_0(wchar_t widechar) { return std::max(0, fish_wcwidth(widechar)); }
|
|
||||||
|
|
||||||
int line_t::wcswidth_min_0(size_t max) const {
|
int line_t::wcswidth_min_0(size_t max) const {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
for (size_t idx = 0, end = std::min(max, text.size()); idx < end; idx++) {
|
for (size_t idx = 0, end = std::min(max, text.size()); idx < end; idx++) {
|
||||||
result += fish_wcwidth_min_0(text[idx].character);
|
auto w = fish_wcwidth_visible(text[idx].character);
|
||||||
|
// A backspace at the start of the line does nothing.
|
||||||
|
if (w > 0 || result > 0) {
|
||||||
|
result += w;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -351,7 +352,11 @@ static size_t measure_run_from(const wchar_t *input, size_t start, size_t *out_e
|
||||||
width = next_tab_stop(width);
|
width = next_tab_stop(width);
|
||||||
} else {
|
} else {
|
||||||
// Ordinary char. Add its width with care to ignore control chars which have width -1.
|
// Ordinary char. Add its width with care to ignore control chars which have width -1.
|
||||||
width += fish_wcwidth_min_0(input[idx]);
|
auto w = fish_wcwidth_visible(input[idx]);
|
||||||
|
// A backspace at the start of the line does nothing.
|
||||||
|
if (w != -1 || width > 0) {
|
||||||
|
width += w;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (out_end) *out_end = idx;
|
if (out_end) *out_end = idx;
|
||||||
|
@ -389,7 +394,7 @@ static void truncate_run(wcstring *run, size_t desired_width, size_t *width,
|
||||||
curr_width = measure_run_from(run->c_str(), 0, nullptr, cache);
|
curr_width = measure_run_from(run->c_str(), 0, nullptr, cache);
|
||||||
idx = 0;
|
idx = 0;
|
||||||
} else {
|
} else {
|
||||||
size_t char_width = fish_wcwidth_min_0(c);
|
size_t char_width = fish_wcwidth_visible(c);
|
||||||
curr_width -= std::min(curr_width, char_width);
|
curr_width -= std::min(curr_width, char_width);
|
||||||
run->erase(idx, 1);
|
run->erase(idx, 1);
|
||||||
}
|
}
|
||||||
|
@ -856,7 +861,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
|
||||||
// Skip over skip_remaining width worth of characters.
|
// Skip over skip_remaining width worth of characters.
|
||||||
size_t j = 0;
|
size_t j = 0;
|
||||||
for (; j < o_line.size(); j++) {
|
for (; j < o_line.size(); j++) {
|
||||||
size_t width = fish_wcwidth_min_0(o_line.char_at(j));
|
size_t width = fish_wcwidth_visible(o_line.char_at(j));
|
||||||
if (skip_remaining < width) break;
|
if (skip_remaining < width) break;
|
||||||
skip_remaining -= width;
|
skip_remaining -= width;
|
||||||
current_width += width;
|
current_width += width;
|
||||||
|
@ -864,7 +869,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
|
||||||
|
|
||||||
// Skip over zero-width characters (e.g. combining marks at the end of the prompt).
|
// Skip over zero-width characters (e.g. combining marks at the end of the prompt).
|
||||||
for (; j < o_line.size(); j++) {
|
for (; j < o_line.size(); j++) {
|
||||||
int width = fish_wcwidth_min_0(o_line.char_at(j));
|
int width = fish_wcwidth_visible(o_line.char_at(j));
|
||||||
if (width > 0) break;
|
if (width > 0) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,7 +893,7 @@ static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring
|
||||||
perform_any_impending_soft_wrap(scr, current_width, static_cast<int>(i));
|
perform_any_impending_soft_wrap(scr, current_width, static_cast<int>(i));
|
||||||
s_move(scr, current_width, static_cast<int>(i));
|
s_move(scr, current_width, static_cast<int>(i));
|
||||||
set_color(o_line.color_at(j));
|
set_color(o_line.color_at(j));
|
||||||
auto width = fish_wcwidth_min_0(o_line.char_at(j));
|
auto width = fish_wcwidth_visible(o_line.char_at(j));
|
||||||
s_write_char(scr, o_line.char_at(j), width);
|
s_write_char(scr, o_line.char_at(j), width);
|
||||||
current_width += width;
|
current_width += width;
|
||||||
}
|
}
|
||||||
|
@ -1035,7 +1040,7 @@ static screen_layout_t compute_layout(screen_t *s, size_t screen_width,
|
||||||
multiline = true;
|
multiline = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
first_line_width += fish_wcwidth_min_0(c);
|
first_line_width += fish_wcwidth_visible(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const size_t first_command_line_width = first_line_width;
|
const size_t first_command_line_width = first_line_width;
|
||||||
|
@ -1050,7 +1055,7 @@ static screen_layout_t compute_layout(screen_t *s, size_t screen_width,
|
||||||
autosuggest_truncated_widths.reserve(1 + autosuggestion_str.size());
|
autosuggest_truncated_widths.reserve(1 + autosuggestion_str.size());
|
||||||
for (size_t i = 0; autosuggestion[i] != L'\0'; i++) {
|
for (size_t i = 0; autosuggestion[i] != L'\0'; i++) {
|
||||||
autosuggest_truncated_widths.push_back(autosuggest_total_width);
|
autosuggest_truncated_widths.push_back(autosuggest_total_width);
|
||||||
autosuggest_total_width += fish_wcwidth_min_0(autosuggestion[i]);
|
autosuggest_total_width += fish_wcwidth_visible(autosuggestion[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1219,7 +1224,7 @@ void s_write(screen_t *s, const wcstring &left_prompt, const wcstring &right_pro
|
||||||
}
|
}
|
||||||
s_desired_append_char(s, effective_commandline.at(i), colors[i], indent[i],
|
s_desired_append_char(s, effective_commandline.at(i), colors[i], indent[i],
|
||||||
first_line_prompt_space,
|
first_line_prompt_space,
|
||||||
fish_wcwidth_min_0(effective_commandline.at(i)));
|
fish_wcwidth_visible(effective_commandline.at(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor may have been at the end too.
|
// Cursor may have been at the end too.
|
||||||
|
|
|
@ -310,3 +310,20 @@ wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) {
|
||||||
void wcs2string_bad_char(wchar_t wc) {
|
void wcs2string_bad_char(wchar_t wc) {
|
||||||
FLOGF(char_encoding, L"Wide character U+%4X has no narrow representation", wc);
|
FLOGF(char_encoding, L"Wide character U+%4X has no narrow representation", wc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int fish_wcwidth_visible(wchar_t widechar) {
|
||||||
|
if (widechar == L'\b') return -1;
|
||||||
|
return std::max(0, fish_wcwidth(widechar));
|
||||||
|
}
|
||||||
|
|
||||||
|
int fish_wcswidth_visible(const wcstring &str) {
|
||||||
|
size_t res = 0;
|
||||||
|
for (wchar_t ch : str) {
|
||||||
|
if (ch == L'\b') {
|
||||||
|
res += -1;
|
||||||
|
} else {
|
||||||
|
res += std::max(0, fish_wcwidth(ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
|
@ -294,4 +294,11 @@ class line_iterator_t {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Like fish_wcwidth, but returns 0 for characters with no real width instead of -1.
|
||||||
|
int fish_wcwidth_visible(wchar_t widechar);
|
||||||
|
|
||||||
|
/// The same, but for all chars. Note that this only makes sense if the string has an arbitrary long prefix - backslashes can move the cursor *before* the string.
|
||||||
|
///
|
||||||
|
/// In typical usage, you probably want to wrap wcwidth_visible to accumulate the width, but never go below 0.
|
||||||
|
int fish_wcswidth_visible(const wcstring &str);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -112,6 +112,23 @@ string length --visible a(set_color blue)b\ncde
|
||||||
# CHECK: 2
|
# CHECK: 2
|
||||||
# CHECK: 3
|
# CHECK: 3
|
||||||
|
|
||||||
|
# Backslashes and visible length:
|
||||||
|
# It can't move us before the start of the line.
|
||||||
|
string length --visible \b
|
||||||
|
# CHECK: 0
|
||||||
|
|
||||||
|
# It can't move us before the start of the line.
|
||||||
|
string length --visible \bf
|
||||||
|
# CHECK: 1
|
||||||
|
|
||||||
|
# But it does erase chars before.
|
||||||
|
string length --visible \bf\b
|
||||||
|
# CHECK: 0
|
||||||
|
|
||||||
|
# Never move past 0.
|
||||||
|
string length --visible \bf\b\b\b\b\b
|
||||||
|
# CHECK: 0
|
||||||
|
|
||||||
string sub --length 2 abcde
|
string sub --length 2 abcde
|
||||||
# CHECK: ab
|
# CHECK: ab
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue