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:
- Check
src/ and src/models/, src/controllers/, src/tasks/ for <lowercase_mod>.winn
- For stdlib modules (IO, String, etc.) → return
nil (no source to jump to)
- 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
Part of v0.9.0 — Developer Tooling
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
Symbol Collection
Walk the AST after parsing, extract:
{module, Line, Name, Body}→ module symbol{function, Line, Name, Params, Body}→ function symbol with arity + param names{import_directive, Line, ModName}→ import symbol{alias_directive, Line, Full, Short}→ alias symbol{struct_def, Line, Fields}→ struct symbol with field list{agent, Line, Name, Body}→ module symbol (agent variant)Doc Comment Extraction
Reuse
winn_comment:extract/1to find#comments. Associate a comment with a function if it appears on the line immediately beforedef:→
greet/1gets doc ="Greets the user by name"LSP Methods to Implement
textDocument/definitionMod.fun()(dot_call)winn_codegen:resolve_dot_call/2mapping → find source file → find function def linefun()(local call)import Modthenfun()Module source resolution:
src/andsrc/models/,src/controllers/,src/tasks/for<lowercase_mod>.winnnil(no source to jump to)textDocument/hoverReturn markdown hover info:
Contents:
textDocument/documentSymbolReturn outline of current file for the breadcrumb bar / outline panel:
Map to LSP
SymbolKind:Module(2)Function(12)Class(5)Struct(23)Namespace(3)New Files
apps/winn/src/winn_lsp_symbols.erlapps/winn/src/winn_lsp_definition.erlapps/winn/src/winn_lsp_hover.erlapps/winn/test/winn_lsp_symbols_tests.erlModified Files
winn_lsp_handler.erlwinn_lsp.erlinitializeresponsewinn_lsp_documents.erlAcceptance Criteria
Mod.fun()jumps to the function in the other filesrc/,src/models/,src/controllers/Part of v0.9.0 — Developer Tooling