From 68c0fd635023453623376c5bff73795cf1944826 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 14 Nov 2024 17:46:09 +0100 Subject: [PATCH] completion: add help text for bookmark names --- cli/src/complete.rs | 75 +++++++++++++++++++++++++++++------- cli/tests/test_completion.rs | 50 ++++++++++++------------ 2 files changed, 87 insertions(+), 38 deletions(-) diff --git a/cli/src/complete.rs b/cli/src/complete.rs index 1928ca53f6..3c41ee1b52 100644 --- a/cli/src/complete.rs +++ b/cli/src/complete.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use clap::builder::StyledStr; use clap::FromArgMatches as _; use clap_complete::CompletionCandidate; use config::Config; @@ -28,19 +29,45 @@ use crate::config::default_config; use crate::config::LayeredConfigs; use crate::ui::Ui; +const BOOKMARK_HELP_TEMPLATE: &str = r#" +[template-aliases] +"bookmark_help()" = """ +" " ++ +if(normal_target, + if(normal_target.description(), + normal_target.description().first_line(), + "(no description set)", + ), + "(conflicted bookmark)", +) +""" +"#; + +/// A helper function for various completer functions. It returns +/// (candidate, help) assuming they are separated by a space. +fn split_help_text(line: &str) -> (&str, Option) { + match line.split_once(' ') { + Some((name, help)) => (name, Some(help.to_string().into())), + None => (line, None), + } +} + pub fn local_bookmarks() -> Vec { with_jj(|mut jj, _| { let output = jj .arg("bookmark") .arg("list") + .arg("--config-toml") + .arg(BOOKMARK_HELP_TEMPLATE) .arg("--template") - .arg(r#"if(!remote, name ++ "\n")"#) + .arg(r#"if(!remote, name ++ bookmark_help()) ++ "\n""#) .output() .map_err(user_error)?; Ok(String::from_utf8_lossy(&output.stdout) .lines() - .map(CompletionCandidate::new) + .map(split_help_text) + .map(|(name, help)| CompletionCandidate::new(name).help(help)) .collect()) }) } @@ -51,14 +78,17 @@ pub fn tracked_bookmarks() -> Vec { .arg("bookmark") .arg("list") .arg("--tracked") + .arg("--config-toml") + .arg(BOOKMARK_HELP_TEMPLATE) .arg("--template") - .arg(r#"if(remote, name ++ "@" ++ remote ++ "\n")"#) + .arg(r#"if(remote, name ++ '@' ++ remote ++ bookmark_help() ++ "\n")"#) .output() .map_err(user_error)?; Ok(String::from_utf8_lossy(&output.stdout) .lines() - .map(CompletionCandidate::new) + .map(split_help_text) + .map(|(name, help)| CompletionCandidate::new(name).help(help)) .collect()) }) } @@ -69,8 +99,14 @@ pub fn untracked_bookmarks() -> Vec { .arg("bookmark") .arg("list") .arg("--all-remotes") + .arg("--config-toml") + .arg(BOOKMARK_HELP_TEMPLATE) .arg("--template") - .arg(r#"if(remote && !tracked, name ++ "@" ++ remote ++ "\n")"#) + .arg( + r#"if(remote && !tracked && remote != "git", + name ++ '@' ++ remote ++ bookmark_help() ++ "\n" + )"#, + ) .output() .map_err(user_error)?; @@ -78,14 +114,17 @@ pub fn untracked_bookmarks() -> Vec { Ok(String::from_utf8_lossy(&output.stdout) .lines() - .filter(|bookmark| !bookmark.ends_with("@git")) - .map(|bookmark| { + .map(|line| { + let (name, help) = split_help_text(line); + let display_order = match prefix.as_ref() { // own bookmarks are more interesting - Some(prefix) if bookmark.starts_with(prefix) => 0, + Some(prefix) if name.starts_with(prefix) => 0, _ => 1, }; - CompletionCandidate::new(bookmark).display_order(Some(display_order)) + CompletionCandidate::new(name) + .help(help) + .display_order(Some(display_order)) }) .collect()) }) @@ -97,8 +136,13 @@ pub fn bookmarks() -> Vec { .arg("bookmark") .arg("list") .arg("--all-remotes") + .arg("--config-toml") + .arg(BOOKMARK_HELP_TEMPLATE) .arg("--template") - .arg(r#"separate("@", name, remote) ++ "\n""#) + .arg( + // only provide help for local refs, remote could be ambiguous + r#"name ++ if(remote, "@" ++ remote, bookmark_help()) ++ "\n""#, + ) .output() .map_err(user_error)?; let stdout = String::from_utf8_lossy(&output.stdout); @@ -107,10 +151,13 @@ pub fn bookmarks() -> Vec { Ok((&stdout .lines() - .chunk_by(|line| line.split_once('@').map(|t| t.0).unwrap_or(line))) + .map(split_help_text) + .chunk_by(|(name, _)| name.split_once('@').map(|t| t.0).unwrap_or(name))) .into_iter() .map(|(bookmark, mut refs)| { - let local = refs.any(|r| !r.contains('@')); + let help = refs.find_map(|(_, help)| help); + + let local = help.is_some(); let mine = prefix.as_ref().is_some_and(|p| bookmark.starts_with(p)); let display_order = match (local, mine) { @@ -119,7 +166,9 @@ pub fn bookmarks() -> Vec { (false, true) => 2, (false, false) => 3, }; - CompletionCandidate::new(bookmark).display_order(Some(display_order)) + CompletionCandidate::new(bookmark) + .help(help) + .display_order(Some(display_order)) }) .collect()) }) diff --git a/cli/tests/test_completion.rs b/cli/tests/test_completion.rs index 659876aff6..54b5cdf6e2 100644 --- a/cli/tests/test_completion.rs +++ b/cli/tests/test_completion.rs @@ -64,10 +64,10 @@ fn test_bookmark_names() { let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "rename", ""]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked - bbb-local - bbb-tracked + aaa-local x + aaa-tracked x + bbb-local x + bbb-tracked x --repository Path to repository to operate on --ignore-working-copy Don't snapshot the working copy, and don't update it --ignore-immutable Allow rewriting immutable commits @@ -82,20 +82,20 @@ fn test_bookmark_names() { let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "rename", "a"]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x "); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "delete", "a"]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x "); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "forget", "a"]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x aaa-untracked "); @@ -104,39 +104,39 @@ fn test_bookmark_names() { &["--", "jj", "bookmark", "list", "--bookmark", "a"], ); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x aaa-untracked "); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "move", "a"]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x "); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "set", "a"]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x "); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "track", "a"]); - insta::assert_snapshot!(stdout, @"aaa-untracked@origin"); + insta::assert_snapshot!(stdout, @"aaa-untracked@origin x"); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "untrack", "a"]); - insta::assert_snapshot!(stdout, @"aaa-tracked@origin"); + insta::assert_snapshot!(stdout, @"aaa-tracked@origin x"); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "git", "push", "-b", "a"]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x "); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "git", "fetch", "-b", "a"]); insta::assert_snapshot!(stdout, @r" - aaa-local - aaa-tracked + aaa-local x + aaa-tracked x aaa-untracked "); } @@ -165,7 +165,7 @@ fn test_global_arg_repository_is_respected() { "a", ], ); - insta::assert_snapshot!(stdout, @"aaa"); + insta::assert_snapshot!(stdout, @"aaa (no description set)"); } #[test] @@ -189,10 +189,10 @@ fn test_aliases_are_resolved() { let test_env = test_env; let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "b", "rename", "a"]); - insta::assert_snapshot!(stdout, @"aaa"); + insta::assert_snapshot!(stdout, @"aaa (no description set)"); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "b2", "rename", "a"]); - insta::assert_snapshot!(stdout, @"aaa"); + insta::assert_snapshot!(stdout, @"aaa (no description set)"); } #[test]