Skip to content

Commit

Permalink
Zsh
Browse files Browse the repository at this point in the history
  • Loading branch information
evanbattaglia committed Sep 10, 2024
1 parent 7683df2 commit 5e0a2a2
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 6 deletions.
3 changes: 1 addition & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
TODO before "public announcement"
* fish and nix support merged
* zsh support/testing
* go through TODOs and do important ones
* combining the three includes (sub, arg, flag) in the JSON format would be great
---
* put on cargo
* more examples / language reference in this repo.
delegate "super aliases"
* bump version number and add deb
* better instructions for installing (build from source, cargo, nix, deb)

Expand Down
153 changes: 153 additions & 0 deletions shell/tabry_zsh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# As of Sep 2024 this is the same as the bash script
# but with readarray replaced, plus these autoloads

autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit

_tabry_rs_executable=${_tabry_rs_executable:-$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"/.. &> /dev/null && pwd)/target/debug/tabry}

_tabry_rs_complete_all() {
if [[ -z "$TABRY_IMPORT_PATH" ]]; then
if [[ -n "$_tabry_rs_imports_path" ]]; then
export TABRY_IMPORT_PATH="$_tabry_rs_imports_path"
else
TABRY_IMPORT_PATH=~/.local/share/tabry
fi
fi

[[ -x "$_tabry_rs_executable" ]] || { echo "tabry_bash.sh: error: can't find tabry executable at $_tabry_rs_executable -- if you are using the script from source rather than using via 'tabry bash', perhaps you need to run 'cargo build'?"; return 1; }
local oldifs="$IFS"
IFS=$'\n'
for cmd in $("$_tabry_rs_executable" commands); do
complete -F _tabry_rs_completions $cmd
done
IFS="$oldifs"
}

_tabry_rs_complete_one_command() {
complete -F _tabry_rs_completions $1
}

_tabry_rs_completions() {
_tabry_rs_completions_internal "$_tabry_rs_executable"
}

_tabry_rs_set_compreply_from_lines() {
# Feed in lines from a variable, quoting each line.
# Using readarray is much faster than using += many times to build the array.
local lines="$1"
local saveifs="$IFS"

COMPREPLY=("${(@f)$(
IFS=$'\n'
while IFS= read -r line; do
if [[ -n "$line" ]]; then
printf '%q\n' "$line"
fi
done <<< "$lines"
IFS="$saveifs"
)}")
}

# This is unchanged from ruby tabry, except to remove the second arg
_tabry_rs_completions_internal()
{
local tabry_bash_executable="$1"

[[ -n "$TABRY_DEBUG" ]] && echo && echo -n tabry start bash: && date +%s.%N >&2
local saveifs="$IFS"
IFS=$'\n'

[[ -n "$TABRY_DEBUG" ]] && printf "%q %q %q %q\n" "$tabry_bash_executable" complete "$COMP_LINE" "$COMP_POINT"
local result=$("$tabry_bash_executable" complete "$COMP_LINE" "$COMP_POINT")
local specials
local specials_line

if [[ $result == *$'\n'$'\n'* ]]; then
# double newline signals use of specials (file, directory)
# Warning: fragile code ahead.
# Split on double-newline to get regular options and specials.
specials="$(echo "$result"|sed '1,/^$/d')"
result="$(echo "$result)"|sed '/^$/q')"

# First, add anything before the double newline in (regular options)
_tabry_rs_set_compreply_from_lines "$result"

