completion: revset expression completer

This commit is contained in:
Daniel Luz 2025-04-28 00:40:25 -03:00 committed by Daniel Luz
parent 8936a7bc4b
commit 87f6db0a70
35 changed files with 287 additions and 129 deletions

View File

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

View File

@ -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<RevisionArg>,
#[arg(
short = 'r',
hide = true,
value_name = "REVSETS",
add = ArgValueCandidates::new(complete::mutable_revisions)
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
revisions_opt: Vec<RevisionArg>,
// TODO: Remove in jj 0.34+

View File

@ -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<RevisionArg>,
/// Move only changes to these paths (instead of all paths)

View File

@ -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<RevisionArg>,
/// 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<RevisionArg>,
}

View File

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

View File

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

View File

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

View File

@ -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<RevisionArg>,
#[arg(
short = 'r',
hide = true,
value_name = "REVSETS",
add = ArgValueCandidates::new(complete::mutable_revisions)
add = ArgValueCompleter::new(complete::revset_expression_mutable)
)]
revisions_opt: Vec<RevisionArg>,
/// The change description to use (don't open editor)

View File

@ -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<Vec<RevisionArg>>,
/// 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<RevisionArg>,
/// 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<RevisionArg>,
/// Restrict the diff to these paths

View File

@ -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<RevisionArg>,
/// 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<RevisionArg>,
/// 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<RevisionArg>,
/// Specify diff editor to be used

View File

@ -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<RevisionArg>,
#[arg(
short = 'r',
hide = true,
value_name = "REVSETS",
add = ArgValueCandidates::new(complete::all_revisions)
add = ArgValueCompleter::new(complete::revset_expression_all),
)]
revisions_opt: Vec<RevisionArg>,
/// 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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
}

View File

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

View File

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

View File

@ -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<RevisionArg>,
/// Render each line using the given template

View File

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

View File

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

View File

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

View File

@ -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<RevisionArg>,
/// Fix only these paths

View File

@ -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<RevisionArg>,
/// 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<RevisionArg>,
/// Specify a new bookmark name and a revision to push under that name, e.g.

View File

@ -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<RevisionArg>,
/// 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<RevisionArg>,
/// Restrict the diff to these paths

View File

@ -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<RevisionArg>,
/// Show revisions modifying the given paths

View File

@ -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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
}

View File

@ -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<RevisionArg>,
}

View File

@ -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<RevisionArg>,
@ -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<RevisionArg>,
/// 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<RevisionArg>,
@ -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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
}

View File

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

View File

@ -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<RevisionArg>,
/// 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<RevisionArg>,
/// 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<RevisionArg>,
/// Prints an error. DO NOT USE.

View File

@ -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<RevisionArg>,
/// 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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
/// 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<Vec<RevisionArg>>,
}

View File

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

View File

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

View File

@ -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<RevisionArg>,
/// 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<RevisionArg>,
}

View File

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

View File

@ -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<RevisionArg>,
/// 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<RevisionArg>,
/// 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<RevisionArg>,
/// The description to use for squashed revision (don't open editor)

View File

@ -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<RevisionArg>,
}

View File

@ -230,7 +230,7 @@ pub fn aliases() -> Vec<CompletionCandidate> {
})
}
fn revisions(revisions: Option<&str>) -> Vec<CompletionCandidate> {
fn revisions(match_prefix: &str, revset_filter: Option<&str>) -> Vec<CompletionCandidate> {
with_jj(|jj, settings| {
// display order
const LOCAL_BOOKMARK_MINE: usize = 0;
@ -257,15 +257,18 @@ fn revisions(revisions: Option<&str>) -> Vec<CompletionCandidate> {
.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));
@ -278,15 +281,16 @@ fn revisions(revisions: Option<&str>) -> Vec<CompletionCandidate> {
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<CompletionCandidate> {
.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<CompletionCandidate> {
// 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<CompletionCandidate> {
.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);
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| {
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<CompletionCandidate> {
revisions(Some("mutable()"))
fn revset_expression(
current: &std::ffi::OsStr,
revset_filter: Option<&str>,
) -> Vec<CompletionCandidate> {
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<CompletionCandidate> {
revisions(None)
pub fn revset_expression_all(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
revset_expression(current, None)
}
pub fn revset_expression_mutable(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
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<CompletionCandidate> {
@ -557,13 +619,8 @@ pub fn branch_name_equals_any_revision(current: &std::ffi::OsStr) -> Vec<Complet
// Don't complete branch names since we want to create a new branch
return Vec::new();
};
all_revisions()
revset_expression(revision.as_ref(), None)
.into_iter()
.filter(|rev| {
rev.get_value()
.to_str()
.is_some_and(|s| s.starts_with(revision))
})
.map(|rev| rev.add_prefix(format!("{branch_name}=")))
.collect()
}
@ -1022,6 +1079,71 @@ mod parse {
mod tests {
use super::*;
#[test]
fn test_split_revset_trailing_name() {
assert_eq!(split_revset_trailing_name(""), Some(("", "")));
assert_eq!(split_revset_trailing_name(" "), Some((" ", "")));
assert_eq!(split_revset_trailing_name("foo"), Some(("", "foo")));
assert_eq!(split_revset_trailing_name(" foo"), Some((" ", "foo")));
assert_eq!(split_revset_trailing_name("foo "), None);
assert_eq!(split_revset_trailing_name("foo_"), Some(("", "foo_")));
assert_eq!(split_revset_trailing_name("foo/"), Some(("", "foo/")));
assert_eq!(split_revset_trailing_name("foo/b"), Some(("", "foo/b")));
assert_eq!(split_revset_trailing_name("foo-"), Some(("", "foo-")));
assert_eq!(split_revset_trailing_name("foo+"), Some(("", "foo+")));
assert_eq!(
split_revset_trailing_name("foo-bar-"),
Some(("", "foo-bar-"))
);
assert_eq!(
split_revset_trailing_name("foo-bar-b"),
Some(("", "foo-bar-b"))
);
assert_eq!(split_revset_trailing_name("foo."), Some(("", "foo.")));
assert_eq!(split_revset_trailing_name("foo..b"), Some(("foo..", "b")));
assert_eq!(split_revset_trailing_name("..foo"), Some(("..", "foo")));
assert_eq!(split_revset_trailing_name("foo(bar"), Some(("foo(", "bar")));
assert_eq!(split_revset_trailing_name("foo(bar)"), None);
assert_eq!(split_revset_trailing_name("(f"), Some(("(", "f")));
assert_eq!(split_revset_trailing_name("foo@"), Some(("", "foo@")));
assert_eq!(split_revset_trailing_name("foo@b"), Some(("", "foo@b")));
assert_eq!(split_revset_trailing_name("..foo@"), Some(("..", "foo@")));
assert_eq!(
split_revset_trailing_name("::F(foo@origin.1..bar@origin."),
Some(("::F(foo@origin.1..", "bar@origin."))
);
}
#[test]
fn test_split_revset_trailing_name_with_trailing_operator() {
assert_eq!(split_revset_trailing_name("foo|"), Some(("foo|", "")));
assert_eq!(split_revset_trailing_name("foo | "), Some(("foo | ", "")));
assert_eq!(split_revset_trailing_name("foo&"), Some(("foo&", "")));
assert_eq!(split_revset_trailing_name("foo~"), Some(("foo~", "")));
assert_eq!(split_revset_trailing_name(".."), Some(("..", "")));
assert_eq!(split_revset_trailing_name("foo.."), Some(("foo..", "")));
assert_eq!(split_revset_trailing_name("::"), Some(("::", "")));
assert_eq!(split_revset_trailing_name("foo::"), Some(("foo::", "")));
assert_eq!(split_revset_trailing_name("("), Some(("(", "")));
assert_eq!(split_revset_trailing_name("foo("), Some(("foo(", "")));
assert_eq!(split_revset_trailing_name("foo()"), None);
assert_eq!(split_revset_trailing_name("foo(bar)"), None);
}
#[test]
fn test_split_revset_trailing_name_with_modifier() {
assert_eq!(split_revset_trailing_name("all:"), Some(("all:", "")));
assert_eq!(split_revset_trailing_name("all: "), Some(("all: ", "")));
assert_eq!(split_revset_trailing_name("all:f"), Some(("all:", "f")));
assert_eq!(split_revset_trailing_name("all: f"), Some(("all: ", "f")));
}
#[test]
fn test_config_keys() {
// Just make sure the schema is parsed without failure.

View File

@ -792,6 +792,22 @@ fn test_revisions() {
[EOF]
");
// complete all revisions in a revset expression
let output = work_dir.complete_fish(["log", "-r", ".."]);
insta::assert_snapshot!(output, @r"
..immutable_bookmark immutable
..mutable_bookmark mutable
..k working_copy
..y mutable
..q immutable
..zq remote_commit
..zz (no description set)
..remote_bookmark@origin remote_commit
..alias_with_newline roots(
..siblings @-+ ~@
[EOF]
");
// complete only mutable revisions
let output = work_dir.complete_fish(["squash", "--into", ""]);
insta::assert_snapshot!(output, @r"
@ -804,6 +820,25 @@ fn test_revisions() {
[EOF]
");
// complete only mutable revisions in a revset expression
let output = work_dir.complete_fish(["abandon", "y::"]);
insta::assert_snapshot!(output, @r"
y::mutable_bookmark mutable
y::k working_copy
y::y mutable
y::zq remote_commit
y::alias_with_newline roots(
y::siblings @-+ ~@
[EOF]
");
// complete remote bookmarks in a revset expression
let output = work_dir.complete_fish(["log", "-r", "remote_bookmark@"]);
insta::assert_snapshot!(output, @r"
remote_bookmark@origin remote_commit
[EOF]
");
// complete args of the default command
test_env.add_config("ui.default-command = 'log'");
let output = work_dir.complete_fish(["-r", ""]);