# DeepSeek TUI LSP Subsystem: Inline Diagnostics After File Edits

> DeepSeek TUI's LSP subsystem provides instant inline diagnostics after file edits. Get compiler-style feedback by querying language servers and receiving compact diagnostic blocks.

- Repository: [Hunter Bown/DeepSeek-TUI](https://github.com/Hmbown/DeepSeek-TUI)
- Tags: internals
- Published: 2026-05-04

---

**DeepSeek TUI automatically injects compiler-style diagnostics into the LLM context immediately after file edits by extracting paths from tool calls, querying language servers via JSON-RPC, and rendering compact `<diagnostics>` blocks before the next model request.**

The Hmbown/DeepSeek-TUI repository implements a sophisticated LSP subsystem that bridges autonomous code editing with real-time error detection. When the AI agent modifies files using tools like `edit_file` or `write_file`, the engine immediately queries language-specific LSP servers and surfaces diagnostics inline. This architecture enables the LLM to reason about compilation errors and warnings without manual inspection, keeping the agent contextually aware of code quality.

## How the LSP Subsystem Captures File Edits

The engine identifies modified files by inspecting tool outputs rather than monitoring the filesystem. This approach ensures diagnostics are always synchronized with the agent's intent and avoids race conditions with disk I/O.

### Extracting Edited Paths from Tool Calls

In [`crates/tui/src/core/engine/lsp_hooks.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/core/engine/lsp_hooks.rs) (lines 12-46), the function `edited_paths_for_tool` examines tool names and inputs to determine which files changed. It handles `edit_file`, `write_file`, and `apply_patch` tools, extracting absolute or workspace-relative paths for downstream LSP queries.

```rust
// Inside lsp_hooks.rs
let paths = edited_paths_for_tool(tool_name, tool_input);
for path in paths {
    let absolute = if path.is_absolute() {
        path.clone()
    } else {
        self.session.workspace.join(&path)
    };
    if let Some(block) = self.lsp_manager.diagnostics_for(&absolute, seq).await {
        self.pending_lsp_blocks.push(block);
    }
}

```

## The LspManager and Transport Layer

The `LspManager` defined in [`crates/tui/src/lsp/mod.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/mod.rs) (lines 1-33) provides a lazy, per-language abstraction over LSP server connections. It maintains transport instances, handles configuration from the `[lsp]` table in `~/.deepseek/config.toml`, and manages server lifecycle to prevent resource waste.

### StdioLspTransport and JSON-RPC Communication

In [`crates/tui/src/lsp/client.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/client.rs) (lines 1-30), the `StdioLspTransport` struct implements the `LspTransport` trait to communicate with language servers over standard I/O. The transport sends `didOpen` and `didChange` notifications, waits for the configured `poll_after_edit_ms` duration, and parses `publishDiagnostics` notifications into structured data.

```rust
// 1. Agent issues an edit_file tool call
let edit_result = tool::edit_file("src/main.rs", new_contents).await;

// 2. Engine runs the post-edit hook automatically
engine.run_post_edit_lsp_hook("edit_file", &json!({ "path": "src/main.rs" })).await;

// 3. Before the next model call the engine flushes diagnostics
engine.flush_pending_lsp_diagnostics().await;

// 4. The LLM now receives a user message containing:
// <diagnostics file="src/main.rs">
//   ERROR [23:5] unexpected token `}`
// </diagnostics>

```

## Rendering Diagnostics for LLM Context

Raw LSP diagnostics undergo normalization before entering the conversation context to ensure the LLM receives structured, token-efficient feedback.

### Normalizing LSP Output to DiagnosticBlock

The `DiagnosticBlock` struct in [`crates/tui/src/lsp/diagnostics.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/diagnostics.rs) (lines 1-110) aggregates multiple `Diagnostic` entries into a renderable XML-like format. Errors are always included, while warnings depend on the `include_warnings` configuration flag. The system truncates output to `max_diagnostics_per_file` to prevent context window overflow.

```rust
let block = DiagnosticBlock {
    file: PathBuf::from("src/main.rs"),
    items: vec![
        Diagnostic {
            line: 12,
            column: 8,
            severity: Severity::Error,
            message: "missing semicolon".into(),
        },
    ],
};
assert_eq!(block.render(),
    "<diagnostics file=\"src/main.rs\">\n  ERROR [12:8] missing semicolon\n</diagnostics>");

```

## Injecting Diagnostics into the Conversation

The engine uses a two-phase process to make diagnostics available to the LLM without blocking the agent's execution flow.

### The Post-Edit Hook

After every successful edit, `run_post_edit_lsp_hook` in [`crates/tui/src/core/engine/lsp_hooks.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/core/engine/lsp_hooks.rs) (lines 73-101) iterates over edited paths and queues diagnostic blocks in `pending_lsp_blocks`. This hook runs immediately after tool execution, ensuring minimal latency between file modification and error detection.

### Flushing Pending Diagnostics

Before the next OpenAI-compatible request, `flush_pending_lsp_diagnostics` (lines 105-127) converts queued blocks into synthetic user messages. According to the DeepSeek-TUI source code, this method injects all pending `<diagnostics>` blocks into the session, making compilation errors and warnings part of the model's immediate reasoning context.

## Configuration and Best-Effort Design

The LSP subsystem operates on a **best-effort** basis: missing binaries, crashes, or timeouts are logged but never block the agent. This design keeps the automated editing pipeline resilient while providing enhanced context when language servers are available.

Configuration options in [`crates/tui/src/config.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/config.rs) control enable/disable states, timeouts, and diagnostic limits via the `[lsp]` table in `~/.deepseek/config.toml`. Users can tune `poll_after_edit_ms` for slower language servers or reduce `max_diagnostics_per_file` to conserve context window space.

## Summary

- **Path extraction** happens via `edited_paths_for_tool` in [`crates/tui/src/core/engine/lsp_hooks.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/core/engine/lsp_hooks.rs) for `edit_file`, `write_file`, and `apply_patch` tools
- **LspManager** in [`crates/tui/src/lsp/mod.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/mod.rs) provides lazy, per-language server management with configurable timeouts
- **StdioLpsTransport** in [`crates/tui/src/lsp/client.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/client.rs) handles JSON-RPC over stdio, sending `didChange` notifications and parsing `publishDiagnostics`
- **Diagnostic rendering** uses `DiagnosticBlock` in [`crates/tui/src/lsp/diagnostics.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/diagnostics.rs) to produce compact XML-like blocks with severity filtering
- **Injection workflow** queues diagnostics via `run_post_edit_lsp_hook` and flushes them via `flush_pending_lsp_diagnostics` before the next model request
- **Best-effort architecture** ensures LSP failures are logged but never block the agent's editing workflow

## Frequently Asked Questions

### How does DeepSeek TUI know which files to check for diagnostics after an edit?

The system extracts file paths from tool outputs using `edited_paths_for_tool` in [`crates/tui/src/core/engine/lsp_hooks.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/core/engine/lsp_hooks.rs). It recognizes `edit_file`, `write_file`, and `apply_patch` tools, resolving relative paths against the workspace root to produce absolute paths for LSP queries. This ensures the diagnostics request always targets the correct file regardless of whether the tool used absolute or relative paths.

### What happens if the LSP server crashes or times out?

The subsystem follows a **best-effort** design philosophy implemented across [`crates/tui/src/lsp/mod.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/mod.rs) and [`crates/tui/src/core/engine/lsp_hooks.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/core/engine/lsp_hooks.rs). Timeouts, missing binaries, and server crashes are logged as warnings but never block the agent's execution. The engine continues without injecting diagnostics for that specific file, maintaining workflow continuity.

### Can I configure which diagnostics appear in the LLM context?

Yes. The `[lsp]` table in `~/.deepseek/config.toml` controls behavior through `LspConfig` defined in [`crates/tui/src/config.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/config.rs). You can enable or disable warnings with `include_warnings`, set `max_diagnostics_per_file` limits to prevent context overflow, and configure `poll_after_edit_ms` timeouts to accommodate slower language servers.

### How are diagnostics formatted when injected into the conversation?

Diagnostics render as compact XML-like blocks using the `render()` method on `DiagnosticBlock` in [`crates/tui/src/lsp/diagnostics.rs`](https://github.com/Hmbown/DeepSeek-TUI/blob/main/crates/tui/src/lsp/diagnostics.rs). Each block includes the file path, line numbers, severity levels, and messages in a structured format the LLM can parse, such as `ERROR [12:8] missing semicolon`. The system wraps these in `<diagnostics file="path">` tags to distinguish them from user input.