Add shell-completions for dx (#5232)

* add naive shell completion using clap_complete

* first pass completions

* simpler toggle

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Samuel ÅkessonandJonathan Kelley authored and GitHub committed 2026-04-07 15:25:11 -07:00
1 parent d8026f8824
commit 689e39b3f6
7 files changed
+91 -17

No files matched your search

Generated
+22
View File
@@ -3452,6 +3452,18 @@ dependencies = [
"terminal_size",
]
[[package]]
name = "clap_complete"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb"
dependencies = [
"clap",
"clap_lex",
"is_executable",
"shlex",
]
[[package]]
name = "clap_derive"
version = "4.5.61"
@@ -4855,6 +4867,7 @@ dependencies = [
"cargo_toml",
"chrono",
"clap",
"clap_complete",
"codemap",
"console 0.16.3",
"console-subscriber",
@@ -9142,6 +9155,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "is_executable"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
+1
View File
@@ -169,6 +169,7 @@ nucleo = "0.5.0"
# 0.5.12 rust-version is too high
home = "=0.5.11"
clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] }
[target.'cfg(target_env = "musl")'.dependencies]
git2 = { version = "0.20.2", features = ["vendored-libgit2"] }
+5
View File
@@ -14,6 +14,7 @@ pub(crate) mod platform_override;
pub(crate) mod print;
pub(crate) mod run;
pub(crate) mod serve;
pub(crate) mod shell_completions;
pub(crate) mod target;
pub(crate) mod translate;
pub(crate) mod update;
@@ -84,6 +85,10 @@ pub(crate) enum Commands {
#[clap(name = "doctor")]
Doctor(doctor::Doctor),
/// Print shell completions for the given shell.
#[clap(name = "completions")]
ShellCompletions(shell_completions::ShellCompletions),
/// Print project information in a structured format, like cargo args, linker args, and other
/// flags DX sets that might be useful in third-party tools.
#[clap(name = "print")]
+7 -2
View File
@@ -1,5 +1,5 @@
#![allow(dead_code)]
use crate::Anonymized;
use crate::{shell_completions::GENERATING_COMPLETIONS, Anonymized};
use clap::parser::ValueSource;
use clap::{ArgMatches, Args, CommandFactory, FromArgMatches, Parser, Subcommand};
use serde_json::{json, Value};
@@ -159,9 +159,14 @@ where
U: Subcommand,
{
fn augment_args(cmd: clap::Command) -> clap::Command {
let cmd = T::augment_args(cmd).disable_help_subcommand(true);
if GENERATING_COMPLETIONS.load(std::sync::atomic::Ordering::Relaxed) {
cmd
} else {
// We use the special `defer` method which lets us recursively call `augment_args` on the inner command
// and thus `from_arg_matches`
T::augment_args(cmd).defer(|cmd| U::augment_subcommands(cmd.disable_help_subcommand(true)))
cmd.defer(|cmd| U::augment_subcommands(cmd))
}
}
fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {
+33
View File
@@ -0,0 +1,33 @@
use super::*;
use crate::{cli, Result};
use clap::CommandFactory;
use clap_complete::{generate, Shell};
use std::sync::atomic::AtomicBool;
/// Generate shell completions for the specified shell.
#[derive(Clone, Debug, Parser)]
pub(crate) struct ShellCompletions {
/// The shell to generate completions for.
#[clap(value_enum)]
pub shell: Shell,
}
/// An annoying, code smelly, way of setting the CLI recursive depth parser to limit early, useful for
/// generating completions here.
///
/// This should be set before calling clap_complete's generation function
pub static GENERATING_COMPLETIONS: AtomicBool = AtomicBool::new(false);
impl ShellCompletions {
pub fn generate_and_print(self) -> Result<StructuredOutput> {
let mut cmd = cli::Cli::command();
GENERATING_COMPLETIONS.swap(true, std::sync::atomic::Ordering::Relaxed);
generate(
self.shell,
&mut cmd,
env!("CARGO_BIN_NAME"),
&mut std::io::stdout(),
);
Ok(StructuredOutput::Success)
}
}
+22 -15
View File
@@ -73,21 +73,6 @@ pub fn verbosity_or_default() -> Verbosity {
crate::VERBOSITY.get().cloned().unwrap_or_default()
}
fn reset_cursor() {
use std::io::IsTerminal;
// if we are running in a terminal, reset the cursor. The tui_active flag is not set for
// the cargo generate TUI, but we still want to reset the cursor.
if std::io::stdout().is_terminal() {
_ = console::Term::stdout().show_cursor();
}
}
/// A trait that emits an anonymous JSON representation of the object, suitable for telemetry.
pub(crate) trait Anonymized {
fn anonymized(&self) -> serde_json::Value;
}
/// A custom layer that wraps our special interception logic based on the mode of the CLI and its verbosity.
///
/// Redirects TUI logs, writes to files, and queues telemetry events.
@@ -114,6 +99,7 @@ struct CapturedPanicError {
thread_name: Option<String>,
location: Option<SavedLocation>,
}
impl std::fmt::Display for CapturedPanicError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
@@ -132,6 +118,11 @@ struct SavedLocation {
column: u32,
}
/// A trait that emits an anonymous JSON representation of the object, suitable for telemetry.
pub(crate) trait Anonymized {
fn anonymized(&self) -> serde_json::Value;
}
impl TraceController {
/// Initialize the CLI and set up the tracing infrastructure
///
@@ -840,6 +831,12 @@ impl TraceController {
}),
),
Commands::Doctor(_cmd) => ("doctor".to_string(), json!({})),
Commands::ShellCompletions(cmd) => (
"completions".to_string(),
json!({
"shell": cmd.shell.to_string(),
}),
),
Commands::Translate(cmd) => (
"translate".to_string(),
json!({
@@ -1437,3 +1434,13 @@ impl PosthogEvent {
Ok(())
}
}
fn reset_cursor() {
use std::io::IsTerminal;
// if we are running in a terminal, reset the cursor. The tui_active flag is not set for
// the cargo generate TUI, but we still want to reset the cursor.
if std::io::stdout().is_terminal() {
_ = console::Term::stdout().show_cursor();
}
}
+1
View File
@@ -75,6 +75,7 @@ async fn main() -> ExitCode {
Commands::Doctor(opts) => opts.doctor().await,
Commands::Print(opts) => opts.print().await,
Commands::Components(opts) => opts.run().await,
Commands::ShellCompletions(opts) => opts.generate_and_print(),
}
})
});