# How the Sway LSP Provides IDE Support and Error Highlighting: A Deep Dive into the Language Server Protocol Implementation

> Discover how Sway LSP offers IDE support and error highlighting. It powers real-time compiler diagnostics for editors, displaying errors, warnings, and hints.

- Repository: [Fuel Labs/sway](https://github.com/FuelLabs/sway)
- Tags: deep-dive
- Published: 2026-03-04

---

**The Sway LSP transforms the compiler into a real-time IDE backend by continuously compiling workspace files and translating compiler diagnostics into LSP Diagnostic messages that editors display as error highlighting, warnings, and inline hints.**

The Sway Language Server Protocol (LSP) implementation in the [FuelLabs/sway](https://github.com/FuelLabs/sway) repository provides comprehensive IDE support and error highlighting by bridging the Sway compiler with editor clients. This architecture enables features like real-time diagnostics, code completion, and semantic highlighting by continuously analyzing source code in the background.

## Architecture Overview: From Compiler to LSP Backend

The Sway LSP operates as a JSON-RPC server that exposes compiler functionality through standardized LSP capabilities. The architecture separates capability declaration, state management, and compilation orchestration into distinct modules.

### Server Capabilities Declaration in [`lib.rs`](https://github.com/FuelLabs/sway/blob/main/lib.rs)

The entry point in [`sway-lsp/src/lib.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/lib.rs) defines every IDE feature the server supports. The `server_capabilities()` function returns a `ServerCapabilities` struct that tells the client which operations are available, including diagnostics, completion, hover, and semantic tokens.

```rust
pub fn server_capabilities() -> ServerCapabilities {
    ServerCapabilities {
        code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
        completion_provider: Some(CompletionOptions {
            trigger_characters: Some(vec![".".to_string()]),
            ..Default::default()
        }),
        definition_provider: Some(OneOf::Left(true)),
        document_highlight_provider: Some(OneOf::Left(true)),
        document_symbol_provider: Some(OneOf::Left(true)),
        hover_provider: Some(HoverProviderCapability::Simple(true)),
        inlay_hint_provider: Some(OneOf::Left(true)),
        rename_provider: Some(OneOf::Right(RenameOptions { … })),
        semantic_tokens_provider: Some(
            SemanticTokensOptions {
                legend: SemanticTokensLegend {
                    token_types: capabilities::semantic_tokens::SUPPORTED_TYPES.to_vec(),
                    token_modifiers: capabilities::semantic_tokens::SUPPORTED_MODIFIERS.to_vec(),
                },
                range: Some(true),
                ..Default::default()
            }
            .into(),
        ),
        text_document_sync: Some(TextDocumentSyncCapability::Kind(
            TextDocumentSyncKind::INCREMENTAL,
        )),
        …
    }
}

```

The `start()` function in the same file wires this service to `stdin` and `stdout`, creating the communication channel that editors use to send file contents and receive error highlighting.

### State Management and Session Handling in [`server_state.rs`](https://github.com/FuelLabs/sway/blob/main/server_state.rs)

[`sway-lsp/src/server_state.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/server_state.rs) contains the `ServerState` struct, which acts as the central coordinator. It manages:

- **Workspace synchronization** – Temporary workspaces that mirror the user's project structure
- **Session storage** – Per-project `Session` objects that cache diagnostics and compilation results
- **Background compilation** – A dedicated thread that runs the compiler without blocking the LSP main loop
- **Token mapping** – The `TokenMap` that connects source ranges to semantic information for features like hover and go-to-definition

## How Error Highlighting Works in the Sway LSP

Error highlighting in the Sway LSP follows a continuous compilation pipeline. When a developer opens or edits a file, the server triggers a background compilation, converts compiler diagnostics into LSP format, and pushes them to the editor.

### Triggering Compilation on File Open in [`notification.rs`](https://github.com/FuelLabs/sway/blob/main/notification.rs)

When the client sends a `textDocument/didOpen` notification, the handler in [`sway-lsp/src/handlers/notification.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/handlers/notification.rs) executes a six-step workflow:

1. **Initialize workspace** – `state.get_or_init_sync_workspace` creates a temporary workspace mirroring the project
2. **Retrieve session** – `state.uri_and_session_from_workspace` gets or creates a `Session` to store diagnostics
3. **Track document** – `state.documents.handle_open_file` records the file for token mapping
4. **Queue compilation** – `send_new_compilation_request` builds a `CompilationContext` and pushes it to the background thread
5. **Await parsing** – `state.wait_for_parsing()` blocks until compilation completes
6. **Publish diagnostics** – `state.publish_diagnostics` forwards results to the client

```rust
pub async fn handle_did_open_text_document(
    state: &ServerState,
    params: DidOpenTextDocumentParams,
) -> Result<(), LanguageServerError> {
    let file_uri = &params.text_document.uri;
    let sync_workspace = state.get_or_init_sync_workspace(file_uri).await?;
    let (uri, session) = state.uri_and_session_from_workspace(&params.text_document.uri)?;
    state.documents.handle_open_file(&uri).await;

    send_new_compilation_request(state, session.clone(), &uri, None, false, sync_workspace);
    state.is_compiling.store(true, Ordering::SeqCst);
    state.wait_for_parsing().await;
    state
        .publish_diagnostics(uri, params.text_document.uri, session)
        .await;

    Ok(())
}

```

### Background Compilation and Token Map Construction

The compilation thread in `ServerState::spawn_compilation_thread` receives the `CompilationContext`, runs `session::compile`, and then traverses the resulting ASTs to build a **TokenMap**.

Three traversal passes populate the map with different levels of semantic information:

- **Lexed pass** ([`traverse/lexed_tree.rs`](https://github.com/FuelLabs/sway/blob/main/traverse/lexed_tree.rs)) – Raw lexer tokens including keywords and literals
- **Parsed pass** ([`traverse/parsed_tree.rs`](https://github.com/FuelLabs/sway/blob/main/traverse/parsed_tree.rs)) – Untyped parser nodes representing syntax structure
- **Typed pass** ([`traverse/typed_tree.rs`](https://github.com/FuelLabs/sway/blob/main/traverse/typed_tree.rs)) – Fully typed AST nodes with semantic information

This TokenMap enables precise error positioning and powers features like hover tooltips and go-to-definition.

### Converting Compiler Diagnostics to LSP Format in [`diagnostic.rs`](https://github.com/FuelLabs/sway/blob/main/diagnostic.rs)

After compilation, [`sway-lsp/src/capabilities/diagnostic.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/capabilities/diagnostic.rs) transforms compiler diagnostics into LSP `Diagnostic` objects. The `get_diagnostics` function processes three severity levels:

- **Errors** – Critical compilation failures
- **Warnings** – Non-fatal issues that should be addressed
- **Info** – Additional context and suggestions

```rust
pub fn get_diagnostics(
    infos: &[CompileInfo],
    warnings: &[CompileWarning],
    errors: &[CompileError],
    source_engine: &SourceEngine,
) -> DiagnosticMap {
    // … iterate over infos, warnings, errors
    //   create Diagnostic with range, severity, message, optional data
}

```

The function also attaches **DiagnosticData** containing additional context (such as unknown symbol names) that code-action providers use to generate auto-import suggestions.

### Publishing Diagnostics to the Editor

`ServerState::publish_diagnostics` retrieves per-file diagnostics from the session and sends them via the LSP client connection:

```rust
pub(crate) async fn publish_diagnostics(
    &self,
    uri: Url,
    workspace_uri: Url,
    session: Arc<Session>,
) {
    let diagnostics = self.diagnostics(&uri, session.clone());
    if let Some(client) = self.client.as_ref() {
        client
            .publish_diagnostics(workspace_uri.clone(), diagnostics, None)
            .await;
    }
}

```

When the client receives this `textDocument/publishDiagnostics` notification, it renders the familiar IDE experience: red squiggly underlines for errors, yellow for warnings, and informational tooltips on hover.

## Incremental Updates and Real-Time Synchronization

The Sway LSP maintains responsiveness through incremental synchronization. When a user types, the client sends `textDocument/didChange` notifications containing only the modified ranges, not the entire file.

The handlers in [`notification.rs`](https://github.com/FuelLabs/sway/blob/main/notification.rs) process these updates through the same pipeline as `didOpen`:

1. Apply changes to the temporary workspace
2. Queue a new compilation request (dropping stale requests if the channel is full)
3. Wait for the background thread to finish
4. Publish updated diagnostics

This ensures that error highlighting updates in real-time as developers write code, without blocking the editor's UI thread.

## Powering Other IDE Features with the Token Map

While error highlighting relies on compiler diagnostics, other IDE features leverage the **TokenMap** built during AST traversal:

- **Completion** ([`capabilities/completion.rs`](https://github.com/FuelLabs/sway/blob/main/capabilities/completion.rs)) – Suggests methods and fields based on typed AST nodes
- **Hover** ([`capabilities/hover/mod.rs`](https://github.com/FuelLabs/sway/blob/main/capabilities/hover/mod.rs)) – Displays type information and documentation by looking up tokens in the map
- **Go-to-Definition** – Uses the token map to resolve identifiers to their declaration sites
- **Semantic Tokens** ([`capabilities/semantic_tokens.rs`](https://github.com/FuelLabs/sway/blob/main/capabilities/semantic_tokens.rs)) – Provides syntax highlighting by iterating over the token map
- **Inlay Hints** – Displays inferred types and parameter names using semantic information from the typed AST

All these features share the same compilation pipeline: the background thread populates the TokenMap, and subsequent LSP requests query this map without recompiling.

## Summary

- The Sway LSP exposes IDE support and error highlighting by implementing the Language Server Protocol in [`sway-lsp/src/lib.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/lib.rs), declaring capabilities like diagnostics, completion, and semantic tokens.
- Error highlighting triggers on file open and change events via `handle_did_open_text_document` in [`notification.rs`](https://github.com/FuelLabs/sway/blob/main/notification.rs), which initializes workspaces and queues compilation requests.
- Background compilation in [`server_state.rs`](https://github.com/FuelLabs/sway/blob/main/server_state.rs) runs the Sway compiler and traverses ASTs to build a TokenMap used for precise error positioning and IDE features.
- The `get_diagnostics` function in [`diagnostic.rs`](https://github.com/FuelLabs/sway/blob/main/diagnostic.rs) converts compiler errors, warnings, and info messages into LSP Diagnostic objects with severity levels and additional data for code actions.
- `publish_diagnostics` sends real-time error highlighting to editors, while incremental sync ensures responsiveness during editing.

## Frequently Asked Questions

### How does the Sway LSP handle compilation errors in real-time?

The Sway LSP handles real-time error highlighting by maintaining a background compilation thread that continuously analyzes the workspace. When a file opens or changes, the `handle_did_open_text_document` function in [`sway-lsp/src/handlers/notification.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/handlers/notification.rs) queues a new compilation request, waits for completion via `state.wait_for_parsing()`, and then calls `publish_diagnostics` to push errors to the editor. This pipeline ensures diagnostics update as you type without freezing the IDE.

### What is the TokenMap and why is it important for IDE support?

The TokenMap is a core data structure built during AST traversal that maps source code ranges to semantic tokens. During background compilation, the server runs three traversal passes—lexed, parsed, and typed—implemented in [`sway-lsp/src/traverse/lexed_tree.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/traverse/lexed_tree.rs), [`parsed_tree.rs`](https://github.com/FuelLabs/sway/blob/main/parsed_tree.rs), and [`typed_tree.rs`](https://github.com/FuelLabs/sway/blob/main/typed_tree.rs). The resulting TokenMap powers error positioning, hover tooltips, go-to-definition, completion suggestions, and semantic highlighting by providing fast lookups of type and symbol information without recompiling.

### How are compiler diagnostics converted to LSP format?

Compiler diagnostics are transformed into LSP-compatible structures in [`sway-lsp/src/capabilities/diagnostic.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/capabilities/diagnostic.rs). The `get_diagnostics` function accepts `CompileInfo`, `CompileWarning`, and `CompileError` vectors from the compiler, then constructs LSP `Diagnostic` objects with appropriate severity levels (Error, Warning, Information). It also attaches `DiagnosticData` containing metadata like unknown symbol names, which enables code-action providers to suggest auto-imports or fixes.

### What happens when I save a file in a Sway project?

When you save a file, the LSP client sends a `textDocument/didSave` notification handled in [`sway-lsp/src/handlers/notification.rs`](https://github.com/FuelLabs/sway/blob/main/sway-lsp/src/handlers/notification.rs). This triggers the same compilation pipeline as opening a file: the server updates the temporary workspace, queues a compilation request on the background thread, waits for parsing to complete, and publishes fresh diagnostics. If the channel already contains pending compilation requests, stale ones are dropped to ensure only the latest saved state gets analyzed, maintaining performance in large projects.