Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: truncate paths in hdr box #956

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ lazy_static = { version = "1.5.0", optional = true }
test-case = "3.3.1"
pretty_assertions = "1.4.1"
mockall = "0.13.0"
rstest = "0.22"

# We need the backtrace feature to enable snapshot name generation in
# single-threaded tests (tests using cargo cross run single-threaded due to
# limitations with QEMU).
insta = { version = "1.40.0" }
rstest = "0.22.0"

[target.'cfg(target_os = "windows")'.dependencies]
# We use directories next to get the windows config path
Expand Down
3 changes: 3 additions & 0 deletions assets/sample_config.json5
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"underline": false,
"prefix": "-",
},
// Truncate the diffed filepaths to the terminal width.
"truncate-paths-to-term-width": true,
},
// We can also define custom render modes which are defined as a
// key-value mapping of tags to rendering configs.
Expand Down Expand Up @@ -72,6 +74,7 @@
"underline": false,
"prefix": "-",
},
"truncate-paths-to-term-width": true,
},
},
},
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod input_processing;
pub mod neg_idx_vec;
pub mod parse;
pub mod render;
pub mod string_utils;

use anyhow::Result;
use input_processing::VectorData;
Expand Down
68 changes: 52 additions & 16 deletions src/render/unified.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::diff::{Hunk, Line, RichHunk, RichHunks};
use crate::render::{
default_option, opt_color_def, ColorDef, DisplayData, EmphasizedStyle, RegularStyle, Renderer,
use crate::{
concat_string,
diff::{Hunk, Line, RichHunk, RichHunks},
render::{
default_option, opt_color_def, ColorDef, DisplayData, EmphasizedStyle, RegularStyle,
Renderer,
},
string_utils::truncate_str,
};
use anyhow::Result;
use console::{Color, Style, Term};
Expand All @@ -14,6 +19,9 @@ const TITLE_SEPARATOR: &str = "=";
/// The ascii separator used after the hunk title
const HUNK_TITLE_SEPARATOR: &str = "-";

/// The string to fill in when truncating paths that are too long for the term width.
const PATH_TRUNCATION_STR: &str = "...";

/// Something similar to the unified diff format.
///
/// NOTE: is a huge misnomer because this isn't really a unified diff.
Expand All @@ -26,6 +34,16 @@ const HUNK_TITLE_SEPARATOR: &str = "-";
pub struct Unified {
pub addition: TextStyle,
pub deletion: TextStyle,

/// Whether to truncate displayed paths to fit the terminal width.
///
/// When diffsitter displays this diff, it will show the filenames being compared first. If the
/// length of a filename exceeds the width of the terminal, diffsitter will truncate the
/// filename so it fits within the reported terminal width. If this is disabled, no truncation
/// will be applied and the path length might wrap around.
///
/// Truncation will not apply if diffsitter can't detect the terminal width.
pub truncate_paths_to_term_width: bool,
}

/// Text style options for additions or deleetions.
Expand Down Expand Up @@ -70,6 +88,7 @@ impl Default for Unified {
underline: false,
prefix: "- ".into(),
},
truncate_paths_to_term_width: true,
}
}
}
Expand Down Expand Up @@ -182,14 +201,14 @@ impl Unified {
let title_len = format!("{old_fname}{divider}{new_fname}").len();
// Set terminal width equal to the title length if there is no terminal info is available, then the title will
// stack horizontally be default
let term_width = if let Some(term_info) = term_info {
let (term_width, detected_term_width) = if let Some(term_info) = term_info {
if let Some((_height, width)) = term_info.size_checked() {
width.into()
(width.into(), true)
} else {
title_len
(title_len, false)
}
} else {
title_len
(title_len, false)
};
// We only display the horizontal title format if we know we have enough horizontal space
// to display it. If we can't determine the terminal width, play it safe and default to
Expand All @@ -207,21 +226,38 @@ impl Unified {
let (styled_title_str, title_sep) = match stack_style {
TitleStack::Horizontal => {
let title_len = old_fname.len() + divider.len() + new_fname.len();
let styled_title_str = format!(
"{}{}{}",
old_fmt.regular.0.apply_to(old_fname),
let styled_title_str = concat_string!(
old_fmt.regular.0.apply_to(old_fname).to_string(),
divider,
new_fmt.regular.0.apply_to(new_fname)
new_fmt.regular.0.apply_to(new_fname).to_string(),
);
let title_sep = TITLE_SEPARATOR.repeat(title_len);
(styled_title_str, title_sep)
}
TitleStack::Vertical => {
let title_len = max(old_fname.len(), new_fname.len());
let styled_title_str = format!(
"{}\n{}",
old_fmt.regular.0.apply_to(old_fname),
new_fmt.regular.0.apply_to(new_fname)
// Truncate a string if the user config option is set and we found the terminal
// width.
let maybe_truncate = |fname: &str| {
if self.truncate_paths_to_term_width && detected_term_width {
truncate_str(old_fname, term_width, PATH_TRUNCATION_STR)
} else {
fname.to_string()
}
};

// Possibly truncate a string (based on config options and term info) and apply
// the given regular style to it.
let style_maybe_trunc = |fname: &str, fmt: &FormattingDirectives| {
let s = maybe_truncate(fname);
fmt.regular.0.apply_to(&s).to_string()
};
let old_fname_to_display = maybe_truncate(old_fname);
let new_fname_to_display = maybe_truncate(new_fname);
let title_len = max(old_fname_to_display.len(), new_fname_to_display.len());
let styled_title_str = concat_string!(
style_maybe_trunc(&old_fname_to_display, old_fmt),
"\n",
style_maybe_trunc(&new_fname_to_display, new_fmt),
);
let title_sep = TITLE_SEPARATOR.repeat(title_len);
(styled_title_str, title_sep)
Expand Down
119 changes: 119 additions & 0 deletions src/string_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/// Truncate a string down to `width` using some fill string.
///
/// This will return the string as-is if it's shorter than the truncation length. That will
/// allocate a new string, because of this function's type signature.
///
/// # Arguments
///
/// - s: The input string that might be truncated.
/// - width: The maximum width of the string.
/// - fill: The placeholder string to use to represent the middle of the string, if it's truncated.
///
/// # Examples
///
/// ```rust
/// # use libdiffsitter::string_utils::truncate_str;
/// let input_str = "hello, world!";
/// let result = truncate_str(&input_str, 7, "...");
/// assert_eq!(result, "he...d!");
/// ```
///
/// # Panics
///
/// If the `fill` string is longer than the provided `width`.
pub fn truncate_str(s: &str, width: usize, fill: &str) -> String {
if fill.len() > width {
panic!(
"The provided fill string (len: {}) is longer than the truncation width ({})",
fill.len(),
width
);
}
if s.len() <= width {
return s.into();
}
// We want to take roughly an equal amount from the front and back of the string.
let length_to_take = (width - fill.len()) / 2;
// Index to take from for the latter half of the string.
let end_idx = s.len() - length_to_take;
format!("{}{}{}", &s[..length_to_take], fill, &s[end_idx..])
}

/// Create a string from multiple objects that support `AsRef<str>`.
///
/// This was lifted from
/// [concat-string](https://github.com/FaultyRAM/concat-string/blob/942a4aa8244d5ff00dd9b9a34ecd0484feaf9a7f/src/lib.rs#L69C1-L79C2)
/// and incorporates a [proposed PR](https://github.com/FaultyRAM/concat-string/pull/1.) to add
/// support for trailing commas.
///
/// This is supposed to be pretty efficient, compared to all of the string concatenation techniques
/// in Rust according to some [benchmarks](https://github.com/hoodie/concatenation_benchmarks-rs).
#[macro_export]
macro_rules! concat_string {
() => { String::with_capacity(0) };
($($s:expr),+ $(,)?) => {{
use std::ops::AddAssign;
let mut len = 0;
$(len.add_assign(AsRef::<str>::as_ref(&$s).len());)+
let mut buf = String::with_capacity(len);
$(buf.push_str($s.as_ref());)+
buf
}};
}

#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_str_eq;
use rstest::*;

#[rstest]
#[case("12345", 4, "..", "1..5")]
#[case("12345", 4, "||", "1||5")]
#[case("12345", 5, "...", "12345")]
#[case("short", 1000, "..", "short")]
#[case("/some/large/path", 10, "..", "/som..path")]
fn test_truncate_str(
#[case] input_str: &str,
#[case] width: usize,
#[case] fill: &str,
#[case] expected: &str,
) {
let actual = truncate_str(&input_str, width, &fill);
assert_str_eq!(actual, expected);
}

#[test]
#[should_panic]
fn test_bad_fill_length() {
truncate_str(".", 1, "ahh too long!");
}

// Concat string tests were copied from
// https://github.com/FaultyRAM/concat-string/blob/942a4aa8244d5ff00dd9b9a34ecd0484feaf9a7f/src/lib.rs

#[test]
fn concat_string_0_args() {
let s = concat_string!();
assert_eq!(s, String::from(""));
}

#[test]
fn concat_string_1_arg() {
let s = concat_string!("foo");
assert_eq!(s, String::from("foo"));
}

#[test]
fn concat_string_str_string() {
// Skipping formatting here because we want to test that the trailing comma works
#[rustfmt::skip]
let s = {
concat_string!(
"foo",
String::from("bar"),
)
};
assert_eq!(s, String::from("foobar"));
}
}
Loading