diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aca80a06..815fec619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,11 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * `jj config edit` will now roll back to previous version if a syntax error has been introduced in the new config. +* When using dynamic command-line completion, revision names will be completed + in more complex expressions. For example, typing + `jj log -r first-bookmark..sec` and then pressing Tab could complete the + expression to `first-bookmark..second-bookmark`. + ### Fixed bugs diff --git a/cli/src/commands/abandon.rs b/cli/src/commands/abandon.rs index a6684eb36..3224e9813 100644 --- a/cli/src/commands/abandon.rs +++ b/cli/src/commands/abandon.rs @@ -16,7 +16,7 @@ use std::collections::HashMap; use std::collections::HashSet; use std::io::Write as _; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::backend::CommitId; use jj_lib::commit::CommitIteratorExt as _; @@ -47,14 +47,14 @@ pub(crate) struct AbandonArgs { /// The revision(s) to abandon (default: @) #[arg( value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions_pos: Vec, #[arg( short = 'r', hide = true, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions_opt: Vec, // TODO: Remove in jj 0.34+ diff --git a/cli/src/commands/absorb.rs b/cli/src/commands/absorb.rs index 55aa6b1ef..9e57ad5f3 100644 --- a/cli/src/commands/absorb.rs +++ b/cli/src/commands/absorb.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use jj_lib::absorb::absorb_hunks; use jj_lib::absorb::split_hunks_to_trees; use jj_lib::absorb::AbsorbSource; @@ -46,7 +46,7 @@ pub(crate) struct AbsorbArgs { long, short, default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] from: RevisionArg, /// Destination revisions to absorb into @@ -56,7 +56,7 @@ pub(crate) struct AbsorbArgs { long, short = 't', visible_alias = "to", default_value = "mutable()", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] into: Vec, /// Move only changes to these paths (instead of all paths) diff --git a/cli/src/commands/backout.rs b/cli/src/commands/backout.rs index d2dfff4fc..4f6801a76 100644 --- a/cli/src/commands/backout.rs +++ b/cli/src/commands/backout.rs @@ -13,7 +13,7 @@ // limitations under the License. use bstr::ByteVec as _; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::object_id::ObjectId as _; use jj_lib::rewrite::merge_commit_trees; @@ -38,7 +38,7 @@ pub(crate) struct BackoutArgs { long, short, default_value = "@", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions: Vec, /// The revision to apply the reverse changes on top of @@ -48,7 +48,7 @@ pub(crate) struct BackoutArgs { long, short, default_value = "@", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] destination: Vec, } diff --git a/cli/src/commands/bookmark/create.rs b/cli/src/commands/bookmark/create.rs index f809098e0..cfe8804f9 100644 --- a/cli/src/commands/bookmark/create.rs +++ b/cli/src/commands/bookmark/create.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; @@ -41,7 +41,7 @@ pub struct BookmarkCreateArgs { long, short, visible_alias = "to", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: Option, diff --git a/cli/src/commands/bookmark/move.rs b/cli/src/commands/bookmark/move.rs index 26fdbe20e..04d53bb34 100644 --- a/cli/src/commands/bookmark/move.rs +++ b/cli/src/commands/bookmark/move.rs @@ -13,6 +13,7 @@ // limitations under the License. use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; @@ -47,7 +48,7 @@ pub struct BookmarkMoveArgs { long, short, group = "source", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] from: Vec, @@ -58,7 +59,7 @@ pub struct BookmarkMoveArgs { #[arg( long, short, value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] to: Option, diff --git a/cli/src/commands/bookmark/set.rs b/cli/src/commands/bookmark/set.rs index 5b329ff7b..cd5fc27e1 100644 --- a/cli/src/commands/bookmark/set.rs +++ b/cli/src/commands/bookmark/set.rs @@ -13,6 +13,7 @@ // limitations under the License. use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::RefTarget; @@ -40,7 +41,7 @@ pub struct BookmarkSetArgs { long, short, visible_alias = "to", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: Option, diff --git a/cli/src/commands/describe.rs b/cli/src/commands/describe.rs index 89ae3e6ac..a103ca01d 100644 --- a/cli/src/commands/describe.rs +++ b/cli/src/commands/describe.rs @@ -17,7 +17,7 @@ use std::io; use std::io::Read as _; use std::iter; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::backend::Signature; use jj_lib::commit::CommitIteratorExt as _; @@ -49,14 +49,14 @@ pub(crate) struct DescribeArgs { /// The revision(s) whose description to edit (default: @) #[arg( value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable) )] revisions_pos: Vec, #[arg( short = 'r', hide = true, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable) )] revisions_opt: Vec, /// The change description to use (don't open editor) diff --git a/cli/src/commands/diff.rs b/cli/src/commands/diff.rs index 4e8fe3f92..f8e711934 100644 --- a/cli/src/commands/diff.rs +++ b/cli/src/commands/diff.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use indexmap::IndexSet; use itertools::Itertools as _; @@ -63,7 +62,7 @@ pub(crate) struct DiffArgs { short, value_name = "REVSETS", alias = "revision", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions: Option>, /// Show changes from this revision @@ -72,7 +71,7 @@ pub(crate) struct DiffArgs { short, conflicts_with = "revisions", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] from: Option, /// Show changes to this revision @@ -81,7 +80,7 @@ pub(crate) struct DiffArgs { short, conflicts_with = "revisions", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] to: Option, /// Restrict the diff to these paths diff --git a/cli/src/commands/diffedit.rs b/cli/src/commands/diffedit.rs index 4c3300cfb..18a3464b1 100644 --- a/cli/src/commands/diffedit.rs +++ b/cli/src/commands/diffedit.rs @@ -14,7 +14,7 @@ use std::io::Write as _; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::matchers::EverythingMatcher; use jj_lib::object_id::ObjectId as _; @@ -54,7 +54,7 @@ pub(crate) struct DiffeditArgs { long, short, value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revision: Option, /// Show changes from this revision @@ -64,7 +64,7 @@ pub(crate) struct DiffeditArgs { long, short, conflicts_with = "revision", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] from: Option, /// Edit changes in this revision @@ -74,7 +74,7 @@ pub(crate) struct DiffeditArgs { long, short, conflicts_with = "revision", value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] to: Option, /// Specify diff editor to be used diff --git a/cli/src/commands/duplicate.rs b/cli/src/commands/duplicate.rs index a8fe1c76b..68c26da89 100644 --- a/cli/src/commands/duplicate.rs +++ b/cli/src/commands/duplicate.rs @@ -15,7 +15,7 @@ use std::io::Write as _; use bstr::ByteVec as _; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::backend::BackendResult; use jj_lib::backend::CommitId; @@ -56,14 +56,14 @@ pub(crate) struct DuplicateArgs { /// The revision(s) to duplicate (default: @) #[arg( value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions_pos: Vec, #[arg( short = 'r', hide = true, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions_opt: Vec, /// The revision(s) to duplicate onto (can be repeated to create a merge @@ -72,7 +72,7 @@ pub(crate) struct DuplicateArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] destination: Option>, /// The revision(s) to insert after (can be repeated to create a merge @@ -83,7 +83,7 @@ pub(crate) struct DuplicateArgs { visible_alias = "after", conflicts_with = "destination", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] insert_after: Option>, /// The revision(s) to insert before (can be repeated to create a merge @@ -94,7 +94,7 @@ pub(crate) struct DuplicateArgs { visible_alias = "before", conflicts_with = "destination", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] insert_before: Option>, } diff --git a/cli/src/commands/edit.rs b/cli/src/commands/edit.rs index 4ba1afe03..d04cce6fb 100644 --- a/cli/src/commands/edit.rs +++ b/cli/src/commands/edit.rs @@ -14,7 +14,7 @@ use std::io::Write as _; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use jj_lib::object_id::ObjectId as _; use tracing::instrument; @@ -34,7 +34,7 @@ use crate::ui::Ui; #[derive(clap::Args, Clone, Debug)] pub(crate) struct EditArgs { /// The commit to edit - #[arg(value_name = "REVSET", add = ArgValueCandidates::new(complete::mutable_revisions))] + #[arg(value_name = "REVSET", add = ArgValueCompleter::new(complete::revset_expression_mutable))] revision: RevisionArg, /// Ignored (but lets you pass `-r` for consistency with other commands) #[arg(short = 'r', hide = true)] diff --git a/cli/src/commands/evolog.rs b/cli/src/commands/evolog.rs index 6c09d6ae1..8c850370e 100644 --- a/cli/src/commands/evolog.rs +++ b/cli/src/commands/evolog.rs @@ -15,6 +15,7 @@ use std::convert::Infallible; use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::commit::Commit; use jj_lib::dag_walk::topo_order_reverse_ok; @@ -46,7 +47,7 @@ pub(crate) struct EvologArgs { long, short, default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: RevisionArg, /// Limit number of revisions to show diff --git a/cli/src/commands/file/annotate.rs b/cli/src/commands/file/annotate.rs index 43032e0ad..31308a0db 100644 --- a/cli/src/commands/file/annotate.rs +++ b/cli/src/commands/file/annotate.rs @@ -47,7 +47,7 @@ pub(crate) struct FileAnnotateArgs { long, short, value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: Option, /// Render each line using the given template diff --git a/cli/src/commands/file/chmod.rs b/cli/src/commands/file/chmod.rs index 6aa8facbb..3acbc30f6 100644 --- a/cli/src/commands/file/chmod.rs +++ b/cli/src/commands/file/chmod.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use jj_lib::backend::TreeValue; use jj_lib::merged_tree::MergedTreeBuilder; @@ -50,7 +49,7 @@ pub(crate) struct FileChmodArgs { long, short, default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revision: RevisionArg, /// Paths to change the executable bit for diff --git a/cli/src/commands/file/list.rs b/cli/src/commands/file/list.rs index 26463970d..045203f19 100644 --- a/cli/src/commands/file/list.rs +++ b/cli/src/commands/file/list.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use tracing::instrument; use crate::cli_util::CommandHelper; @@ -31,7 +31,7 @@ pub(crate) struct FileListArgs { long, short, default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: RevisionArg, diff --git a/cli/src/commands/file/show.rs b/cli/src/commands/file/show.rs index 2945bd823..2692b7242 100644 --- a/cli/src/commands/file/show.rs +++ b/cli/src/commands/file/show.rs @@ -15,7 +15,6 @@ use std::io; use std::io::Write as _; -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use jj_lib::backend::BackendResult; use jj_lib::conflicts::materialize_merge_result; @@ -49,7 +48,7 @@ pub(crate) struct FileShowArgs { long, short, default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: RevisionArg, /// Paths to print diff --git a/cli/src/commands/fix.rs b/cli/src/commands/fix.rs index 74285c07b..de207697e 100644 --- a/cli/src/commands/fix.rs +++ b/cli/src/commands/fix.rs @@ -17,7 +17,7 @@ use std::io::Write as _; use std::path::Path; use std::process::Stdio; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::backend::CommitId; use jj_lib::backend::FileId; @@ -108,7 +108,7 @@ pub(crate) struct FixArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] source: Vec, /// Fix only these paths diff --git a/cli/src/commands/git/push.rs b/cli/src/commands/git/push.rs index 56376b08a..fe507f4cd 100644 --- a/cli/src/commands/git/push.rs +++ b/cli/src/commands/git/push.rs @@ -160,7 +160,7 @@ pub struct GitPushArgs { // While `-r` will often be used with mutable revisions, immutable // revisions can be useful as parts of revsets or to push // special-purpose branches. - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions: Vec, /// Push this commit by creating a bookmark based on its change ID (can be @@ -177,7 +177,7 @@ pub struct GitPushArgs { // recently created mutable revisions, even though it can in theory // be used with immutable ones as well. We can change it if the guess // turns out to be wrong. - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] change: Vec, /// Specify a new bookmark name and a revision to push under that name, e.g. diff --git a/cli/src/commands/interdiff.rs b/cli/src/commands/interdiff.rs index 871b1c63c..6fea9fc13 100644 --- a/cli/src/commands/interdiff.rs +++ b/cli/src/commands/interdiff.rs @@ -15,7 +15,6 @@ use std::slice; use clap::ArgGroup; -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use tracing::instrument; @@ -41,7 +40,7 @@ pub(crate) struct InterdiffArgs { long, short, value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] from: Option, /// Show changes to this revision @@ -49,7 +48,7 @@ pub(crate) struct InterdiffArgs { long, short, value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] to: Option, /// Restrict the diff to these paths diff --git a/cli/src/commands/log.rs b/cli/src/commands/log.rs index fe4df0e93..cc1367b70 100644 --- a/cli/src/commands/log.rs +++ b/cli/src/commands/log.rs @@ -74,7 +74,7 @@ pub(crate) struct LogArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions: Vec, /// Show revisions modifying the given paths diff --git a/cli/src/commands/new.rs b/cli/src/commands/new.rs index 291940bac..277d31bec 100644 --- a/cli/src/commands/new.rs +++ b/cli/src/commands/new.rs @@ -15,7 +15,7 @@ use std::collections::HashSet; use std::io::Write as _; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::backend::CommitId; use jj_lib::repo::Repo as _; @@ -49,7 +49,7 @@ pub(crate) struct NewArgs { #[arg( default_value = "@", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions: Option>, /// Ignored (but lets you pass `-d`/`-r` for consistency with other @@ -98,7 +98,7 @@ pub(crate) struct NewArgs { conflicts_with = "revisions", value_name = "REVSETS", verbatim_doc_comment, - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] insert_after: Option>, /// Insert the new change before the given commit(s) @@ -137,7 +137,7 @@ pub(crate) struct NewArgs { conflicts_with = "revisions", value_name = "REVSETS", verbatim_doc_comment, - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] insert_before: Option>, } diff --git a/cli/src/commands/parallelize.rs b/cli/src/commands/parallelize.rs index b619065be..1a1fbc1b4 100644 --- a/cli/src/commands/parallelize.rs +++ b/cli/src/commands/parallelize.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use indexmap::IndexSet; use itertools::Itertools as _; use jj_lib::backend::CommitId; @@ -59,7 +59,7 @@ pub(crate) struct ParallelizeArgs { /// Revisions to parallelize #[arg( value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions: Vec, } diff --git a/cli/src/commands/rebase.rs b/cli/src/commands/rebase.rs index f1d760179..aacd8e1c8 100644 --- a/cli/src/commands/rebase.rs +++ b/cli/src/commands/rebase.rs @@ -16,7 +16,7 @@ use std::io::Write as _; use std::sync::Arc; use clap::ArgGroup; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::backend::CommitId; use jj_lib::commit::Commit; @@ -278,7 +278,7 @@ pub(crate) struct RebaseArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] branch: Vec, @@ -294,7 +294,7 @@ pub(crate) struct RebaseArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] source: Vec, /// Rebase the given revisions, rebasing descendants onto this revision's @@ -308,7 +308,7 @@ pub(crate) struct RebaseArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions: Vec, @@ -336,7 +336,7 @@ pub struct RebaseDestinationArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] destination: Option>, /// The revision(s) to insert after (can be repeated to create a merge @@ -347,7 +347,7 @@ pub struct RebaseDestinationArgs { visible_alias = "after", conflicts_with = "destination", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] insert_after: Option>, /// The revision(s) to insert before (can be repeated to create a merge @@ -358,7 +358,7 @@ pub struct RebaseDestinationArgs { visible_alias = "before", conflicts_with = "destination", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] insert_before: Option>, } diff --git a/cli/src/commands/resolve.rs b/cli/src/commands/resolve.rs index 40b6c5f2c..53324a9c0 100644 --- a/cli/src/commands/resolve.rs +++ b/cli/src/commands/resolve.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::object_id::ObjectId as _; @@ -50,7 +49,7 @@ pub(crate) struct ResolveArgs { long, short, default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revision: RevisionArg, /// Instead of resolving conflicts, list all the conflicts diff --git a/cli/src/commands/restore.rs b/cli/src/commands/restore.rs index 0e92354ec..a9f6eb0f4 100644 --- a/cli/src/commands/restore.rs +++ b/cli/src/commands/restore.rs @@ -14,7 +14,6 @@ use std::io::Write as _; -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use indoc::formatdoc; use itertools::Itertools as _; @@ -58,7 +57,7 @@ pub(crate) struct RestoreArgs { long, short, value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] from: Option, /// Revision to restore into (destination) @@ -66,7 +65,7 @@ pub(crate) struct RestoreArgs { long, short = 't', visible_alias = "to", value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] into: Option, /// Undo the changes in a revision as compared to the merge of its parents. @@ -81,7 +80,7 @@ pub(crate) struct RestoreArgs { long, short, value_name = "REVSET", conflicts_with_all = ["into", "from"], - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] changes_in: Option, /// Prints an error. DO NOT USE. diff --git a/cli/src/commands/revert.rs b/cli/src/commands/revert.rs index 6f63502a1..771eea3ce 100644 --- a/cli/src/commands/revert.rs +++ b/cli/src/commands/revert.rs @@ -16,7 +16,7 @@ use std::collections::HashSet; use bstr::ByteVec as _; use clap::ArgGroup; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use indexmap::IndexSet; use itertools::Itertools as _; use jj_lib::backend::CommitId; @@ -48,14 +48,14 @@ pub(crate) struct RevertArgs { #[arg( long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] revisions: Vec, /// The revision(s) to apply the reverse changes on top of #[arg( long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] destination: Option>, /// The revision(s) to insert the reverse changes after (can be repeated to @@ -66,7 +66,7 @@ pub(crate) struct RevertArgs { visible_alias = "after", conflicts_with = "destination", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::all_revisions), + add = ArgValueCompleter::new(complete::revset_expression_all), )] insert_after: Option>, /// The revision(s) to insert the reverse changes before (can be repeated to @@ -77,7 +77,7 @@ pub(crate) struct RevertArgs { visible_alias = "before", conflicts_with = "destination", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] insert_before: Option>, } diff --git a/cli/src/commands/show.rs b/cli/src/commands/show.rs index 253a80d8c..b116108fb 100644 --- a/cli/src/commands/show.rs +++ b/cli/src/commands/show.rs @@ -13,6 +13,7 @@ // limitations under the License. use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use jj_lib::matchers::EverythingMatcher; use tracing::instrument; @@ -30,7 +31,7 @@ pub(crate) struct ShowArgs { #[arg( default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::all_revisions) + add = ArgValueCompleter::new(complete::revset_expression_all), )] revision: RevisionArg, /// Ignored (but lets you pass `-r` for consistency with other commands) diff --git a/cli/src/commands/sign.rs b/cli/src/commands/sign.rs index 765e9ad1a..a0836818d 100644 --- a/cli/src/commands/sign.rs +++ b/cli/src/commands/sign.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use indexmap::IndexSet; use itertools::Itertools as _; use jj_lib::commit::Commit; @@ -52,7 +52,7 @@ pub struct SignArgs { #[arg( long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions: Vec, diff --git a/cli/src/commands/simplify_parents.rs b/cli/src/commands/simplify_parents.rs index c09015716..55c386796 100644 --- a/cli/src/commands/simplify_parents.rs +++ b/cli/src/commands/simplify_parents.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use itertools::Itertools as _; use jj_lib::backend::BackendError; use jj_lib::revset::RevsetExpression; @@ -28,7 +28,7 @@ pub(crate) struct SimplifyParentsArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] source: Vec, /// Simplify specified revision(s) (can be repeated) @@ -40,7 +40,7 @@ pub(crate) struct SimplifyParentsArgs { long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions: Vec, } diff --git a/cli/src/commands/split.rs b/cli/src/commands/split.rs index 400c916b4..299da87c6 100644 --- a/cli/src/commands/split.rs +++ b/cli/src/commands/split.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::io::Write as _; -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use jj_lib::commit::Commit; use jj_lib::matchers::Matcher; @@ -67,7 +66,7 @@ pub(crate) struct SplitArgs { long, short, default_value = "@", value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revision: RevisionArg, /// Split the revision into two parallel revisions instead of a parent and diff --git a/cli/src/commands/squash.rs b/cli/src/commands/squash.rs index eccb11099..115d3cbe8 100644 --- a/cli/src/commands/squash.rs +++ b/cli/src/commands/squash.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use indoc::formatdoc; use itertools::Itertools as _; @@ -69,7 +68,7 @@ pub(crate) struct SquashArgs { long, short, value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions) + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revision: Option, /// Revision(s) to squash from (default: @) @@ -77,7 +76,7 @@ pub(crate) struct SquashArgs { long, short, conflicts_with = "revision", value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] from: Vec, /// Revision to squash into (default: @) @@ -86,7 +85,7 @@ pub(crate) struct SquashArgs { conflicts_with = "revision", visible_alias = "to", value_name = "REVSET", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] into: Option, /// The description to use for squashed revision (don't open editor) diff --git a/cli/src/commands/unsign.rs b/cli/src/commands/unsign.rs index 9c22afb66..c3bf3a68c 100644 --- a/cli/src/commands/unsign.rs +++ b/cli/src/commands/unsign.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use clap_complete::ArgValueCandidates; +use clap_complete::ArgValueCompleter; use indexmap::IndexSet; use itertools::Itertools as _; use jj_lib::commit::Commit; @@ -38,7 +38,7 @@ pub struct UnsignArgs { #[arg( long, short, value_name = "REVSETS", - add = ArgValueCandidates::new(complete::mutable_revisions), + add = ArgValueCompleter::new(complete::revset_expression_mutable), )] revisions: Vec, } diff --git a/cli/src/complete.rs b/cli/src/complete.rs index e3684b9a2..6f7df2f6a 100644 --- a/cli/src/complete.rs +++ b/cli/src/complete.rs @@ -230,7 +230,7 @@ pub fn aliases() -> Vec { }) } -fn revisions(revisions: Option<&str>) -> Vec { +fn revisions(match_prefix: &str, revset_filter: Option<&str>) -> Vec { with_jj(|jj, settings| { // display order const LOCAL_BOOKMARK_MINE: usize = 0; @@ -257,36 +257,40 @@ fn revisions(revisions: Option<&str>) -> Vec { .arg( r#"if(remote != "git", name ++ if(remote, "@" ++ remote) ++ bookmark_help() ++ "\n")"#, ); - if let Some(revs) = revisions { + if let Some(revs) = revset_filter { 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); + candidates.extend( + stdout + .lines() + .map(split_help_text) + .filter(|(bookmark, _)| bookmark.starts_with(match_prefix)) + .map(|(bookmark, help)| { + let local = !bookmark.contains('@'); + let mine = prefix.as_ref().is_some_and(|p| bookmark.starts_with(p)); - 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)) - })); + 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 + // revset_filter is set. This is not a big loss, since tags usually point // to immutable revisions anyway. - if revisions.is_none() { + if revset_filter.is_none() { let output = jj .build() .arg("tag") @@ -295,6 +299,7 @@ fn revisions(revisions: Option<&str>) -> Vec { .arg(BOOKMARK_HELP_TEMPLATE) .arg("--template") .arg(r#"name ++ bookmark_help() ++ "\n""#) + .arg(format!("glob:{}*", glob::Pattern::escape(match_prefix))) .output() .map_err(user_error)?; let stdout = String::from_utf8_lossy(&output.stdout); @@ -309,7 +314,7 @@ fn revisions(revisions: Option<&str>) -> Vec { // change IDs - let revisions = revisions + let revisions = revset_filter .map(String::from) .or_else(|| settings.get_string("revsets.short-prefixes").ok()) .or_else(|| settings.get_string("revsets.log").ok()) @@ -329,35 +334,92 @@ fn revisions(revisions: Option<&str>) -> Vec { .map_err(user_error)?; let stdout = String::from_utf8_lossy(&output.stdout); - candidates.extend(stdout.lines().map(|line| { - let (id, desc) = split_help_text(line); - CompletionCandidate::new(id) - .help(desc) - .display_order(Some(CHANGE_ID)) - })); + candidates.extend( + stdout + .lines() + .map(split_help_text) + .filter(|(id, _)| id.starts_with(match_prefix)) + .map(|(id, desc)| { + CompletionCandidate::new(id) + .help(desc) + .display_order(Some(CHANGE_ID)) + }), + ); // revset aliases let revset_aliases = load_revset_aliases(&Ui::null(), settings.config())?; let mut symbol_names: Vec<_> = revset_aliases.symbol_names().collect(); symbol_names.sort(); - candidates.extend(symbol_names.into_iter().map(|symbol| { - let (_, defn) = revset_aliases.get_symbol(symbol).unwrap(); - CompletionCandidate::new(symbol) - .help(Some(defn.into())) - .display_order(Some(REVSET_ALIAS)) - })); + candidates.extend( + symbol_names + .into_iter() + .filter(|symbol| symbol.starts_with(match_prefix)) + .map(|symbol| { + let (_, defn) = revset_aliases.get_symbol(symbol).unwrap(); + CompletionCandidate::new(symbol) + .help(Some(defn.into())) + .display_order(Some(REVSET_ALIAS)) + }), + ); Ok(candidates) }) } -pub fn mutable_revisions() -> Vec { - revisions(Some("mutable()")) +fn revset_expression( + current: &std::ffi::OsStr, + revset_filter: Option<&str>, +) -> Vec { + let Some(current) = current.to_str() else { + return Vec::new(); + }; + let (prepend, match_prefix) = split_revset_trailing_name(current).unwrap_or(("", current)); + let candidates = revisions(match_prefix, revset_filter); + if prepend.is_empty() { + candidates + } else { + candidates + .into_iter() + .map(|candidate| candidate.add_prefix(prepend)) + .collect() + } } -pub fn all_revisions() -> Vec { - revisions(None) +pub fn revset_expression_all(current: &std::ffi::OsStr) -> Vec { + revset_expression(current, None) +} + +pub fn revset_expression_mutable(current: &std::ffi::OsStr) -> Vec { + revset_expression(current, Some("mutable()")) +} + +/// Identifies if an incomplete expression ends with a name, or may be continued +/// with a name. +/// +/// If the expression ends with an name or a partial name, returns a tuple that +/// splits the string at the point the name starts. +/// If the expression is empty or ends with a prefix or infix operator that +/// could plausibly be followed by a name, returns a tuple where the first +/// item is the entire input string, and the second item is empty. +/// Otherwise, returns `None`. +/// +/// The input expression may be incomplete (e.g. missing closing parentheses), +/// and the ability to reject invalid expressions is limited. +fn split_revset_trailing_name(incomplete_revset_str: &str) -> Option<(&str, &str)> { + let final_part = incomplete_revset_str + .rsplit_once([':', '~', '|', '&', '(', ',']) + .map(|(_, rest)| rest) + .unwrap_or(incomplete_revset_str); + let final_part = final_part + .rsplit_once("..") + .map(|(_, rest)| rest) + .unwrap_or(final_part) + .trim_ascii_start(); + + let re = regex::Regex::new(r"^(?:[\p{XID_CONTINUE}_/]+[@.+-])*[\p{XID_CONTINUE}_/]*$").unwrap(); + re.is_match(final_part) + .then(|| incomplete_revset_str.split_at(incomplete_revset_str.len() - final_part.len())) } pub fn operations() -> Vec { @@ -557,13 +619,8 @@ pub fn branch_name_equals_any_revision(current: &std::ffi::OsStr) -> Vec