Skip to content

Commit

Permalink
[#133] Added shell completion (#134)
Browse files Browse the repository at this point in the history
Resolves #133 

This pr aims to add a tab completion script so users don't have to
remember every command if they want to experiment with config options.

### Additional tasks

- [x] Documentation for changes provided/changed
- [x] Tests added
- [x] Updated CHANGELOG.md

Co-authored-by: Dmitrii Kovanikov <[email protected]>
  • Loading branch information
MitchellBerend and chshersh authored Oct 20, 2022
1 parent 38663d0 commit 395b21c
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 2 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,28 @@ jobs:
ls -l $SYNC_DIR
if [[ ! -x $SYNC_DIR/rg ]]; then echo "error on: rg"; false; fi
- if: matrix.os == 'ubuntu-latest'
name: "Integration test: [ubuntu-latest] [check bash completion]"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cargo run -- completion bash --rename test-name > /dev/null 2> log
cat log | tail -n +5 > logg
diff logg tests/bash-completion
if [[ $? == 1 ]]; then logg test/bash-completion; false; fi
- if: matrix.os == 'macos-latest'
name: "Integration test: [macos-latest] [check zsh completion]"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cargo run -- completion zsh --rename test-name > /dev/null 2> log
cat log | tail -n +5 > logg
diff logg tests/zsh-completion
if [[ $? == 1 ]]; then logg test/zsh-completion; false; fi
- if: matrix.os != 'windows-latest'
name: "Integration test: [unix] [sync-ripgrep]"
env:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ available [on GitHub][2].
Adds new error message RepoError::NotFound when fetch_release_info
returns 404
(by [@crudiedo][crudiedo])
* [#133](https://github.com/chshersh/tool-sync/issues/133)
Added shell completion
(by [@MitchellBerend][MitchellBerend])


### Fixed

Expand Down
10 changes: 10 additions & 0 deletions 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 @@ -24,6 +24,7 @@ serde = { version = "1.0", features = ["derive"] }
ureq = { version = "2.5.0", features = ["json"] }
zip = { version = "0.6.3", default-features = false, features = ["deflate"] }

clap_complete = "4.0.2"
console = "0.15.2"
dirs = "4.0.0"
flate2 = "1.0"
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,31 @@ By default `tool-sync` reads the configuration from from the `$HOME/.tool.toml`
path. But you can specify a custom location via the
`--config=/path/to/my/config` flag.

### Autocomplete

`tool-sync` can generate it's own autocomplete scripts for some common shell
emulators. you can run `tool completion --help` to see the supported shells.

If you decide to rename the `tool` binary a `--rename` flag will help make sure
the autocompletion will keep working.


The following example will assume you have renamed the binary to `random-name`
and you are using bash. You can run the following command to get completion for
the current session of your shell.

```shell
random-name completion bash --rename random-name
```

If you want to have these autocomplete functions in the next (and the ones after
that) as well you can add the following to your `~/.bashrc`:

```shell
eval "$(random-name completion bash --rename random-name)"
```


### Quick start

`tool-sync` has a command to generate a default configuration with examples and
Expand Down
86 changes: 86 additions & 0 deletions src/completion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! This file contains all logic revolving the generation of the shell completion script

use clap_complete::Shell;

// This function can break when clap_complete adds support for a new shell type
pub fn rename_completion_suggestion(shell: &Shell, bin_name: &str) -> Result<(), RenameError> {
let completion_str: String = match shell {
Shell::Zsh => zsh_completion_help(bin_name),
Shell::Bash => bash_completion_help(bin_name),
Shell::Fish => fish_completion_help(bin_name),
Shell::Elvish => elvish_completion_help(bin_name),
Shell::PowerShell => powershell_completion_help(bin_name),
_ => return Err(RenameError::NewShellFound(shell.to_owned())),
};

eprintln!(
"\n\n############################\n{}\n############################",
completion_str
);

Ok(())
}

pub enum RenameError {
NewShellFound(Shell),
}

impl std::fmt::Display for RenameError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
RenameError::NewShellFound(shell) => write!(f, "[Rename error]: {}", shell),
}
}
}

//##################//
// Helper functions //
//##################//

fn zsh_completion_help(bin_name: &str) -> String {
format!(
r##"Generate a `_{bin_name}` completion script and put it somewhere in your `$fpath`:
`{bin_name} completion zsh --rename {bin_name} > /usr/local/share/zsh/site-functions/_{bin_name}`
Ensure that the following is present in your `~/.zshrc`:
`autoload -U compinit`
`compinit -i`"##
)
}

fn bash_completion_help(bin_name: &str) -> String {
format!(
r##"First, ensure that you install `bash-completion` using your package manager.
After, add this to your `~/.bash_profile`:
`eval "$({bin_name} completion bash --rename {bin_name})"`"##
)
}

