diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index 137a7eebd..aaec9ecc9 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -1602,8 +1602,8 @@ pub fn print_checkout_stats( stats.skipped_files )?; writeln!( - ui.hint(), - "Hint: Inspect the changes compared to the intended target with `jj diff --from {}`. + ui.hint_with_heading("Hint: "), + "Inspect the changes compared to the intended target with `jj diff --from {}`. Discard the conflicting changes with `jj restore --from {}`.", short_commit_hash(new_commit.id()), short_commit_hash(new_commit.id()) @@ -1629,7 +1629,7 @@ pub fn print_trackable_remote_branches(ui: &Ui, view: &View) -> io::Result<()> { } writeln!( - ui.hint(), + ui.hint_no_heading(), "The following remote branches aren't associated with the existing local branches:" )?; let mut formatter = ui.stderr_formatter(); @@ -1639,8 +1639,8 @@ pub fn print_trackable_remote_branches(ui: &Ui, view: &View) -> io::Result<()> { } drop(formatter); writeln!( - ui.hint(), - "Hint: Run `jj branch track {names}` to keep local branches updated on future pulls.", + ui.hint_with_heading("Hint: "), + "Run `jj branch track {names}` to keep local branches updated on future pulls.", names = remote_branch_names.join(" "), )?; Ok(()) @@ -2204,11 +2204,11 @@ fn resolve_default_command( let args = get_string_or_array(config, "ui.default-command").optional()?; if args.is_none() { writeln!( - ui.hint(), - "Hint: Use `jj -h` for a list of available commands." + ui.hint_with_heading("Hint: "), + "Use `jj -h` for a list of available commands." )?; writeln!( - ui.hint(), + ui.hint_no_heading(), "Run `jj config set --user ui.default-command log` to disable this message." )?; } diff --git a/cli/src/command_error.rs b/cli/src/command_error.rs index 6908a81ed..ea741a48e 100644 --- a/cli/src/command_error.rs +++ b/cli/src/command_error.rs @@ -506,7 +506,7 @@ fn try_handle_command_result( CommandErrorKind::Config => { print_error(ui, "Config error", err, hints)?; writeln!( - ui.hint(), + ui.hint_no_heading(), "For help, see https://github.com/martinvonz/jj/blob/main/docs/config.md." )?; Ok(ExitCode::from(1)) @@ -534,7 +534,7 @@ fn print_error(ui: &Ui, prefix: &str, err: &dyn error::Error, hints: &[String]) writeln!(ui.error(), "{prefix}: {err}")?; print_error_sources(ui, err.source())?; for hint in hints { - writeln!(ui.hint(), "Hint: {hint}")?; + writeln!(ui.hint_with_heading("Hint: "), "{hint}")?; } Ok(()) } @@ -577,7 +577,7 @@ fn handle_clap_error(ui: &mut Ui, err: &clap::Error, hints: &[String]) -> io::Re } write!(ui.stderr(), "{clap_str}")?; for hint in hints { - writeln!(ui.hint(), "Hint: {hint}")?; + writeln!(ui.hint_with_heading("Hint: "), "{hint}")?; } Ok(ExitCode::from(2)) } diff --git a/cli/src/commands/branch.rs b/cli/src/commands/branch.rs index bbc2c30ed..afaaaeb2e 100644 --- a/cli/src/commands/branch.rs +++ b/cli/src/commands/branch.rs @@ -325,10 +325,10 @@ fn cmd_branch_rename( "Warning: Branch {old_branch} has tracking remote branches which were not renamed." )?; writeln!( - ui.hint(), - "Hint: to rename the branch on the remote, you can `jj git push --branch \ - {old_branch}` first (to delete it on the remote), and then `jj git push --branch \ - {new_branch}`. `jj git push --all` would also be sufficient." + ui.hint_with_heading("Hint: "), + "to rename the branch on the remote, you can `jj git push --branch {old_branch}` \ + first (to delete it on the remote), and then `jj git push --branch {new_branch}`. \ + `jj git push --all` would also be sufficient." )?; } diff --git a/cli/src/commands/git.rs b/cli/src/commands/git.rs index 37c1eabb9..95668c2b3 100644 --- a/cli/src/commands/git.rs +++ b/cli/src/commands/git.rs @@ -597,9 +597,8 @@ fn get_default_fetch_remotes( // if nothing was explicitly configured, try to guess if remote != DEFAULT_REMOTE { writeln!( - ui.hint(), - "Fetching from the only existing remote: {}", - remote + ui.hint_no_heading(), + "Fetching from the only existing remote: {remote}" )?; } Ok(vec![remote]) @@ -1040,7 +1039,10 @@ fn get_default_push_remote( } else if let Some(remote) = get_single_remote(git_repo)? { // similar to get_default_fetch_remotes if remote != DEFAULT_REMOTE { - writeln!(ui.hint(), "Pushing to the only existing remote: {}", remote)?; + writeln!( + ui.hint_no_heading(), + "Pushing to the only existing remote: {remote}" + )?; } Ok(remote) } else { @@ -1058,7 +1060,7 @@ impl RejectedBranchUpdateReason { fn print(&self, ui: &Ui) -> io::Result<()> { writeln!(ui.warning(), "{}", self.message)?; if let Some(hint) = &self.hint { - writeln!(ui.hint(), "Hint: {hint}")?; + writeln!(ui.hint_with_heading("Hint: "), "{hint}")?; } Ok(()) } diff --git a/cli/src/commands/util.rs b/cli/src/commands/util.rs index 2ec9f0c3c..b0f1a7033 100644 --- a/cli/src/commands/util.rs +++ b/cli/src/commands/util.rs @@ -134,7 +134,10 @@ fn cmd_util_completion( "Warning: `jj util completion --{shell}` will be removed in a future version, and \ this will be a hard error" )?; - writeln!(ui.hint(), "Hint: Use `jj util completion {shell}` instead") + writeln!( + ui.hint_with_heading("Hint: "), + "Use `jj util completion {shell}` instead" + ) }; let shell = match (args.shell, args.fish, args.zsh, args.bash) { (Some(s), false, false, false) => s, diff --git a/cli/src/config/colors.toml b/cli/src/config/colors.toml index 66fea8540..75ec17a9a 100644 --- a/cli/src/config/colors.toml +++ b/cli/src/config/colors.toml @@ -2,6 +2,7 @@ "error" = "red" "warning" = "yellow" "hint" = "cyan" +"hint heading" = { bold = true } "conflict_description" = "yellow" "conflict_description difficult" = "red" diff --git a/cli/src/git_util.rs b/cli/src/git_util.rs index c4a30fde5..14e14d71c 100644 --- a/cli/src/git_util.rs +++ b/cli/src/git_util.rs @@ -408,8 +408,8 @@ pub fn print_failed_git_export( .any(|failed| matches!(failed.reason, FailedRefExportReason::FailedToSet(_))) { writeln!( - ui.hint(), - r#"Hint: Git doesn't allow a branch name that looks like a parent directory of + ui.hint_with_heading("Hint: "), + r#"Git doesn't allow a branch name that looks like a parent directory of another (e.g. `foo` and `foo/bar`). Try to rename the branches that failed to export or their "parent" branches."#, )?; diff --git a/cli/src/merge_tools/mod.rs b/cli/src/merge_tools/mod.rs index 92deb61de..505d04b88 100644 --- a/cli/src/merge_tools/mod.rs +++ b/cli/src/merge_tools/mod.rs @@ -124,7 +124,7 @@ fn editor_args_from_settings( } else { let default_editor = BUILTIN_EDITOR_NAME; writeln!( - ui.hint(), + ui.hint_no_heading(), "Using default editor '{default_editor}'; run `jj config set --user {key} :builtin` \ to disable this message." ) diff --git a/cli/src/ui.rs b/cli/src/ui.rs index 0fd01b79b..81dba91b5 100644 --- a/cli/src/ui.rs +++ b/cli/src/ui.rs @@ -23,7 +23,7 @@ use tracing::instrument; use crate::command_error::{config_error_with_message, CommandError}; use crate::config::CommandNameAndArgs; -use crate::formatter::{Formatter, FormatterFactory, LabeledWriter}; +use crate::formatter::{Formatter, FormatterFactory, HeadingLabeledWriter, LabeledWriter}; const BUILTIN_PAGER_NAME: &str = ":builtin"; @@ -373,10 +373,19 @@ impl Ui { }) } - pub fn hint(&self) -> LabeledWriter, &'static str> { + /// Writer to print hint without the "Hint: " heading. + pub fn hint_no_heading(&self) -> LabeledWriter, &'static str> { LabeledWriter::new(self.stderr_formatter(), "hint") } + /// Writer to print hint with the given heading. + pub fn hint_with_heading( + &self, + heading: H, + ) -> HeadingLabeledWriter, &'static str, H> { + HeadingLabeledWriter::new(self.stderr_formatter(), "hint", heading) + } + pub fn warning(&self) -> LabeledWriter, &'static str> { LabeledWriter::new(self.stderr_formatter(), "warning") } diff --git a/cli/tests/test_global_opts.rs b/cli/tests/test_global_opts.rs index 7cc3257f7..c8937827a 100644 --- a/cli/tests/test_global_opts.rs +++ b/cli/tests/test_global_opts.rs @@ -385,6 +385,20 @@ fn test_color_config() { "###); } +#[test] +fn test_color_ui_messages() { + let test_env = TestEnvironment::default(); + test_env.add_config("ui.color = 'always'"); + + // hint and error + let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["-R."]); + insta::assert_snapshot!(stderr, @r###" + Hint: Use `jj -h` for a list of available commands. + Run `jj config set --user ui.default-command log` to disable this message. + Error: There is no jj repo in "." + "###); +} + #[test] fn test_early_args() { // Test that help output parses early args