How to Implement Undo and Redo Functionality for InputText Widgets in Dear ImGui

Dear ImGui provides a minimal single-step undo mechanism for InputText widgets via ImGuiInputTextState, but implementing full multi-step undo/redo requires integrating the ImGuiInputTextFlags_CallbackEdit flag with custom history stacks.

The ocornut/imgui library includes a lightweight undo system that records only the most recent edit state, allowing users to revert a single change with Ctrl+Z. To build a comprehensive undo/redo history with multiple steps, you must intercept text edits through callbacks and manage your own state stacks. This approach leverages the widget's internal architecture while keeping your application logic independent of ImGui's private internals.

Understanding ImGui's Built-In Undo System

The core undo functionality lives inside ImGuiInputTextState, defined in imgui_widgets.cpp at approximately line 4308. Each time the user modifies text, ImGui calls InputTextReconcileUndoState (located around line 4572 in the same file) to store a snapshot of the previous buffer.

This built-in mechanism has a critical limitation: it maintains only one previous state. When you press Ctrl+Z, ImGui restores that single snapshot, but there is no corresponding redo buffer or history chain. The internal state is updated automatically during the widget's edit loop, making it suitable for immediate recovery of accidental keystrokes but inadequate for complex editing workflows.

Implementing Multi-Step Undo/Redo with Callbacks

To extend beyond single-step undo, you must capture buffer states before they change and maintain separate undo and redo stacks. This requires intercepting edit events before the widget applies them.

Setting Up the History Structure

Create a structure to hold your undo and redo stacks. Each stack should store complete string copies of the buffer state at specific moments:

struct TextHistory {
    std::vector<std::string> undoStack;
    std::vector<std::string> redoStack;
};

Store this structure as user data that you pass to the InputText callback.

Capturing Edits with ImGuiInputTextFlags_CallbackEdit

Enable edit notifications by passing the ImGuiInputTextFlags_CallbackEdit flag when calling ImGui::InputText(). This flag instructs ImGui to invoke your callback function every time the buffer content changes, including deletions and insertions.

In your callback function, push the current buffer content (the pre-edit state) onto your undo stack. This preserves the state before the modification occurs:

static int EditCallback(ImGuiInputTextCallbackData* data)
{
    TextHistory* hist = (TextHistory*)data->UserData;
    
    if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit) {
        // Capture state before the edit is applied
        hist->undoStack.emplace_back(data->Buf);
        // New edits invalidate redo history
        hist->redoStack.clear();
    }
    return 0;
}

Clearing the redo stack during new edits is essential to prevent time-travel paradoxes in your history chain.

Handling Undo and Redo Shortcuts

Detect keyboard shortcuts using ImGui's key state functions. Implement separate handlers for undo (Ctrl+Z) and redo (Ctrl+Y or Ctrl+Shift+Z).

For undo operations:

  1. Check that the undo stack is not empty
  2. Push the current buffer content onto the redo stack
  3. Pop the most recent state from the undo stack
  4. Copy that state back into the widget's buffer
  5. Mark the item as edited so ImGui recognizes the change

For redo operations, reverse the stack operations: move current state to undo, pop from redo, and restore.

After modifying the buffer externally, call ImGui::MarkItemEdited with the widget's ID to ensure ImGui processes the change in its input pipeline.

Complete Implementation Example

The following example demonstrates a fully functional undo/redo system wrapping an InputText widget:

struct TextHistory {
    std::vector<std::string> undoStack;
    std::vector<std::string> redoStack;
};

static int EditCallback(ImGuiInputTextCallbackData* data)
{
    TextHistory* hist = (TextHistory*)data->UserData;
    
    if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit) {
        hist->undoStack.emplace_back(data->Buf);
        hist->redoStack.clear();
    }
    return 0;
}

