Skip to content

LSP Phase 1: Diagnostics — compile errors + lint warnings in editor #118

@gregwinn

Description

@gregwinn

Summary

Build a minimal LSP server that pushes compiler diagnostics and lint warnings to the editor in real-time. This is the foundation that Phase 2 (navigation) and Phase 3 (completion) build on.

Architecture

VS Code extension (language-winn-vscode)
  │ stdin/stdout JSON-RPC
  ▼
winn_lsp.erl (gen_server)
  ├── winn_lsp_transport.erl    # JSON-RPC framing over stdio
  ├── winn_lsp_handler.erl      # LSP method dispatch
  ├── winn_lsp_documents.erl    # In-memory document buffer (ETS)
  └── winn_lsp_diagnostic.erl   # #diag → LSP Diagnostic conversion
          │
          ▼
    Existing compiler pipeline
    (winn_lexer → winn_parser → winn_semantic → winn_lint)

LSP Methods to Implement

Method Direction Description
initialize request Return server capabilities
initialized notification No-op acknowledgment
shutdown / exit request/notif Clean shutdown
textDocument/didOpen notification Store document, run diagnostics
textDocument/didChange notification Update buffer, re-run diagnostics
textDocument/didSave notification Re-run diagnostics
textDocument/didClose notification Remove from buffer
textDocument/publishDiagnostics server→client Push diagnostics

Diagnostic Pipeline

On every didOpen/didChange/didSave:

1. Get source from document buffer
2. winn_lexer:string(Source) → lex errors OR tokens
3. winn_newline_filter:filter(Tokens)
4. winn_parser:parse(Filtered) → parse errors OR AST
5. winn_lint:check_string(Source) → lint warnings
6. Convert all #diag records → LSP Diagnostic JSON
7. Publish via textDocument/publishDiagnostics

If lexing/parsing fails, still publish those errors (don't require a valid AST for diagnostics).

#diag → LSP Diagnostic Mapping

%% Winn #diag record:
#diag{severity, title, file, line, col, span_len, message, hint}

%% LSP Diagnostic:
#{
  <<"range">> => #{
    <<"start">> => #{<<"line">> => Line - 1, <<"character">> => Col - 1},
    <<"end">>   => #{<<"line">> => Line - 1, <<"character">> => Col - 1 + SpanLen}
  },
  <<"severity">> => 1 (error) | 2 (warning),
  <<"source">>   => <<"winn">>,
  <<"message">>  => Message,
  <<"code">>     => RuleName  %% for lint rules
}

New Files

File Purpose ~Lines
apps/winn/src/winn_lsp.erl gen_server entry point, process lifecycle 80
apps/winn/src/winn_lsp_transport.erl JSON-RPC read/write over stdio 120
apps/winn/src/winn_lsp_handler.erl Method dispatch + request handlers 150
apps/winn/src/winn_lsp_documents.erl ETS-backed document buffer 60
apps/winn/src/winn_lsp_diagnostic.erl #diag → LSP conversion 80
apps/winn/test/winn_lsp_tests.erl Unit tests for handler + diagnostics 150

CLI Integration

winn lsp        # Start LSP server on stdio (for editor use)

Add parse_args(["lsp"]) -> lsp and handler in winn_cli.erl.

VS Code Extension Changes

Update language-winn-vscode:

  • Add vscode-languageclient dependency
  • Configure LanguageClient to spawn winn lsp via stdio
  • Remove existing compile-on-save shell spawning (LSP replaces it)

Acceptance Criteria

  • winn lsp starts and responds to initialize
  • Syntax errors appear as red squiggles on save
  • Lint warnings appear as yellow squiggles on save
  • Fixing an error clears the diagnostic
  • Opening/closing files doesn't leak memory
  • Clean shutdown on editor close

Part of v0.9.0 — Developer Tooling

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions