-
Notifications
You must be signed in to change notification settings - Fork 395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support external subcommands: rg, diff, git-show (etc.) #1769
Conversation
Nice, very interesting!
Some miscellaneous comments about subcommands; not sure how relevant to this PR:
|
I used
Yes, and in that case I would give priority to the subcommand. By using
I think these are not used that often, I would leave them as flags (these being located in
Already present, see
Sure, should be possible as well. |
Now also possible! However enabling all git-X commands directly would mean checking if "git $X" is a valid git subcommand first (while parsing the command line, repeatedly), so I use a specific list: pub const SUBCOMMANDS: &[&str] = &[RG, "show", "log", "diff", "grep", "blame", GIT]; Are there any other useful commands? Also, can you check why the rg test fails on macOS? |
The ones that are coming to mind are |
It fails because of delta/src/subcommands/generic_subcmd.rs Line 127 in acb7c12
https://docs.rs/grep-cli/latest/grep_cli/fn.resolve_binary.html
This API has always seemed very surprising to me. I've opened BurntSushi/ripgrep#2928 to ask about it and maybe make the docstring clearer. |
I use |
src/subcommands/mod.rs
Outdated
// start subprocesses: | ||
// diff (fileA, fileB), and generic subcommands | ||
pub mod diff; | ||
mod generic_subcmd; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That name is a bit ... generic I think. Ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
external!
Done.
Oh, thanks! Never would have suspected that, re-implemented some of
Indeed, I have also asked myself that for simpler stuff like writing my own |
And one more: I recently learned of the existence of |
Just to mention one more -- I'm attracted to adding a "doctor" command that users can run to obtain a standard suite of diagnostics describing their environment, something like the direction started in #1193. Would something like that fit with the work in this branch as a |
Added. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've gone through the code changes now; all looks very nice. Please excuse/ignore the code golf suggestions -- that was just helping me get into the code.
Before we proceed let's revisit the overall argument here. For rg
this is clearly an improvement: otherwise to make delta-rg integration ergonomic you have to write a shell wrapper.
Another possibility along the lines of the rg
integration might be visualizing difftastic JSON output.
But what's your thinking about the utility of this for git xxx
commands? delta integrates with git transparently via git's external pager mechanism, and many shell completion projects exist giving intricate completion over git commands. Is there a clear advantage to being able to invoke git commands prefixed by delta
, especially given that it wouldn't have shell completion? Also, if we were to do it, are we sure that we want to inject 9 (currently) git command names into delta's top-level subcommand namespace? Or should it only support delta git xxx
? delta diff
in particular seems to have potentially confusing semantics given the presence of the delta a b
diff. Sorry I've been slow to bring up these hesitations.
if let Some(subcmd) = &minus_file { | ||
if let Some(arg) = subcmd.to_str() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is basically just code golf, done while trying to get into the substance of the PR. Feel free to ignore.
if let Some(subcmd) = &minus_file { | |
if let Some(arg) = subcmd.to_str() { | |
if let Some(arg) = minus_file.as_ref().and_then(|p| p.to_str()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait for if-let chains :)
} | ||
Ok(matches) => { | ||
// subcommands take precedence over diffs | ||
let minus_file = matches.get_one::<PathBuf>("minus_file").map(PathBuf::from); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is our support for delta file_a file_b
going to cause trouble when introducing standard clap subcommands?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything clap parses takes precedence, in fact, here intervention is required to "steal" this argument from the command line parser.
src/subcommands/external.rs
Outdated
for (i, arg) in args.iter().enumerate() { | ||
let arg = if let Some(arg) = arg.to_str() { | ||
arg | ||
} else { | ||
continue; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, more code golf suggestions:
for (i, arg) in args.iter().enumerate() { | |
let arg = if let Some(arg) = arg.to_str() { | |
arg | |
} else { | |
continue; | |
}; | |
for (i, arg) in args.iter().filter_map(|a| a.to_str()).enumerate() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent, Done.
src/subcommands/external.rs
Outdated
SubCmdKind::Rg | ||
} else { | ||
SubCmdKind::Git( | ||
args[i..] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructs Git("git")
in the case of delta git show
. Is that intentional / does it matter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the single .pop()
is not enough, fixed.
src/subcommands/external.rs
Outdated
.to_string(), | ||
) | ||
}; | ||
subcmd.extend(args[i + 1..].iter().map(|arg| arg.into())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More ignorable code golf:
There might be an attractive alternative way to write the above using .split_first()
? The best I've come up with is
let invalid_placeholder = OsString::from("?");
let (subsubcmd, rest) = args[i..]
.split_first()
.unwrap_or((&invalid_placeholder, &[]));
let kind = if arg == RG {
SubCmdKind::Rg
} else {
SubCmdKind::Git(subsubcmd.to_string_lossy().to_string())
};
subcmd.extend(rest.iter().cloned());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, merged with the above.
} | ||
} | ||
|
||
pub fn extract(args: &[OsString], orig_error: Error) -> (ArgMatches, SubCommand) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A docstring could be good. My attempt:
pub fn extract(args: &[OsString], orig_error: Error) -> (ArgMatches, SubCommand) { | |
/// Find the first arg that is a registered external subcommand and return a | |
/// tuple containing: | |
/// 0. The args prior to that point | |
/// 1. A SubCommand representing the external subcommand and its subsequent args | |
pub fn extract(args: &[OsString], orig_error: Error) -> (ArgMatches, SubCommand) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -1283,31 +1274,55 @@ impl Opt { | |||
Call::Help(help) | |||
} | |||
Err(e) => { | |||
e.exit(); | |||
// Calls `e.exit()` if error persists. | |||
let (matches, subcmd) = subcommands::extract(args, e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clever...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that... praise? It must be, so elegant!
src/main.rs
Outdated
} | ||
let mut cmd = cmd.unwrap(); | ||
|
||
let cmd_stdout = cmd.stdout.as_mut().expect("Failed to open stdout"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a so-far-upheld tradition of using unwrap_or_else(|| panic(...))
because of my personal belief that expect()
is confusing as reader and author, but I'd certainly understand if you wished to discontinue that tradition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
This is slightly off-topic because I think this would be an internal rather than an external subcommand, but I just found myself doing |
Apart from I sometimes try to use |
Let's make them sit behind |
Done. This is untested on Windows, but the process logic is from |
Apologies if the problem is at my end but I think that something might have gone wrong in the last change? When I do |
482affb
to
53ae80c
Compare
No, you are perfectly right! Fixed, and not just testing Ah, the test doesn't work because the CI runners do a shallow |
The possible command line now is: delta <delta-args> [SUBCMD <subcmd-args>] If the entire command line fails to parse because SUBCMD is unknown, then try (until the next arg fails) parsing <delta-args> only, and then parse and call SUBCMD with args, its output is piped into delta. Other subcommands also take precedence over the diff/git-diff mode (`delta a b`, where e.g. a=git and b=show), and any diff call gets converted into an external subcommand first. Available are: delta rg .. => rg --json .. | delta delta a b .. => git diff --no-index a b .. | delta delta git show .. => git <color-on> show .. | delta and all other git-CMDS, of which add -p, blame, checkout -p, diff, grep, log -p, reflog -p, and stash show -p produce a diff. Because --json is automatically added for `delta rg ..`, it avoids the parsing ambiguities of and is easier to type than `rg .. | delta`. The piping is not done by the shell but delta, so the subcommands are child processes of delta.
This info then takes precedence over whatever start_determining_calling_process_in_thread() finds or rather doesn't find. (The simple yet generous SeqCst is used on purpose for the atomic operations.)
Excellent, merged! |
The possible command line now is:
delta <delta-args> [SUBCMD <subcmd-args>]
If the entire command line fails to parse because SUBCMD is unknown,
then try (until the next arg fails) parsing only,
and then parse and call SUBCMD.., output is piped into delta.
Other subcommands also take precedence over the diff/git-diff mode
(
delta a b
, where e.g. a=show and b=HEAD), and any diff call getsconverted into an external subcommand first.
Available are:
delta rg .. => rg --json .. | delta
delta a b .. => git diff a b .. | delta
delta show .. => git <color-on> show .. | delta
And various other git-CMDS: add, blame, checkout, diff, grep, log, reflog and stash.
The piping is not done by the shell, but delta, so the subcommands
are now child processes of delta.
This info then takes precedence over whatever
start_determining_calling_process_in_thread()
finds or ratherdoesn't find.
(The simple yet generous SeqCst is used on purpose for the atomic operations.)
Looking at the
Call { Delta, .. }
enum from the--help
PR, and how the diff sub-command works (both how-@
is needed to add extra args, and how its ouput is fed back into delta when running as a child process) I had the idea of generalizing that.Deltas
rg
integration is great, butrg --json .. | delta
is a bit clunky, now it is justdelta rg ..
. This also allows using delta without touching any git configs ("try before you edit anything"), as a proof of conceptdelta show
is implemented here.This is a bit ... creative with the clap command line parser, but it is all quite contained.