How Custom Keybinding Strings Are Parsed in Herdr: A Complete Technical Guide

Herdr converts textual keybinding descriptions from TOML configuration into runtime BindingTrigger objects through a multi-stage parser in src/config/keybinds.rs that handles modifier keys, numeric ranges, and leader-key prefixes.

Herdr reads keybinding definitions from user configuration files and converts each string into a runtime trigger that the input engine can match against incoming events. Understanding how custom keybinding strings are parsed in Herdr reveals the transformation pipeline from human-readable entries like "ctrl+1..5" to internal Rust structures used by this terminal workspace manager.

The Entry Point: parse_binding_string

The conversion begins in src/config/keybinds.rs at approximately line 802, where the parse_binding_string function serves as the main entry point. This function accepts a raw string slice and returns an Option<ParsedBinding>, determining whether the input represents a single binding or a range of consecutive bindings.

// src/config/keybinds.rs (approx. L802-L841)
pub fn parse_binding_string(raw: &str) -> Option<ParsedBinding> {
    // Detects ranges, parses modifiers, and constructs the binding
}

At lines 313–322, the source code defines the ParsedBinding enum, which has two variants: Single(ResolvedBinding) for individual triggers and Range(Vec<ResolvedBinding>) for expanded sequences.

Detecting and Expanding Range Bindings

When a keybinding string contains the ".." operator, Herdr treats it as a range expression. The helper function parse_range_modifiers, located around lines 920–938, extracts any common modifiers that apply to the entire range before expansion.

For example, the string "ctrl+1..5" creates five distinct bindings (ctrl+1, ctrl+2, ctrl+3, ctrl+4, ctrl+5), each inheriting the CONTROL modifier while receiving its own numeric key code. The base key of the first element is parsed using parse_key_combo, then the range is expanded into a vector of ResolvedBinding structs.

Parsing Modifier Tokens

Before processing the actual key, the parser identifies modifier prefixes separated by + characters. Around lines 909–915, parse_modifier_token maps textual tokens to KeyModifiers bitflags:

  • ctrl → CONTROL
  • alt → ALT
  • shift → SHIFT
  • cmd → SUPER
  • hyper → HYPER
  • meta → META

These modifiers accumulate into a single KeyModifiers value that applies to the final binding.

Processing Key Combinations

The function parse_key_combo (lines 959–1017) handles the token following the optional modifiers. It recognizes several key categories:

  • Single-character keys: Letters, numbers, and Unicode characters like "ö"
  • Named keys: "tab", "minus", "esc", "enter"
  • Function keys: "f5", "f12", etc.
  • Special handling: The parser normalizes Shift+Tab to the BackTab key code

This function returns a KeyCode that represents the physical key to match.

Building the Runtime Trigger

Around lines 854–866, the parsed (KeyCode, KeyModifiers) pair is wrapped in a BindingTrigger. The variant depends on the binding type:

  • BindingTrigger::Direct: Standard immediate-action bindings
  • BindingTrigger::Prefix: Leader-key bindings marked with a leading "!" in the configuration string

The presence of the "!" prefix in the TOML entry determines which variant the constructor returns.

Integration with Live Configuration

After parsing, the results flow into the running application state. In src/server/keybindings.rs (lines 10–16), the apply_keybindings function copies the parsed keybindings.prefix and keybindings.keybinds vectors into the AppState. Meanwhile, src/ui/keybind_help.rs renders the human-readable help overlay using these stored bindings, and src/config/io.rs orchestrates the initial loading from TOML.

Configuration Examples

The following TOML demonstrates supported binding patterns:


# ~/.config/herdr/config.toml

[keys]

# Direct single binding

new_workspace = "n"
help = "ctrl+h"

# Prefix (leader) binding marked with "!"

workspace_picker = "!ctrl+space"

# Range binding expands to ctrl+1, ctrl+2, ctrl+3

pane_select = "ctrl+1..3"

In Rust, the parsing functions return structured data:

use herdr::config::keybinds::parse_binding_string;

let single = parse_binding_string("ctrl+h").unwrap();
// ParsedBinding::Single with Direct trigger

let prefix = parse_binding_string("!ctrl+space").unwrap();
// ParsedBinding::Single with Prefix trigger

let range = parse_binding_string("ctrl+1..3").unwrap();
// ParsedBinding::Range with three ResolvedBindings

Summary

  • Entry point: The parse_binding_string function in src/config/keybinds.rs initiates parsing and returns a ParsedBinding enum.
  • Range expansion: The ".." syntax creates multiple bindings via parse_range_modifiers (L920), with each inheriting common modifiers.
  • Modifier mapping: Tokens like ctrl and alt convert to KeyModifiers via parse_modifier_token (L909).
  • Key resolution: parse_key_combo (L959) handles Unicode, named keys, and special cases like Shift+Tab normalization.
  • Trigger types: Leading "!" creates BindingTrigger::Prefix variants for leader-key modes, while standard bindings use BindingTrigger::Direct.
  • Runtime integration: apply_keybindings in src/server/keybindings.rs activates parsed bindings in the application state.

Frequently Asked Questions

How does Herdr handle the Shift+Tab key combination?

Herdr normalizes Shift+Tab to the BackTab key code during parsing. In the parse_key_combo function around lines 959–1017, the parser detects when the SHIFT modifier accompanies the Tab key and automatically substitutes the BackTab KeyCode instead of passing the raw combination.

Can I use Unicode characters in Herdr keybinding strings?

Yes. The parse_key_combo function accepts Unicode characters directly, such as "ö" or "ñ", and converts them into the appropriate KeyCode variants. The parser treats these as single-character keys without requiring special escaping in the TOML configuration file.

What is the difference between Direct and Prefix binding triggers?

According to the source code around lines 854–866, BindingTrigger::Direct executes an action immediately when the key combination is pressed. BindingTrigger::Prefix (created when the binding string starts with "!") acts as a leader key that puts Herdr into a modal state, waiting for a subsequent key to complete the command sequence.

Where does the parsed keybinding data get stored in the application?

The apply_keybindings function in src/server/keybindings.rs (lines 10–16) transfers the parsed LiveKeybindConfig into the running AppState. This makes the bindings available to the input processing layer and the help system rendered in src/ui/keybind_help.rs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →