Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ strip = true
arboard = { version = "3.6.1", default-features = false, features = ["wayland-data-control", "windows-sys"] }
chrono = "0.4.42"
clap = { version = "4.5.54", features = ["derive"] }
clap_complete = "4.5.58"
crossterm = "0.28.1"
etcetera = "0.11.0"
figment = { version = "0.10.19", features = ["toml"] }
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Or install from your package manager:

[![Packaging status](https://repology.org/badge/vertical-allrepos/gitu.svg)](https://repology.org/project/gitu/versions)

Shell completions can be generated with `gitu completion <shell>`, see [Shell completions](docs/installing.md#shell-completions).

### Contributing
PRs are welcome!
This may help to get you started: [Development & Tooling](docs/dev-tooling.md)
20 changes: 20 additions & 0 deletions docs/installing.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,24 @@ nix.settings = {
}
```

## Shell completions
Gitu can print a completion script for your shell to stdout via the
`completion` subcommand. Supported shells are `bash`, `elvish`, `fish`,
`powershell` and `zsh`.

Bash:
```shell
gitu completion bash > ~/.local/share/bash-completion/completions/gitu
```

Zsh (the target directory must be on your `$fpath`):
```shell
gitu completion zsh > ~/.zfunc/_gitu
```

Fish:
```shell
gitu completion fish > ~/.config/fish/completions/gitu.fish
```


4 changes: 4 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ impl App {
None,
)?]
}
// `completion` prints its script and exits in `main`, before the app is ever built.
Some(cli::Commands::Completion { .. }) => {
unreachable!("completion is handled before the app starts")
}
None => vec![screen::status::create(
Arc::clone(&config),
Rc::clone(&repo),
Expand Down
18 changes: 17 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::io;
use std::path::PathBuf;

use clap::{Parser, Subcommand};
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::Shell;

#[derive(Default, Debug, Parser)]
#[command(name = "gitu")]
Expand Down Expand Up @@ -44,4 +46,18 @@ pub enum Commands {
#[clap(short, long)]
rev: Option<String>,
},
/// Print a shell completion script to stdout.
///
/// Example (bash): `gitu completion bash > ~/.local/share/bash-completion/completions/gitu`
Completion {
/// The shell to generate a completion script for
shell: Shell,
},
}

/// Write a shell completion script for `gitu` to the given writer.
pub fn completions(shell: Shell, out: &mut impl io::Write) {
let mut cmd = Args::command();
let bin_name = cmd.get_name().to_string();
clap_complete::generate(shell, &mut cmd, bin_name, out);
}
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clap::Parser;
use gitu::{
Res,
cli::Args,
cli::{self, Args, Commands},
config::{self, Config},
error::Error,
term::{self, Term},
Expand All @@ -18,6 +18,13 @@ pub fn main() -> Res<()> {
return Ok(());
}

// Generating completions doesn't need a git repository or the terminal,
// so handle it before any of that setup happens.
if let Some(Commands::Completion { shell }) = args.command {
cli::completions(shell, &mut std::io::stdout());
return Ok(());
}

if args.log {
simple_logging::log_to_file(gitu::LOG_FILE_NAME, LevelFilter::Debug)
.map_err(Error::OpenLogFile)?;
Expand Down
42 changes: 42 additions & 0 deletions src/tests/completion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use clap::Parser;
use clap_complete::Shell;

use crate::cli::{Args, Commands, completions};

#[test]
fn completion_subcommand_parses() {
let args = Args::try_parse_from(["gitu", "completion", "fish"]).unwrap();
assert!(matches!(
args.command,
Some(Commands::Completion { shell: Shell::Fish })
));
}

#[test]
fn completion_rejects_unknown_shell() {
assert!(Args::try_parse_from(["gitu", "completion", "not-a-shell"]).is_err());
}

#[test]
fn generates_non_empty_script_for_every_shell() {
for shell in [
Shell::Bash,
Shell::Elvish,
Shell::Fish,
Shell::PowerShell,
Shell::Zsh,
] {
let mut out = Vec::new();
completions(shell, &mut out);
let script = String::from_utf8(out).expect("completion script should be valid UTF-8");

assert!(
!script.trim().is_empty(),
"{shell} completion script should not be empty"
);
assert!(
script.contains("gitu"),
"{shell} completion script should reference the binary name"
);
}
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod blame;
mod branch;
mod cherry_pick;
mod commit;
mod completion;
mod discard;
mod editor;
mod fetch;
Expand Down