Skip to content

Commit 63d8015

Browse files
committed
internal/lsp/cache: minor simplifications to Symbols
Minor cleanups based on studying the code in preparation for saving a persistent index: - Remove unused error result from Symbols method. - Remove unnecessary fields from symbolHandle. - Add various explanatory comments. - In workspace_symbols.go: - separate extract and match phases of collectSymbols clearly - replace symbolCollector and matchWorker types by simple parameters - combine loops (roots, buildMatcher) - move buildMatcher creation down to where it is needed. Change-Id: Ifcad61a9a8c7d70f573024bcfa76d476552ee428 Reviewed-on: https://go-review.googlesource.com/c/tools/+/412822 Reviewed-by: Robert Findley <[email protected]>
1 parent 59bd4fa commit 63d8015

File tree

5 files changed

+88
-120
lines changed

5 files changed

+88
-120
lines changed

internal/lsp/cache/cache.go

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (h *fileHandle) Saved() bool {
6868
return true
6969
}
7070

71+
// GetFile stats and (maybe) reads the file, updates the cache, and returns it.
7172
func (c *Cache) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
7273
return c.getFile(ctx, uri)
7374
}

internal/lsp/cache/snapshot.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -997,9 +997,7 @@ func (s *snapshot) activePackageHandles(ctx context.Context) ([]*packageHandle,
997997
}
998998

999999
// Symbols extracts and returns the symbols for each file in all the snapshot's views.
1000-
func (s *snapshot) Symbols(ctx context.Context) (map[span.URI][]source.Symbol, error) {
1001-
// Keep going on errors, but log the first failure.
1002-
// Partial results are better than no symbol results.
1000+
func (s *snapshot) Symbols(ctx context.Context) map[span.URI][]source.Symbol {
10031001
var (
10041002
group errgroup.Group
10051003
nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
@@ -1023,10 +1021,12 @@ func (s *snapshot) Symbols(ctx context.Context) (map[span.URI][]source.Symbol, e
10231021
return nil
10241022
})
10251023
}
1024+
// Keep going on errors, but log the first failure.
1025+
// Partial results are better than no symbol results.
10261026
if err := group.Wait(); err != nil {
10271027
event.Error(ctx, "getting snapshot symbols", err)
10281028
}
1029-
return result, nil
1029+
return result
10301030
}
10311031

