diff --git a/Cargo.lock b/Cargo.lock index 91a03d4f6..621a2df7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -396,6 +396,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.5.0" @@ -2602,6 +2611,7 @@ dependencies = [ "byte-unit", "cfg-if", "clap", + "clap_complete", "error-chain", "filetime", "flate2", diff --git a/Cargo.toml b/Cargo.toml index f63747a40..33b796f0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,7 @@ watchexec-signals = "2.0" watchexec-supervisor = "1.0" zip = { version = "^0.6", default-features = false, features = ["deflate"] } time = "0.3.36" +clap_complete = "4.5.1" [features] default = ["geturl-reqwest", "serialization"] diff --git a/docs/src/ref/v2cli.md b/docs/src/ref/v2cli.md index 4d18d794e..286853200 100644 --- a/docs/src/ref/v2cli.md +++ b/docs/src/ref/v2cli.md @@ -40,6 +40,33 @@ nextonic build You can use various Unix `argv[0]` hacks to achieve this, or you can just rename or symlink the `tectonic` binary to `nextonic` manually. +## Shell completions + +Shell completions for the `nextonic` executable can be generated using +`nextonic show shell-completions`, plus the name of your shell. Currently, +completions are only available for the `nextonic` command. + +As an example, you can generate shell completions for zsh and save it to a +file with the following command: + +```zsh +nextonic show shell-completions zsh > _nextonic +``` + +You can then move the completion file to a location where zsh can auto load; +for example, `/usr/local/share/zsh/site-functions` would probably work for +most Unix systems. Alternatively, you can evaluate the completions directly +in you `~/.zshrc`, e.g. + +```zsh +eval "$(nextonic show shell-completions zsh)" +``` + +Currently supported shells are listed in the `--help` text: + +```zsh +nextonic show shell-completions --help +``` ## External tools diff --git a/src/bin/tectonic/compile.rs b/src/bin/tectonic/compile.rs index 25c243f1d..99a55213f 100644 --- a/src/bin/tectonic/compile.rs +++ b/src/bin/tectonic/compile.rs @@ -22,7 +22,7 @@ use tectonic::{ #[derive(Debug, Parser)] pub struct CompileOptions { /// The file to process, or "-" to process the standard input stream - #[arg(name = "input")] + #[arg(name = "input", value_hint = clap::ValueHint::FilePath)] input: String, /// The name of the "format" file used to initialize the TeX engine diff --git a/src/bin/tectonic/v2cli/commands/show.rs b/src/bin/tectonic/v2cli/commands/show.rs index f24e08749..dc5b44427 100644 --- a/src/bin/tectonic/v2cli/commands/show.rs +++ b/src/bin/tectonic/v2cli/commands/show.rs @@ -1,8 +1,8 @@ -use clap::Parser; +use clap::{CommandFactory, Parser}; use tectonic::{config::PersistentConfig, errors::Result}; use tectonic_status_base::StatusBackend; -use crate::v2cli::{CommandCustomizations, TectonicCommand}; +use crate::v2cli::{CommandCustomizations, TectonicCommand, V2CliOptions}; /// `show`: Show various useful pieces of information. #[derive(Debug, Eq, PartialEq, Parser)] @@ -16,18 +16,24 @@ enum ShowCommands { #[command(name = "user-cache-dir")] /// Print the location of the default per-user cache directory UserCacheDir(ShowUserCacheDirCommand), + + #[command(name = "shell-completions")] + /// Print shell completions code for some given shell + ShellCompletions(ShowShellCompletionsCommand), } impl TectonicCommand for ShowCommand { fn customize(&self, cc: &mut CommandCustomizations) { match &self.command { ShowCommands::UserCacheDir(c) => c.customize(cc), + ShowCommands::ShellCompletions(c) => c.customize(cc), } } fn execute(self, config: PersistentConfig, status: &mut dyn StatusBackend) -> Result { match self.command { ShowCommands::UserCacheDir(c) => c.execute(config, status), + ShowCommands::ShellCompletions(c) => c.execute(config, status), } } } @@ -47,3 +53,29 @@ impl ShowUserCacheDirCommand { Ok(0) } } + +#[derive(Debug, Eq, PartialEq, Parser)] +struct ShowShellCompletionsCommand { + /// Target shell for the generated completion code + shell: clap_complete::Shell, +} + +impl ShowShellCompletionsCommand { + fn customize(&self, cc: &mut CommandCustomizations) { + cc.always_stderr = true; + } + + /// Generates shell completions at runtime + fn execute(self, _config: PersistentConfig, _status: &mut dyn StatusBackend) -> Result { + // The current v1 & v2 cli mixture makes it a bit difficult to offer + // clean completions for the `tectonic` command, so for now we only + // target the `nextonic` command exclusively for the v2 cli. + clap_complete::generate( + self.shell, + &mut V2CliOptions::command(), + "nextonic", + &mut std::io::stdout(), + ); + Ok(0) + } +} diff --git a/tests/executable.rs b/tests/executable.rs index a70c748fe..8e6365973 100644 --- a/tests/executable.rs +++ b/tests/executable.rs @@ -933,6 +933,19 @@ fn v2_dump_suffix() { assert!(saw_first && saw_second); } +/// Checks that shell completions are correctly generated +#[cfg(feature = "serialization")] +#[test] +fn v2_show_shell_completions() { + let (_tempdir, temppath) = setup_v2(); + let output = run_tectonic(&temppath, &["-X", "show", "shell-completions", "zsh"]); + success_or_panic(&output); + + if !String::from_utf8_lossy(&output.stdout).contains("compdef _nextonic nextonic") { + panic!("shell completions generation failed.") + } +} + const SHELL_ESCAPE_TEST_DOC: &str = r"\immediate\write18{mkdir shellwork} \immediate\write18{echo 123 >shellwork/persist} \ifnum123=\input{shellwork/persist}