Skip to main content

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, plus any runtime
# bindings a query engine had to drop for lack of a source location
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) and tmuxinator (reads session files).

The nvim engine needs a literal desc at the call site. nvim reports no source location for a Lua keymap: lnum is 0 and sid collapses to init.lua for anything loaded through require. So the engine recovers a location by grepping your config for the description as written, and a binding it cannot place is left out, since there would be nothing for --edit to jump to. That filter is what keeps several hundred plugin bindings out of the listing, but it also hides your own binding if its desc reached nvim through a variable:

-- invisible to confhelp: the literal never appears in a file
local set = function(lhs, rhs, desc)
  vim.keymap.set("n", lhs, rhs, { desc = desc })
end
set("<leader>ff", cmd, "Find files")

-- visible: the call site spells the description out
set("<leader>ff", cmd, { desc = "Find files" })

confhelp --check reports how many runtime bindings were dropped this way. Plugin bindings are expected in that list; yours are not. To see only your own:

confhelp --check 2>/dev/null | grep '^\[nvim\] <leader>'

Descriptions should be unique. The index is keyed by description text, so two bindings sharing one resolve to the same location (the first in sorted file order).

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
path = ".config/nvim"      # where to grep for desc literals, relative to base_dir

# tmuxinator query engine options
[engine.tmuxinator]
path = "~/.config/tmuxinator"

# 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

Metadata

Release files for confhelp 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for confhelp 0.8.0
File Size Uploaded
confhelp-0.8.0.tar.gz 284.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for confhelp 0.8.0
File Interpreter ABI Platform
confhelp-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 300.0 kB

Release files / confhelp-0.8.0.tar.gz

Download URL confhelp-0.8.0.tar.gz
Size 284.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ba5f58e3142d3037e3f634eb31e31582a4242c06eb4251ea48a1815c006f6190
BLAKE2b-256 checksum
How to use checksums
f9e04a3e909afe1696adcb372fdadf7c29cce7efeed4a6914bdae77630ac9168
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.

Transparency log

Release files / confhelp-0.8.0-py3-none-any.whl

Download URL confhelp-0.8.0-py3-none-any.whl
Size 15.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ef9acef4cc0c1d41f4c772acc5cb003e7463fbceceaacc2dbd8fe40a663caaaf
BLAKE2b-256 checksum
How to use checksums
b3b5a349ff09c1eb8f11c48b6c7134ec600e36afdd3f6f17070ce6ad3f15b883
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page