while IFS= read -r specials_line; do
if [[ "$specials_line" == "file" ]]; then
# File special
# doesn't seem to be a "plusfiles" like there is for "plusdirs"
# TODO check if we need to shellescape
COMPREPLY+=($(compgen -A file "${COMP_WORDS[$COMP_CWORD]}"))
elif [[ "$specials_line" == "dir" ]]; then
# Directory special
# If there are only directory results, use nospace to not add a space after it,
# like "cd" tab completion does.
# TODO check if we need to shellescape
[[ ${#COMPREPLY[@]} -eq 0 ]] && compopt -o nospace
compopt -o plusdirs
elif [[ "$specials" == "description_if_optionless" ]]; then
# "description_if_optionless" special: Options are are meant to be description or examples
# and not actual options. Add an empty option so we won't tab complete.
compopt -o nosort
COMPREPLY+=('')
elif [[ "$specials_line" == "delegate "* ]]; then
local delegate_cmd="${specials_line#delegate }"

# split delegate_cmd to get the actual command (first word):
# this is not reliable but will work for now...
local delegate_cmd_arg0="${delegate_cmd%% *}"
local complete_fn=$(complete -p "$delegate_cmd_arg0" 2>/dev/null | sed 's/.*-F \([^ ]*\) .*/\1/')
if [[ -z "$complete_fn" ]]; then
_completion_loader "$delegate_cmd_arg0"
fi
complete_fn=$(complete -p "$delegate_cmd_arg0" 2>/dev/null | sed 's/.*-F \([^ ]*\) .*/\1/')
if [[ -z "$complete_fn" ]]; then
echo "Error: Could not find completion function for $delegate_cmd_arg0" >&2
return 1
fi

# Backup
local comp_cword="$COMP_CWORD"
local comp_point="$COMP_POINT"
local comp_line="$COMP_LINE"
local comp_words=("${COMP_WORDS[@]}")
local comp_key="$COMP_KEY"
local comp_type="$COMP_TYPE"
local comp_reply=("${COMPREPLY[@]}")

# get completions -> COMPREPLY
IFS="$saveifs"
COMP_LINE="$delegate_cmd"
COMP_POINT="${#delegate_cmd}"
#__compal__split_cmd_line "$COMP_LINE"
COMP_WORDS=("${__compal__retval[@]}" "")
COMP_WORDS=($delegate_cmd "")
COMP_CWORD=$((${#COMP_WORDS[@]} - 1))
COMPREPLY=()
"$complete_fn"

# Reset the completion variables
IFS=$'\n'
COMP_CWORD="$comp_cword"
COMP_POINT="$comp_point"
COMP_LINE="$comp_line"
COMP_WORDS=("${comp_words[@]}")
COMP_KEY="$comp_key"
COMP_TYPE="$comp_type"

# Concatenate the completions
COMPREPLY=("${comp_reply[@]}" "${COMPREPLY[@]}")
fi
done <<< "$specials"
else
_tabry_rs_set_compreply_from_lines "$result"
COMPREPLY=()
while IFS= read -r line; do
COMPREPLY+=($(printf "%q" "$line"))
done <<< "$result"
fi

IFS="$saveifs"
[[ -n "$TABRY_DEBUG" ]] && echo -n tabry end bash: && date +%s.%N >&2
}
17 changes: 14 additions & 3 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ pub fn bash(imports_path: Option<&str>, no_auto: bool) {
}
}

const TABRY_ZSH_SH: &str = include_str!("../../shell/tabry_zsh.sh");
pub fn zsh(imports_path: Option<&str>, no_auto: bool) {
if let Some(path) = imports_path {
println!("_tabry_rs_imports_path={}", escape(path));
}
println!("_tabry_rs_executable={}", escaped_exe());
print!("{}", TABRY_ZSH_SH);

if !no_auto {
// TODO name things consistently between fish + zsh
println!("_tabry_rs_complete_all");
}
}

const TABRY_FISH_SH: &str = include_str!("../../shell/tabry_fish.fish");
pub fn fish(imports_path: Option<&str>, no_auto: bool) {
if let Some(path) = imports_path {
Expand All @@ -115,6 +129,3 @@ pub fn fish(imports_path: Option<&str>, no_auto: bool) {
println!("tabry_completion_init_all");
}
}



21 changes: 20 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ enum Subcommands {
/// Import path (colon-separated)
import_path: Option<String>,
},

/// Output completion script for zsh
/// Usage in ~/.zsh_profile: `source <(tabry zsh)` or
/// `source <(tabry zsh --no-auto); _tabry_rs_complete_one_command mycmd`
Zsh {
#[arg(long)]
/// Do not automatically init completions for all tabry/JSON files in TABRY_IMPORT_PATH
/// (manually use _tabry_rs_complete_one_command for each command if you use this option)
no_auto: bool,

#[arg(index=1)]
/// Import path (colon-separated)
import_path: Option<String>,
},

/// Output completion script for bash
/// Usage in ~/.bash_profile: `tabry fish | source` or
/// `tabry fish | source; tabry_completion_init mycmd`
Expand All @@ -37,11 +52,14 @@ enum Subcommands {
/// Import path (colon-separated)
import_path: Option<String>,
},

/// List commands for which there is a .tabry/.json file in TABRY_IMPORT_PATH
Commands,

/// Compile a tabry file to json (usually done automatically via tabry complete).
/// Usage: tabry compile < [tabry file] > [json file]
Compile,

/// Return completions (usually used via shell script)
Complete {
/// TODO desc
Expand All @@ -53,13 +71,14 @@ enum Subcommands {

fn main() {
use Subcommands::*;
use tabry::app::{bash, commands, compile, run_as_compline, fish};
use tabry::app::*;
let cli = Cli::parse();
match cli.command {
Complete { compline, comppoint } => run_as_compline(&compline, &comppoint).unwrap(),
Compile => compile(),
Commands => commands(),
Bash { import_path, no_auto } => bash(import_path.as_deref(), no_auto),
Zsh { import_path, no_auto } => zsh(import_path.as_deref(), no_auto),
Fish { import_path, no_auto } => fish(import_path.as_deref(), no_auto),
}
}

0 comments on commit 5e0a2a2

Please sign in to comment.