Skip to main content

Config-driven parser for keybindings with fzf interface

Project description

confhelp

PyPI Python CI License: MIT

Find and edit your keybindings instantly.

demo

The Problem

Over time you accumulate tmux bindings, zsh aliases, vim mappings, custom functions - scattered across dozens of files. You know you set something up, but where?

Two pain points:

  1. Finding bindings - "What key did I bind for git status?" "Do I have an alias for docker compose?"
  2. Editing bindings - You remember it exists, now you need to change it. Which file? What line? grep → open → scroll → find → edit. Every. Single. Time.

The Solution

confhelp -b ~/dotfiles --edit

Fuzzy search all your bindings → select one → opens $EDITOR at exact file:line.

Install

pip install confhelp

Usage

# Output all bindings (uses base_dirs from config)
confhelp

# Interactive fzf selection
confhelp --select

# Select and open in $EDITOR at line
confhelp --edit

# JSON output
confhelp -f json

# Override base directory
confhelp -b ~/other-dotfiles

# Show keys defined more than once
confhelp --conflicts

# Report lines that look like bindings but failed to parse
confhelp --check

Example output:

[tmux]   prefix+g   display-popup -w 80%...   .tmux.conf:42
[alias]  gs         git status                .zsh_aliases:15
[bind]   ^[e        edit-command-line         .zshrc:89

The --edit flag drops you directly into the file at the exact line. Change the binding, save, done.

Binding Sources

confhelp supports two ways to extract bindings:

1. Regex Parsing (data-driven)

Define patterns in TOML to extract bindings from config files. Works for any text-based config format - tmux, zsh, aliases, etc. You specify paths, regex, and capture groups.

2. Structured Parsing (data-driven)

For configs that store bindings in structured formats (TOML arrays, YAML lists, JSON), use the structured parser. It navigates the parsed data directly - no regex needed.

[zledit-actions]
parser = "toml"
paths = ["~/.config/zledit/config.toml"]
binding_path = "actions"
key = "binding"
desc = "description"
type = "zledit"

Parses configs like:

[[actions]]
binding = 'ctrl-o'
description = 'edit'
script = '~/.config/zledit/scripts/edit.sh'

3. Query Engines (code-driven)

Some tools (like nvim) store bindings in ways that can't be reliably parsed with regex - runtime keymaps, plugin-generated bindings, multi-line table formats. Query engines run the tool itself to extract bindings.

Currently available: nvim (runs nvim headlessly to query keymaps)

Architecture note: Query engines are currently hardcoded. Future versions may support registering custom extractors - any code that returns (type, key, desc, file, line) tuples could become a binding source. This would allow community-contributed engines for tools like vim, emacs, i3, sway, etc.

Config Format

Define parsers in TOML. Each section describes how to extract bindings from a set of files:

# Default directories to search (no -b needed)
base_dirs = ["~/dotfiles", "~/work-dotfiles"]

# Query engines - run external tools to get bindings
# Available: "nvim" (queries nvim headlessly for keymaps)
query_engines = ["nvim"]

# tmux: bind [-n] <key> <command>
# Example: bind r source-file ~/.tmux.conf
#          bind-key -n M-l popup -E lazygit
# Captures: (1) key=r or M-l, (2) command
[tmux]
paths = [".tmux.conf"]
match_line = "^bind"
regex = 'bind(?:-key)?\s+(?:-n\s+)?(\S+)(.*)'
key_group = 1
desc_group = 2
type = "tmux"
truncate = 100

# zsh alias: alias [-gs] <name>=<command>
# Example: alias gs='git status'
#          alias -g C='| xsel --clipboard'
# Captures: (1) name=gs or C, (2) command
[alias]
paths = [".zsh_aliases", ".zsh_claude"]
regex = "alias\\s+(?:-[gs]\\s+)?([^=]+)=(.*)"
key_group = 1
desc_group = 2
type = "alias"
strip_quotes = true

# zsh-abbr: "abbrev" 'expansion'
# Example: "ga" 'git add'
# Captures: (1) abbrev=ga, (2) expansion=git add
[abbrev]
paths = [".zsh_abbreviations"]
match_line = '".*"'
regex = '''"([^"]+)"\s+'([^']+)''''
key_group = 1
desc_group = 2
type = "abbrev"

# nvim query engine options
[engine.nvim]
truncate = 60

# nvim via regex (alternative to query engine - parses source files directly)
# Only catches single-line vim.keymap.set calls with inline desc
[nvim-regex]
paths = [".config/nvim/lua/**/*.lua"]
match_line = "vim.keymap.set"
regex = 'vim\.keymap\.set\([^,]+,\s*"([^"]+)".*desc\s*=\s*"([^"]+)"'
key_group = 1
desc_group = 2
type = "nvim"

Config Options

Top-level options:

Option Description
base_dirs Default directories to search
query_engines List of query engines to enable (e.g., ["nvim"])

Section options (regex parsing):

Option Description
paths List of files or glob patterns (e.g., **/*.lua)
regex Pattern with capture groups for key/desc (Python re syntax)
key_group Capture group number for the key
desc_group Capture group number for description
match_line Only process lines matching this pattern
skip_comment Skip lines starting with #
truncate Max length for description
strip_quotes Remove surrounding quotes from desc
desc_literal Use fixed string as description
desc_from_comment Extract desc from trailing # comment

Section options (structured parsing):

Option Description
parser Format: toml, yaml, or json
paths List of config files to parse
binding_path Dot notation path to bindings array (e.g., bindings.keys)
key Field name for binding key (supports field1+field2 to combine)
desc Field name for description
type Binding type label
truncate Max length for description

Regex Tips

Patterns use Python's re module. Test patterns at regex101.com (select Python flavor).

Quick CLI test:

echo "bind r reload" | python -c "import re,sys; m=re.search(r'bind\s+(\S+)\s+(.*)', sys.stdin.read()); print(m.groups() if m else 'no match')"

Output Formats

  • pipe (default): pipe-delimited [type]|key|desc|file:line
  • tsv: Tab-separated
  • json: JSON array

The pipe format works well with column -t -s'|' for aligned display.

Integration Examples

confhelp outputs text. How you display it is up to you.

fzf with Multiple Actions

Use --expect to handle different keys. Ctrl+P extracts and opens paths from entries:

result=$(confhelp -b ~/dotfiles | column -t -s'|' | fzf \
    --header='Enter=edit | Ctrl-P=open path | Ctrl-O=copy' \
    --expect=ctrl-p,ctrl-o)

key=$(echo "$result" | head -1)
selection=$(echo "$result" | tail -1)

case "$key" in
    ctrl-p)
        # Extract path from selection (supports /, ~, $HOME prefixes)
        path=$(echo "$selection" | grep -oE '(/[^ ]+|~[^ ]+|\$HOME[^ ]+)' | head -1)
        path="${path/#\~/$HOME}"
        [[ -e "$path" ]] && $EDITOR "$path"
        ;;
    ctrl-o)
        echo "$selection" | xsel -ib
        ;;
    *)
        # Enter: parse file:line and open in editor
        file_line=$(echo "$selection" | awk '{print $NF}')
        file="${file_line%:*}"
        line="${file_line##*:}"
        $EDITOR "+$line" "$file"
        ;;
esac

Alacritty Popup

Spawn a centered popup window:

alacritty --class popup -e bash -c 'confhelp -b ~/dotfiles --edit'

See examples/alacritty-popup.sh for a complete implementation with Ctrl+P path support.

tmux Popup

tmux display-popup -w 80% -h 80% -E 'confhelp -b ~/dotfiles --select'

Rofi/dmenu

confhelp -b ~/dotfiles | rofi -dmenu

Acknowledgments

Inspired by Extracto.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

confhelp-0.7.0.tar.gz (280.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

confhelp-0.7.0-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file confhelp-0.7.0.tar.gz.

File metadata

  • Download URL: confhelp-0.7.0.tar.gz
  • Upload date:
  • Size: 280.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for confhelp-0.7.0.tar.gz
Algorithm Hash digest
SHA256 615de23c20245695cb54dcb742669fddc59aac8b07e329315df5f166b8b23fce
MD5 bbcccb603bde2d12d6b1d7a6396020b0
BLAKE2b-256 46370155c4c4f3ceec182a199f51c419c1a74aff07350a293dc96f2e670be7a1

See more details on using hashes here.

Provenance

The following attestation bundles were made for confhelp-0.7.0.tar.gz:

Publisher: publish.yml on Piotr1215/confhelp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file confhelp-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: confhelp-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 13.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for confhelp-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8c52c652cdcd6c5d3ea0f08fb83cf73b4002b8513c765821e0f40faf2fc3dce7
MD5 e0214773e8802ebfada61e274426deea
BLAKE2b-256 ea8d9d2a3dd5c007f8bf03e8ddb1d3779478cc86b959f246c72f5f325f206fdd

See more details on using hashes here.

Provenance

The following attestation bundles were made for confhelp-0.7.0-py3-none-any.whl:

Publisher: publish.yml on Piotr1215/confhelp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page