Includes diff tools, diff editors, and merge editors.
+6
-1
cli/src/commands/commit.rs
+6
-1
cli/src/commands/commit.rs
···
12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
+
use clap_complete::ArgValueCandidates;
15
16
use clap_complete::ArgValueCompleter;
16
17
use indoc::writedoc;
17
18
use jj_lib::backend::Signature;
···
37
38
#[arg(short, long)]
38
39
interactive: bool,
39
40
/// Specify diff editor to be used (implies --interactive)
40
-
#[arg(long, value_name = "NAME")]
41
+
#[arg(
42
+
long,
43
+
value_name = "NAME",
44
+
add = ArgValueCandidates::new(complete::diff_editors),
45
+
)]
41
46
tool: Option<String>,
42
47
/// The change description to use (don't open editor)
43
48
#[arg(long = "message", short, value_name = "MESSAGE")]
+6
-1
cli/src/commands/diffedit.rs
+6
-1
cli/src/commands/diffedit.rs
···
14
14
15
15
use std::io::Write as _;
16
16
17
+
use clap_complete::ArgValueCandidates;
17
18
use clap_complete::ArgValueCompleter;
18
19
use itertools::Itertools as _;
19
20
use jj_lib::matchers::EverythingMatcher;
···
77
78
)]
78
79
to: Option<RevisionArg>,
79
80
/// Specify diff editor to be used
80
-
#[arg(long, value_name = "NAME")]
81
+
#[arg(
82
+
long,
83
+
value_name = "NAME",
84
+
add = ArgValueCandidates::new(complete::diff_editors),
85
+
)]
81
86
tool: Option<String>,
82
87
/// Preserve the content (not the diff) when rebasing descendants
83
88
///
+7
-1
cli/src/commands/resolve.rs
+7
-1
cli/src/commands/resolve.rs
···
12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
+
use clap_complete::ArgValueCandidates;
15
16
use clap_complete::ArgValueCompleter;
16
17
use itertools::Itertools as _;
17
18
use jj_lib::object_id::ObjectId as _;
···
61
62
///
62
63
/// The built-in merge tools `:ours` and `:theirs` can be used to choose
63
64
/// side #1 and side #2 of the conflict respectively.
64
-
#[arg(long, conflicts_with = "list", value_name = "NAME")]
65
+
#[arg(
66
+
long,
67
+
conflicts_with = "list",
68
+
value_name = "NAME",
69
+
add = ArgValueCandidates::new(complete::merge_tools),
70
+
)]
65
71
tool: Option<String>,
66
72
/// Only resolve conflicts in these paths. You can use the `--list` argument
67
73
/// to find paths to use here.
+6
-1
cli/src/commands/restore.rs
+6
-1
cli/src/commands/restore.rs
···
14
14
15
15
use std::io::Write as _;
16
16
17
+
use clap_complete::ArgValueCandidates;
17
18
use clap_complete::ArgValueCompleter;
18
19
use indoc::formatdoc;
19
20
use itertools::Itertools as _;
···
96
97
#[arg(long, short)]
97
98
interactive: bool,
98
99
/// Specify diff editor to be used (implies --interactive)
99
-
#[arg(long, value_name = "NAME")]
100
+
#[arg(
101
+
long,
102
+
value_name = "NAME",
103
+
add = ArgValueCandidates::new(complete::diff_editors),
104
+
)]
100
105
tool: Option<String>,
101
106
/// Preserve the content (not the diff) when rebasing descendants
102
107
#[arg(long)]
+6
-1
cli/src/commands/split.rs
+6
-1
cli/src/commands/split.rs
···
14
14
use std::collections::HashMap;
15
15
use std::io::Write as _;
16
16
17
+
use clap_complete::ArgValueCandidates;
17
18
use clap_complete::ArgValueCompleter;
18
19
use jj_lib::backend::CommitId;
19
20
use jj_lib::commit::Commit;
···
70
71
#[arg(long, short)]
71
72
interactive: bool,
72
73
/// Specify diff editor to be used (implies --interactive)
73
-
#[arg(long, value_name = "NAME")]
74
+
#[arg(
75
+
long,
76
+
value_name = "NAME",
77
+
add = ArgValueCandidates::new(complete::diff_editors),
78
+
)]
74
79
tool: Option<String>,
75
80
/// The revision to split
76
81
#[arg(
+6
-1
cli/src/commands/squash.rs
+6
-1
cli/src/commands/squash.rs
···
12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
+
use clap_complete::ArgValueCandidates;
15
16
use clap_complete::ArgValueCompleter;
16
17
use indoc::formatdoc;
17
18
use itertools::Itertools as _;
···
99
100
#[arg(long, short)]
100
101
interactive: bool,
101
102
/// Specify diff editor to be used (implies --interactive)
102
-
#[arg(long, value_name = "NAME")]
103
+
#[arg(
104
+
long,
105
+
value_name = "NAME",
106
+
add = ArgValueCandidates::new(complete::diff_editors),
107
+
)]
103
108
tool: Option<String>,
104
109
/// Move only changes to these paths (instead of all paths)
105
110
#[arg(
+48
cli/src/complete.rs
+48
cli/src/complete.rs
···
34
34
use crate::config::ConfigArgKind;
35
35
use crate::config::ConfigEnv;
36
36
use crate::config::CONFIG_SCHEMA;
37
+
use crate::merge_tools::configured_merge_tools;
38
+
use crate::merge_tools::MergeEditor;
37
39
use crate::revset_util::load_revset_aliases;
38
40
use crate::ui::Ui;
39
41
···
471
473
472
474
473
475
476
+
.collect())
477
+
})
478
+
}
479
+
pub fn merge_tools() -> Vec<CompletionCandidate> {
480
+
with_jj(|_, settings| {
481
+
Ok([":builtin", ":ours", ":theirs"]
482
+
.into_iter()
483
+
.chain(
484
+
configured_merge_tools(settings)
485
+
.filter(|name| MergeEditor::dummy_with_name(name, settings).is_ok()),
486
+
)
487
+
.map(CompletionCandidate::new)
488
+
.collect())
489
+
})
490
+
}
491
+
492
+
/// Approximate list of known diff editors
493
+
///
494
+
/// Diff tools can be used without configuration. Some merge tools that are
495
+
/// configured for 3-way merging may not work for diffing/diff editing, and we
496
+
/// can't tell which these are. So, this not reliable, but probably good enough
497
+
/// for command-line completion.
498
+
pub fn diff_editors() -> Vec<CompletionCandidate> {
499
+
let builtin_format_kinds: Vec<String> = crate::diff_util::BuiltinFormatKind::ALL_VARIANTS
500
+
.iter()
501
+
.map(|kind| format!(":{}", kind.to_arg_name()))
502
+
.collect();
503
+
with_jj(|_, settings| {
504
+
Ok(std::iter::once(":builtin")
505
+
.chain(builtin_format_kinds.iter().map(|s| s.as_str()))
506
+
.chain(configured_merge_tools(settings))
507
+
.map(CompletionCandidate::new)
508
+
.collect())
509
+
})
510
+
}
511
+
512
+
/// Approximate list of known diff tools
513
+
///
514
+
/// Diff tools can be used without configuration. Some merge tools that are
515
+
/// configured for 3-way merging may not work for diffing/diff editing, and we
516
+
/// can't tell which these are. So, this not reliable, but probably good enough
517
+
/// for command-line completion.
518
+
pub fn diff_tools() -> Vec<CompletionCandidate> {
519
+
with_jj(|_, settings| {
520
+
Ok(configured_merge_tools(settings)
521
+
.map(CompletionCandidate::new)
474
522
.collect())
475
523
})
476
524
}
+98
-1
cli/src/diff_util.rs
+98
-1
cli/src/diff_util.rs
···
23
23
24
24
use bstr::BStr;
25
25
use bstr::BString;
26
+
use clap_complete::ArgValueCandidates;
26
27
use futures::executor::block_on_stream;
27
28
use futures::stream::BoxStream;
28
29
use futures::StreamExt as _;
···
124
125
///
125
126
/// A builtin format can also be specified as `:<name>`. For example,
126
127
/// `--tool=:git` is equivalent to `--git`.
127
-
#[arg(long)]
128
+
#[arg(
129
+
long,
130
+
add = ArgValueCandidates::new(crate::complete::diff_tools),
131
+
)]
128
132
pub tool: Option<String>,
129
133
/// Number of lines of context to show
130
134
#[arg(long)]
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
}
157
+
158
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
159
+
pub enum BuiltinFormatKind {
160
+
Summary,
161
+
Stat,
162
+
Types,
163
+
164
+
165
+
166
+
}
167
+
168
+
impl BuiltinFormatKind {
169
+
// Alternatively, we could use or vendor one of the crates `strum`,
170
+
// `enum-iterator`, or `variant_count` (for a check that the length of the array
171
+
// is correct). The latter is very simple and is also a nightly feature.
172
+
pub const ALL_VARIANTS: &[BuiltinFormatKind] = &[
173
+
Self::Summary,
174
+
Self::Stat,
175
+
Self::Types,
176
+
Self::NameOnly,
177
+
Self::Git,
178
+
Self::ColorWords,
179
+
];
180
+
181
+
fn from_name(name: &str) -> Result<Self, String> {
182
+
match name {
183
+
"summary" => Ok(Self::Summary),
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
}
222
+
}
223
+
224
+
pub fn to_arg_name(self) -> &'static str {
225
+
match self {
226
+
Self::Summary => "summary",
227
+
Self::Stat => "stat",
+22
-6
cli/src/merge_tools/mod.rs
+22
-6
cli/src/merge_tools/mod.rs
···
207
207
}
208
208
}
209
209
210
+
/// List configured merge tools (diff editors, diff tools, merge editors)
211
+
pub fn configured_merge_tools(settings: &UserSettings) -> impl Iterator<Item = &str> {
212
+
settings.table_keys("merge-tools")
213
+
}
214
+
210
215
/// Loads external diff/merge tool options from `[merge-tools.<name>]`.
211
216
pub fn get_external_tool_config(
212
217
settings: &UserSettings,
···
380
385
Self::new_inner(name, tool, path_converter, conflict_marker_style)
381
386
}
382
387
388
+
/// For the purposes of testing or checking basic config
389
+
pub fn dummy_with_name(
390
+
name: &str,
391
+
settings: &UserSettings,
392
+
) -> Result<Self, MergeToolConfigError> {
393
+
Self::with_name(
394
+
name,
395
+
settings,
396
+
RepoPathUiConverter::Fs {
397
+
cwd: "".into(),
398
+
base: "".into(),
399
+
},
400
+
ConflictMarkerStyle::Diff,
401
+
)
402
+
}
403
+
383
404
/// Loads the default 3-way merge editor from the settings.
384
405
pub fn from_settings(
385
406
ui: &Ui,
···
765
786
let get = |name, config_text| {
766
787
let config = config_from_string(config_text);
767
788
let settings = UserSettings::from_config(config).unwrap();
768
-
let path_converter = RepoPathUiConverter::Fs {
769
-
cwd: "".into(),
770
-
base: "".into(),
771
-
};
772
-
MergeEditor::with_name(name, &settings, path_converter, ConflictMarkerStyle::Diff)
773
-
.map(|editor| editor.tool)
789
+
MergeEditor::dummy_with_name(name, &settings).map(|editor| editor.tool)
774
790
};
775
791
776
792
insta::assert_debug_snapshot!(get(":builtin", "").unwrap(), @"Builtin");
+61
cli/tests/test_completion.rs
+61
cli/tests/test_completion.rs
···
1089
1089
");
1090
1090
}
1091
1091
1092
+
#[test]
1093
+
fn test_merge_tools() {
1094
+
let mut test_env = TestEnvironment::default();
1095
+
test_env.add_env_var("COMPLETE", "fish");
1096
+
let dir = test_env.env_root();
1097
+
1098
+
let output = test_env.run_jj_in(dir, ["--", "jj", "diff", "--tool", ""]);
1099
+
insta::assert_snapshot!(output, @r"
1100
+
diffedit3
1101
+
diffedit3-ssh
1102
+
difft
1103
+
kdiff3
1104
+
meld
1105
+
meld-3
1106
+
mergiraf
1107
+
smerge
1108
+
vimdiff
1109
+
vscode
1110
+
vscodium
1111
+
[EOF]
1112
+
");
1113
+
// Includes :builtin
1114
+
let output = test_env.run_jj_in(dir, ["--", "jj", "diffedit", "--tool", ""]);
1115
+
insta::assert_snapshot!(output, @r"
1116
+
:builtin
1117
+
:summary
1118
+
:stat
1119
+
:types
1120
+
:name-only
1121
+
:git
1122
+
:color-words
1123
+
diffedit3
1124
+
diffedit3-ssh
1125
+
difft
1126
+
kdiff3
1127
+
meld
1128
+
meld-3
1129
+
mergiraf
1130
+
smerge
1131
+
vimdiff
1132
+
vscode
1133
+
vscodium
1134
+
[EOF]
1135
+
");
1136
+
// Only includes configured merge editors
1137
+
let output = test_env.run_jj_in(dir, ["--", "jj", "resolve", "--tool", ""]);
1138
+
insta::assert_snapshot!(output, @r"
1139
+
:builtin
1140
+
:ours
1141
+
:theirs
1142
+
kdiff3
1143
+
meld
1144
+
mergiraf
1145
+
smerge
1146
+
vimdiff
1147
+
vscode
1148
+
vscodium
1149
+
[EOF]
1150
+
");
1151
+
}
1152
+
1092
1153
fn create_commit(
1093
1154
work_dir: &TestWorkDir,
1094
1155
name: &str,
History
1 round
0 comments
ilyagr.bsky.social
submitted
#0
2 commits
expand
collapse
cli completion: complete
--tool args for merge tools
Includes diff tools, diff editors, and merge editors.
add new builtin formats
no conflicts, ready to merge