mirror of
https://github.com/martinvonz/jj.git
synced 2024-11-24 15:18:53 +00:00
text_util: add functions to truncate labeled text
Some checks are pending
binaries / Build binary artifacts (linux-aarch64-gnu, ubuntu-24.04, aarch64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-aarch64-musl, ubuntu-24.04, aarch64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-gnu, ubuntu-24.04, x86_64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-musl, ubuntu-24.04, x86_64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (macos-aarch64, macos-14, aarch64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (macos-x86_64, macos-13, x86_64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (win-x86_64, windows-2022, x86_64-pc-windows-msvc) (push) Waiting to run
nix / flake check (macos-14) (push) Waiting to run
nix / flake check (ubuntu-latest) (push) Waiting to run
build / build (, macos-13) (push) Waiting to run
build / build (, macos-14) (push) Waiting to run
build / build (, ubuntu-latest) (push) Waiting to run
build / build (, windows-latest) (push) Waiting to run
build / build (--all-features, ubuntu-latest) (push) Waiting to run
build / Build jj-lib without Git support (push) Waiting to run
build / Check protos (push) Waiting to run
build / Check formatting (push) Waiting to run
build / Check that MkDocs can build the docs (push) Waiting to run
build / Check that MkDocs can build the docs with Poetry 1.8 (push) Waiting to run
build / cargo-deny (advisories) (push) Waiting to run
build / cargo-deny (bans licenses sources) (push) Waiting to run
build / Clippy check (push) Waiting to run
Codespell / Codespell (push) Waiting to run
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Some checks are pending
binaries / Build binary artifacts (linux-aarch64-gnu, ubuntu-24.04, aarch64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-aarch64-musl, ubuntu-24.04, aarch64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-gnu, ubuntu-24.04, x86_64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-musl, ubuntu-24.04, x86_64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (macos-aarch64, macos-14, aarch64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (macos-x86_64, macos-13, x86_64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (win-x86_64, windows-2022, x86_64-pc-windows-msvc) (push) Waiting to run
nix / flake check (macos-14) (push) Waiting to run
nix / flake check (ubuntu-latest) (push) Waiting to run
build / build (, macos-13) (push) Waiting to run
build / build (, macos-14) (push) Waiting to run
build / build (, ubuntu-latest) (push) Waiting to run
build / build (, windows-latest) (push) Waiting to run
build / build (--all-features, ubuntu-latest) (push) Waiting to run
build / Build jj-lib without Git support (push) Waiting to run
build / Check protos (push) Waiting to run
build / Check formatting (push) Waiting to run
build / Check that MkDocs can build the docs (push) Waiting to run
build / Check that MkDocs can build the docs with Poetry 1.8 (push) Waiting to run
build / cargo-deny (advisories) (push) Waiting to run
build / cargo-deny (bans licenses sources) (push) Waiting to run
build / Clippy check (push) Waiting to run
Codespell / Codespell (push) Waiting to run
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
This will be used by truncate_start/end() template functions. I considered adding a template function that supports both padding and truncation, but the function interface looked a bit messy. There may be (max_width, ellipsis, left|middle|right) parameters for truncation, and (min_width, fill_char, left|center|right) for padding. I'm not going to add ellipsis and centering support, but it's weird if pad(center) implied truncate(middle).
This commit is contained in:
parent
8a1cdd9215
commit
0eb4fe8389
1 changed files with 204 additions and 0 deletions
|
@ -16,6 +16,7 @@ use std::borrow::Cow;
|
|||
use std::cmp;
|
||||
use std::io;
|
||||
|
||||
use bstr::ByteSlice as _;
|
||||
use unicode_width::UnicodeWidthChar as _;
|
||||
|
||||
use crate::formatter::FormatRecorder;
|
||||
|
@ -104,6 +105,13 @@ fn truncate_start_pos(text: &str, max_width: usize) -> (usize, usize) {
|
|||
)
|
||||
}
|
||||
|
||||
fn truncate_start_pos_bytes(text: &[u8], max_width: usize) -> (usize, usize) {
|
||||
truncate_start_pos_with_indices(
|
||||
text.char_indices().rev().map(|(_, end, c)| (end, c)),
|
||||
max_width,
|
||||
)
|
||||
}
|
||||
|
||||
fn truncate_start_pos_with_indices(
|
||||
char_indices_rev: impl Iterator<Item = (usize, char)>,
|
||||
max_width: usize,
|
||||
|
@ -125,6 +133,14 @@ fn truncate_end_pos(text: &str, max_width: usize) -> (usize, usize) {
|
|||
truncate_end_pos_with_indices(text.char_indices(), text.len(), max_width)
|
||||
}
|
||||
|
||||
fn truncate_end_pos_bytes(text: &[u8], max_width: usize) -> (usize, usize) {
|
||||
truncate_end_pos_with_indices(
|
||||
text.char_indices().map(|(start, _, c)| (start, c)),
|
||||
text.len(),
|
||||
max_width,
|
||||
)
|
||||
}
|
||||
|
||||
fn truncate_end_pos_with_indices(
|
||||
char_indices_fwd: impl Iterator<Item = (usize, char)>,
|
||||
text_len: usize,
|
||||
|
@ -198,6 +214,57 @@ fn trim_start_zero_width_chars(text: &str) -> &str {
|
|||
text.trim_start_matches(|c: char| c.width().unwrap_or(0) == 0)
|
||||
}
|
||||
|
||||
/// Returns bytes length of leading 0-width characters.
|
||||
fn count_start_zero_width_chars_bytes(text: &[u8]) -> usize {
|
||||
text.char_indices()
|
||||
.find(|(_, _, c)| c.width().unwrap_or(0) != 0)
|
||||
.map(|(start, _, _)| start)
|
||||
.unwrap_or(text.len())
|
||||
}
|
||||
|
||||
/// Writes text truncated to `max_width` by removing leading characters. Returns
|
||||
/// width of the truncated text, which may be shorter than `max_width`.
|
||||
///
|
||||
/// The input `recorded_content` should be a single-line text.
|
||||
pub fn write_truncated_start(
|
||||
formatter: &mut dyn Formatter,
|
||||
recorded_content: &FormatRecorder,
|
||||
max_width: usize,
|
||||
) -> io::Result<usize> {
|
||||
let data = recorded_content.data();
|
||||
let (start, truncated_width) = truncate_start_pos_bytes(data, max_width);
|
||||
let truncated_start = start + count_start_zero_width_chars_bytes(&data[start..]);
|
||||
recorded_content.replay_with(formatter, |formatter, range| {
|
||||
let start = cmp::max(range.start, truncated_start);
|
||||
if start < range.end {
|
||||
formatter.write_all(&data[start..range.end])?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(truncated_width)
|
||||
}
|
||||
|
||||
/// Writes text truncated to `max_width` by removing trailing characters.
|
||||
/// Returns width of the truncated text, which may be shorter than `max_width`.
|
||||
///
|
||||
/// The input `recorded_content` should be a single-line text.
|
||||
pub fn write_truncated_end(
|
||||
formatter: &mut dyn Formatter,
|
||||
recorded_content: &FormatRecorder,
|
||||
max_width: usize,
|
||||
) -> io::Result<usize> {
|
||||
let data = recorded_content.data();
|
||||
let (truncated_end, truncated_width) = truncate_end_pos_bytes(data, max_width);
|
||||
recorded_content.replay_with(formatter, |formatter, range| {
|
||||
let end = cmp::min(range.end, truncated_end);
|
||||
if range.start < end {
|
||||
formatter.write_all(&data[range.start..end])?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(truncated_width)
|
||||
}
|
||||
|
||||
/// Indents each line by the given prefix preserving labels.
|
||||
pub fn write_indented(
|
||||
formatter: &mut dyn Formatter,
|
||||
|
@ -536,6 +603,143 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_truncated_labeled() {
|
||||
let mut recorder = FormatRecorder::new();
|
||||
for (label, word) in [("red", "foo"), ("cyan", "bar")] {
|
||||
recorder.push_label(label).unwrap();
|
||||
write!(recorder, "{word}").unwrap();
|
||||
recorder.pop_label().unwrap();
|
||||
}
|
||||
|
||||
// Truncate start
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 6).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 5).map(|_| ())),
|
||||
@"[38;5;1moo[39m[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 3).map(|_| ())),
|
||||
@"[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 2).map(|_| ())),
|
||||
@"[38;5;6mar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
|
||||
// Truncate end
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 6).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m[38;5;6mbar[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 5).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m[38;5;6mba[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 3).map(|_| ())),
|
||||
@"[38;5;1mfoo[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 2).map(|_| ())),
|
||||
@"[38;5;1mfo[39m"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_truncated_non_ascii_chars() {
|
||||
let mut recorder = FormatRecorder::new();
|
||||
write!(recorder, "a\u{300}bc\u{300}一二三").unwrap();
|
||||
|
||||
// Truncate start
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 1).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 2).map(|_| ())),
|
||||
@"三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 3).map(|_| ())),
|
||||
@"三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 6).map(|_| ())),
|
||||
@"一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 7).map(|_| ())),
|
||||
@"c̀一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 9).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 10).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
|
||||
// Truncate end
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 1).map(|_| ())),
|
||||
@"à"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 4).map(|_| ())),
|
||||
@"àbc̀"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 5).map(|_| ())),
|
||||
@"àbc̀一"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 9).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 10).map(|_| ())),
|
||||
@"àbc̀一二三"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_truncated_empty_content() {
|
||||
let recorder = FormatRecorder::new();
|
||||
|
||||
// Truncate start
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_start(formatter, &recorder, 1).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
|
||||
// Truncate end
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 0).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
format_colored(|formatter| write_truncated_end(formatter, &recorder, 1).map(|_| ())),
|
||||
@""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_byte_line_to_words() {
|
||||
assert_eq!(split_byte_line_to_words(b""), vec![]);
|
||||
|
|
Loading…
Reference in a new issue