Skip to content

Commit

Permalink
completion: include bookmarks and tags in revisions
Browse files Browse the repository at this point in the history
  • Loading branch information
senekor committed Nov 27, 2024
1 parent faa4fff commit 92adf89
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 21 deletions.
105 changes: 94 additions & 11 deletions cli/src/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,90 @@ pub fn aliases() -> Vec<CompletionCandidate> {
})
}

fn revisions(revisions: &str) -> Vec<CompletionCandidate> {
with_jj(|jj, _| {
fn revisions(revisions: Option<&str>) -> Vec<CompletionCandidate> {
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::<String>("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")
Expand All @@ -232,22 +314,23 @@ fn revisions(revisions: &str) -> Vec<CompletionCandidate> {
.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<CompletionCandidate> {
revisions("mutable()")
revisions(Some("mutable()"))
}

pub fn all_revisions() -> Vec<CompletionCandidate> {
revisions("all()")
revisions(None)
}

pub fn operations() -> Vec<CompletionCandidate> {
Expand Down
57 changes: 47 additions & 10 deletions cli/tests/test_completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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
");
}

Expand Down

0 comments on commit 92adf89

Please sign in to comment.