completion: teach jj about aliases

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: 325402dc94
This commit is contained in:
Remo Senekowitsch 2024-11-07 22:08:49 +01:00
parent ef8e664555
commit 26ccc1c4cc
4 changed files with 78 additions and 2 deletions

View file

@ -3248,8 +3248,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."

View file

@ -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),

View file

@ -144,6 +144,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.

View file

@ -242,3 +242,57 @@ 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");
// 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");
// cannot load aliases from --config-toml flag
let stdout = test_env.jj_cmd_success(
test_env.env_root(),
&[
"--",
"jj",
"--config-toml",
"aliases.cli-alias = ['bookmark']",
"cli-al",
],
);
insta::assert_snapshot!(stdout, @"");
}