Understanding Lazygit's Window and Panel Management System Architecture

Lazygit organizes its terminal UI into logical windows mapped to gocui views through a three-layer architecture: WindowHelper manages view-to-window mappings, WindowArrangementHelper calculates dimensions using the boxlayout library, and the layout engine applies these coordinates to the actual gocui views.

The lazygit window and panel management system provides a flexible, config-driven approach to terminal UI layout. Unlike traditional TUI applications that hardcode coordinates, lazygit uses a declarative system where logical windows (screen regions like "status" or "files") are separated from concrete views (the actual gocui boxes that render content). This architecture allows dynamic panel swapping, responsive resizing, and support for both portrait and landscape modes.

Core Concepts: Windows, Views, and Contexts

At the heart of lazygit's architecture are three distinct concepts that work together to render the interface.

Windows represent logical regions on the screen. These are named slots such as "status", "files", "commits", "main", and "extras". A window defines where content should appear, but not what content renders there.

Views are concrete instances of *gocui.View from the underlying gocui library. These are the actual boxes that handle rendering, input, and scrolling. A view can be moved between windows, allowing dynamic UI changes like when the commit-files view shifts between the side panel and main panel.

Contexts carry the window identity. Every UI panel in lazygit implements a context interface that embeds BaseContext from pkg/gui/context/base_context.go. Each context stores a windowName field that determines which logical window it occupies:

func (self *BaseContext) GetWindowName() string { return self.windowName }

This separation allows the layout engine to ask "what context is in window X?" and "what window is context Y in?" independently of the actual rendering coordinates.

The Three-Layer Layout Engine

Lazygit's window and panel management system operates through three tightly-coupled layers that transform abstract UI state into concrete terminal coordinates.

Layer 1: Window-to-View Mapping

The first layer maintains the relationship between logical windows and concrete views. This responsibility lives in pkg/gui/controllers/helpers/window_helper.go within the WindowHelper struct.

WindowHelper stores the mapping in a thread-safe map called WindowViewNameMap of type ThreadSafeMap[string,string]. This map tracks which view currently occupies each window slot.

Key operations include retrieving the view for a window:

func (self *WindowHelper) GetViewNameForWindow(window string) string {
    viewName, ok := self.windowViewNameMap().Get(window)
    if !ok {
        panic(fmt.Sprintf("Viewname not found for window: %s", window))
    }
    return viewName
}

When a context (panel) is activated, it registers its window and view through SetWindowContext:

func (self *WindowHelper) SetWindowContext(c types.Context) {
    if c.IsTransient() {
        self.resetWindowContext(c)
    }
    self.windowViewNameMap().Set(c.GetWindowName(), c.GetViewName())
}

The helper also handles edge cases like moving views between windows. When a view moves to a new window (such as the commit-files view shifting panels), resetWindowContext updates the mapping for the previous window to ensure no window remains empty.

Additional helper methods like CurrentWindow(), TopViewInWindow(), and SideWindows() support navigation and layout decisions throughout the application.

Layer 2: Dimension Calculation

The second layer computes exact screen coordinates for every window based on terminal size, user configuration, and current UI state. This logic resides in pkg/gui/controllers/helpers/window_arrangement_helper.go.

Lazygit uses the boxlayout library (from lazycore) to describe the UI as a tree of boxes. Each box has either a weight (relative size) or a fixed size, allowing flexible responsive layouts.

The WindowArrangementHelper.GetWindowDimensions() method builds a WindowArrangementArgs struct containing:

  • Terminal dimensions (Width, Height)
  • User configuration (UserConfig) including panel widths, portrait mode settings, and split mode preferences
  • Current focus state (CurrentWindow, CurrentSideWindow)
  • UI state flags (ShowExtrasWindow, InSearchPrompt, IsAnyModeActive)

This struct feeds into the pure function GetWindowDimensions(args) which:

  1. Determines portrait vs landscape mode via shouldUsePortraitMode
  2. Calculates weights for side-panel and main-panel sections using getMidSectionWeights
  3. Constructs a hierarchical boxlayout.Box tree modeling:
    • Side panels (status, files, branches, commits, stash) with accordion expansion support (sidePanelChildren)
    • Main panels (mainPanelChildren) supporting horizontal or vertical splits
    • Optional extras panel (command log) and bottom info line
  4. Calls boxlayout.ArrangeWindows to convert the tree into map[string]boxlayout.Dimensions
func GetWindowDimensions(args WindowArrangementArgs) map[string]boxlayout.Dimensions {
    // … build the box tree …
    layerOneWindows := boxlayout.ArrangeWindows(root, 0, 0, args.Width, args.Height)
    limitWindows   := boxlayout.ArrangeWindows(&boxlayout.Box{Window: "limit"}, 0, 0, args.Width, args.Height)
    return MergeMaps(layerOneWindows, limitWindows)
}

