-
Notifications
You must be signed in to change notification settings - Fork 343
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
completion: teach rename about local bookmarks
- Loading branch information
Showing
10 changed files
with
267 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Copyright 2024 The Jujutsu Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use clap::FromArgMatches as _; | ||
use clap_complete::CompletionCandidate; | ||
use jj_lib::workspace::DefaultWorkspaceLoaderFactory; | ||
use jj_lib::workspace::WorkspaceLoaderFactory as _; | ||
|
||
use crate::cli_util::expand_args; | ||
use crate::cli_util::find_workspace_dir; | ||
use crate::cli_util::GlobalArgs; | ||
use crate::command_error::user_error; | ||
use crate::command_error::CommandError; | ||
use crate::config::default_config; | ||
use crate::config::LayeredConfigs; | ||
use crate::ui::Ui; | ||
|
||
pub fn local_bookmarks() -> Vec<CompletionCandidate> { | ||
with_jj(|mut jj| { | ||
let output = jj | ||
.arg("bookmark") | ||
.arg("list") | ||
.arg("--template") | ||
.arg(r#"if(!remote, name ++ "\n")"#) | ||
.output() | ||
.map_err(user_error)?; | ||
|
||
Ok(String::from_utf8_lossy(&output.stdout) | ||
.lines() | ||
.map(CompletionCandidate::new) | ||
.collect()) | ||
}) | ||
} | ||
|
||
/// Shell out to jj during dynamic completion generation | ||
/// | ||
/// In case of errors, print them and early return an empty vector. | ||
fn with_jj<F>(completion_fn: F) -> Vec<CompletionCandidate> | ||
where | ||
F: FnOnce(std::process::Command) -> Result<Vec<CompletionCandidate>, CommandError>, | ||
{ | ||
get_jj_command() | ||
.and_then(completion_fn) | ||
.unwrap_or_else(|e| { | ||
eprintln!("{}", e.error); | ||
Vec::new() | ||
}) | ||
} | ||
|
||
/// Shell out to jj during dynamic completion generation | ||
/// | ||
/// This is necessary because dynamic completion code needs to be aware of | ||
/// global configuration like custom storage backends. Dynamic completion | ||
/// code via clap_complete doesn't accept arguments, so they cannot be passed | ||
/// that way. Another solution would've been to use global mutable state, to | ||
/// give completion code access to custom backends. Shelling out was chosen as | ||
/// the preferred method, because it's more maintainable and the performance | ||
/// requirements of completions aren't very high. | ||
fn get_jj_command() -> Result<std::process::Command, CommandError> { | ||
let current_exe = std::env::current_exe().map_err(user_error)?; | ||
let mut command = std::process::Command::new(current_exe); | ||
|
||
// Snapshotting could make completions much slower in some situations | ||
// and be undesired by the user. | ||
command.arg("--ignore-working-copy"); | ||
command.arg("--color=never"); | ||
command.arg("--no-pager"); | ||
|
||
// Parse some of the global args we care about for passing along to the | ||
// child process. This shouldn't fail, since none of the global args are | ||
// required. | ||
let app = crate::commands::default_app(); | ||
let config = config::Config::builder() | ||
.add_source(default_config()) | ||
.build() | ||
.expect("default config should be valid"); | ||
let mut layered_configs = LayeredConfigs::from_environment(config); | ||
let ui = Ui::with_config(&layered_configs.merge()).expect("default config should be valid"); | ||
let cwd = std::env::current_dir() | ||
.and_then(|cwd| cwd.canonicalize()) | ||
.map_err(user_error)?; | ||
let maybe_cwd_workspace_loader = DefaultWorkspaceLoaderFactory.create(find_workspace_dir(&cwd)); | ||
layered_configs.read_user_config().map_err(user_error)?; | ||
if let Ok(loader) = &maybe_cwd_workspace_loader { | ||
layered_configs | ||
.read_repo_config(loader.repo_path()) | ||
.map_err(user_error)?; | ||
} | ||
let config = layered_configs.merge(); | ||
// skip 2 because of the clap_complete prelude: jj -- jj <actual args...> | ||
let args = std::env::args_os().skip(2); | ||
let args = expand_args(&ui, &app, args, &config)?; | ||
let args = app | ||
.clone() | ||
.disable_version_flag(true) | ||
.disable_help_flag(true) | ||
.ignore_errors(true) | ||
.try_get_matches_from(args)?; | ||
let args: GlobalArgs = GlobalArgs::from_arg_matches(&args)?; | ||
|
||
if let Some(repository) = args.repository { | ||
command.arg("--repository"); | ||
command.arg(repository); | ||
} | ||
if let Some(at_operation) = args.at_operation { | ||
command.arg("--at-operation"); | ||
command.arg(at_operation); | ||
} | ||
for config_toml in args.early_args.config_toml { | ||
command.arg("--config-toml"); | ||
command.arg(config_toml); | ||
} | ||
|
||
Ok(command) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Copyright 2024 The Jujutsu Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use crate::common::TestEnvironment; | ||
|
||
#[test] | ||
fn test_bookmark_rename() { | ||
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"); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "aaa"]); | ||
test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "bbb"]); | ||
|
||
let mut test_env = test_env; | ||
// Every shell hook is a little different, e.g. the zsh hooks add some | ||
// additional environment variables. But this is irrelevant for the purpose | ||
// of testing our own logic, so it's fine to test a single shell only. | ||
test_env.add_env_var("COMPLETE", "fish"); | ||
let test_env = test_env; | ||
|
||
let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "rename", ""]); | ||
insta::assert_snapshot!(stdout, @r" | ||
aaa | ||
bbb | ||
--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 | ||
--at-operation Operation to load the repo at | ||
--debug Enable debug logging | ||
--color When to colorize output (always, never, debug, auto) | ||
--quiet Silence non-primary command output | ||
--no-pager Disable the pager | ||
--config-toml Additional configuration options (can be repeated) | ||
--help Print help (see more with '--help') | ||
"); | ||
|
||
let stdout = test_env.jj_cmd_success(&repo_path, &["--", "jj", "bookmark", "rename", "a"]); | ||
insta::assert_snapshot!(stdout, @"aaa"); | ||
} | ||
|
||
#[test] | ||
fn test_global_arg_repository_is_respected() { | ||
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"); | ||
|
||
test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "aaa"]); | ||
|
||
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( | ||
test_env.env_root(), | ||
&[ | ||
"--", | ||
"jj", | ||
"--repository", | ||
"repo", | ||
"bookmark", | ||
"rename", | ||
"a", | ||
], | ||
); | ||
insta::assert_snapshot!(stdout, @"aaa"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters