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

> Learn how custom keybinding strings are parsed in Herdr. This technical guide details the TOML to BindingTrigger object conversion, handling modifiers, ranges, and leader keys in Rust.

- Repository: [Can Celik/herdr](https://github.com/ogulcancelik/herdr)
- Tags: deep-dive
- Published: 2026-05-31

---

**Herdr converts textual keybinding descriptions from TOML configuration into runtime `BindingTrigger` objects through a multi-stage parser in [`src/config/keybinds.rs`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/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.

```rust
// 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`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/src/ui/keybind_help.rs)** renders the human-readable help overlay using these stored bindings, and **[`src/config/io.rs`](https://github.com/ogulcancelik/herdr/blob/main/src/config/io.rs)** orchestrates the initial loading from TOML.

## Configuration Examples

The following TOML demonstrates supported binding patterns:

```toml

# ~/.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:

```rust
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`](https://github.com/ogulcancelik/herdr/blob/main/src/config/keybinds.rs#L802) 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`](https://github.com/ogulcancelik/herdr/blob/main/src/server/keybindings.rs#L10) 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`](https://github.com/ogulcancelik/herdr/blob/main/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`](https://github.com/ogulcancelik/herdr/blob/main/src/ui/keybind_help.rs).