Skip to content

Commit 88b3687

Browse files
Add LSP DocumentSymbolProvider support (#4096)
1 parent c3207ef commit 88b3687

File tree

3 files changed

+63
-35
lines changed

3 files changed

+63
-35
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## [Unreleased]
44

5-
- No changes yet.
5+
- Add `textDocument/documentSymbol` support for `buf lsp serve`.
66

77
## [v1.59.0] - 2025-10-20
88

private/buf/buflsp/file.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"io"
2525
"io/fs"
26+
"iter"
2627
"log/slog"
2728
"os"
2829
"slices"
@@ -1162,3 +1163,39 @@ func readAllAsString(reader io.Reader) (string, error) {
11621163
}
11631164
return builder.String(), nil
11641165
}
1166+
1167+
// GetSymbols retrieves symbols for the file. If a query is passed, matches only symbols matching
1168+
// the case-insensitive substring match to the symbol.
1169+
//
1170+
// This operation requires [IndexSymbols].
1171+
func (f *file) GetSymbols(query string) iter.Seq[protocol.SymbolInformation] {
1172+
return func(yield func(protocol.SymbolInformation) bool) {
1173+
if f.ir.IsZero() {
1174+
return
1175+
}
1176+
// Search through all symbols in this file.
1177+
for _, sym := range f.symbols {
1178+
if sym.ir.IsZero() {
1179+
continue
1180+
}
1181+
// Only include definitions: static and referenceable symbols.
1182+
// Skip references, imports, builtins, and tags
1183+
_, isStatic := sym.kind.(*static)
1184+
_, isReferenceable := sym.kind.(*referenceable)
1185+
if !isStatic && !isReferenceable {
1186+
continue
1187+
}
1188+
symbolInfo := sym.GetSymbolInformation()
1189+
if symbolInfo.Name == "" {
1190+
continue // Symbol information not supported for this symbol.
1191+
}
1192+
// Filter by query (case-insensitive substring match)
1193+
if query != "" && !strings.Contains(strings.ToLower(symbolInfo.Name), query) {
1194+
continue
1195+
}
1196+
if !yield(symbolInfo) {
1197+
return
1198+
}
1199+
}
1200+
}
1201+
}

private/buf/buflsp/server.go

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"errors"
2020
"fmt"
2121
"runtime/debug"
22-
"slices"
2322
"strings"
2423
"unicode/utf16"
2524

@@ -32,6 +31,8 @@ import (
3231

3332
const (
3433
serverName = "buf-lsp"
34+
35+
maxSymbolResults = 1000
3536
)
3637

3738
const (
@@ -141,6 +142,7 @@ func (s *server) Initialize(
141142
Full: true,
142143
},
143144
WorkspaceSymbolProvider: true,
145+
DocumentSymbolProvider: true,
144146
},
145147
ServerInfo: info,
146148
}, nil
@@ -529,45 +531,34 @@ func (s *server) Symbols(
529531
ctx context.Context,
530532
params *protocol.WorkspaceSymbolParams,
531533
) ([]protocol.SymbolInformation, error) {
532-
const maxResults = 1000 // Limit results to avoid overwhelming clients
533534
query := strings.ToLower(params.Query)
534-
535535
var results []protocol.SymbolInformation
536536
for _, file := range s.fileManager.uriToFile.Range {
537-
if file.ir.IsZero() {
538-
continue
539-
}
540-
// Search through all symbols in this file.
541-
for _, sym := range file.symbols {
542-
if sym.ir.IsZero() {
543-
continue
544-
}
545-
// Only include definitions: static and referenceable symbols.
546-
// Skip references, imports, builtins, and tags
547-
_, isStatic := sym.kind.(*static)
548-
_, isReferenceable := sym.kind.(*referenceable)
549-
if !isStatic && !isReferenceable {
550-
continue
551-
}
552-
symbolInfo := sym.GetSymbolInformation()
553-
if symbolInfo.Name == "" {
554-
continue // Symbol information not supported for this symbol.
555-
}
556-
// Filter by query (case-insensitive substring match)
557-
if query != "" && !strings.Contains(strings.ToLower(symbolInfo.Name), query) {
558-
continue
559-
}
560-
results = append(results, symbolInfo)
561-
if len(results) >= maxResults {
537+
for symbol := range file.GetSymbols(query) {
538+
results = append(results, symbol)
539+
if len(results) > maxSymbolResults {
562540
break
563541
}
564542
}
565543
}
566-
slices.SortFunc(results, func(a, b protocol.SymbolInformation) int {
567-
if a.Name != b.Name {
568-
return strings.Compare(a.Name, b.Name)
569-
}
570-
return strings.Compare(a.ContainerName, b.ContainerName)
571-
})
572544
return results, nil
573545
}
546+
547+
// DocumentSymbol is the entry point for document symbol search.
548+
func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) (
549+
result []any, // []protocol.SymbolInformation
550+
err error,
551+
) {
552+
file := s.fileManager.Get(params.TextDocument.URI)
553+
if file == nil {
554+
return nil, nil
555+
}
556+
anyResults := []any{}
557+
for symbol := range file.GetSymbols("") {
558+
anyResults = append(anyResults, symbol)
559+
if len(anyResults) > maxSymbolResults {
560+
break
561+
}
562+
}
563+
return anyResults, nil
564+
}

0 commit comments

Comments
 (0)