fn fish_completion_help(bin_name: &str) -> String {
format!(
r##"Generate a `tool.fish` completion script:
`{bin_name} completion fish --rename {bin_name} > ~/.config/fish/completions/{bin_name}.fish`"##
)
}

fn elvish_completion_help(_bin_name: &str) -> String {
r##"This suggestion is missing, if you use this and know how to implement this please file an issue over at https://github.com/chshersh/tool-sync/issues"##.into()
}

fn powershell_completion_help(bin_name: &str) -> String {
format!(
r##"Open your profile script with:
`mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue`
`notepad $profile`
Add the line and save the file:
`Invoke-Expression -Command $({bin_name} completion powershell --rename {bin_name} | Out-String)`"##
)
}
52 changes: 52 additions & 0 deletions src/config/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,58 @@ pub struct Cli {

#[derive(Subcommand, Debug)]
pub enum Command {
/// Generate shell completion scripts for GitHub CLI commands.
///
/// You will need to set up completions manually, follow the instructions below. The exact
/// config file locations might vary based on your system. Make sure to restart your
/// shell before testing whether completions are working.
///
/// ### bash
///
/// First, ensure that you install `bash-completion` using your package manager.
///
/// After, add this to your `~/.bash_profile`:
///
/// `eval "$(tool completion bash)"`
///
/// ### zsh
///
/// Generate a `_tool` completion script and put it somewhere in your `$fpath`:
///
/// `tool completion zsh > /usr/local/share/zsh/site-functions/_tool`
///
/// Ensure that the following is present in your `~/.zshrc`:
///
/// `autoload -U compinit`
/// `compinit -i`
///
/// Zsh version 5.7 or later is recommended.
///
/// ### fish
///
/// Generate a `tool.fish` completion script:
///
/// `tool completion fish > ~/.config/fish/completions/tool.fish`
///
/// ### PowerShell
///
/// Open your profile script with:
///
/// `mkdir -Path (Split-Path -Parent $profile) -ErrorAction SilentlyContinue`
/// `notepad $profile`
///
/// Add the line and save the file:
///
/// `Invoke-Expression -Command $(tool completion powershell | Out-String)`
Completion {
shell: clap_complete::Shell,

/// If you want to rename the binary this option should be filled with the name you choose
/// to rename `tool` to.
#[arg(long, value_name = "string")]
rename: Option<String>,
},

/// Sync all tools specified in configuration file or the only one specified in the command line
Sync { tool: Option<String> },

Expand Down
26 changes: 24 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
mod completion;
mod config;
mod infra;
mod install;
mod model;
mod sync;

use clap::Parser;
use clap::{CommandFactory, Parser};
use clap_complete::generate;

use std::path::PathBuf;

use crate::completion::rename_completion_suggestion;
use crate::config::cli::{Cli, Command};
use crate::infra::err;

const DEFAULT_CONFIG_PATH: &str = ".tool.toml";

pub fn run() {
let cli = Cli::parse();

Expand All @@ -18,6 +24,9 @@ pub fn run() {
let config_path = resolve_config_path(cli.config);

match cli.command {
Command::Completion { shell, rename } => {
generate_completion(shell, rename);
}
Command::DefaultConfig { path } => match path {
true => print_default_path(),
false => config::template::generate_default_config(),
Expand All @@ -27,7 +36,20 @@ pub fn run() {
}
}

const DEFAULT_CONFIG_PATH: &str = ".tool.toml";
fn generate_completion(shell: clap_complete::Shell, rename: Option<String>) {
let mut cmd: clap::Command = Cli::command();
match rename {
Some(cmd_name) => {
generate(shell, &mut cmd, &cmd_name, &mut std::io::stdout());
rename_completion_suggestion(&shell, &cmd_name)
.unwrap_or_else(|e| err::abort_suggest_issue(e));
}
None => {
let cmd_name: String = cmd.get_name().into();
generate(shell, &mut cmd, cmd_name, &mut std::io::stdout());
}
};
}

fn resolve_config_path(config_path: Option<PathBuf>) -> PathBuf {
match config_path {
Expand Down
7 changes: 7 additions & 0 deletions tests/bash-completion
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
############################
First, ensure that you install `bash-completion` using your package manager.

After, add this to your `~/.bash_profile`:

`eval "$(test-name completion bash --rename test-name)"`
############################
10 changes: 10 additions & 0 deletions tests/zsh-completion
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
############################
Generate a `_test-name` completion script and put it somewhere in your `$fpath`:
`test-name completion zsh --rename test-name > /usr/local/share/zsh/site-functions/_test-name`

Ensure that the following is present in your `~/.zshrc`:

`autoload -U compinit`

`compinit -i`
############################

0 comments on commit 395b21c

Please sign in to comment.