Skip to content

LSP Phase 2: Navigation — go-to-definition, hover, document symbols #119

@gregwinn

Description

@gregwinn

Summary

Add navigation features to the Winn LSP server: go-to-definition, hover information, and document symbol outline. Requires building a per-file symbol table from the AST.

Prerequisites

Symbol Table

Build a symbol index per file on parse. Store in ETS keyed by {FilePath, SymbolName}.

Symbol Record

-record(symbol, {
    name     :: atom(),          %% function/variable/module name
    kind     :: module | function | variable | import | alias,
    file     :: string(),
    line     :: integer(),
    col      :: integer(),       %% may need lexer enhancement
    arity    :: integer() | none,%% for functions
    doc      :: binary() | none, %% extracted from # comment above def
    params   :: [atom()] | none  %% parameter names for signature
}).

Symbol Collection

Walk the AST after parsing, extract:

  • Module definitions: {module, Line, Name, Body} → module symbol
  • Function definitions: {function, Line, Name, Params, Body} → function symbol with arity + param names
  • Import directives: {import_directive, Line, ModName} → import symbol
  • Alias directives: {alias_directive, Line, Full, Short} → alias symbol
  • Struct definitions: {struct_def, Line, Fields} → struct symbol with field list
  • Agent definitions: {agent, Line, Name, Body} → module symbol (agent variant)

Doc Comment Extraction

Reuse winn_comment:extract/1 to find # comments. Associate a comment with a function if it appears on the line immediately before def:

# Greets the user by name
def greet(name)
  IO.puts("Hello, #{name}")
end

greet/1 gets doc = "Greets the user by name"

LSP Methods to Implement

textDocument/definition

Call Type Resolution Strategy
Mod.fun() (dot_call) Resolve via winn_codegen:resolve_dot_call/2 mapping → find source file → find function def line
fun() (local call) Search current module's symbol table for matching name/arity
import Mod then fun() Check import list → resolve to module source → find function
Variable Find assignment site in current function scope

Module source resolution:

  1. Check src/ and src/models/, src/controllers/, src/tasks/ for <lowercase_mod>.winn
  2. For stdlib modules (IO, String, etc.) → return nil (no source to jump to)
  3. For aliased modules → expand alias, then search

textDocument/hover

Return markdown hover info:

**greet/1**`def greet(name)`

Greets the user by name

Contents:

  • Function signature (name, params, arity)
  • Doc comment if present
  • Module name for dot_calls
  • For variables: type is unknown (no type system), show assignment location

textDocument/documentSymbol

Return outline of current file for the breadcrumb bar / outline panel:

▸ module HelloWorld
  ▸ def main()
  ▸ def greet(name)
  ▸ import IO

Map to LSP SymbolKind:

  • Module → Module (2)
  • Function → Function (12)
  • Agent → Class (5)
  • Struct → Struct (23)
  • Import → Namespace (3)

New Files

File Purpose ~Lines
apps/winn/src/winn_lsp_symbols.erl Build + query symbol table from AST 200
apps/winn/src/winn_lsp_definition.erl Go-to-definition resolver 150
apps/winn/src/winn_lsp_hover.erl Hover info builder 100
apps/winn/test/winn_lsp_symbols_tests.erl Symbol extraction tests 150

Modified Files

File Change
winn_lsp_handler.erl Add handlers for definition, hover, documentSymbol
winn_lsp.erl Register symbol capabilities in initialize response
winn_lsp_documents.erl Store symbol table alongside document buffer

Acceptance Criteria

  • Ctrl+click on a function call jumps to its definition (same file)
  • Ctrl+click on Mod.fun() jumps to the function in the other file
  • Hover over a function shows signature + doc comment
  • Outline panel shows module structure
  • Stdlib calls (IO.puts, etc.) show hover info but no jump (no source)
  • Works across files in src/, src/models/, src/controllers/

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