How to Create Custom Keybinding Modes in Flow Control

Flow Control stores keybindings in JSON namespace files where modes are defined as subsections with inheritance, press/release arrays, and activation via the input_mode setting or the f4 key cycle.

The open-source terminal editor Flow Control (neurocyte/flow) uses a hierarchical keybinding system that separates namespaces (JSON configuration files) from modes (contextual subsections within those files). Understanding this architecture allows you to define custom modes that inherit from existing ones and bind unique command sequences without modifying the core source code.

Understanding Keybinding Namespaces and Modes

Flow Control organizes keybindings into namespaces—JSON files that contain multiple modes. A namespace corresponds to the input_mode configuration value (such as flow, vim, or emacs), while a mode represents a specific operational context like normal, select, or mini/find.

When the editor initializes, it loads the namespace file defined by input_mode and constructs runtime BindingSet structures for each mode. The Namespace.load function in src/keybind/keybind.zig (lines 71–115) handles this parsing using std.json.parseFromSlice, while BindingSet.load (lines 90–115) resolves inheritance chains.

Locating and Creating Your Custom Keybinding File

Built-in namespaces reside in the source tree at src/keybind/builtin/*.json. For example, the default flow namespace is defined in src/keybind/builtin/flow.json. However, you should not edit these directly.

Instead, Flow Control uses the get_or_create_namespace_config_file function in src/keybind/keybind.zig (lines 22–32) to manage user-specific configurations. This helper checks the user config directory ($XDG_CONFIG_HOME/flow/keys/) for the requested namespace file. If the file does not exist, it copies the built-in JSON to the user directory as a starting point.

To begin creating a custom mode, ensure the namespace file exists in your user config directory by running the "Edit keybindings" command from the palette (implemented in src/command.zig), which opens the current namespace JSON file for editing.

Defining a Custom Keybinding Mode

A custom mode is a top-level JSON object within the namespace file. The object name becomes the mode identifier, and it accepts several properties:

  • inherit – Specifies a parent mode to inherit non-conflicting bindings from.
  • on_match_failure – Defines behavior when a key sequence does not match (e.g., "ignore" or "beep").
  • press and release – Arrays of binding tuples in the format [key-chord, command, ...args].

For example, to create a mode named mycustom that inherits from normal and binds Ctrl+t to open_terminal, add the following to your namespace file:

"mycustom": {
    "inherit": "normal",
    "press": [
        ["ctrl+t", "open_terminal"]
    ]
}

The inheritance system, handled by BindingSet.load, merges parent bindings with child definitions, allowing you to override specific keys while retaining the base behavior of standard modes like normal or select.

Activating and Testing Your Custom Mode

Once you save the namespace file, Flow Control automatically reloads the configuration on the next startup. To activate the mode immediately without restarting, use the input-mode cycle key (f4 by default), which switches between available modes in the current namespace.

Alternatively, you can create a separate namespace entirely by copying the JSON file to a new name (e.g., mykeys.json) in the user config directory and setting input_mode = "mykeys" in $XDG_CONFIG_HOME/flow/config. The get_or_create_namespace_config_file logic treats this as a distinct namespace, loading it independently from the built-in definitions.

Summary

  • Flow Control uses JSON namespace files stored in $XDG_CONFIG_HOME/flow/keys/ to define keybindings, with individual modes as top-level objects within those files.
  • The get_or_create_namespace_config_file function in src/keybind/keybind.zig automatically seeds user configurations from built-in templates located at src/keybind/builtin/*.json.
  • Custom modes support inheritance via the inherit property, allowing you to extend base modes like normal without redefining every binding.
  • Activation occurs through the input_mode configuration setting, the f4 key cycle, or the "Edit keybindings" command palette entry.

Frequently Asked Questions

How do I inherit from multiple parent modes in Flow Control?

Flow Control supports the inherit property (singular) for direct parent mode inheritance. If you need to combine bindings from multiple sources, create a chain where your custom mode inherits from an intermediate mode that itself inherits from another parent. The BindingSet.load implementation in src/keybind/keybind.zig (lines 90–115) resolves these inheritance chains recursively.

Where does Flow Control store user-defined keybinding files?

User-defined keybinding namespaces are stored in the keys subdirectory of the Flow Control configuration directory, typically $XDG_CONFIG_HOME/flow/keys/ on Linux systems. The get_or_create_namespace_config_file function in src/keybind/keybind.zig (lines 22–32) manages these paths, automatically creating the directory structure and seeding initial files from built-in templates if they do not exist.

Can I switch to my custom mode without restarting Flow Control?

Yes, you can activate custom modes immediately without restarting by using the input-mode cycle key (f4 by default), which iterates through available modes in the current namespace. Alternatively, if you created a new namespace file entirely, you can force a reload by switching the input_mode setting in your configuration and triggering a mode change via the command palette or the f4 cycle.

What is the difference between a namespace and a mode in Flow Control?

A namespace is a complete JSON configuration file (such as flow.json or vim.json) that defines an entire keybinding scheme, selected by the input_mode configuration setting. A mode is a subsection within that namespace (such as normal, insert, or mycustom) that represents a specific operational context with its own set of key bindings. Namespaces are loaded by Namespace.load in src/keybind/keybind.zig, while modes are instantiated as BindingSet structures within those namespaces.

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 →