10321032
func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]source.Metadata, error) {
@@ -1137,11 +1137,10 @@ func (s *snapshot) getSymbolHandle(uri span.URI) *symbolHandle {
11371137
return s.symbols[uri]
11381138
}
11391139

1140-
func (s *snapshot) addSymbolHandle(sh *symbolHandle) *symbolHandle {
1140+
func (s *snapshot) addSymbolHandle(uri span.URI, sh *symbolHandle) *symbolHandle {
11411141
s.mu.Lock()
11421142
defer s.mu.Unlock()
11431143

1144-
uri := sh.fh.URI()
11451144
// If the package handle has already been cached,
11461145
// return the cached handle instead of overriding it.
11471146
if sh, ok := s.symbols[uri]; ok {
@@ -1338,7 +1337,7 @@ func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.Versi
13381337
return fh, nil
13391338
}
13401339

1341-
fh, err := s.view.session.cache.getFile(ctx, f.URI())
1340+
fh, err := s.view.session.cache.getFile(ctx, f.URI()) // read the file
13421341
if err != nil {
13431342
return nil, err
13441343
}

internal/lsp/cache/symbols.go

+13-17
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@ import (
1818
"golang.org/x/tools/internal/memoize"
1919
)
2020

21+
// A symbolHandle contains a handle to the result of symbolizing a file.
2122
type symbolHandle struct {
2223
handle *memoize.Handle
23-
24-
fh source.FileHandle
25-
26-
// key is the hashed key for the package.
27-
key symbolHandleKey
2824
}
2925

3026
// symbolData contains the data produced by extracting symbols from a file.
@@ -33,30 +29,30 @@ type symbolData struct {
3329
err error
3430
}
3531

36-
type symbolHandleKey source.Hash
37-
32+
// buildSymbolHandle returns a handle to the result of symbolizing a file,
33+
// if necessary creating it and saving it in the snapshot.
3834
func (s *snapshot) buildSymbolHandle(ctx context.Context, fh source.FileHandle) *symbolHandle {
3935
if h := s.getSymbolHandle(fh.URI()); h != nil {
4036
return h
4137
}
38+
type symbolHandleKey source.Hash
4239
key := symbolHandleKey(fh.FileIdentity().Hash)
43-
h := s.generation.Bind(key, func(_ context.Context, arg memoize.Arg) interface{} {
40+
handle := s.generation.Bind(key, func(_ context.Context, arg memoize.Arg) interface{} {
4441
snapshot := arg.(*snapshot)
45-
data := &symbolData{}
46-
data.symbols, data.err = symbolize(snapshot, fh)
47-
return data
42+
symbols, err := symbolize(snapshot, fh)
43+
return &symbolData{symbols, err}
4844
}, nil)
4945

5046
sh := &symbolHandle{
51-
handle: h,
52-
fh: fh,
53-
key: key,
47+
handle: handle,
5448
}
55-
return s.addSymbolHandle(sh)
49+
50+
return s.addSymbolHandle(fh.URI(), sh)
5651
}
5752

58-
// symbolize extracts symbols from a file. It uses a parsed file already
59-
// present in the cache but otherwise does not populate the cache.
53+
// symbolize reads and parses a file and extracts symbols from it.
54+
// It may use a parsed file already present in the cache but
55+
// otherwise does not populate the cache.
6056
func symbolize(snapshot *snapshot, fh source.FileHandle) ([]source.Symbol, error) {
6157
src, err := fh.Read()
6258
if err != nil {

internal/lsp/source/view.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ type Snapshot interface {
175175
ActivePackages(ctx context.Context) ([]Package, error)
176176

177177
// Symbols returns all symbols in the snapshot.
178-
Symbols(ctx context.Context) (map[span.URI][]Symbol, error)
178+
Symbols(ctx context.Context) map[span.URI][]Symbol
179179

180180
// Metadata returns package metadata associated with the given file URI.
181181
MetadataForFile(ctx context.Context, uri span.URI) ([]Metadata, error)

internal/lsp/source/workspace_symbol.go

+67-95
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,26 @@ const maxSymbols = 100
5050
// with a different configured SymbolMatcher per View. Therefore we assume that
5151
// Session level configuration will define the SymbolMatcher to be used for the
5252
// WorkspaceSymbols method.
53-
func WorkspaceSymbols(ctx context.Context, matcherType SymbolMatcher, style SymbolStyle, views []View, query string) ([]protocol.SymbolInformation, error) {
53+
func WorkspaceSymbols(ctx context.Context, matcher SymbolMatcher, style SymbolStyle, views []View, query string) ([]protocol.SymbolInformation, error) {
5454
ctx, done := event.Start(ctx, "source.WorkspaceSymbols")
5555
defer done()
5656
if query == "" {
5757
return nil, nil
5858
}
59-
sc := newSymbolCollector(matcherType, style, query)
60-
return sc.walk(ctx, views)
59+
60+
var s symbolizer
61+
switch style {
62+
case DynamicSymbols:
63+
s = dynamicSymbolMatch
64+
case FullyQualifiedSymbols:
65+
s = fullyQualifiedSymbolMatch
66+
case PackageQualifiedSymbols:
67+
s = packageSymbolMatch
68+
default:
69+
panic(fmt.Errorf("unknown symbol style: %v", style))
70+
}
71+
72+
return collectSymbols(ctx, views, matcher, s, query)
6173
}
6274

6375
// A matcherFunc returns the index and score of a symbol match.
@@ -136,43 +148,6 @@ func packageSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]strin
136148
return nil, 0
137149
}
138150

139-
// symbolCollector holds context as we walk Packages, gathering symbols that
140-
// match a given query.
141-
//
142-
// How we match symbols is parameterized by two interfaces:
143-
// - A matcherFunc determines how well a string symbol matches a query. It
144-
// returns a non-negative score indicating the quality of the match. A score
145-
// of zero indicates no match.
146-
// - A symbolizer determines how we extract the symbol for an object. This
147-
// enables the 'symbolStyle' configuration option.
148-
type symbolCollector struct {
149-
// These types parameterize the symbol-matching pass.
150-
matchers []matcherFunc
151-
symbolizer symbolizer
152-
153-
symbolStore
154-
}
155-
156-
func newSymbolCollector(matcher SymbolMatcher, style SymbolStyle, query string) *symbolCollector {
157-
var s symbolizer
158-
switch style {
159-
case DynamicSymbols:
160-
s = dynamicSymbolMatch
161-
case FullyQualifiedSymbols:
162-
s = fullyQualifiedSymbolMatch
163-
case PackageQualifiedSymbols:
164-
s = packageSymbolMatch
165-
default:
166-
panic(fmt.Errorf("unknown symbol style: %v", style))
167-
}
168-
sc := &symbolCollector{symbolizer: s}
169-
sc.matchers = make([]matcherFunc, runtime.GOMAXPROCS(-1))
170-
for i := range sc.matchers {
171-
sc.matchers[i] = buildMatcher(matcher, query)
172-
}
173-
return sc
174-
}
175-
176151
func buildMatcher(matcher SymbolMatcher, query string) matcherFunc {
177152
switch matcher {
178153
case SymbolFuzzy:
@@ -302,36 +277,42 @@ func (c comboMatcher) match(chunks []string) (int, float64) {
302277
return first, score
303278
}
304279

305-
func (sc *symbolCollector) walk(ctx context.Context, views []View) ([]protocol.SymbolInformation, error) {
306-
// Use the root view URIs for determining (lexically) whether a uri is in any
307-
// open workspace.
308-
var roots []string
309-
for _, v := range views {
310-
roots = append(roots, strings.TrimRight(string(v.Folder()), "/"))
311-
}
312-
313-
results := make(chan *symbolStore)
314-
matcherlen := len(sc.matchers)
315-
files := make(map[span.URI]symbolFile)
280+
// collectSymbols calls snapshot.Symbols to walk the syntax trees of
281+
// all files in the views' current snapshots, and returns a sorted,
282+
// scored list of symbols that best match the parameters.
283+
//
284+
// How it matches symbols is parameterized by two interfaces:
285+
// - A matcherFunc determines how well a string symbol matches a query. It
286+
// returns a non-negative score indicating the quality of the match. A score
287+
// of zero indicates no match.
288+
// - A symbolizer determines how we extract the symbol for an object. This
289+
// enables the 'symbolStyle' configuration option.
290+
//
291+
func collectSymbols(ctx context.Context, views []View, matcherType SymbolMatcher, symbolizer symbolizer, query string) ([]protocol.SymbolInformation, error) {
316292

293+
// Extract symbols from all files.
294+
var work []symbolFile
295+
var roots []string
296+
seen := make(map[span.URI]bool)
297+
// TODO(adonovan): opt: parallelize this loop? How often is len > 1?
317298
for _, v := range views {
318299
snapshot, release := v.Snapshot(ctx)
319300
defer release()
320-
psyms, err := snapshot.Symbols(ctx)
321-
if err != nil {
322-
return nil, err
323-
}
301+
302+
// Use the root view URIs for determining (lexically)
303+
// whether a URI is in any open workspace.
304+
roots = append(roots, strings.TrimRight(string(v.Folder()), "/"))
324305

325306
filters := v.Options().DirectoryFilters
326307
folder := filepath.ToSlash(v.Folder().Filename())
327-
for uri, syms := range psyms {
308+
for uri, syms := range snapshot.Symbols(ctx) {
328309
norm := filepath.ToSlash(uri.Filename())
329310
nm := strings.TrimPrefix(norm, folder)
330311
if FiltersDisallow(nm, filters) {
331312
continue
332313
}
333314
// Only scan each file once.
334-
if _, ok := files[uri]; ok {
315+
if seen[uri] {
335316
continue
336317
}
337318
mds, err := snapshot.MetadataForFile(ctx, uri)
@@ -343,39 +324,37 @@ func (sc *symbolCollector) walk(ctx context.Context, views []View) ([]protocol.S
343324
// TODO: should use the bug reporting API
344325
continue
345326
}
346-
files[uri] = symbolFile{uri, mds[0], syms}
327+
seen[uri] = true
328+
work = append(work, symbolFile{uri, mds[0], syms})
347329
}
348330
}
349331

350-
var work []symbolFile
351-
for _, f := range files {
352-
work = append(work, f)
353-
}
354-
355-
// Compute matches concurrently. Each symbolWorker has its own symbolStore,
332+
// Match symbols in parallel.
333+
// Each worker has its own symbolStore,
356334
// which we merge at the end.
357-
for i, matcher := range sc.matchers {
358-
go func(i int, matcher matcherFunc) {
359-
w := &symbolWorker{
360-
symbolizer: sc.symbolizer,
361-
matcher: matcher,
362-
ss: &symbolStore{},
363-
roots: roots,
364-
}
365-
for j := i; j < len(work); j += matcherlen {
366-
w.matchFile(work[j])
335+
nmatchers := runtime.GOMAXPROCS(-1) // matching is CPU bound
336+
results := make(chan *symbolStore)
337+
for i := 0; i < nmatchers; i++ {
338+
go func(i int) {
339+
matcher := buildMatcher(matcherType, query)
340+
store := new(symbolStore)
341+
// Assign files to workers in round-robin fashion.
342+
for j := i; j < len(work); j += nmatchers {
343+
matchFile(store, symbolizer, matcher, roots, work[j])
367344
}
368-
results <- w.ss
369-
}(i, matcher)
345+
results <- store
346+
}(i)
370347
}
371348

372-
for i := 0; i < matcherlen; i++ {
373-
ss := <-results
374-
for _, si := range ss.res {
375-
sc.store(si)
349+
// Gather and merge results as they arrive.
350+
var unified symbolStore
351+
for i := 0; i < nmatchers; i++ {
352+
store := <-results
353+
for _, syms := range store.res {
354+
unified.store(syms)
376355
}
377356
}
378-
return sc.results(), nil
357+
return unified.results(), nil
379358
}
380359

381360
// FilterDisallow is code from the body of cache.pathExcludedByFilter in cache/view.go
@@ -407,20 +386,13 @@ type symbolFile struct {
407386
syms []Symbol
408387
}
409388

410-
// symbolWorker matches symbols and captures the highest scoring results.
411-
type symbolWorker struct {
412-
symbolizer symbolizer
413-
matcher matcherFunc
414-
ss *symbolStore
415-
roots []string
416-
}
417-
418-
func (w *symbolWorker) matchFile(i symbolFile) {
389+
// matchFile scans a symbol file and adds matching symbols to the store.
390+
func matchFile(store *symbolStore, symbolizer symbolizer, matcher matcherFunc, roots []string, i symbolFile) {
419391
for _, sym := range i.syms {
420-
symbolParts, score := w.symbolizer(sym.Name, i.md, w.matcher)
392+
symbolParts, score := symbolizer(sym.Name, i.md, matcher)
421393

422394
// Check if the score is too low before applying any downranking.
423-
if w.ss.tooLow(score) {
395+
if store.tooLow(score) {
424396
continue
425397
}
426398

@@ -463,7 +435,7 @@ func (w *symbolWorker) matchFile(i symbolFile) {
463435
}
464436

465437
inWorkspace := false
466-
for _, root := range w.roots {
438+
for _, root := range roots {
467439
if strings.HasPrefix(string(i.uri), root) {
468440
inWorkspace = true
469441
break
@@ -484,7 +456,7 @@ func (w *symbolWorker) matchFile(i symbolFile) {
484456
}
485457
score *= 1.0 - depth*depthFactor
486458

487-
if w.ss.tooLow(score) {
459+
if store.tooLow(score) {
488460
continue
489461
}
490462

@@ -496,7 +468,7 @@ func (w *symbolWorker) matchFile(i symbolFile) {
496468
rng: sym.Range,
497469
container: i.md.PackagePath(),
498470
}
499-
w.ss.store(si)
471+
store.store(si)
500472
}
501473
}
502474

0 commit comments

Comments
 (0)