Skip to content

Commit 321237a

Browse files
authored
feat: add autocomplete for buddies aliases (#2)
for now this only works with the default `buddies_file` location (`~/.config/git-squad/buddies.yaml`)
1 parent 871d27b commit 321237a

File tree

7 files changed

+115
-23
lines changed

7 files changed

+115
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- allow passing multiple buddies to `with` command
88
- allow passing multiple buddies to `without` command
9+
- add tab completions for buddies aliases
910

1011
## 0.2.0 -- 2025-04-04
1112

Cargo.lock

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ include = [
1919

2020
[dependencies]
2121
clap = { version = "4.5", features = ["derive"] }
22-
clap_complete = "4.5"
22+
clap_complete = { version = "4.5", features = ["unstable-dynamic"] }
2323
serde = { version = "1.0", features = ["derive"] }
2424
serde_yaml = "0.9"
2525
anyhow = "1.0"

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ manually edit commit templates.
1212
- Add and remove co-authors in the current git session
1313
- Automatically updates your git commit template
1414
- Simple command-line interface
15+
- Shell completions for commands and buddies
1516

1617
## Usage
1718

@@ -21,15 +22,16 @@ Manage co-authors in git commit messages with ease
2122
Usage: git-squad [OPTIONS] [COMMAND]
2223
2324
Commands:
24-
with Add a buddy to the current session
25-
without Remove a buddy from the current session
26-
alone Remove all buddies from the current session
27-
create Create a new buddy
28-
forget Delete a buddy from the list of available buddies
29-
info List both active and available buddies
30-
list List all available buddies
31-
active List active buddies in the current session
32-
help Print this message or the help of the given subcommand(s)
25+
with Add buddies to the current session
26+
without Remove buddies from the current session
27+
alone Remove all buddies from the current session
28+
create Create a new buddy
29+
forget Delete a buddy from the list of available buddies
30+
info List both active and available buddies
31+
list List all available buddies
32+
active List active buddies in the current session
33+
completions Generate completions for your shell
34+
help Print this message or the help of the given subcommand(s)
3335
3436
Options:
3537
--buddies-file <BUDDIES_FILE> Use a custom buddy file instead of ~/.config/git-squad/buddies.yaml

src/buddy.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ pub struct Buddy {
99
}
1010

1111
impl Buddy {
12+
pub fn format_buddy(&self) -> String {
13+
format!("{} <{}>", self.name, self.email)
14+
}
15+
1216
pub fn format_co_author(&self) -> String {
13-
format!("Co-authored-by: {} <{}>", self.name, self.email)
17+
format!("Co-authored-by: {}", self.format_buddy())
1418
}
1519
}
1620

src/cli.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
use std::{io, path::PathBuf};
22

3+
use clap::builder::StyledStr;
34
use clap::{CommandFactory, Parser, Subcommand};
4-
use clap_complete::{Generator, Shell, generate};
5+
use clap_complete::engine::{ArgValueCompleter, CompletionCandidate};
6+
use clap_complete::env::Shells;
7+
use clap_complete::{CompleteEnv, Shell, generate};
8+
9+
use crate::config::ConfigService;
10+
use crate::config::FileConfig;
511

612
#[derive(Debug, Parser)]
713
#[command(name = "git-squad")]
@@ -16,23 +22,41 @@ pub struct Cli {
1622
}
1723

1824
impl Cli {
25+
pub fn new() -> Self {
26+
CompleteEnv::with_factory(Cli::command).complete();
27+
Self::parse()
28+
}
29+
1930
pub fn get_command(&self) -> Command {
2031
self.command.clone().unwrap_or(Command::Info)
2132
}
2233
}
2334

24-
pub fn print_completions<G: Generator>(generator: G) {
25-
let mut cmd = Cli::command();
26-
let name = cmd.get_name().to_string();
27-
generate(generator, &mut cmd, name, &mut io::stdout());
35+
pub fn print_completions(shell: Shell) -> anyhow::Result<()> {
36+
print_completions_internal(shell, &mut Cli::command())
37+
}
38+
39+
fn print_completions_internal(shell: Shell, cmd: &mut clap::Command) -> anyhow::Result<()> {
40+
generate(shell, cmd, cmd.get_name().to_string(), &mut io::stdout());
41+
42+
println!();
43+
44+
let name = cmd.get_name();
45+
if let Some(completer) = Shells::builtins().completer(shell.to_string().as_str()) {
46+
completer.write_registration("COMPLETE", name, name, name, &mut io::stdout())?;
47+
}
48+
49+
Ok(())
2850
}
2951

3052
#[derive(Debug, Subcommand, Clone)]
3153
pub enum Command {
3254
/// Add buddies to the current session
3355
With {
3456
/// The aliases of the buddies to add
35-
#[arg( required = true, num_args = 1..,)]
57+
#[arg( required = true,
58+
num_args = 1..,
59+
add = ArgValueCompleter::new(alias_completer))]
3660
// TODO: I would rather use NonEmpty<String> here but clap makes
3761
// this really cumbersome
3862
aliases: Vec<String>,
@@ -41,7 +65,9 @@ pub enum Command {
4165
/// Remove buddies from the current session
4266
Without {
4367
/// The aliases of the buddies to remove
44-
#[arg( required = true, num_args = 1..,)]
68+
#[arg( required = true,
69+
num_args = 1..,
70+
add = ArgValueCompleter::new(alias_completer))]
4571
// TODO: I would rather use NonEmpty<String> here but clap makes
4672
// this really cumbersome
4773
aliases: Vec<String>,
@@ -59,6 +85,7 @@ pub enum Command {
5985
/// Delete a buddy from the list of available buddies
6086
Forget {
6187
/// The alias for the buddy to delete
88+
#[arg(add = ArgValueCompleter::new(alias_completer))]
6289
alias: String,
6390
},
6491

@@ -75,6 +102,24 @@ pub enum Command {
75102
Completions { shell: Shell },
76103
}
77104

78-
pub fn parse() -> Cli {
79-
Cli::parse()
105+
fn alias_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
106+
// TODO: support completions with custom buddies_file locations
107+
let conf = FileConfig { buddies_file: None };
108+
109+
if let Ok(buddies) = conf.load_buddies() {
110+
let current = current.to_str().unwrap_or_default();
111+
return buddies
112+
.buddies
113+
.iter()
114+
.filter(|s| {
115+
s.alias.starts_with(current) || s.name.starts_with(current) || s.email.starts_with(current)
116+
})
117+
.map(|b| {
118+
let help = Some(StyledStr::from(b.format_buddy()));
119+
CompletionCandidate::new(b.alias.clone()).help(help)
120+
})
121+
.collect();
122+
}
123+
124+
return vec![];
80125
}

src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ mod git;
55

66
use anyhow::Result;
77
use buddy::{Buddies, Buddy};
8-
use cli::{Cli, Command, print_completions};
8+
use cli::{print_completions, Cli, Command};
99
use config::ConfigService;
1010
use std::io::{self, Write};
1111

1212
fn main() -> Result<()> {
13-
let cli = cli::parse();
13+
let cli = Cli::new();
1414

1515
match cli.get_command() {
1616
Command::With { aliases } => {
@@ -107,7 +107,7 @@ fn main() -> Result<()> {
107107

108108
Command::Active => command_active(&cli)?,
109109

110-
Command::Completions { shell } => print_completions(shell),
110+
Command::Completions { shell } => print_completions(shell)?,
111111
}
112112

113113
Ok(())

0 commit comments

Comments
 (0)