From 92adf896b9095b09cbe86bd1af495b3a1c70563a Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 17 Nov 2024 20:33:04 +0100 Subject: [PATCH] completion: include bookmarks and tags in revisions --- cli/src/complete.rs | 105 +++++++++++++++++++++++++++++++---- cli/tests/test_completion.rs | 57 +++++++++++++++---- 2 files changed, 141 insertions(+), 21 deletions(-) diff --git a/cli/src/complete.rs b/cli/src/complete.rs index e3c2088f0c..a6012ffcd9 100644 --- a/cli/src/complete.rs +++ b/cli/src/complete.rs @@ -216,8 +216,90 @@ pub fn aliases() -> Vec { }) } -fn revisions(revisions: &str) -> Vec { - with_jj(|jj, _| { +fn revisions(revisions: Option<&str>) -> Vec { + with_jj(|jj, config| { + // display order + const LOCAL_BOOKMARK_MINE: usize = 0; + const LOCAL_BOOKMARK: usize = 1; + const TAG: usize = 2; + const CHANGE_ID: usize = 3; + const REMOTE_BOOKMARK_MINE: usize = 4; + const REMOTE_BOOKMARK: usize = 5; + + let mut candidates = Vec::new(); + + // bookmarks + + let prefix = config.get::("git.push-bookmark-prefix").ok(); + + let mut cmd = jj.build(); + cmd.arg("bookmark") + .arg("list") + .arg("--all-remotes") + .arg("--config-toml") + .arg(BOOKMARK_HELP_TEMPLATE) + .arg("--template") + .arg( + r#"if(remote != "git", name ++ if(remote, "@" ++ remote) ++ bookmark_help() ++ "\n")"#, + ); + if let Some(revs) = revisions { + cmd.arg("--revisions").arg(revs); + } + let output = cmd.output().map_err(user_error)?; + let stdout = String::from_utf8_lossy(&output.stdout); + + candidates.extend(stdout.lines().map(|line| { + let (bookmark, help) = split_help_text(line); + + let local = !bookmark.contains('@'); + let mine = prefix.as_ref().is_some_and(|p| bookmark.starts_with(p)); + + let display_order = match (local, mine) { + (true, true) => LOCAL_BOOKMARK_MINE, + (true, false) => LOCAL_BOOKMARK, + (false, true) => REMOTE_BOOKMARK_MINE, + (false, false) => REMOTE_BOOKMARK, + }; + CompletionCandidate::new(bookmark) + .help(help) + .display_order(Some(display_order)) + })); + + // tags + + // Tags cannot be filtered by revisions. In order to avoid suggesting + // immutable tags for mutable revision args, we skip tags entirely if + // revisions is set. This is not a big loss, since tags usually point + // to immutable revisions anyway. + if revisions.is_none() { + let output = jj + .build() + .arg("tag") + .arg("list") + .arg("--config-toml") + .arg(BOOKMARK_HELP_TEMPLATE) + .arg("--template") + .arg(r#"name ++ bookmark_help() ++ "\n""#) + .output() + .map_err(user_error)?; + let stdout = String::from_utf8_lossy(&output.stdout); + + candidates.extend(stdout.lines().map(|line| { + let (name, desc) = split_help_text(line); + CompletionCandidate::new(name) + .help(desc) + .display_order(Some(TAG)) + })); + } + + // change IDs + + let revisions = revisions + .map(String::from) + .or_else(|| config.get_string("revsets.short-prefixes").ok()) + .or_else(|| config.get_string("revsets.log").ok()) + .unwrap_or_default(); + let output = jj .build() .arg("log") @@ -232,22 +314,23 @@ fn revisions(revisions: &str) -> Vec { .map_err(user_error)?; let stdout = String::from_utf8_lossy(&output.stdout); - Ok(stdout - .lines() - .map(|line| { - let (id, desc) = split_help_text(line); - CompletionCandidate::new(id).help(desc) - }) - .collect()) + candidates.extend(stdout.lines().map(|line| { + let (id, desc) = split_help_text(line); + CompletionCandidate::new(id) + .help(desc) + .display_order(Some(CHANGE_ID)) + })); + + Ok(candidates) }) } pub fn mutable_revisions() -> Vec { - revisions("mutable()") + revisions(Some("mutable()")) } pub fn all_revisions() -> Vec { - revisions("all()") + revisions(None) } pub fn operations() -> Vec { diff --git a/cli/tests/test_completion.rs b/cli/tests/test_completion.rs index 971222a7cf..617a60b98c 100644 --- a/cli/tests/test_completion.rs +++ b/cli/tests/test_completion.rs @@ -355,10 +355,37 @@ fn test_revisions() { test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]); let repo_path = test_env.env_root().join("repo"); + // create remote to test remote branches + test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "origin"]); + let origin_path = test_env.env_root().join("origin"); + let origin_git_repo_path = origin_path + .join(".jj") + .join("repo") + .join("store") + .join("git"); + test_env.jj_cmd_ok( + &repo_path, + &[ + "git", + "remote", + "add", + "origin", + origin_git_repo_path.to_str().unwrap(), + ], + ); + test_env.jj_cmd_ok(&origin_path, &["b", "c", "remote_bookmark"]); + test_env.jj_cmd_ok(&origin_path, &["commit", "-m", "remote_commit"]); + test_env.jj_cmd_ok(&origin_path, &["git", "export"]); + test_env.jj_cmd_ok(&repo_path, &["git", "fetch"]); + + test_env.jj_cmd_ok(&repo_path, &["b", "c", "immutable_bookmark"]); test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "immutable"]); + test_env.add_config(r#"revset-aliases."immutable_heads()" = "immutable_bookmark""#); + + test_env.jj_cmd_ok(&repo_path, &["b", "c", "mutable_bookmark"]); test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "mutable"]); - test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "main", "-r", "@--"]); - test_env.add_config(r#"revset-aliases."immutable_heads()" = "main""#); + + test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "working_copy"]); let mut test_env = test_env; test_env.add_env_var("COMPLETE", "fish"); @@ -371,27 +398,37 @@ fn test_revisions() { // complete all revisions let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "diff", "--from", ""]); insta::assert_snapshot!(stdout, @r" - k (no description set) - r mutable + immutable_bookmark immutable + mutable_bookmark mutable + k working_copy + y mutable q immutable - z (no description set) + zq remote_commit + zz (no description set) + remote_bookmark@origin remote_commit "); // complete only mutable revisions let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "squash", "--into", ""]); insta::assert_snapshot!(stdout, @r" - k (no description set) - r mutable + mutable_bookmark mutable + k working_copy + y mutable + zq remote_commit "); // complete args of the default command test_env.add_config("ui.default-command = 'log'"); let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "-r", ""]); insta::assert_snapshot!(stdout, @r" - k (no description set) - r mutable + immutable_bookmark immutable + mutable_bookmark mutable + k working_copy + y mutable q immutable - z (no description set) + zq remote_commit + zz (no description set) + remote_bookmark@origin remote_commit "); }