mirror of
https://github.com/ohmyzsh/ohmyzsh.git
synced 2026-01-23 02:35:38 +01:00
feat(zsh-vi-man): add plugin for smart man page lookup
Adds zsh-vi-man plugin that provides smart man page lookup for zsh vi mode and emacs mode. Press K in vi normal mode, Ctrl-X k in emacs mode, or Ctrl-K in vi insert mode on any command or option to open its man page. Features: - Smart subcommand detection (git commit → man git-commit) - Option jumping (grep -r → jumps to -r entry in man page) - Combined options support (rm -rf → finds both -r and -f) - Pipe support (cat file | grep -i → opens man grep) - Multiple pager support (less, vim, nvim)
This commit is contained in:
parent
0f45f82c0a
commit
89e0439e21
6 changed files with 511 additions and 0 deletions
112
plugins/zsh-vi-man/README.md
Normal file
112
plugins/zsh-vi-man/README.md
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
# zsh-vi-man plugin
|
||||||
|
|
||||||
|
Smart man page lookup for zsh vi mode (and emacs mode).
|
||||||
|
|
||||||
|
Press `K` in vi normal mode, `Ctrl-X k` in emacs mode, or `Ctrl-K` in vi insert mode on any
|
||||||
|
command or option to instantly open its man page. If your cursor is on an option (like `-r`
|
||||||
|
or `--recursive`), it will jump directly to that option in the man page.
|
||||||
|
|
||||||
|
To use it, add `zsh-vi-man` to the plugins array in your zshrc file:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
plugins=(... zsh-vi-man)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Smart Detection**: Automatically finds the right man page for subcommands
|
||||||
|
(`git commit` → `man git-commit`, `docker run` → `man docker-run`)
|
||||||
|
- **Option Jumping**: Opens man page directly at the option definition
|
||||||
|
(`grep -r` → jumps to `-r` entry)
|
||||||
|
- **Combined Options**: Works with combined short options (`rm -rf` → finds both `-r` and `-f`)
|
||||||
|
- **Value Extraction**: Handles options with values (`--color=always` → searches `--color`)
|
||||||
|
- **Pipe Support**: Detects correct command in pipelines (`cat file | grep -i` → opens `man grep`)
|
||||||
|
- **Multiple Formats**: Supports various man page styles (GNU, jq-style, find-style)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Vi Normal Mode (Default)
|
||||||
|
|
||||||
|
1. Type a command (e.g., `ls -la` or `git commit --amend`)
|
||||||
|
2. Press `Escape` to enter vi normal mode
|
||||||
|
3. Move cursor to any word
|
||||||
|
4. Press `K` to open the man page
|
||||||
|
|
||||||
|
### Emacs Mode / Vi Insert Mode
|
||||||
|
|
||||||
|
Without leaving insert mode or if using emacs mode:
|
||||||
|
|
||||||
|
- **Emacs mode**: Press `Ctrl-X` then `k`
|
||||||
|
- **Vi insert mode**: Press `Ctrl-K`
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
| Command | Cursor On | Result |
|
||||||
|
| :--------------------- | :------------- | :----------------------------------- |
|
||||||
|
| `ls -la` | `ls` | Opens `man ls` |
|
||||||
|
| `ls -la` | `-la` | Opens `man ls`, jumps to `-l` |
|
||||||
|
| `git commit --amend` | `commit` | Opens `man git-commit` |
|
||||||
|
| `grep --color=auto` | `--color=auto` | Opens `man grep`, jumps to `--color` |
|
||||||
|
| `cat file \| sort -r` | `-r` | Opens `man sort`, jumps to `-r` |
|
||||||
|
| `find . -name "*.txt"` | `-name` | Opens `man find`, jumps to `-name` |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Set these variables **before** Oh My Zsh is sourced:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
# Vi normal mode key (default: K)
|
||||||
|
ZVM_MAN_KEY='?'
|
||||||
|
|
||||||
|
# Emacs mode key sequence (default: ^Xk, i.e., Ctrl-X k)
|
||||||
|
ZVM_MAN_KEY_EMACS='^X^K' # Example: Ctrl-X Ctrl-K
|
||||||
|
|
||||||
|
# Vi insert mode key (default: ^K, i.e., Ctrl-K)
|
||||||
|
ZVM_MAN_KEY_INSERT='^H' # Example: Ctrl-H
|
||||||
|
|
||||||
|
# Enable/disable emacs mode binding (default: true)
|
||||||
|
ZVM_MAN_ENABLE_EMACS=false
|
||||||
|
|
||||||
|
# Enable/disable vi insert mode binding (default: true)
|
||||||
|
ZVM_MAN_ENABLE_INSERT=false
|
||||||
|
|
||||||
|
# Use a different pager (default: less, supports nvim/vim)
|
||||||
|
ZVM_MAN_PAGER='nvim'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Keybindings not working?**
|
||||||
|
|
||||||
|
If keybindings don't work after sourcing the plugin, try running:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
zvm_man_rebind
|
||||||
|
```
|
||||||
|
|
||||||
|
This can happen if:
|
||||||
|
|
||||||
|
- Your plugin manager loads plugins before setting up keymaps
|
||||||
|
- You call `bindkey -e` or `bindkey -v` after the plugin loads
|
||||||
|
- Another plugin resets your keybindings
|
||||||
|
|
||||||
|
For persistent issues, add this to your `.zshrc` **after** sourcing Oh My Zsh:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
# Ensure zsh-vi-man bindings are set
|
||||||
|
zvm_man_rebind
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with zsh-vi-mode
|
||||||
|
|
||||||
|
This plugin works seamlessly with [zsh-vi-mode](https://github.com/jeffreytse/zsh-vi-mode).
|
||||||
|
It automatically detects zsh-vi-mode and hooks into its lazy keybindings system.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
- [Tuna Cuma](https://github.com/TunaCuma)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](https://github.com/TunaCuma/zsh-vi-man/blob/main/LICENSE) for details.
|
||||||
|
|
||||||
50
plugins/zsh-vi-man/lib/keybinding.zsh
Normal file
50
plugins/zsh-vi-man/lib/keybinding.zsh
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# lib/keybinding.zsh - Keybinding management for different shell modes
|
||||||
|
# Supports vi normal/insert modes and emacs mode
|
||||||
|
|
||||||
|
# Internal function to bind keys in all configured modes
|
||||||
|
_zvm_man_bind_key() {
|
||||||
|
# Bind in vi normal mode (always enabled)
|
||||||
|
bindkey -M vicmd "${ZVM_MAN_KEY}" zvm-man 2>/dev/null
|
||||||
|
|
||||||
|
# Handle emacs mode binding
|
||||||
|
if [[ "${ZVM_MAN_ENABLE_EMACS}" == true ]]; then
|
||||||
|
bindkey -M emacs "${ZVM_MAN_KEY_EMACS}" zvm-man 2>/dev/null
|
||||||
|
else
|
||||||
|
# Remove existing binding if disabled
|
||||||
|
bindkey -M emacs -r "${ZVM_MAN_KEY_EMACS}" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle vi insert mode binding
|
||||||
|
if [[ "${ZVM_MAN_ENABLE_INSERT}" == true ]]; then
|
||||||
|
bindkey -M viins "${ZVM_MAN_KEY_INSERT}" zvm-man 2>/dev/null
|
||||||
|
else
|
||||||
|
# Remove existing binding if disabled
|
||||||
|
bindkey -M viins -r "${ZVM_MAN_KEY_INSERT}" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Public function for users to manually rebind if needed
|
||||||
|
# Usage: zvm_man_rebind
|
||||||
|
zvm_man_rebind() {
|
||||||
|
_zvm_man_bind_key
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup keybindings with zsh-vi-mode compatibility
|
||||||
|
# Handles both immediate binding and lazy loading scenarios
|
||||||
|
zvm_setup_keybindings() {
|
||||||
|
if (( ${+functions[zvm_after_lazy_keybindings]} )); then
|
||||||
|
# zsh-vi-mode is loaded with lazy keybindings, hook into it
|
||||||
|
if [[ -z "${ZVM_LAZY_KEYBINDINGS}" ]] || [[ "${ZVM_LAZY_KEYBINDINGS}" == true ]]; then
|
||||||
|
zvm_after_lazy_keybindings_commands+=(_zvm_man_bind_key)
|
||||||
|
else
|
||||||
|
_zvm_man_bind_key
|
||||||
|
fi
|
||||||
|
elif (( ${+functions[zvm_after_init]} )); then
|
||||||
|
# zsh-vi-mode without lazy keybindings
|
||||||
|
zvm_after_init_commands+=(_zvm_man_bind_key)
|
||||||
|
else
|
||||||
|
# Standalone or other vi-mode setups - bind immediately
|
||||||
|
_zvm_man_bind_key
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
128
plugins/zsh-vi-man/lib/pager.zsh
Normal file
128
plugins/zsh-vi-man/lib/pager.zsh
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# lib/pager.zsh - Pager-specific man page opening logic
|
||||||
|
# Handles different pagers: less, nvim, vim, etc.
|
||||||
|
|
||||||
|
# Detect the pager type from ZVM_MAN_PAGER
|
||||||
|
# Output: "nvim", "vim", "less", or "other"
|
||||||
|
zvm_detect_pager_type() {
|
||||||
|
local pager_basename="${ZVM_MAN_PAGER##*/}"
|
||||||
|
|
||||||
|
if [[ "$pager_basename" =~ ^nvim ]]; then
|
||||||
|
echo "nvim"
|
||||||
|
elif [[ "$pager_basename" =~ ^vim$ ]]; then
|
||||||
|
echo "vim"
|
||||||
|
elif [[ "$pager_basename" =~ ^less$ ]]; then
|
||||||
|
echo "less"
|
||||||
|
else
|
||||||
|
echo "other"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Open man page with neovim
|
||||||
|
# Uses neovim's built-in Man command with search
|
||||||
|
# Input: $1 = man_page, $2 = search_pattern (vim regex, optional)
|
||||||
|
# Returns: 0 on success, 1 on failure
|
||||||
|
zvm_open_nvim() {
|
||||||
|
local man_page="$1"
|
||||||
|
local search_term="$2"
|
||||||
|
|
||||||
|
# Run nvim exactly as user would: nvim +"Man <cmd>" +only +/<search>
|
||||||
|
# No stdin/stdout/stderr redirects - let nvim have full terminal access
|
||||||
|
# This prevents issues with plugins like image.nvim that need terminal size
|
||||||
|
# Set MANWIDTH to a large number to prevent line wrapping in man pages
|
||||||
|
if [[ -n "$search_term" ]]; then
|
||||||
|
MANWIDTH=999 ${ZVM_MAN_PAGER} +"Man ${man_page}" +only +"/${search_term}" || \
|
||||||
|
MANWIDTH=999 ${ZVM_MAN_PAGER} +"Man ${man_page}" +only || \
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
MANWIDTH=999 ${ZVM_MAN_PAGER} +"Man ${man_page}" +only || \
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Open man page with vim
|
||||||
|
# Vim doesn't have :Man built-in like nvim, so we need to load man.vim first
|
||||||
|
# Input: $1 = man_page, $2 = search_pattern (vim regex, optional)
|
||||||
|
# Returns: 0 on success, 1 on failure
|
||||||
|
zvm_open_vim() {
|
||||||
|
local man_page="$1"
|
||||||
|
local search_term="$2"
|
||||||
|
|
||||||
|
# Vim requires loading the man.vim ftplugin first before :Man works
|
||||||
|
# Set MANWIDTH to prevent line wrapping
|
||||||
|
# Use </dev/tty to ensure vim gets proper terminal access from zle widget
|
||||||
|
if [[ -n "$search_term" ]]; then
|
||||||
|
MANWIDTH=999 ${ZVM_MAN_PAGER} \
|
||||||
|
+"runtime ftplugin/man.vim" \
|
||||||
|
+"Man ${man_page}" \
|
||||||
|
+only \
|
||||||
|
+"/${search_term}" </dev/tty || \
|
||||||
|
MANWIDTH=999 ${ZVM_MAN_PAGER} \
|
||||||
|
+"runtime ftplugin/man.vim" \
|
||||||
|
+"Man ${man_page}" \
|
||||||
|
+only </dev/tty || \
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
MANWIDTH=999 ${ZVM_MAN_PAGER} \
|
||||||
|
+"runtime ftplugin/man.vim" \
|
||||||
|
+"Man ${man_page}" \
|
||||||
|
+only </dev/tty || \
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Open man page with less or other traditional pagers
|
||||||
|
# Uses the -p flag for pattern search
|
||||||
|
# Input: $1 = man_page, $2 = search_pattern (ERE, optional)
|
||||||
|
# Returns: 0 on success, 1 on failure
|
||||||
|
zvm_open_less() {
|
||||||
|
local man_page="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
|
||||||
|
# Always pipe through ZVM_MAN_PAGER to override system MANPAGER
|
||||||
|
# This prevents issues when MANPAGER is set to nvim/vim but ZVM_MAN_PAGER is less
|
||||||
|
# Use -R to pass through raw escape sequences (needed for LESS_TERMCAP_* colors)
|
||||||
|
if [[ -n "$pattern" ]]; then
|
||||||
|
MANPAGER=cat man "$man_page" 2>/dev/null | ${ZVM_MAN_PAGER} -R -p "${pattern}" 2>/dev/null || \
|
||||||
|
MANPAGER=cat man "$man_page" 2>/dev/null | ${ZVM_MAN_PAGER} -R || \
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
MANPAGER=cat man "$man_page" 2>/dev/null | ${ZVM_MAN_PAGER} -R || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main entry point: open man page with appropriate pager
|
||||||
|
# Automatically detects pager type and uses correct pattern format
|
||||||
|
# Input: $1 = man_page, $2 = word (for pattern building)
|
||||||
|
# Returns: 0 on success, 1 on failure
|
||||||
|
zvm_open_man_page() {
|
||||||
|
local man_page="$1"
|
||||||
|
local word="$2"
|
||||||
|
local pager_type
|
||||||
|
|
||||||
|
pager_type=$(zvm_detect_pager_type)
|
||||||
|
|
||||||
|
case "$pager_type" in
|
||||||
|
nvim)
|
||||||
|
local nvim_pattern
|
||||||
|
nvim_pattern=$(zvm_build_nvim_pattern "$word")
|
||||||
|
zvm_open_nvim "$man_page" "$nvim_pattern"
|
||||||
|
;;
|
||||||
|
vim)
|
||||||
|
local vim_pattern
|
||||||
|
vim_pattern=$(zvm_build_nvim_pattern "$word")
|
||||||
|
zvm_open_vim "$man_page" "$vim_pattern"
|
||||||
|
;;
|
||||||
|
less|other)
|
||||||
|
local less_pattern
|
||||||
|
less_pattern=$(zvm_build_less_pattern "$word")
|
||||||
|
zvm_open_less "$man_page" "$less_pattern"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
49
plugins/zsh-vi-man/lib/parser.zsh
Normal file
49
plugins/zsh-vi-man/lib/parser.zsh
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# lib/parser.zsh - Word and command parsing utilities
|
||||||
|
# Extracts words and commands from the command line buffer
|
||||||
|
|
||||||
|
# Get the word at the current cursor position
|
||||||
|
# Uses LBUFFER and RBUFFER which are ZLE special variables
|
||||||
|
zvm_parse_word_at_cursor() {
|
||||||
|
local left="${LBUFFER##*[[:space:]]}"
|
||||||
|
local right="${RBUFFER%%[[:space:]]*}"
|
||||||
|
echo "${left}${right}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the current command segment (handles pipes)
|
||||||
|
# Returns the text after the last pipe before cursor
|
||||||
|
zvm_get_current_segment() {
|
||||||
|
local segment="${LBUFFER##*|}"
|
||||||
|
# Trim leading whitespace
|
||||||
|
segment="${segment#"${segment%%[![:space:]]*}"}"
|
||||||
|
echo "$segment"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the command name from a segment
|
||||||
|
# Takes the first word of the segment
|
||||||
|
zvm_parse_command() {
|
||||||
|
local segment
|
||||||
|
segment=$(zvm_get_current_segment)
|
||||||
|
echo "${segment%%[[:space:]]*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine the man page to open, checking for subcommands
|
||||||
|
# Input: $1 = command, $2 = current_segment
|
||||||
|
# Output: man page name (e.g., "git-commit" or just "git")
|
||||||
|
zvm_determine_man_page() {
|
||||||
|
local cmd="$1"
|
||||||
|
local segment="$2"
|
||||||
|
local man_page="$cmd"
|
||||||
|
|
||||||
|
local rest="${segment#*[[:space:]]}"
|
||||||
|
local potential_subcommand="${rest%%[[:space:]]*}"
|
||||||
|
|
||||||
|
# Check for subcommand man pages (e.g., git-commit, docker-run)
|
||||||
|
if [[ -n "$potential_subcommand" && ! "$potential_subcommand" =~ ^- ]]; then
|
||||||
|
if man -w "${cmd}-${potential_subcommand}" &>/dev/null; then
|
||||||
|
man_page="${cmd}-${potential_subcommand}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$man_page"
|
||||||
|
}
|
||||||
|
|
||||||
107
plugins/zsh-vi-man/lib/pattern.zsh
Normal file
107
plugins/zsh-vi-man/lib/pattern.zsh
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
# lib/pattern.zsh - Search pattern builders for different pagers
|
||||||
|
# Generates regex patterns for option search in man pages
|
||||||
|
|
||||||
|
# Build search pattern for less pager (ERE - Extended Regular Expressions)
|
||||||
|
# Patterns match option definitions: lines starting with whitespace then dash
|
||||||
|
# Supports comma-separated (GNU style) and slash-separated (jq style) options
|
||||||
|
# Input: $1 = word (the option, e.g., "-l", "--recursive", "-rf")
|
||||||
|
# Output: ERE pattern string
|
||||||
|
zvm_build_less_pattern() {
|
||||||
|
local word="$1"
|
||||||
|
local pattern=""
|
||||||
|
|
||||||
|
if [[ -z "$word" ]]; then
|
||||||
|
echo ""
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Long option with value: --color=always -> search for --color
|
||||||
|
# Use -[^[:space:],/]* instead of -.* to prevent matching across descriptions
|
||||||
|
# Use (-[^[:space:],/]*[,/][[:space:]]+)* to allow multiple preceding options
|
||||||
|
if [[ "$word" =~ ^--[^=]+= ]]; then
|
||||||
|
local opt="${word%%=*}"
|
||||||
|
pattern="^[[:space:]]*${opt}([,/=:[[:space:]]|$)|^[[:space:]]*(-[^[:space:],/]*[,/][[:space:]]+)+${opt}([,/=:[[:space:]]|$)"
|
||||||
|
|
||||||
|
# Combined short options: -rf -> search for -[rf] to find individual options
|
||||||
|
# Also includes fallback for single-dash long options like find's -name, -type
|
||||||
|
# Use (-[^[:space:],/]*[,/][[:space:]]+)+ to allow multiple preceding options
|
||||||
|
elif [[ "$word" =~ ^-[a-zA-Z]{2,}$ ]]; then
|
||||||
|
local chars="${word:1}"
|
||||||
|
# Pattern 1: individual chars (e.g., -r or -f from -rf)
|
||||||
|
# Pattern 2: the full word as-is (e.g., -name for find)
|
||||||
|
pattern="^[[:space:]]*-[${chars}][,/:[:space:]]|^[[:space:]]*(-[^[:space:],/]*[,/][[:space:]]+)+-[${chars}][,/:[:space:]]|^[[:space:]]*${word}([,/:[:space:]]|$)|^[[:space:]]*(-[^[:space:],/]*[,/][[:space:]]+)+${word}([,/:[:space:]]|$)"
|
||||||
|
|
||||||
|
# Single short option: -r -> match at start of option definition line
|
||||||
|
# Use (-[^[:space:],/]*[,/][[:space:]]+)+ to allow multiple preceding options
|
||||||
|
elif [[ "$word" =~ ^-[a-zA-Z]$ ]]; then
|
||||||
|
pattern="^[[:space:]]*${word}[,/:[:space:]]|^[[:space:]]*(-[^[:space:],/]*[,/][[:space:]]+)+${word}([,/:[:space:]]|$)"
|
||||||
|
|
||||||
|
# Long option without value: --recursive
|
||||||
|
# Use (-[^[:space:],/]*[,/][[:space:]]+)+ to allow multiple preceding options
|
||||||
|
elif [[ "$word" =~ ^-- ]]; then
|
||||||
|
pattern="^[[:space:]]*${word}([,/=:[[:space:]]|$)|^[[:space:]]*(-[^[:space:],/]*[,/][[:space:]]+)+${word}([,/=:[[:space:]]|$)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$pattern"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build search pattern for vim/neovim (Vim regex syntax)
|
||||||
|
# Matches option definitions: lines starting with whitespace then dash
|
||||||
|
# Supports comma-separated (GNU style) and slash-separated (jq style) options
|
||||||
|
# Uses word boundaries to prevent partial matches (e.g., --slurp vs --slurpfile)
|
||||||
|
# Input: $1 = word (the option, e.g., "-l", "--recursive", "-rf")
|
||||||
|
# Output: Vim search pattern string
|
||||||
|
zvm_build_nvim_pattern() {
|
||||||
|
local word="$1"
|
||||||
|
local search_term=""
|
||||||
|
|
||||||
|
if [[ -z "$word" ]]; then
|
||||||
|
echo ""
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Long option with value: --color=always -> search for --color
|
||||||
|
# Match: at line start OR after comma/slash separator
|
||||||
|
# End: followed by delimiter (comma, slash, equals, colon, space) or EOL
|
||||||
|
# Use \(-[^[:space:],/]*[,/][[:space:]]*\)\+ to allow multiple preceding options
|
||||||
|
if [[ "$word" =~ ^--[^=]+= ]]; then
|
||||||
|
local opt="${word%%=*}"
|
||||||
|
search_term="^[[:space:]]*${opt}\\([,/=:[[:space:]]\\|$\\)\\|^[[:space:]]*\\(-[^[:space:],/]*[,/][[:space:]]*\\)\\+${opt}\\([,/=:[[:space:]]\\|$\\)"
|
||||||
|
|
||||||
|
# Combined short options: -la -> search for -l or -a
|
||||||
|
# Also includes the full word as fallback for find-style -name, -type
|
||||||
|
# Use \(-[^[:space:],/]*[,/][[:space:]]*\)\+ to allow multiple preceding options
|
||||||
|
elif [[ "$word" =~ ^-[a-zA-Z]{2,}$ ]]; then
|
||||||
|
local chars="${word:1}"
|
||||||
|
local alternation=""
|
||||||
|
# Pattern for individual chars (e.g., -r or -f from -rf)
|
||||||
|
for (( i=0; i<${#chars}; i++ )); do
|
||||||
|
local char="-${chars:$i:1}"
|
||||||
|
if [[ -n "$alternation" ]]; then
|
||||||
|
alternation="${alternation}\\|^[[:space:]]*${char}[,/:[:space:]]\\|^[[:space:]]*\\(-[^[:space:],/]*[,/][[:space:]]*\\)\\+${char}\\([,/:[:space:]]\\|$\\)"
|
||||||
|
else
|
||||||
|
alternation="^[[:space:]]*${char}[,/:[:space:]]\\|^[[:space:]]*\\(-[^[:space:],/]*[,/][[:space:]]*\\)\\+${char}\\([,/:[:space:]]\\|$\\)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# Add full word as fallback (for find-style -name, -exec, etc.)
|
||||||
|
alternation="${alternation}\\|^[[:space:]]*${word}\\([,/:[:space:]]\\|$\\)\\|^[[:space:]]*\\(-[^[:space:],/]*[,/][[:space:]]*\\)\\+${word}\\([,/:[:space:]]\\|$\\)"
|
||||||
|
search_term="${alternation}"
|
||||||
|
|
||||||
|
# Single short option: -r
|
||||||
|
# Match: at line start OR after comma/slash separator
|
||||||
|
# End: followed by delimiter or EOL
|
||||||
|
# Use \(-[^[:space:],/]*[,/][[:space:]]*\)\+ to allow multiple preceding options
|
||||||
|
elif [[ "$word" =~ ^-[a-zA-Z]$ ]]; then
|
||||||
|
search_term="^[[:space:]]*${word}[,/:[:space:]]\\|^[[:space:]]*\\(-[^[:space:],/]*[,/][[:space:]]*\\)\\+${word}\\([,/:[:space:]]\\|$\\)"
|
||||||
|
|
||||||
|
# Long option without value: --recursive
|
||||||
|
# Match: at line start OR after comma/slash separator
|
||||||
|
# End: followed by delimiter or EOL (prevents --slurp matching --slurpfile)
|
||||||
|
# Use \(-[^[:space:],/]*[,/][[:space:]]*\)\+ to allow multiple preceding options
|
||||||
|
elif [[ "$word" =~ ^-- ]]; then
|
||||||
|
search_term="^[[:space:]]*${word}\\([,/=:[[:space:]]\\|$\\)\\|^[[:space:]]*\\(-[^[:space:],/]*[,/][[:space:]]*\\)\\+${word}\\([,/=:[[:space:]]\\|$\\)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$search_term"
|
||||||
|
}
|
||||||
|
|
||||||
65
plugins/zsh-vi-man/zsh-vi-man.plugin.zsh
Normal file
65
plugins/zsh-vi-man/zsh-vi-man.plugin.zsh
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# zsh-vi-man plugin
|
||||||
|
# Smart man page lookup for zsh vi mode (now with emacs mode support!)
|
||||||
|
#
|
||||||
|
# Press K in vi normal mode, Ctrl-X k in emacs mode, or Ctrl-K in vi insert mode
|
||||||
|
# to open the man page for the current command. If your cursor is on an option
|
||||||
|
# (like -r or --recursive), it will jump directly to that option in the man page.
|
||||||
|
#
|
||||||
|
# https://github.com/TunaCuma/zsh-vi-man
|
||||||
|
# MIT License - Copyright (c) 2025 Tuna Cuma
|
||||||
|
|
||||||
|
# Configuration variables (can be set before sourcing)
|
||||||
|
# ZVM_MAN_KEY: the key to trigger man page lookup in vi normal mode (default: K)
|
||||||
|
# ZVM_MAN_KEY_EMACS: the key sequence for emacs mode (default: ^Xk, i.e., Ctrl-X k)
|
||||||
|
# ZVM_MAN_KEY_INSERT: the key for vi insert mode (default: ^K, i.e., Ctrl-K)
|
||||||
|
# ZVM_MAN_PAGER: the pager to use (default: less, supports nvim/vim)
|
||||||
|
# ZVM_MAN_ENABLE_EMACS: enable emacs mode binding (default: true)
|
||||||
|
# ZVM_MAN_ENABLE_INSERT: enable vi insert mode binding (default: true)
|
||||||
|
|
||||||
|
: ${ZVM_MAN_KEY:=K}
|
||||||
|
: ${ZVM_MAN_KEY_EMACS:='^Xk'}
|
||||||
|
: ${ZVM_MAN_KEY_INSERT:='^K'}
|
||||||
|
: ${ZVM_MAN_PAGER:=less}
|
||||||
|
: ${ZVM_MAN_ENABLE_EMACS:=true}
|
||||||
|
: ${ZVM_MAN_ENABLE_INSERT:=true}
|
||||||
|
|
||||||
|
# Get the directory where this script is located
|
||||||
|
typeset -g ZVM_LIB_DIR="${0:A:h}/lib"
|
||||||
|
|
||||||
|
# Source modular components
|
||||||
|
source "${ZVM_LIB_DIR}/parser.zsh"
|
||||||
|
source "${ZVM_LIB_DIR}/pattern.zsh"
|
||||||
|
source "${ZVM_LIB_DIR}/pager.zsh"
|
||||||
|
source "${ZVM_LIB_DIR}/keybinding.zsh"
|
||||||
|
|
||||||
|
# Main widget function - orchestrates the man page lookup
|
||||||
|
function zvm-man() {
|
||||||
|
# Parse current context
|
||||||
|
local word=$(zvm_parse_word_at_cursor)
|
||||||
|
local cmd=$(zvm_parse_command)
|
||||||
|
|
||||||
|
if [[ -z "$cmd" ]]; then
|
||||||
|
zle -M "No command found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine the man page to open (may include subcommand)
|
||||||
|
local current_segment=$(zvm_get_current_segment)
|
||||||
|
local man_page=$(zvm_determine_man_page "$cmd" "$current_segment")
|
||||||
|
|
||||||
|
# Clear screen and open man page with appropriate pager
|
||||||
|
zle -I
|
||||||
|
|
||||||
|
if ! zvm_open_man_page "$man_page" "$word"; then
|
||||||
|
zle -M "No manual entry for ${man_page}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
zle reset-prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register the widget
|
||||||
|
zle -N zvm-man
|
||||||
|
|
||||||
|
# Setup keybindings
|
||||||
|
zvm_setup_keybindings
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue