From 287c4b51263f58afff33dee448becb136cffbdd4 Mon Sep 17 00:00:00 2001 From: tinger Date: Mon, 6 May 2024 18:23:36 +0200 Subject: [PATCH] cli: add `ui.color = "debug"` When using `ui.color = "debug"`, changes in the output style additionally include delimiters << and >>, as well as all active labels at this point separated by ::. The output is otherwise unformatted and the delimiters and labels inherit the style of the content they apply to. --- CHANGELOG.md | 2 + cli/src/cli_util.rs | 2 +- cli/src/config-schema.json | 1 + cli/src/formatter.rs | 85 ++++++++++++++++++++++++++++++-- cli/src/ui.rs | 23 +++++++-- cli/tests/cli-reference@.md.snap | 2 +- cli/tests/test_global_opts.rs | 2 +- docs/config.md | 5 +- 8 files changed, 109 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9700c7f75b..4aea072016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Conflict markers now include an explanation of what each part of the conflict represents. +* `ui.color = "debug"` prints active labels alongside the regular colored output. + ### Fixed bugs ## [0.17.0] - 2024-05-01 diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index d32750ce9d..c08f2b725e 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -2407,7 +2407,7 @@ pub struct GlobalArgs { #[derive(clap::Args, Clone, Debug)] pub struct EarlyArgs { - /// When to colorize output (always, never, auto) + /// When to colorize output (always, never, debug, auto) #[arg(long, value_name = "WHEN", global = true)] pub color: Option, /// Silence non-primary command output diff --git a/cli/src/config-schema.json b/cli/src/config-schema.json index 6aad73449e..7ccb03bd9e 100644 --- a/cli/src/config-schema.json +++ b/cli/src/config-schema.json @@ -77,6 +77,7 @@ "enum": [ "always", "never", + "debug", "auto" ], "default": "auto" diff --git a/cli/src/formatter.rs b/cli/src/formatter.rs index 965b3d48e4..af68b7b8a1 100644 --- a/cli/src/formatter.rs +++ b/cli/src/formatter.rs @@ -132,18 +132,19 @@ pub struct FormatterFactory { enum FormatterFactoryKind { PlainText, Sanitized, - Color { rules: Arc }, + Color { rules: Arc, debug: bool }, } impl FormatterFactory { pub fn prepare( config: &config::Config, + debug: bool, color: bool, sanitized: bool, ) -> Result { let kind = if color { let rules = Arc::new(rules_from_config(config)?); - FormatterFactoryKind::Color { rules } + FormatterFactoryKind::Color { rules, debug } } else if sanitized { FormatterFactoryKind::Sanitized } else { @@ -159,8 +160,12 @@ impl FormatterFactory { match &self.kind { FormatterFactoryKind::PlainText => Box::new(PlainTextFormatter::new(output)), FormatterFactoryKind::Sanitized => Box::new(SanitizingFormatter::new(output)), - FormatterFactoryKind::Color { rules } => { - Box::new(ColorFormatter::new(output, rules.clone())) + FormatterFactoryKind::Color { rules, debug } => { + if *debug { + Box::new(DebugFormatter::new(output, rules.clone())) + } else { + Box::new(ColorFormatter::new(output, rules.clone())) + } } } } @@ -522,6 +527,58 @@ impl Drop for ColorFormatter { } } +pub struct DebugFormatter { + formatter: ColorFormatter, +} + +impl DebugFormatter { + pub fn new(output: W, rules: Arc) -> Self { + Self { + formatter: ColorFormatter::new(output, rules), + } + } + + pub fn for_config(output: W, config: &config::Config) -> Result { + let rules = rules_from_config(config)?; + Ok(Self::new(output, Arc::new(rules))) + } +} + +impl Write for DebugFormatter { + fn write(&mut self, data: &[u8]) -> Result { + let labels = self.formatter.labels.clone(); + write!(&mut self.formatter, "<<")?; + for (idx, label) in labels.iter().enumerate() { + if idx != 0 { + write!(&mut self.formatter, " ")?; + } + write!(&mut self.formatter, "{}", label)?; + } + write!(&mut self.formatter, "::")?; + self.formatter.write(data)?; + write!(&mut self.formatter, ">>")?; + Ok(data.len()) + } + + fn flush(&mut self) -> Result<(), Error> { + self.formatter.flush() + } +} + +impl Formatter for DebugFormatter { + fn raw(&mut self) -> &mut dyn Write { + self.formatter.raw() + } + + fn push_label(&mut self, label: &str) -> io::Result<()> { + self.formatter.push_label(label) + } + + fn pop_label(&mut self) -> io::Result<()> { + self.formatter.pop_label() + } +} + /// Like buffered formatter, but records `push`/`pop_label()` calls. /// /// This allows you to manipulate the recorded data without losing labels. @@ -1095,6 +1152,26 @@ mod tests { insta::assert_snapshot!(String::from_utf8(output).unwrap(), @" inside "); } + #[test] + fn test_debug_formatter() { + // Behaves like the color formatter, but surrounds each write with <<...>>, + // adding the active labels before the actual content separated by a ::. + let config = config_from_string( + r#" + colors.outer = "green" + "#, + ); + let mut output: Vec = vec![]; + let mut formatter = DebugFormatter::for_config(&mut output, &config).unwrap(); + formatter.push_label("outer").unwrap(); + formatter.push_label("inner").unwrap(); + write!(formatter, " inside ").unwrap(); + formatter.pop_label().unwrap(); + formatter.pop_label().unwrap(); + drop(formatter); + insta::assert_snapshot!(String::from_utf8(output).unwrap(), @"<>"); + } + #[test] fn test_heading_labeled_writer() { let config = config_from_string( diff --git a/cli/src/ui.rs b/cli/src/ui.rs index 4907b4940e..59a3c70a3d 100644 --- a/cli/src/ui.rs +++ b/cli/src/ui.rs @@ -162,6 +162,7 @@ impl Write for UiStderr<'_> { } pub struct Ui { + debug: bool, color: bool, quiet: bool, pager_cmd: CommandNameAndArgs, @@ -179,6 +180,7 @@ fn progress_indicator_setting(config: &config::Config) -> bool { pub enum ColorChoice { Always, Never, + Debug, #[default] Auto, } @@ -190,6 +192,7 @@ impl FromStr for ColorChoice { match s { "always" => Ok(ColorChoice::Always), "never" => Ok(ColorChoice::Never), + "debug" => Ok(ColorChoice::Debug), "auto" => Ok(ColorChoice::Auto), _ => Err("must be one of always, never, or auto"), } @@ -201,6 +204,7 @@ impl fmt::Display for ColorChoice { let s = match self { ColorChoice::Always => "always", ColorChoice::Never => "never", + ColorChoice::Debug => "debug", ColorChoice::Auto => "auto", }; write!(f, "{s}") @@ -215,10 +219,15 @@ fn color_setting(config: &config::Config) -> ColorChoice { .unwrap_or_default() } +fn debug_color(choice: ColorChoice) -> bool { + matches!(choice, ColorChoice::Debug) +} + fn use_color(choice: ColorChoice) -> bool { match choice { ColorChoice::Always => true, ColorChoice::Never => false, + ColorChoice::Debug => true, ColorChoice::Auto => io::stdout().is_terminal(), } } @@ -249,14 +258,17 @@ fn pager_setting(config: &config::Config) -> Result Result { - let color = use_color(color_setting(config)); + let color = color_setting(config); + let debug = debug_color(color); + let color = use_color(color); let quiet = be_quiet(config); // Sanitize ANSI escape codes if we're printing to a terminal. Doesn't affect // ANSI escape codes that originate from the formatter itself. let sanitize = io::stdout().is_terminal(); - let formatter_factory = FormatterFactory::prepare(config, color, sanitize)?; + let formatter_factory = FormatterFactory::prepare(config, debug, color, sanitize)?; let progress_indicator = progress_indicator_setting(config); Ok(Ui { + debug, color, quiet, formatter_factory, @@ -268,13 +280,16 @@ impl Ui { } pub fn reset(&mut self, config: &config::Config) -> Result<(), CommandError> { - self.color = use_color(color_setting(config)); + let color = color_setting(config); + self.debug = debug_color(color); + self.color = use_color(color); self.quiet = be_quiet(config); self.paginate = pagination_setting(config)?; self.pager_cmd = pager_setting(config)?; self.progress_indicator = progress_indicator_setting(config); let sanitize = io::stdout().is_terminal(); - self.formatter_factory = FormatterFactory::prepare(config, self.color, sanitize)?; + self.formatter_factory = + FormatterFactory::prepare(config, self.debug, self.color, sanitize)?; Ok(()) } diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index e97ae17665..e1e818a4c4 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -164,7 +164,7 @@ To get started, see the tutorial at https://github.com/martinvonz/jj/blob/main/d Possible values: `true`, `false` -* `--color ` — When to colorize output (always, never, auto) +* `--color ` — When to colorize output (always, never, debug, auto) * `--quiet` — Silence non-primary command output Possible values: `true`, `false` diff --git a/cli/tests/test_global_opts.rs b/cli/tests/test_global_opts.rs index a0c145de54..b937ea3985 100644 --- a/cli/tests/test_global_opts.rs +++ b/cli/tests/test_global_opts.rs @@ -595,7 +595,7 @@ fn test_help() { --ignore-immutable Allow rewriting immutable commits --at-operation Operation to load the repo at [default: @] [aliases: at-op] --debug Enable debug logging - --color When to colorize output (always, never, auto) + --color When to colorize output (always, never, debug, auto) --quiet Silence non-primary command output --no-pager Disable the pager --config-toml Additional configuration options (can be repeated) diff --git a/docs/config.md b/docs/config.md index ec27c07e3b..7674e7dd0e 100644 --- a/docs/config.md +++ b/docs/config.md @@ -80,8 +80,9 @@ Don't forget to change these to your own details! ### Colorizing output -Possible values are `always`, `never` and `auto` (default: `auto`). -`auto` will use color only when writing to a terminal. +Possible values are `always`, `never`, `debug` and `auto` (default: `auto`). +`auto` will use color only when writing to a terminal. `debug` will print the +active labels alongside the regular colorized output. This setting overrides the `NO_COLOR` environment variable (if set).