void ShowUndoRedoInput()
{
    static char buffer[256] = "";
    static TextHistory history;
    
    // Render input with edit callback enabled
    ImGui::InputText("##editable", buffer, IM_ARRAYSIZE(buffer),
                     ImGuiInputTextFlags_CallbackEdit,
                     EditCallback, &history);
    
    const ImGuiID itemId = ImGui::GetItemID();
    
    // Undo: Ctrl+Z
    if (ImGui::IsKeyDown(ImGuiMod_Ctrl) && ImGui::IsKeyPressed(ImGuiKey_Z)) {
        if (!history.undoStack.empty()) {
            // Save current state for redo
            history.redoStack.emplace_back(buffer);
            
            // Restore previous state
            std::string prevState = history.undoStack.back();
            history.undoStack.pop_back();
            ImStrncpy(buffer, prevState.c_str(), IM_ARRAYSIZE(buffer));
            
            ImGui::MarkItemEdited(itemId);
        }
    }
    
    // Redo: Ctrl+Y (or optionally check for Ctrl+Shift+Z)
    if (ImGui::IsKeyDown(ImGuiMod_Ctrl) && ImGui::IsKeyPressed(ImGuiKey_Y)) {
        if (!history.redoStack.empty()) {
            // Save current state for undo
            history.undoStack.emplace_back(buffer);
            
            // Restore next state
            std::string nextState = history.redoStack.back();
            history.redoStack.pop_back();
            ImStrncpy(buffer, nextState.c_str(), IM_ARRAYSIZE(buffer));
            
            ImGui::MarkItemEdited(itemId);
        }
    }
}

This implementation captures buffer states before each modification, maintains independent undo and redo chains, and properly notifies ImGui of external buffer modifications.

Key Source Files and Functions

Understanding the internal architecture helps when debugging or extending this functionality:

  • imgui_widgets.cpp (line 4308): Contains the ImGuiInputTextState structure definition, which manages the widget's internal text buffer and undo record.
  • imgui_widgets.cpp (line 4572): Implements InputTextReconcileUndoState, the function responsible for saving the single-step undo snapshot within ImGui's internal state.
  • imgui.cpp (line 4222): Initializes the global ImGuiInputTextState instance within the ImGuiContext setup.
  • imgui_demo.cpp (line 1100): Provides reference implementations of InputText callbacks demonstrating various flag combinations.

Summary

  • Dear ImGui's InputText widget includes a single-step undo mechanism via InputTextReconcileUndoState, accessible with Ctrl+Z, but lacks built-in redo support.
  • To implement multi-step undo/redo, use the ImGuiInputTextFlags_CallbackEdit flag to capture buffer states before each edit.
  • Maintain separate undoStack and redoStack vectors in your application code, clearing the redo stack whenever new edits occur.
  • Detect Ctrl+Z and Ctrl+Y shortcuts using ImGui::IsKeyDown and ImGui::IsKeyPressed, then swap buffer contents between your stacks and the widget.
  • Call ImGui::MarkItemEdited after programmatically modifying the buffer to ensure ImGui processes the change.

Frequently Asked Questions

Does Dear ImGui support redo operations natively?

No, the built-in undo system in ImGuiInputTextState only stores a single previous state to support one-level undo via Ctrl+Z. There is no native redo buffer or history chain. To support redo functionality, you must implement custom history stacks using the callback mechanism described above.

How do I prevent the redo history from becoming inconsistent with new edits?

Clear your redo stack immediately upon capturing a new edit in your callback function. When the user types new content after undoing, the previous "future" states in the redo stack are no longer valid. The standard pattern is to call redoStack.clear() inside your ImGuiInputTextFlags_CallbackEdit handler whenever you push a new state onto the undo stack.

What is the maximum depth I should allow for undo stacks?

ImGui imposes no limits on your custom undo implementation, as you manage the history vectors independently. However, consider memory constraints when storing large text buffers. A practical approach limits the undo stack to 50-100 entries or implements a ring buffer for very large documents, discarding the oldest states when exceeding your defined threshold.

Can I use this approach with InputTextMultiline widgets?

Yes, the callback system works identically for ImGui::InputTextMultiline(). Pass the same ImGuiInputTextFlags_CallbackEdit flag and callback function pointer. The ImGuiInputTextCallbackData structure provides the same buffer access regardless of whether the widget is single-line or multi-line, making the undo/redo implementation code reusable across both widget types.

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 →