feat: add Outline sidebar panel with LSP document symbols#321
Conversation
Adds an Outline tab to the sidebar showing the symbol tree of the active file (functions, methods, types with fields, markdown headings), with click/enter to jump. - LSP textDocument/documentSymbol support: hierarchical DocumentSymbol and flat SymbolInformation response shapes, hierarchical capability advertised at initialize - Selection reveals the symbol in the editor without stealing focus; enter/click on a leaf jumps and focuses the editor; outline selection follows the editor cursor (nearest enclosing symbol) - Built-in fallbacks when no server can answer: markdown ATX headings and a go/parser-based Go outline (covers vendored files, module-less files, and gopls 'no views' failures); fallback renders immediately as provisional and is replaced by the server response - Diagnostic empty states: server not installed, no server for language, loading, and the actual server error text - didOpen is now sent for tabs that become active without passing through OnFileOpen (files opened as CLI args), fixing clangd errors for those tabs; documentSymbol retries once to absorb cold starts - Single-file launches root the server at the file's directory instead of an empty rootURI - Refresh on panel activation, tab switch, save, and external reload Unit tests for protocol parsing and both fallback parsers, e2e tests for rendering/navigation/jump/empty states, functional tests for the real binary.
There was a problem hiding this comment.
Pull request overview
Adds an Outline sidebar panel that shows document symbols for the active file, integrating LSP textDocument/documentSymbol with built-in fallbacks (Markdown headings + Go parser) and navigation behaviors (reveal on selection, jump on activate, cursor-follow selection). This fits into the app’s sidebar/navigation UX alongside Explore/Find/Changes.
Changes:
- Add Outline panel UI + commands/menus and wire refresh triggers (panel activation, tab switch, save, external reload).
- Add LSP protocol + client support for
textDocument/documentSymbol, handling bothDocumentSymbol[]andSymbolInformation[]. - Add Markdown/Go symbol fallbacks plus unit, functional, and e2e tests for outline behavior.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/functional/outline.test.js | Functional coverage for markdown outline rendering, nesting, jumping, and empty state. |
| tests/e2e/outline_test.go | E2E coverage for fallback rendering, keyboard/mouse navigation, focus handoff, and cursor-follow selection. |
| internal/lsp/protocol.go | Adds document symbol capability + symbol/documentSymbol protocol types. |
| internal/lsp/client.go | Implements DocumentSymbols request and parsing for both response shapes. |
| internal/lsp/client_test.go | Unit tests for parsing hierarchical/flat/empty documentSymbol responses. |
| internal/app/widgets.go | Adds Outline panel to sidebar and stores it on App. |
| internal/app/watch.go | Refreshes symbols after external file reload. |
| internal/app/symbols_panel.go | New Outline panel widget + Markdown fallback symbol extraction. |
| internal/app/symbols_panel_test.go | Unit tests for markdown parsing, node building, and nearest-selection logic. |
| internal/app/symbols_go.go | New Go fallback outline via go/parser + AST walking. |
| internal/app/symbols_go_test.go | Unit tests for Go fallback symbols and partial-parse tolerance. |
| internal/app/menus.go | Adds Outline entry to the View menu. |
| internal/app/lsp_convert.go | Adds SymbolsResult event payload type. |
| internal/app/eventloop.go | Wires outline refresh on active-file changes and selection follow on cursor moves. |
| internal/app/commands_view.go | Registers sidebar.outline (“Show Outline”) command. |
| internal/app/commands_editor.go | Refreshes symbols on save. |
| internal/app/callbacks.go | Adds outline “Refresh” more-menu item and reveal/jump callbacks. |
| internal/app/app.go | Adds Symbols panel field to App. |
| internal/app/app_lsp.go | Adds symbol refresh/request pipeline, didOpen healing, and single-file root workdir fallback. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Show the built-in outline immediately when available; the server | ||
| // response replaces it. Otherwise indicate that a request is running. | ||
| if len(fallback) > 0 { | ||
| a.ApplySymbols(fallback) | ||
| } else if a.Symbols.Tree.ItemCount() == 0 { | ||
| a.Symbols.SetStatus("Loading symbols…") | ||
| } |
There was a problem hiding this comment.
Addressed in cffcdf2: the loading state now replaces the outline whenever the request targets a different file than the displayed content; same-file refreshes keep the current outline to avoid flicker.
| inFence := false | ||
| for i, line := range lines { | ||
| trimmed := strings.TrimSpace(line) | ||
| if strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~") { | ||
| inFence = !inFence | ||
| continue | ||
| } |
There was a problem hiding this comment.
Addressed in cffcdf2: fences now track their opening marker character, so ~~~ no longer closes a ``` block; added a regression test.
- Clear stale outline when switching files: show the loading state whenever the request is for a different file than the displayed content; same-file refreshes keep the current outline to avoid flicker. - Markdown fences now only close on the marker character that opened them, so a ~~~ line no longer terminates a ``` block and headings inside code blocks stay out of the outline.
# Conflicts: # internal/app/eventloop.go
Summary
Adds an Outline tab to the sidebar (after Changes) showing the symbol tree of the active file — functions, methods, types with fields, markdown sections — with click/enter to jump to the symbol.
textDocument/documentSymbolsupport ininternal/lsp: handles both hierarchicalDocumentSymbol[]and flatSymbolInformation[]response shapes (flat is converted and position-sorted); advertiseshierarchicalDocumentSymbolSupport.go/parser-based Go outline (functions, methods with receivers, structs/interfaces with members, consts/vars). Covers vendored files and module-less files where gopls answersno views, and machines without gopls entirely. The fallback renders immediately as a provisional outline and is replaced by the server response when it arrives.OnFileOpennow getdidOpensent (fixes clangd-32602: trying to get AST for non-added documenton those tabs, and heals hover/completion/diagnostics for them too).documentSymbolretries once to absorb server cold starts.ttt file.go) now root the language server at the file's directory instead of initializing with an emptyrootURI.Show Outline, View menu entry. No keybinding (ctrl+k ois taken); palette/menu only.Testing
tests/e2e/outline_test.go): fallback rendering, keyboard navigation + jump + focus handoff, click jump, empty state, cursor-follow.tests/functional/outline.test.js): headings render, activate jumps and focuses (verified by typing a marker), nesting indentation, empty state.