# How Zed Implements Diagnostic Markers and Error Display

> Discover how Zed implements diagnostic markers and error display. See inline gutter markers, hover popovers with markdown, and a project wide diagnostics panel for efficient code analysis.

- Repository: [Zed Industries/zed](https://github.com/zed-industries/zed)
- Tags: internals
- Published: 2026-03-01

---

**Zed implements diagnostic markers by treating Language Server Protocol (LSP) diagnostics as first-class data that flows from the Language crate through the Editor to specialized renderers, displaying inline gutter markers, hover popovers with markdown, and a project-wide diagnostics panel.**

The zed-industries/zed repository handles compiler and language-server diagnostics through a multi-layered architecture that transforms raw LSP payloads into visible UI elements. Understanding how Zed implements diagnostic markers and error display reveals a flexible system where buffer-level storage meets pluggable rendering.

## The Diagnostic Data Model

### The Diagnostic Struct

In [`crates/language/src/buffer.rs`](https://github.com/zed-industries/zed/blob/main/crates/language/src/buffer.rs), the **Diagnostic** struct captures the complete LSP diagnostic payload including `source`, `code`, `severity`, `message`, `markdown`, `group_id`, and `is_primary` flags (lines 53-80). This struct serves as the canonical representation for all diagnostic data entering the editor, supporting both error and informational severities.

### Buffer-Level Diagnostic Sets

Each **Buffer** maintains a **DiagnosticSet** implemented as a `BTreeMap<Point, DiagnosticEntry>`, enabling efficient range-based queries and severity filtering. This storage mechanism allows the editor to quickly retrieve relevant diagnostics when rendering specific line ranges or responding to hover events.

## The Diagnostic Rendering Pipeline

### Global Renderer Registration

The editor delegates all visual representation to a global **DiagnosticRenderer** trait defined in [`crates/editor/src/editor.rs`](https://github.com/zed-industries/zed/blob/main/crates/editor/src/editor.rs) (lines 393-425). The default renderer implementation resides in [`crates/diagnostics/src/diagnostic_renderer.rs`](https://github.com/zed-industries/zed/blob/main/crates/diagnostics/src/diagnostic_renderer.rs) and registers at startup via `editor::set_diagnostic_renderer`, which is invoked in [`crates/diagnostics/src/diagnostics.rs`](https://github.com/zed-industries/zed/blob/main/crates/diagnostics/src/diagnostics.rs) (lines 70-71).

### Building Diagnostic Blocks

The default renderer's `diagnostic_blocks_for_group` method constructs **DiagnosticBlock** instances for each diagnostic group, generating markdown bodies, copy-button text, and severity-based color mappings (lines 22-86). These blocks encapsulate both the visual styling and interactive elements displayed to users.

## Inline Gutter Markers

In [`crates/editor/src/element.rs`](https://github.com/zed-industries/zed/blob/main/crates/editor/src/element.rs), the **EditorElement::layout_inline_diagnostics** method (lines 55-115) traverses visible rows and filters diagnostics by the user-configured `max_severity` setting. The method creates colored gutter elements using a local `severity_to_color` mapping that matches the theme's status colors, skipping diagnostics from the currently active group to avoid visual clutter.

## Hover Popovers and Project-Wide Panels

### Hover State and Diagnostic Popovers

When hovering over a line containing diagnostics, `Editor::hover_state` instantiates a **DiagnosticPopover** defined in [`crates/editor/src/hover_popover.rs`](https://github.com/zed-industries/zed/blob/main/crates/editor/src/hover_popover.rs) (lines 314-334). This popover renders markdown generated by the renderer's `DiagnosticBlock::render_block` method and includes a copy button for quick error message extraction.

### Status Bar and Diagnostics Panel

The **DiagnosticIndicator** in [`crates/diagnostics/src/items.rs`](https://github.com/zed-industries/zed/blob/main/crates/diagnostics/src/items.rs) (lines 17-65) observes project events such as `DiagnosticsUpdated` and `DiskBasedDiagnosticsFinished`, displaying error and warning counts in the status bar. Clicking this indicator opens the **ProjectDiagnosticsEditor**, which aggregates all diagnostics grouped by file and severity across the entire project.

## Extending the Diagnostic System

Developers can override the default visualization by implementing the `DiagnosticRenderer` trait and registering it via `set_diagnostic_renderer`.

### Registering a Custom Renderer

```rust
use editor::{set_diagnostic_renderer, DiagnosticRenderer};
use gpui::AppContext;
use std::sync::Arc;

struct SimpleRenderer;

impl DiagnosticRenderer for SimpleRenderer {
    fn render_group(
        &self,
        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
        buffer_id: BufferId,
        snapshot: EditorSnapshot,
        editor: gpui::WeakEntity<Editor>,
        language_registry: Option<Arc<LanguageRegistry>>,
        cx: &mut AppContext,
    ) -> Vec<BlockProperties<Anchor>> {
        let mut blocks = editor::default_diagnostic_renderer().render_group(
            diagnostic_group,
            buffer_id,
            snapshot,
            editor,
            language_registry,
            cx,
        );
        for block in &mut blocks {
            block.style = BlockStyle::Overlay(Color::from_rgba8(0x80, 0x00, 0x80, 0xFF));
        }
        blocks
    }

    fn render_hover(&self, ..) -> Option<gpui::Entity<Markdown>> { None }
    fn open_link(&self, ..) {}
}

// In your app start-up code
set_diagnostic_renderer(SimpleRenderer, cx);

```

### Creating Diagnostics Programmatically

For static analysis extensions, manually inject diagnostics into the editor's `inline_diagnostics` map:

```rust
use language::{Diagnostic, DiagnosticSeverity, Point, Range};

let diag = Diagnostic {
    source: Some("my-linter".into()),
    registration_id: None,
    code: Some(NumberOrString::String("M001".into())),
    code_description: None,
    severity: DiagnosticSeverity::WARNING,
    message: "Avoid using `foo` here".into(),
    markdown: None,
    group_id: 0,
    is_primary: true,
};

let range = Range {
    start: Point::new(9, 5),
    end: Point::new(9, 15),
};

editor.update(cx, |ed, cx| {
    ed.add_inline_diagnostic(range, diag);
});

```

### Opening the Diagnostics Panel

Extensions can trigger the project-wide view programmatically:

```rust
use workspace::Workspace;
use diagnostics::ProjectDiagnosticsEditor;

workspace.update(cx, |ws, cx| {
    ProjectDiagnosticsEditor::deploy(ws, &Default::default(), window, cx);
});

```

## Summary

- **Diagnostic storage**: The `Diagnostic` struct and `DiagnosticSet` in the Language crate provide the foundation for buffer-level diagnostic tracking.
- **Renderer abstraction**: The global `DiagnosticRenderer` trait decouples data from presentation, enabling custom visual implementations.
- **Inline display**: `EditorElement::layout_inline_diagnostics` renders severity-colored gutter markers based on visible ranges.
- **Interactive UI**: Hover popovers and the status-bar `DiagnosticIndicator` provide detailed error information and project-wide navigation.
- **Extensibility**: The `set_diagnostic_renderer` API allows complete customization of how Zed displays errors, warnings, and hints.

## Frequently Asked Questions

### What is the DiagnosticRenderer trait in Zed?

The `DiagnosticRenderer` trait defined in [`crates/editor/src/editor.rs`](https://github.com/zed-industries/zed/blob/main/crates/editor/src/editor.rs) (lines 393-425) is the core interface for converting diagnostic data into UI elements. It provides methods like `render_group` for creating visual blocks and `render_hover` for popover content, allowing developers to customize every aspect of error display.

### How does Zed store diagnostics internally?

Zed stores diagnostics per-buffer using a `DiagnosticSet`, which is a `BTreeMap<Point, DiagnosticEntry>` defined in the Language crate. This structure enables efficient querying by line range and severity, supporting fast lookups when the editor renders inline markers or hover states.

### Can I customize the colors of diagnostic markers in Zed?

Yes, by implementing a custom `DiagnosticRenderer` and registering it via `set_diagnostic_renderer`, you can override the default `severity_to_color` mappings. The custom renderer receives `DiagnosticEntryRef` instances and returns `BlockProperties` with your chosen colors, styles, and interactive elements.

### How do I access the project-wide diagnostics panel programmatically?

Use `ProjectDiagnosticsEditor::deploy` from the `diagnostics` crate, passing the current `Workspace` instance. This opens the panel that aggregates all diagnostics across files, as triggered by the `DiagnosticIndicator` in the status bar when users click the error count display.