Skip to content

Commit

Permalink
Add dynamic shell completions
Browse files Browse the repository at this point in the history
More of an experiment, but I stumbled upon this when trying to make shell
completions suggest existing marks, and I just had to add it
  • Loading branch information
junglerobba committed Nov 13, 2024
1 parent 335fea2 commit 7b80413
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 43 deletions.
99 changes: 81 additions & 18 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exclude = ["images/*"]

git2 = { version= "0.19", features = [ "vendored-openssl" ] }
clap = { version = "4.5", features = ["cargo", "derive"] }
clap_complete = { version = "4.5", features = [ "unstable-dynamic" ] }
serde_derive = "1.0"
serde = "1.0"
error-stack = "0.5"
Expand All @@ -31,7 +32,6 @@ dirs = "5.0"
nucleo = "0.5.0"
ratatui = { version = "0.29", features = ["serde"] }
crossterm = "0.28"
clap_complete = "4.5"

[lib]
name = "tms"
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,20 @@ session switch. This can be configured with settings like this.
bind -r '(' switch-client -p\; refresh-client -S
bind -r ')' switch-client -n\; refresh-client -S
```

## Shell completions

### Bash
```bash
echo "source <(COMPLETE=bash tms)" >> ~/.bashrc
```

### Zsh
```zsh
echo "source <(COMPLETE=zsh tms) >> ~/.zshrc"
```

### Fish
```fish
echo "COMPLETE=fish tms | source" >> ~/.config/fish/config.fish
```
6 changes: 6 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@
cargoDeps = final.rustPlatform.importCargoLock {
lockFile = self + "/Cargo.lock";
};
postInstall = final.lib.optionalString (final.stdenv.buildPlatform.canExecute final.stdenv.hostPlatform) ''
installShellCompletion --cmd tms \
--bash <(COMPLETE=bash $out/bin/tms) \
--fish <(COMPLETE=fish $out/bin/tms) \
--zsh <(COMPLETE=zsh $out/bin/tms)
'';
});
};

Expand Down
24 changes: 1 addition & 23 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use crate::{
tmux::Tmux,
Result, TmsError,
};
use clap::{Args, Command, CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Generator, Shell};
use clap::{Args, Parser, Subcommand};
use error_stack::ResultExt;
use git2::{build::RepoBuilder, FetchOptions, RemoteCallbacks, Repository};
use ratatui::style::Color;
Expand All @@ -25,8 +24,6 @@ use ratatui::style::Color;
#[command(author, version)]
///Scan for all git folders in specified directorires, select one and open it as a new tmux session
pub struct Cli {
#[arg(long = "generate", value_enum)]
generator: Option<Shell>,
#[command(subcommand)]
command: Option<CliCommand>,
}
Expand Down Expand Up @@ -154,12 +151,6 @@ pub struct OpenSessionCommand {

impl Cli {
pub fn handle_sub_commands(&self, tmux: &Tmux) -> Result<SubCommandGiven> {
if let Some(generator) = self.generator {
let mut cmd = Cli::command();
print_completions(generator, &mut cmd);
return Ok(SubCommandGiven::Yes);
}

// Get the configuration from the config file
let config = Config::new().change_context(TmsError::ConfigError)?;

Expand Down Expand Up @@ -771,19 +762,6 @@ fn open_session_command(args: &OpenSessionCommand, config: Config, tmux: &Tmux)
}
}

fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
let name = if let Ok(exe) = std::env::current_exe() {
if let Some(exe) = exe.file_name() {
exe.to_string_lossy().to_string()
} else {
cmd.get_name().to_string()
}
} else {
cmd.get_name().to_string()
};
generate(gen, cmd, name, &mut std::io::stdout());
}

pub enum SubCommandGiven {
Yes,
No(Box<Config>),
Expand Down
20 changes: 19 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use clap::Parser;
use std::env;

use clap::{CommandFactory, Parser};
use clap_complete::CompleteEnv;
use error_stack::Report;

use tms::{
Expand All @@ -18,6 +21,21 @@ fn main() -> Result<()> {
#[cfg(any(not(debug_assertions), test))]
Report::install_debug_hook::<std::panic::Location>(|_value, _context| {});

let bin_name = std::env::current_exe()
.ok()
.and_then(|exe| exe.file_name().map(|exe| exe.to_string_lossy().to_string()))
.unwrap_or("tms".into());
match CompleteEnv::with_factory(Cli::command)
.bin(bin_name)
.try_complete(env::args_os(), None)
{
Ok(true) => return Ok(()),
Err(e) => {
panic!("failed to generate completions: {e}");
}
Ok(false) => {}
};

// Use CLAP to parse the command line arguments
let cli_args = Cli::parse();

Expand Down
15 changes: 15 additions & 0 deletions src/marks.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{env::current_dir, path::PathBuf};

use clap::{Args, Subcommand};
use clap_complete::{ArgValueCandidates, CompletionCandidate};
use error_stack::ResultExt;

use crate::{
Expand All @@ -14,6 +15,7 @@ use crate::{
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true)]
pub struct MarksCommand {
#[arg(add = ArgValueCandidates::new(get_completion_candidates))]
/// The index of the mark to open
index: Option<usize>,
#[command(subcommand)]
Expand Down Expand Up @@ -43,20 +45,33 @@ pub struct MarksSetCommand {

#[derive(Debug, Args)]
pub struct MarksOpenCommand {
#[arg(add = ArgValueCandidates::new(get_completion_candidates))]
/// The index of the mark to open
index: usize,
}

#[derive(Debug, Args)]
#[group(required = true, multiple = false)]
pub struct MarksDeleteCommand {
#[arg(add = ArgValueCandidates::new(get_completion_candidates))]
/// Index of mark to delete
index: Option<usize>,
#[arg(long, short)]
/// Delete all items
all: bool,
}

fn get_completion_candidates() -> Vec<CompletionCandidate> {
let config = Config::new().unwrap_or_default();
let marks = get_marks(&config).unwrap_or_default();
marks
.iter()
.map(|(index, session)| {
CompletionCandidate::new(index.to_string()).help(Some(session.name.clone().into()))
})
.collect::<Vec<_>>()
}

pub fn marks_command(args: &MarksCommand, config: Config, tmux: &Tmux) -> Result<()> {
match (&args.cmd, args.index) {
(None, None) => list(config),
Expand Down

0 comments on commit 7b80413

Please sign in to comment.