mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Fix behaviour in the presence of non-visible width
Padding with an unprintable character is now disallowed, like it was for other zero-length characters. `string shorten` now ignores escape sequences and non-printable characters when calculating the visible width of the ellipsis used (except for `\b`, which is treated as a width of -1). Previously `fish_wcswidth` returned a length of -1 when the ellipsis-str contained any non-printable character, causing the command to poentially print a larger width than expected. This also fixes an integer overflows in `string shorten`'s `max` and `max2`, when the cumulative sum of character widths turned negative (e.g. with any non-printable characters, or `\b` after the changes above). The overflow potentially caused strings containing non-printable characters to be truncated. This adds test that verify the fixed behaviour.
This commit is contained in:
parent
20be990fd9
commit
6dd2cd2b20
6 changed files with 151 additions and 59 deletions
|
@ -51,6 +51,8 @@ Other improvements
|
||||||
- A bug that prevented certain executables from being offered in tab-completions when root has been fixed (:issue:`9639`).
|
- A bug that prevented certain executables from being offered in tab-completions when root has been fixed (:issue:`9639`).
|
||||||
- Builin `jobs` will print commands with non-printable chars escaped (:issue:`9808`)
|
- Builin `jobs` will print commands with non-printable chars escaped (:issue:`9808`)
|
||||||
- An integer overflow in `string repeat` leading to a near-infinite loop has been fixed (:issue:`9899`).
|
- An integer overflow in `string repeat` leading to a near-infinite loop has been fixed (:issue:`9899`).
|
||||||
|
- `string shorten` behaves better in the presence of non-printable characters, including fixing an integer overflow that shortened strings more than intended. (:issue:`9854`)
|
||||||
|
- `string pad` no longer allows non-printable characters as padding. (:issue:`9854`)
|
||||||
|
|
||||||
For distributors
|
For distributors
|
||||||
----------------
|
----------------
|
||||||
|
|
|
@ -259,7 +259,7 @@ enum Direction {
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(self) fn width_without_escapes(ins: &wstr, start_pos: usize) -> i32 {
|
pub(self) fn width_without_escapes(ins: &wstr, start_pos: usize) -> usize {
|
||||||
let mut width: i32 = 0;
|
let mut width: i32 = 0;
|
||||||
for c in ins[start_pos..].chars() {
|
for c in ins[start_pos..].chars() {
|
||||||
let w = fish_wcwidth_visible(c);
|
let w = fish_wcwidth_visible(c);
|
||||||
|
@ -287,8 +287,9 @@ pub(self) fn width_without_escapes(ins: &wstr, start_pos: usize) -> i32 {
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// we subtracted less than we added
|
||||||
return width;
|
debug_assert!(width >= 0, "line has negative width");
|
||||||
|
return width as usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(self) fn escape_code_length(code: &wstr) -> Option<usize> {
|
pub(self) fn escape_code_length(code: &wstr) -> Option<usize> {
|
||||||
|
|
|
@ -41,8 +41,8 @@ impl StringSubCommand<'_> for Length {
|
||||||
// Carriage-return returns us to the beginning. The longest substring without
|
// Carriage-return returns us to the beginning. The longest substring without
|
||||||
// carriage-return determines the overall width.
|
// carriage-return determines the overall width.
|
||||||
for reset in split_string(&line, '\r') {
|
for reset in split_string(&line, '\r') {
|
||||||
let n = width_without_escapes(&reset, 0) as usize;
|
let n = width_without_escapes(&reset, 0);
|
||||||
max = max.max(n);
|
max = usize::max(max, n);
|
||||||
}
|
}
|
||||||
if max > 0 {
|
if max > 0 {
|
||||||
nnonempty += 1;
|
nnonempty += 1;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::wutil::{fish_wcstol, fish_wcswidth};
|
use crate::fallback::fish_wcwidth;
|
||||||
|
use crate::wutil::fish_wcstol;
|
||||||
|
|
||||||
pub struct Pad {
|
pub struct Pad {
|
||||||
char_to_pad: char,
|
char_to_pad: char,
|
||||||
pad_char_width: i32,
|
pad_char_width: usize,
|
||||||
pad_from: Direction,
|
pad_from: Direction,
|
||||||
width: usize,
|
width: usize,
|
||||||
}
|
}
|
||||||
|
@ -33,25 +34,23 @@ impl StringSubCommand<'_> for Pad {
|
||||||
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
fn parse_opt(&mut self, name: &wstr, c: char, arg: Option<&wstr>) -> Result<(), StringError> {
|
||||||
match c {
|
match c {
|
||||||
'c' => {
|
'c' => {
|
||||||
let arg = arg.expect("option -c requires an argument");
|
let [pad_char] = arg.unwrap().as_char_slice() else {
|
||||||
if arg.len() != 1 {
|
|
||||||
return Err(invalid_args!(
|
return Err(invalid_args!(
|
||||||
"%ls: Padding should be a character '%ls'\n",
|
"%ls: Padding should be a character '%ls'\n",
|
||||||
name,
|
name,
|
||||||
Some(arg)
|
arg
|
||||||
));
|
));
|
||||||
}
|
};
|
||||||
let pad_char_width = fish_wcswidth(arg.slice_to(1));
|
let pad_char_width = fish_wcwidth(*pad_char);
|
||||||
// can we ever have negative width?
|
if pad_char_width <= 0 {
|
||||||
if pad_char_width == 0 {
|
|
||||||
return Err(invalid_args!(
|
return Err(invalid_args!(
|
||||||
"%ls: Invalid padding character of width zero '%ls'\n",
|
"%ls: Invalid padding character of width zero '%ls'\n",
|
||||||
name,
|
name,
|
||||||
Some(arg)
|
arg
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
self.pad_char_width = pad_char_width;
|
self.pad_char_width = pad_char_width as usize;
|
||||||
self.char_to_pad = arg.char_at(0);
|
self.char_to_pad = *pad_char;
|
||||||
}
|
}
|
||||||
'r' => self.pad_from = Direction::Right,
|
'r' => self.pad_from = Direction::Right,
|
||||||
'w' => {
|
'w' => {
|
||||||
|
@ -71,8 +70,8 @@ impl StringSubCommand<'_> for Pad {
|
||||||
optind: &mut usize,
|
optind: &mut usize,
|
||||||
args: &[&'args wstr],
|
args: &[&'args wstr],
|
||||||
) -> Option<libc::c_int> {
|
) -> Option<libc::c_int> {
|
||||||
let mut max_width = 0i32;
|
let mut max_width = 0usize;
|
||||||
let mut inputs: Vec<(Cow<'args, wstr>, i32)> = Vec::new();
|
let mut inputs: Vec<(Cow<'args, wstr>, usize)> = Vec::new();
|
||||||
let mut print_newline = true;
|
let mut print_newline = true;
|
||||||
|
|
||||||
for (arg, want_newline) in Arguments::new(args, optind, streams) {
|
for (arg, want_newline) in Arguments::new(args, optind, streams) {
|
||||||
|
@ -82,7 +81,7 @@ impl StringSubCommand<'_> for Pad {
|
||||||
print_newline = want_newline;
|
print_newline = want_newline;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pad_width = max_width.max(self.width as i32);
|
let pad_width = max_width.max(self.width);
|
||||||
|
|
||||||
for (input, width) in inputs {
|
for (input, width) in inputs {
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
|
@ -91,14 +90,14 @@ impl StringSubCommand<'_> for Pad {
|
||||||
let remaining_width = (pad_width - width) % self.pad_char_width;
|
let remaining_width = (pad_width - width) % self.pad_char_width;
|
||||||
let mut padded: WString = match self.pad_from {
|
let mut padded: WString = match self.pad_from {
|
||||||
Direction::Left => repeat(self.char_to_pad)
|
Direction::Left => repeat(self.char_to_pad)
|
||||||
.take(pad as usize)
|
.take(pad)
|
||||||
.chain(repeat(' ').take(remaining_width as usize))
|
.chain(repeat(' ').take(remaining_width))
|
||||||
.chain(input.chars())
|
.chain(input.chars())
|
||||||
.collect(),
|
.collect(),
|
||||||
Direction::Right => input
|
Direction::Right => input
|
||||||
.chars()
|
.chars()
|
||||||
.chain(repeat(' ').take(remaining_width as usize))
|
.chain(repeat(' ').take(remaining_width))
|
||||||
.chain(repeat(self.char_to_pad).take(pad as usize))
|
.chain(repeat(self.char_to_pad).take(pad))
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::common::get_ellipsis_str;
|
use crate::common::get_ellipsis_str;
|
||||||
use crate::fallback::fish_wcwidth;
|
|
||||||
use crate::wcstringutil::split_string;
|
use crate::wcstringutil::split_string;
|
||||||
use crate::wutil::{fish_wcstol, fish_wcswidth};
|
use crate::wutil::fish_wcstol;
|
||||||
|
|
||||||
pub struct Shorten<'args> {
|
pub struct Shorten<'args> {
|
||||||
chars_to_shorten: &'args wstr,
|
ellipsis: &'args wstr,
|
||||||
|
ellipsis_width: usize,
|
||||||
max: Option<usize>,
|
max: Option<usize>,
|
||||||
no_newline: bool,
|
no_newline: bool,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
direction: Direction,
|
shorten_from: Direction,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Shorten<'_> {
|
impl Default for Shorten<'_> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
chars_to_shorten: get_ellipsis_str(),
|
ellipsis: get_ellipsis_str(),
|
||||||
|
ellipsis_width: width_without_escapes(get_ellipsis_str(), 0),
|
||||||
max: None,
|
max: None,
|
||||||
no_newline: false,
|
no_newline: false,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
direction: Direction::Right,
|
shorten_from: Direction::Right,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +43,10 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
arg: Option<&'args wstr>,
|
arg: Option<&'args wstr>,
|
||||||
) -> Result<(), StringError> {
|
) -> Result<(), StringError> {
|
||||||
match c {
|
match c {
|
||||||
'c' => self.chars_to_shorten = arg.expect("option --char requires an argument"),
|
'c' => {
|
||||||
|
self.ellipsis = arg.unwrap();
|
||||||
|
self.ellipsis_width = width_without_escapes(self.ellipsis, 0);
|
||||||
|
}
|
||||||
'm' => {
|
'm' => {
|
||||||
self.max = Some(
|
self.max = Some(
|
||||||
fish_wcstol(arg.unwrap())?
|
fish_wcstol(arg.unwrap())?
|
||||||
|
@ -51,7 +55,7 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
'N' => self.no_newline = true,
|
'N' => self.no_newline = true,
|
||||||
'l' => self.direction = Direction::Left,
|
'l' => self.shorten_from = Direction::Left,
|
||||||
'q' => self.quiet = true,
|
'q' => self.quiet = true,
|
||||||
_ => return Err(StringError::UnknownOption),
|
_ => return Err(StringError::UnknownOption),
|
||||||
}
|
}
|
||||||
|
@ -67,7 +71,6 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
) -> Option<libc::c_int> {
|
) -> Option<libc::c_int> {
|
||||||
let mut min_width = usize::MAX;
|
let mut min_width = usize::MAX;
|
||||||
let mut inputs = Vec::new();
|
let mut inputs = Vec::new();
|
||||||
let mut ell = self.chars_to_shorten;
|
|
||||||
|
|
||||||
let iter = Arguments::new(args, optind, streams);
|
let iter = Arguments::new(args, optind, streams);
|
||||||
|
|
||||||
|
@ -93,23 +96,23 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
// or we handle the lines separately.
|
// or we handle the lines separately.
|
||||||
let mut splits = split_string(&arg, '\n').into_iter();
|
let mut splits = split_string(&arg, '\n').into_iter();
|
||||||
if self.no_newline && splits.len() > 1 {
|
if self.no_newline && splits.len() > 1 {
|
||||||
let mut s = match self.direction {
|
let mut s = match self.shorten_from {
|
||||||
Direction::Right => splits.next(),
|
Direction::Right => splits.next(),
|
||||||
Direction::Left => splits.last(),
|
Direction::Left => splits.last(),
|
||||||
}
|
}
|
||||||
.unwrap();
|
.unwrap();
|
||||||
s.push_utfstr(ell);
|
s.push_utfstr(self.ellipsis);
|
||||||
let width = width_without_escapes(&s, 0);
|
let width = width_without_escapes(&s, 0);
|
||||||
|
|
||||||
if width > 0 && (width as usize) < min_width {
|
if width > 0 && width < min_width {
|
||||||
min_width = width as usize;
|
min_width = width;
|
||||||
}
|
}
|
||||||
inputs.push(s);
|
inputs.push(s);
|
||||||
} else {
|
} else {
|
||||||
for s in splits {
|
for s in splits {
|
||||||
let width = width_without_escapes(&s, 0);
|
let width = width_without_escapes(&s, 0);
|
||||||
if width > 0 && (width as usize) < min_width {
|
if width > 0 && width < min_width {
|
||||||
min_width = width as usize;
|
min_width = width;
|
||||||
}
|
}
|
||||||
inputs.push(s);
|
inputs.push(s);
|
||||||
}
|
}
|
||||||
|
@ -118,18 +121,12 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
|
|
||||||
let ourmax: usize = self.max.unwrap_or(min_width);
|
let ourmax: usize = self.max.unwrap_or(min_width);
|
||||||
|
|
||||||
// TODO: Can we have negative width
|
let (ell, ell_width) = if self.ellipsis_width > ourmax {
|
||||||
|
|
||||||
let ell_width: i32 = {
|
|
||||||
let w = fish_wcswidth(ell);
|
|
||||||
if w > ourmax as i32 {
|
|
||||||
// If we can't even print our ellipsis, we substitute nothing,
|
// If we can't even print our ellipsis, we substitute nothing,
|
||||||
// truncating instead.
|
// truncating instead.
|
||||||
ell = L!("");
|
(L!(""), 0)
|
||||||
0
|
|
||||||
} else {
|
} else {
|
||||||
w
|
(self.ellipsis, self.ellipsis_width)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut nsub = 0usize;
|
let mut nsub = 0usize;
|
||||||
|
@ -153,7 +150,7 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
let mut pos = 0usize;
|
let mut pos = 0usize;
|
||||||
let mut max = 0usize;
|
let mut max = 0usize;
|
||||||
// Collect how much of the string we can use without going over the maximum.
|
// Collect how much of the string we can use without going over the maximum.
|
||||||
if self.direction == Direction::Left {
|
if self.shorten_from == Direction::Left {
|
||||||
// Our strategy for keeping from the end.
|
// Our strategy for keeping from the end.
|
||||||
// This is rather unoptimized - actually going *backwards* from the end
|
// This is rather unoptimized - actually going *backwards* from the end
|
||||||
// is extremely tricky because we would have to subtract escapes again.
|
// is extremely tricky because we would have to subtract escapes again.
|
||||||
|
@ -165,7 +162,7 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
// If we're at the beginning and it fits, we sits.
|
// If we're at the beginning and it fits, we sits.
|
||||||
//
|
//
|
||||||
// Otherwise we require it to fit the ellipsis
|
// Otherwise we require it to fit the ellipsis
|
||||||
if (w <= ourmax as i32 && pos == 0) || (w + ell_width <= ourmax as i32) {
|
if (w <= ourmax && pos == 0) || (w + ell_width <= ourmax) {
|
||||||
out = line.slice_from(pos);
|
out = line.slice_from(pos);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -191,23 +188,24 @@ impl<'args> StringSubCommand<'args> for Shorten<'args> {
|
||||||
streams.out.append1('\n');
|
streams.out.append1('\n');
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
/* Direction::Right */
|
/* shorten the right side */
|
||||||
// Going from the left.
|
// Going from the left.
|
||||||
// This is somewhat easier.
|
// This is somewhat easier.
|
||||||
while max <= ourmax && pos < line.len() {
|
while max <= ourmax && pos < line.len() {
|
||||||
pos += skip_escapes(&line, pos);
|
pos += skip_escapes(&line, pos);
|
||||||
let w = fish_wcwidth(line.char_at(pos));
|
let w = fish_wcwidth_visible(line.char_at(pos)) as isize;
|
||||||
if w <= 0 || max + w as usize + ell_width as usize <= ourmax {
|
if w <= 0 || max + w as usize + ell_width <= ourmax {
|
||||||
// If it still fits, even if it is the last, we add it.
|
// If it still fits, even if it is the last, we add it.
|
||||||
max += w as usize;
|
max = max.saturating_add_signed(w);
|
||||||
pos += 1;
|
pos += 1;
|
||||||
} else {
|
} else {
|
||||||
// We're at the limit, so see if the entire string fits.
|
// We're at the limit, so see if the entire string fits.
|
||||||
let mut max2: usize = max + w as usize;
|
let mut max2 = max + w as usize;
|
||||||
let mut pos2 = pos + 1;
|
let mut pos2 = pos + 1;
|
||||||
while pos2 < line.len() {
|
while pos2 < line.len() {
|
||||||
pos2 += skip_escapes(&line, pos2);
|
pos2 += skip_escapes(&line, pos2);
|
||||||
max2 += fish_wcwidth(line.char_at(pos2)) as usize;
|
let w = fish_wcwidth_visible(line.char_at(pos2)) as isize;
|
||||||
|
max2 = max2.saturating_add_signed(w);
|
||||||
pos2 += 1;
|
pos2 += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,10 @@ string pad -c_ --width 5 longer-than-width-param x
|
||||||
string pad -c ab -w4 .
|
string pad -c ab -w4 .
|
||||||
# CHECKERR: string pad: Padding should be a character 'ab'
|
# CHECKERR: string pad: Padding should be a character 'ab'
|
||||||
|
|
||||||
|
# nonprintable characters does not make sense
|
||||||
|
string pad -c \u07 .
|
||||||
|
# CHECKERR: string pad: Invalid padding character of width zero {{'\a'}}
|
||||||
|
|
||||||
# Visible length. Let's start off simple, colors are ignored:
|
# Visible length. Let's start off simple, colors are ignored:
|
||||||
string length --visible (set_color red)abc
|
string length --visible (set_color red)abc
|
||||||
# CHECK: 3
|
# CHECK: 3
|
||||||
|
@ -945,6 +949,37 @@ string shorten foo foobar
|
||||||
# CHECK: foo
|
# CHECK: foo
|
||||||
# CHECK: fo…
|
# CHECK: fo…
|
||||||
|
|
||||||
|
# pad with a bell, it has zero width, that's fine
|
||||||
|
string shorten -c \a foo foobar | string escape
|
||||||
|
# CHECK: foo
|
||||||
|
# CHECK: foo\cg
|
||||||
|
|
||||||
|
string shorten -c \aw foo foobar | string escape
|
||||||
|
# CHECK: foo
|
||||||
|
# CHECK: fo\cgw
|
||||||
|
|
||||||
|
# backspace is fine!
|
||||||
|
string shorten -c \b foo foobar | string escape
|
||||||
|
# CHECK: foo
|
||||||
|
# CHECK: foo\b
|
||||||
|
|
||||||
|
string shorten -c \ba foo foobar | string escape
|
||||||
|
# CHECK: foo
|
||||||
|
# CHECK: fo\ba
|
||||||
|
|
||||||
|
string shorten -c cool\b\b\b\b foo foobar | string escape
|
||||||
|
# CHECK: foo
|
||||||
|
# CHECK: foocool\b\b\b\b
|
||||||
|
|
||||||
|
string shorten -c cool\b\b\b\b\b foo foobar | string escape
|
||||||
|
# CHECK: foo
|
||||||
|
# negative width ellipsis is fine
|
||||||
|
# CHECK: foocool\b\b\b\b\b
|
||||||
|
|
||||||
|
string shorten -c \a\aXX foo foobar | string escape
|
||||||
|
# CHECK: foo
|
||||||
|
# CHECK: f\cg\cgXX
|
||||||
|
|
||||||
# A weird case - our minimum width here is 1,
|
# A weird case - our minimum width here is 1,
|
||||||
# so everything that goes over the width becomes "x"
|
# so everything that goes over the width becomes "x"
|
||||||
for i in (seq 1 10)
|
for i in (seq 1 10)
|
||||||
|
@ -995,6 +1030,11 @@ string shorten -m6 (set_color blue)s(set_color red)t(set_color --bold brwhite)ri
|
||||||
# Note that red sequence that we still pass on because it's width 0.
|
# Note that red sequence that we still pass on because it's width 0.
|
||||||
# CHECK: \e\[34ms\e\[31mt\e\[1m\e\[37mrin\e\[31m…
|
# CHECK: \e\[34ms\e\[31mt\e\[1m\e\[37mrin\e\[31m…
|
||||||
|
|
||||||
|
# See that colors aren't counted in ellipsis
|
||||||
|
string shorten -c (set_color blue)s(set_color red)t(set_color --bold brwhite)rin(set_color red)g -m 8 abcdefghijklmno | string escape
|
||||||
|
# Renders like "abstring" in colors
|
||||||
|
# CHECK: ab\e\[34ms\e\[31mt\e\[1m\e\[37mrin\e\[31mg
|
||||||
|
|
||||||
set -l str (set_color blue)s(set_color red)t(set_color --bold brwhite)rin(set_color red)g(set_color yellow)-shorten
|
set -l str (set_color blue)s(set_color red)t(set_color --bold brwhite)rin(set_color red)g(set_color yellow)-shorten
|
||||||
for i in (seq 1 (string length -V -- $str))
|
for i in (seq 1 (string length -V -- $str))
|
||||||
set -l len (string shorten -m$i -- $str | string length -V)
|
set -l len (string shorten -m$i -- $str | string length -V)
|
||||||
|
@ -1042,3 +1082,55 @@ string shorten -m0 foo bar asodjsaoidj
|
||||||
# CHECK: foo
|
# CHECK: foo
|
||||||
# CHECK: bar
|
# CHECK: bar
|
||||||
# CHECK: asodjsaoidj
|
# CHECK: asodjsaoidj
|
||||||
|
|
||||||
|
# backspaces are weird
|
||||||
|
string shorten abc ab abcdef(string repeat -n 6 \b) | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# CHECK: ab
|
||||||
|
# this line has length zero, since backspace removes it all
|
||||||
|
# CHECK: abcdef\b\b\b\b\b\b
|
||||||
|
|
||||||
|
# due to an integer overflow this might truncate the third backspaced one, it should not
|
||||||
|
string shorten abc ab abcdef(string repeat -n 7 \b) | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# CHECK: ab
|
||||||
|
# this line has length zero, since backspace removes it all
|
||||||
|
# CHECK: abcdef\b\b\b\b\b\b\b
|
||||||
|
|
||||||
|
# due to an integer overflow this might truncate
|
||||||
|
string shorten abc \bab ab abcdef | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# backspace does not contribute length at the start
|
||||||
|
# CHECK: \bab
|
||||||
|
# CHECK: ab
|
||||||
|
# CHECK: a…
|
||||||
|
|
||||||
|
string shorten abc \babc ab abcdef | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# CHECK: \ba…
|
||||||
|
# CHECK: ab
|
||||||
|
# CHECK: a…
|
||||||
|
|
||||||
|
# non-printable-escape-chars (in this case bell)
|
||||||
|
string shorten abc ab abcdef(string repeat -n 6 \a) | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# CHECK: ab
|
||||||
|
# CHECK: a…
|
||||||
|
|
||||||
|
string shorten abc ab abcdef(string repeat -n 7 \a) | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# CHECK: ab
|
||||||
|
# CHECK: a…
|
||||||
|
|
||||||
|
string shorten abc \aab ab abcdef | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# non-printables have length 0
|
||||||
|
# CHECK: \cgab
|
||||||
|
# CHECK: ab
|
||||||
|
# CHECK: a…
|
||||||
|
|
||||||
|
string shorten abc \aabc ab abcdef | string escape
|
||||||
|
# CHECK: a…
|
||||||
|
# CHECK: \cga…
|
||||||
|
# CHECK: ab
|
||||||
|
# CHECK: a…
|
||||||
|
|
Loading…
Reference in a new issue