The resulting map associates every logical window name (e.g., "main", "files") with concrete coordinates (X0, Y0, X1, Y1).

Layer 3: Layout Rendering

The third layer applies calculated dimensions to the actual gocui views and handles visibility. This occurs in pkg/gui/layout.go within the layout method.

Every UI redraw (triggered by resize, focus change, or mode change) executes:

  1. Collect UI strings such as informationStr and appStatus
  2. Retrieve dimensions from the arrangement helper:
viewDimensions := gui.getWindowDimensions(informationStr, appStatus) // ← WindowArrangementHelper
  1. Iterate over contexts with controlled bounds. For each context:

    • Look up the window name via context.GetWindowName() (defined in BaseContext)
    • Retrieve saved dimensions for that window
    • Call gocui.Gui.SetView with new coordinates, or hide the view if the window is inactive
    • Queue re-render if size or width changed
  2. Handle transient pop-ups by showing them only when their window is current:

view.Visible = gui.helpers.Window.GetViewNameForWindow(context.GetWindowName()) == context.GetViewName()
  1. Execute post-layout functions queued via AfterLayout, completing the render cycle.

Dynamic View Management

The lazygit window and panel management system supports dynamic view relocation, allowing views to move between windows based on user interaction or application state.

Moving Views Between Windows

A concrete example is the commit-files view, which can appear in either the side panel or the main panel depending on context. The movement process involves:

  1. Updating the context's window name:
commitFilesContext.SetWindowName("files") // move it into the side-panel
  1. Updating the mapping via WindowHelper:
gui.helpers.Window.SetWindowContext(commitFilesContext) // update the map
  1. Triggering a layout refresh:
gui.refreshSidePanels() // triggers a layout pass

The SetWindowContext call updates the internal WindowViewNameMap, ensuring the next layout pass places the view in the dimensions of the "files" window.

Handling Transient Views

Transient views (such as confirmation dialogs or search prompts) use the visibility check in layout.go to appear only when active. The system compares the current view name for a window against the context's view name:

view.Visible = gui.helpers.Window.GetViewNameForWindow(context.GetWindowName()) == context.GetViewName()

This ensures that when a transient context closes, its view becomes invisible, and the underlying window content returns to focus.

Summary

The lazygit window and panel management system implements a sophisticated three-layer architecture that separates logical window management from physical rendering:

  • WindowHelper (pkg/gui/controllers/helpers/window_helper.go) maintains thread-safe mappings between window names and view names, enabling dynamic view relocation and focus management.
  • WindowArrangementHelper (pkg/gui/controllers/helpers/window_arrangement_helper.go) computes exact screen coordinates using the boxlayout library, supporting responsive layouts for portrait/landscape modes and configurable panel widths.
  • Layout Engine (pkg/gui/layout.go) applies calculated dimensions to gocui views, handles visibility for transient pop-ups, and triggers re-renders when state changes.

This declarative approach allows lazygit to support complex UI behaviors—such as moving the commit-files view between panels or adapting to terminal resizing—without hardcoded coordinates or manual view positioning.

Frequently Asked Questions

How does lazygit distinguish between windows and views?

Lazygit treats windows as logical named slots on the screen (such as "status", "files", or "main"), while views are concrete *gocui.View instances that handle actual rendering and input. The WindowHelper maintains a mapping between these concepts using WindowViewNameMap, allowing views to move between windows while preserving the logical layout structure defined by the window arrangement helper.

What determines the size and position of panels in lazygit?

Panel dimensions are calculated by WindowArrangementHelper.GetWindowDimensions() in pkg/gui/controllers/helpers/window_arrangement_helper.go. This function uses the boxlayout library to construct a tree of boxes with weights or fixed sizes, considering terminal dimensions, user configuration (such as SidePanelWidth), current focus state, and whether portrait mode is active. The result is a map of window names to concrete coordinates (X0, Y0, X1, Y1).

Can views be moved between different windows in lazygit?

Yes, views can be relocated between windows dynamically. This is achieved by updating the context's window name via SetWindowName(), calling WindowHelper.SetWindowContext() to update the internal mapping, and triggering a layout refresh. The commit-files view demonstrates this pattern, moving between the side panel and main panel depending on user interaction, without requiring changes to the layout calculation logic.

How does lazygit handle window resizing and layout updates?

When the terminal resizes or UI state changes (focus shifts, mode changes), the layout method in pkg/gui/layout.go executes. It retrieves current dimensions from WindowArrangementHelper, iterates over all contexts to apply new coordinates via gocui.Gui.SetView, and manages visibility for transient views. The system queues re-renders for views whose dimensions changed, ensuring the UI adapts smoothly to new terminal sizes without manual coordinate adjustments.

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 →