mirror of
https://github.com/dandavison/delta.git
synced 2026-10-12 02:12:41 +00:00
Make --show-config display all supported config keys
Include settings for disabled features, paint additional styles and decorations, and preserve hunk-header attributes. Run tests from the repository root: cargo test subcommands::show_config::tests cargo test Checks: cargo fmt --all --check; cargo clippy -- -D warnings
This commit is contained in:
+3
-5
@@ -136,15 +136,13 @@ pub fn run_app(
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
let _show_config = opt.show_config;
|
||||
let config = config::Config::from(opt);
|
||||
|
||||
if _show_config {
|
||||
if opt.show_config {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
subcommands::show_config::show_config(&config, &mut stdout)?;
|
||||
subcommands::show_config::show_config(opt, &mut stdout)?;
|
||||
return Ok(0);
|
||||
}
|
||||
let config = config::Config::from(opt);
|
||||
|
||||
// The following block structure is because of `writer` and related lifetimes:
|
||||
let pager_cfg = (&config).into();
|
||||
|
||||
+360
-26
@@ -1,5 +1,6 @@
|
||||
use std::io::Write;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::cli;
|
||||
@@ -10,7 +11,72 @@ use crate::paint::BgFillMethod;
|
||||
use crate::style;
|
||||
use crate::utils::bat::output::PagingMode;
|
||||
|
||||
pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
pub fn show_config(opt: cli::Opt, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
let options = option_values(&opt);
|
||||
let config = config::Config::from(opt);
|
||||
let mut output = Vec::new();
|
||||
write_config(&config, &mut output)?;
|
||||
write_additional_styles(&config, &mut output)?;
|
||||
write_decoration_styles(&config, &mut output)?;
|
||||
write_additional_values(&config, &mut output)?;
|
||||
for (key, value) in options {
|
||||
writeln!(output, " {key:29} = {value}")?;
|
||||
}
|
||||
for line in output.split_inclusive(|&byte| byte == b'\n').sorted() {
|
||||
writer.write_all(line)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn option_values(opt: &cli::Opt) -> Vec<(String, String)> {
|
||||
let mut options = Vec::new();
|
||||
macro_rules! values {
|
||||
($($field:ident),* $(,)?) => {
|
||||
$(options.push((stringify!($field).replace('_', "-"), opt.$field.to_string()));)*
|
||||
};
|
||||
}
|
||||
macro_rules! strings {
|
||||
($($field:ident),* $(,)?) => {
|
||||
$(options.push((stringify!($field).replace('_', "-"), format_option_value(&opt.$field)));)*
|
||||
};
|
||||
}
|
||||
macro_rules! optional_strings {
|
||||
($($field:ident),* $(,)?) => {
|
||||
$(options.push((stringify!($field).replace('_', "-"), format_option_value(opt.$field.as_deref().unwrap_or(""))));)*
|
||||
};
|
||||
}
|
||||
values!(
|
||||
dark,
|
||||
light,
|
||||
diff_highlight,
|
||||
diff_so_fancy,
|
||||
no_gitconfig,
|
||||
raw
|
||||
);
|
||||
strings!(
|
||||
config,
|
||||
blame_separator_format,
|
||||
wrap_max_lines,
|
||||
wrap_right_percent,
|
||||
);
|
||||
optional_strings!(features, map_styles);
|
||||
options.push((
|
||||
"file-transformation".to_string(),
|
||||
format_option_value(opt.file_regex_replacement.as_deref().unwrap_or("")),
|
||||
));
|
||||
options.push((
|
||||
"detect-dark-light".to_string(),
|
||||
opt.detect_dark_light
|
||||
.to_possible_value()
|
||||
.unwrap()
|
||||
.get_name()
|
||||
.to_string(),
|
||||
));
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
fn write_config(config: &config::Config, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
// styles first
|
||||
writeln!(
|
||||
writer,
|
||||
@@ -37,7 +103,34 @@ pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::
|
||||
.join(" "),
|
||||
commit_style = config.commit_style.to_painted_string(),
|
||||
file_style = config.file_style.to_painted_string(),
|
||||
hunk_header_style = config.hunk_header_style.to_painted_string(),
|
||||
hunk_header_style = config.hunk_header_style.paint(format!(
|
||||
"{}{}{}{}",
|
||||
if matches!(
|
||||
config.hunk_header_style_include_file_path,
|
||||
config::HunkHeaderIncludeFilePath::Yes
|
||||
) {
|
||||
"file "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if matches!(
|
||||
config.hunk_header_style_include_line_number,
|
||||
config::HunkHeaderIncludeLineNumber::Yes
|
||||
) {
|
||||
"line-number "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if matches!(
|
||||
config.hunk_header_style_include_code_fragment,
|
||||
config::HunkHeaderIncludeCodeFragment::No
|
||||
) {
|
||||
"omit-code-fragment "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
config.hunk_header_style,
|
||||
)),
|
||||
minus_emph_style = config.minus_emph_style.to_painted_string(),
|
||||
minus_empty_line_marker_style = config.minus_empty_line_marker_style.to_painted_string(),
|
||||
minus_non_emph_style = config.minus_non_emph_style.to_painted_string(),
|
||||
@@ -72,13 +165,11 @@ pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::
|
||||
" hyperlinks = {hyperlinks}",
|
||||
hyperlinks = config.hyperlinks
|
||||
)?;
|
||||
if config.hyperlinks {
|
||||
writeln!(
|
||||
writer,
|
||||
" hyperlinks-file-link-format = {hyperlinks_file_link_format}",
|
||||
hyperlinks_file_link_format = format_option_value(&config.hyperlinks_file_link_format),
|
||||
)?
|
||||
}
|
||||
writeln!(
|
||||
writer,
|
||||
" hyperlinks-file-link-format = {hyperlinks_file_link_format}",
|
||||
hyperlinks_file_link_format = format_option_value(&config.hyperlinks_file_link_format),
|
||||
)?;
|
||||
writeln!(
|
||||
writer,
|
||||
" inspect-raw-lines = {inspect_raw_lines}
|
||||
@@ -94,27 +185,23 @@ pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::
|
||||
" line-numbers = {line_numbers}",
|
||||
line_numbers = config.line_numbers
|
||||
)?;
|
||||
if config.line_numbers {
|
||||
writeln!(
|
||||
writer,
|
||||
" line-numbers-minus-style = {line_numbers_minus_style}
|
||||
writeln!(
|
||||
writer,
|
||||
" line-numbers-minus-style = {line_numbers_minus_style}
|
||||
line-numbers-zero-style = {line_numbers_zero_style}
|
||||
line-numbers-plus-style = {line_numbers_plus_style}
|
||||
line-numbers-left-style = {line_numbers_left_style}
|
||||
line-numbers-right-style = {line_numbers_right_style}
|
||||
line-numbers-left-format = {line_numbers_left_format}
|
||||
line-numbers-right-format = {line_numbers_right_format}",
|
||||
line_numbers_minus_style =
|
||||
config.line_numbers_style_minusplus[Minus].to_painted_string(),
|
||||
line_numbers_zero_style = config.line_numbers_zero_style.to_painted_string(),
|
||||
line_numbers_plus_style = config.line_numbers_style_minusplus[Plus].to_painted_string(),
|
||||
line_numbers_left_style = config.line_numbers_style_leftright[Left].to_painted_string(),
|
||||
line_numbers_right_style =
|
||||
config.line_numbers_style_leftright[Right].to_painted_string(),
|
||||
line_numbers_left_format = format_option_value(&config.line_numbers_format[Left]),
|
||||
line_numbers_right_format = format_option_value(&config.line_numbers_format[Right]),
|
||||
)?
|
||||
}
|
||||
line_numbers_minus_style = config.line_numbers_style_minusplus[Minus].to_painted_string(),
|
||||
line_numbers_zero_style = config.line_numbers_zero_style.to_painted_string(),
|
||||
line_numbers_plus_style = config.line_numbers_style_minusplus[Plus].to_painted_string(),
|
||||
line_numbers_left_style = config.line_numbers_style_leftright[Left].to_painted_string(),
|
||||
line_numbers_right_style = config.line_numbers_style_leftright[Right].to_painted_string(),
|
||||
line_numbers_left_format = format_option_value(&config.line_numbers_format[Left]),
|
||||
line_numbers_right_format = format_option_value(&config.line_numbers_format[Right]),
|
||||
)?;
|
||||
writeln!(
|
||||
writer,
|
||||
" max-line-distance = {max_line_distance}
|
||||
@@ -165,6 +252,192 @@ pub fn show_config(config: &config::Config, writer: &mut dyn Write) -> std::io::
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_additional_styles(config: &config::Config, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
for (key, style) in [
|
||||
("blame-code-style", config.blame_code_style),
|
||||
("blame-separator-style", config.blame_separator_style),
|
||||
(
|
||||
"grep-context-line-style",
|
||||
Some(config.grep_context_line_style),
|
||||
),
|
||||
(
|
||||
"grep-header-file-style",
|
||||
Some(config.classic_grep_header_file_style),
|
||||
),
|
||||
("grep-match-line-style", Some(config.grep_match_line_style)),
|
||||
("grep-match-word-style", Some(config.grep_match_word_style)),
|
||||
(
|
||||
"hunk-header-file-style",
|
||||
Some(config.hunk_header_file_style),
|
||||
),
|
||||
(
|
||||
"hunk-header-line-number-style",
|
||||
Some(config.hunk_header_line_number_style),
|
||||
),
|
||||
("inline-hint-style", Some(config.inline_hint_style)),
|
||||
(
|
||||
"merge-conflict-ours-diff-header-style",
|
||||
Some(config.merge_conflict_ours_diff_header_style),
|
||||
),
|
||||
(
|
||||
"merge-conflict-theirs-diff-header-style",
|
||||
Some(config.merge_conflict_theirs_diff_header_style),
|
||||
),
|
||||
] {
|
||||
let value = style
|
||||
.map(|style| style.to_painted_string().to_string())
|
||||
.unwrap_or_else(|| "''".to_string());
|
||||
writeln!(writer, " {key:29} = {value}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_decoration_styles(config: &config::Config, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
let grep_header_style = match config.grep_output_type {
|
||||
Some(config::GrepType::Ripgrep) => config.ripgrep_header_style,
|
||||
_ => config.classic_grep_header_style,
|
||||
};
|
||||
for (key, style) in [
|
||||
(
|
||||
"commit-decoration-style",
|
||||
config.commit_style.decoration_style,
|
||||
),
|
||||
("file-decoration-style", config.file_style.decoration_style),
|
||||
(
|
||||
"hunk-header-decoration-style",
|
||||
config.hunk_header_style.decoration_style,
|
||||
),
|
||||
(
|
||||
"grep-header-decoration-style",
|
||||
grep_header_style.decoration_style,
|
||||
),
|
||||
(
|
||||
"merge-conflict-ours-diff-header-decoration-style",
|
||||
config
|
||||
.merge_conflict_ours_diff_header_style
|
||||
.decoration_style,
|
||||
),
|
||||
(
|
||||
"merge-conflict-theirs-diff-header-decoration-style",
|
||||
config
|
||||
.merge_conflict_theirs_diff_header_style
|
||||
.decoration_style,
|
||||
),
|
||||
] {
|
||||
writeln!(writer, " {key:29} = {}", painted_decoration_style(style))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_additional_values(config: &config::Config, writer: &mut dyn Write) -> std::io::Result<()> {
|
||||
for (key, value) in [
|
||||
("blame-format", config.blame_format.as_str()),
|
||||
(
|
||||
"blame-timestamp-format",
|
||||
config.blame_timestamp_format.as_str(),
|
||||
),
|
||||
(
|
||||
"blame-timestamp-output-format",
|
||||
config
|
||||
.blame_timestamp_output_format
|
||||
.as_deref()
|
||||
.unwrap_or(""),
|
||||
),
|
||||
("default-language", config.default_language.as_str()),
|
||||
("diff-args", config.diff_args.as_str()),
|
||||
("file-copied-label", config.file_copied_label.as_str()),
|
||||
(
|
||||
"grep-separator-symbol",
|
||||
config.grep_separator_symbol.as_str(),
|
||||
),
|
||||
(
|
||||
"grep-output-type",
|
||||
match config.grep_output_type {
|
||||
Some(config::GrepType::Classic) => "classic",
|
||||
Some(config::GrepType::Ripgrep) => "ripgrep",
|
||||
None => "",
|
||||
},
|
||||
),
|
||||
("hunk-label", config.hunk_label.as_str()),
|
||||
(
|
||||
"hyperlinks-commit-link-format",
|
||||
config
|
||||
.hyperlinks_commit_link_format
|
||||
.as_deref()
|
||||
.unwrap_or(""),
|
||||
),
|
||||
(
|
||||
"merge-conflict-begin-symbol",
|
||||
config.merge_conflict_begin_symbol.as_str(),
|
||||
),
|
||||
(
|
||||
"merge-conflict-end-symbol",
|
||||
config.merge_conflict_end_symbol.as_str(),
|
||||
),
|
||||
] {
|
||||
writeln!(writer, " {key:29} = {}", format_option_value(value))?;
|
||||
}
|
||||
for (key, value) in [
|
||||
("wrap-left-symbol", config.wrap_config.left_symbol.as_str()),
|
||||
(
|
||||
"wrap-right-symbol",
|
||||
config.wrap_config.right_symbol.as_str(),
|
||||
),
|
||||
(
|
||||
"wrap-right-prefix-symbol",
|
||||
config.wrap_config.right_prefix_symbol.as_str(),
|
||||
),
|
||||
] {
|
||||
writeln!(
|
||||
writer,
|
||||
" {key:29} = {}",
|
||||
config.inline_hint_style.paint(format_option_value(value))
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
writer,
|
||||
" {:29} = {}",
|
||||
"commit-regex",
|
||||
format_option_value(config.commit_regex.as_str())
|
||||
)?;
|
||||
writeln!(writer, " {:29} = {}", "color-only", config.color_only)?;
|
||||
writeln!(
|
||||
writer,
|
||||
" {:29} = {}",
|
||||
"relative-paths", config.relative_paths
|
||||
)?;
|
||||
writeln!(
|
||||
writer,
|
||||
" {:29} = {}",
|
||||
"line-buffer-size", config.line_buffer_size
|
||||
)?;
|
||||
writeln!(
|
||||
writer,
|
||||
" {:29} = {}",
|
||||
"max-syntax-highlighting-length", config.max_syntax_length
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn painted_decoration_style(decoration: style::DecorationStyle) -> String {
|
||||
use style::DecorationStyle::*;
|
||||
let (ansi_term_style, attributes) = match decoration {
|
||||
NoDecoration => return "none".to_string(),
|
||||
Box(style) => (style, "box"),
|
||||
Underline(style) => (style, "ul"),
|
||||
Overline(style) => (style, "ol"),
|
||||
UnderOverline(style) => (style, "ul ol"),
|
||||
BoxWithUnderline(style) => (style, "box ul"),
|
||||
BoxWithOverline(style) => (style, "box ol"),
|
||||
BoxWithUnderOverline(style) => (style, "box ul ol"),
|
||||
};
|
||||
let style = style::Style {
|
||||
ansi_term_style,
|
||||
..style::Style::default()
|
||||
};
|
||||
style.paint(format!("{style} {attributes}")).to_string()
|
||||
}
|
||||
|
||||
// Heuristics determining whether to quote string option values when printing values intended for
|
||||
// git config.
|
||||
fn format_option_value<S>(s: S) -> String
|
||||
@@ -185,6 +458,8 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::tests::integration_test_utils;
|
||||
|
||||
use super::*;
|
||||
@@ -193,9 +468,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_show_config() {
|
||||
let config = integration_test_utils::make_config_from_args(&[]);
|
||||
let opt = integration_test_utils::make_options_from_args(&[]);
|
||||
let mut writer = Cursor::new(vec![0; 1024]);
|
||||
show_config(&config, &mut writer).unwrap();
|
||||
show_config(opt, &mut writer).unwrap();
|
||||
let mut s = String::new();
|
||||
writer.rewind().unwrap();
|
||||
writer.read_to_string(&mut s).unwrap();
|
||||
@@ -203,4 +478,63 @@ mod tests {
|
||||
assert!(s.contains(" commit-style = raw\n"));
|
||||
assert!(s.contains(r" word-diff-regex = '\w+'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_show_config_includes_all_config_keys() {
|
||||
let opt = integration_test_utils::make_options_from_args(&[]);
|
||||
let mut writer = Vec::new();
|
||||
show_config(opt, &mut writer).unwrap();
|
||||
let output = String::from_utf8(writer).unwrap();
|
||||
let output = ansi::strip_ansi_codes(&output);
|
||||
let keys: Vec<_> = output
|
||||
.lines()
|
||||
.map(|line| line.split_once('=').unwrap().0.trim())
|
||||
.collect();
|
||||
let actual: BTreeSet<_> = keys.iter().copied().collect();
|
||||
assert_eq!(keys.len(), actual.len(), "duplicate config keys");
|
||||
let names = cli::Opt::get_argument_and_option_names();
|
||||
let expected: BTreeSet<_> = names
|
||||
.values()
|
||||
.map(String::as_str)
|
||||
.filter(|name| {
|
||||
!matches!(
|
||||
*name,
|
||||
"24-bit-color" | "parse-ansi" | "show-colors" | "show-themes"
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(actual, expected);
|
||||
assert!(
|
||||
keys.windows(2).all(|pair| pair[0] < pair[1]),
|
||||
"config keys are not sorted alphabetically"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_show_config_paints_decoration_and_additional_styles() {
|
||||
let opt = integration_test_utils::make_options_from_args(&[
|
||||
"--file-decoration-style=bold red blue box ul ol",
|
||||
"--grep-context-line-style=italic green",
|
||||
"--hunk-header-style=file line-number omit-code-fragment green",
|
||||
]);
|
||||
let mut writer = Vec::new();
|
||||
show_config(opt, &mut writer).unwrap();
|
||||
let output = String::from_utf8(writer).unwrap();
|
||||
for (key, value) in [
|
||||
("file-decoration-style", "bold red blue box ul ol"),
|
||||
("grep-context-line-style", "italic green"),
|
||||
(
|
||||
"hunk-header-style",
|
||||
"file line-number omit-code-fragment green",
|
||||
),
|
||||
] {
|
||||
let line = output
|
||||
.lines()
|
||||
.find(|line| line.trim_start().starts_with(key))
|
||||
.unwrap();
|
||||
let printed_value = line.split_once('=').unwrap().1.trim();
|
||||
assert_eq!(ansi::strip_ansi_codes(printed_value), value);
|
||||
assert!(printed_value.contains('\x1b'), "{} is not painted", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user