Skip to content

Commit

Permalink
completion: teach jj about aliases
Browse files Browse the repository at this point in the history
This makes completions suggest aliases from the user or repository
configuration. It's more useful for long aliases that aren't used very often,
but serve the purpose of "executable documentation" of complex and useful jj
commands.

An earlier patch "resolved" aliases, meaning that any arguments following an
alias would be completed as if the normal command had been used. So it only
made sure that using aliases doesn't degrade the rest of the completions.
Commit ID: 325402d
  • Loading branch information
senekor committed Nov 14, 2024
1 parent c552080 commit dc463af
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 2 deletions.
8 changes: 6 additions & 2 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3079,8 +3079,12 @@ fn handle_shell_completion(
let resolved_aliases = expand_args(ui, app, env::args_os().skip(2), config)?;
args.extend(resolved_aliases.into_iter().map(OsString::from));
}
let ran_completion = clap_complete::CompleteEnv::with_factory(|| app.clone())
.try_complete(args.iter(), Some(cwd))?;
let ran_completion = clap_complete::CompleteEnv::with_factory(|| {
app.clone()
// for completing aliases
.allow_external_subcommands(true)
})
.try_complete(args.iter(), Some(cwd))?;
assert!(
ran_completion,
"This function should not be called without the COMPLETE variable set."
Expand Down
3 changes: 3 additions & 0 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,20 @@ use std::fmt::Debug;
use clap::CommandFactory;
use clap::FromArgMatches;
use clap::Subcommand;
use clap_complete::engine::SubcommandCandidates;
use tracing::instrument;

use crate::cli_util::Args;
use crate::cli_util::CommandHelper;
use crate::command_error::user_error_with_hint;
use crate::command_error::CommandError;
use crate::complete;
use crate::ui::Ui;

#[derive(clap::Parser, Clone, Debug)]
#[command(disable_help_subcommand = true)]
#[command(after_long_help = help::show_keyword_hint_after_help())]
#[command(add = SubcommandCandidates::new(complete::aliases))]
enum Command {
Abandon(abandon::AbandonArgs),
Absorb(absorb::AbsorbArgs),
Expand Down
15 changes: 15 additions & 0 deletions cli/src/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ pub fn git_remotes() -> Vec<CompletionCandidate> {
})
}

pub fn aliases() -> Vec<CompletionCandidate> {
with_jj(|_, config| {
Ok(config
.get_table("aliases")?
.into_keys()
// This is opinionated, but many people probably have several
// single- or two-letter aliases they use all the time. These
// aliases don't need to be completed and they would only clutter
// the output of `jj <TAB>`.
.filter(|alias| alias.len() > 2)
.map(CompletionCandidate::new)
.collect())
})
}

/// Shell out to jj during dynamic completion generation
///
/// In case of errors, print them and early return an empty vector.
Expand Down
44 changes: 44 additions & 0 deletions cli/tests/test_completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,47 @@ fn test_remote_names() {
);
insta::assert_snapshot!(stdout, @r"origin");
}

#[test]
fn test_aliases_are_completed() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");

// user config alias
test_env.add_config(r#"aliases.user-alias = ["bookmark"]"#);
// repo config alias
test_env.jj_cmd_ok(
&repo_path,
&[
"config",
"set",
"--repo",
"aliases.repo-alias",
"['bookmark']",
],
);

let mut test_env = test_env;
test_env.add_env_var("COMPLETE", "fish");
let test_env = test_env;

let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "user-al"]);
insta::assert_snapshot!(stdout, @"user-alias");

let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "repo-al"]);
insta::assert_snapshot!(stdout, @"repo-alias");

// make sure --repository flag is respected
let stdout = test_env.jj_cmd_success(
test_env.env_root(),
&[
"--",
"jj",
"--repository",
repo_path.to_str().unwrap(),
"repo-al",
],
);
insta::assert_snapshot!(stdout, @"repo-alias");
}

0 comments on commit dc463af

Please sign in to comment.