Skip to main content
Zep uses a flexible keymap system that allows you to add custom keybindings to any mode. The system supports command composition with counts, registers, and special key sequences.

KeyMap Basics

The KeyMap class provides a tree-based structure for matching key sequences to command IDs. Each mode (Vim, Standard, etc.) maintains multiple keymaps for different contexts.
Keymaps are organized as trees where each node represents a token (key or special sequence). This allows efficient matching of multi-key sequences like dd or ci(.

Key Sequence Format

Special Keys

Zep uses angle brackets for special keys:
"<C-x>"     // Ctrl+x
"<S-Return>" // Shift+Return  
"<C-S-z>"   // Ctrl+Shift+z
"<Left>"    // Left arrow
"<PageDown>" // Page down
"<F8>"      // Function key F8

Regular Keys

Regular characters are used directly:
"dd"   // d followed by d
"yy"   // y followed by y
"gg"   // g followed by g  
"abc"  // a, b, c sequence

Wildcards

Special tokens for pattern matching:
  • <D> - Matches digits (for command counts)
  • <.> - Matches any single character
  • <R> - Matches register specification ("a, "b, etc.)

Available Special Keys

// Navigation
<Left>, <Right>, <Up>, <Down>
<Home>, <End>
<PageUp>, <PageDown>

// Editing  
<Return>, <Escape>, <Backspace>, <Tab>, <Del>

// Function Keys
<F1> through <F12>

// Modifiers (can be combined)
C-  // Control
S-  // Shift
Modifier keys must be capitalized and separated by hyphens: <C-S-x> not <c-s-x>

Adding Keybindings

The keymap_add function adds a command to one or more keymaps:
#include "zep/keymap.h"

// Add to single keymap
keymap_add(myKeyMap, "<C-x>", id_MyCommand);

// Add to multiple keymaps  
keymap_add({ &normalMap, &insertMap }, { "<C-s>" }, id_Save);

// Add multiple key sequences for same command
keymap_add(myKeyMap, { "dd", "<Del>" }, id_DeleteLine);

Function Signature

bool keymap_add(
    KeyMap& map,                          // Target keymap
    const std::string& strCommand,        // Key sequence
    const StringId& commandId,            // Command to execute
    KeyMapAdd opt = KeyMapAdd::Replace    // Replace or error on duplicate
);

// Multi-map version
bool keymap_add(
    const std::vector<KeyMap*>& maps,     // Multiple keymaps
    const std::vector<std::string>& commands, // Multiple sequences  
    const StringId& commandId,
    KeyMapAdd opt = KeyMapAdd::Replace
);

Command IDs

Commands are identified by StringId objects. Zep provides a macro for declaring them:
// Define a command ID
DECLARE_COMMANDID(MyCustomCommand)

// This expands to:
// const StringId id_MyCustomCommand("MyCustomCommand");

Built-in Command IDs

Zep includes many predefined commands in include/zep/keymap.h:18-173:
id_InsertMode, id_NormalMode, id_VisualMode
id_Undo, id_Redo
id_Copy, id_Paste  
id_MotionUp, id_MotionDown, id_MotionLeft, id_MotionRight
id_DeleteLine, id_DeleteWord
id_Save
// ... and many more

Extending a Mode

Create a custom mode by inheriting from ZepMode or an existing mode:
class MyCustomMode : public ZepMode_Vim
{
public:
    MyCustomMode(ZepEditor& editor) : ZepMode_Vim(editor) {}
    
    virtual void SetupKeyMaps() override
    {
        // Call parent to get standard Vim keys
        ZepMode_Vim::SetupKeyMaps();
        
        // Add custom keybindings
        keymap_add({ &m_normalMap }, { "<C-t>" }, id_MyCustomCommand);
        keymap_add({ &m_insertMap }, { "<C-Space>" }, id_AutoComplete);
        
        // Override existing binding
        keymap_add(m_normalMap, "dd", id_MyDeleteLine, KeyMapAdd::Replace);
    }
    
    const char* Name() const override { return "MyMode"; }
};

Command Counts and Registers

Add keybindings that accept counts and registers:
// Accept optional count before command
// Example: 5dd (delete 5 lines)
AddKeyMapWithCountRegisters(
    { &m_normalMap }, 
    { "<D>d<D>d", "dd" }, 
    id_DeleteLine
);

// Accept register specification
// Example: "ayy (yank to register a)  
AddKeyMapWithCountRegisters(
    { &m_normalMap },
    { "<R>yy" },
    id_YankLine  
);

// Accept any character
// Example: f<char> (find character)
AddKeyMapWithCountRegisters(
    { &m_normalMap },
    { "f<.>" },
    id_Find
);

Accessing Captured Data

In your command handler:
KeyMapResult result;
keymap_find(map, "5dd", result);

// result.TotalCount() returns 5
// result.captureNumbers contains [5]
// result.foundMapping == id_DeleteLine

Querying Keymaps

Find what command a key sequence maps to:
KeyMapResult result;
keymap_find(myKeyMap, "<C-x>5dd", result);

if (result.foundMapping != StringId())
{
    // Command found
    int count = result.TotalCount();           // Total count
    char reg = result.RegisterName();          // Register ('a', 'b', etc.)
    auto& nums = result.captureNumbers;        // All captured numbers
    auto& chars = result.captureChars;         // All captured chars
    
    // Execute the command
    ExecuteCommand(result.foundMapping, count, reg);
}
else if (result.needMoreChars)
{
    // Partial match, need more input
    DisplayStatus("Waiting for more keys...");
}
else  
{
    // No match, treat as text input
    InsertText(result.commandWithoutGroups);
}

Debugging Keymaps

Visualize the keymap structure:
#include <sstream>

std::ostringstream str;
keymap_dump(myKeyMap, str);
std::cout << str.str();

// Output shows tree structure:
// d
//   d : DeleteLine
//   w : DeleteWord  
//   <D>
//     w : DeleteWord
// y
//   y : YankLine

Complete Example

#include "zep/mode.h"
#include "zep/keymap.h"

// 1. Define command ID
DECLARE_COMMANDID(DuplicateLine)

// 2. Create custom mode
class MyEditorMode : public ZepMode_Vim
{
public:
    MyEditorMode(ZepEditor& editor) : ZepMode_Vim(editor) {}
    
    void SetupKeyMaps() override
    {
        ZepMode_Vim::SetupKeyMaps();
        
        // Bind Ctrl+D to duplicate current line
        keymap_add({ &m_normalMap, &m_insertMap }, 
                   { "<C-d>" }, 
                   id_DuplicateLine);
    }
    
    void HandleCommand(const StringId& cmd, const KeyMapResult& result) override
    {
        if (cmd == id_DuplicateLine)
        {
            // Get current line
            auto& buffer = GetCurrentWindow()->GetBuffer();
            auto cursor = GetCurrentWindow()->GetBufferCursor();
            auto lineStart = buffer.GetLinePos(cursor, LineLocation::LineBegin);
            auto lineEnd = buffer.GetLinePos(cursor, LineLocation::LineEnd);
            
            // Duplicate it
            std::string line(buffer.begin() + lineStart, 
                           buffer.begin() + lineEnd);
            buffer.Insert(lineEnd, "\n" + line);
            return;
        }
        
        // Fallback to parent  
        ZepMode_Vim::HandleCommand(cmd, result);
    }
};

// 3. Use the mode
void SetupEditor(ZepEditor& editor)
{
    auto* mode = new MyEditorMode(editor);
    editor.RegisterMode(mode);
    editor.SetGlobalMode("MyMode");
}

Implementation Reference

The keymap system is implemented in:
  • include/zep/keymap.h:174-232 - KeyMap structures and functions
  • src/keymap.cpp - Implementation details
  • src/mode_vim.cpp:78-269 - Example usage in Vim mode
  • src/mode_standard.cpp:49-89 - Example usage in Standard mode

Vim Mode

See how Vim mode uses keymaps

Standard Mode

See standard mode keybindings

Build docs developers (and LLMs) love