@@ -50,14 +50,26 @@ const maxSymbols = 100
50
50
// with a different configured SymbolMatcher per View. Therefore we assume that
51
51
// Session level configuration will define the SymbolMatcher to be used for the
52
52
// 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 ) {
54
54
ctx , done := event .Start (ctx , "source.WorkspaceSymbols" )
55
55
defer done ()
56
56
if query == "" {
57
57
return nil , nil
58
58
}
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 )
61
73
}
62
74
63
75
// A matcherFunc returns the index and score of a symbol match.
@@ -136,43 +148,6 @@ func packageSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]strin
136
148
return nil , 0
137
149
}
138
150
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
-
176
151
func buildMatcher (matcher SymbolMatcher , query string ) matcherFunc {
177
152
switch matcher {
178
153
case SymbolFuzzy :
@@ -302,36 +277,42 @@ func (c comboMatcher) match(chunks []string) (int, float64) {
302
277
return first , score
303
278
}
304
279
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 ) {
316
292
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?
317
298
for _ , v := range views {
318
299
snapshot , release := v .Snapshot (ctx )
319
300
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 ()), "/" ))
324
305
325
306
filters := v .Options ().DirectoryFilters
326
307
folder := filepath .ToSlash (v .Folder ().Filename ())
327
- for uri , syms := range psyms {
308
+ for uri , syms := range snapshot . Symbols ( ctx ) {
328
309
norm := filepath .ToSlash (uri .Filename ())
329
310
nm := strings .TrimPrefix (norm , folder )
330
311
if FiltersDisallow (nm , filters ) {
331
312
continue
332
313
}
333
314
// Only scan each file once.
334
- if _ , ok := files [uri ]; ok {
315
+ if seen [uri ] {
335
316
continue
336
317
}
337
318
mds , err := snapshot .MetadataForFile (ctx , uri )
@@ -343,39 +324,37 @@ func (sc *symbolCollector) walk(ctx context.Context, views []View) ([]protocol.S
343
324
// TODO: should use the bug reporting API
344
325
continue
345
326
}
346
- files [uri ] = symbolFile {uri , mds [0 ], syms }
327
+ seen [uri ] = true
328
+ work = append (work , symbolFile {uri , mds [0 ], syms })
347
329
}
348
330
}
349
331
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,
356
334
// 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 ])
367
344
}
368
- results <- w . ss
369
- }(i , matcher )
345
+ results <- store
346
+ }(i )
370
347
}
371
348
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 )
376
355
}
377
356
}
378
- return sc .results (), nil
357
+ return unified .results (), nil
379
358
}
380
359
381
360
// FilterDisallow is code from the body of cache.pathExcludedByFilter in cache/view.go
@@ -407,20 +386,13 @@ type symbolFile struct {
407
386
syms []Symbol
408
387
}
409
388
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 ) {
419
391
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 )
421
393
422
394
// Check if the score is too low before applying any downranking.
423
- if w . ss .tooLow (score ) {
395
+ if store .tooLow (score ) {
424
396
continue
425
397
}
426
398
@@ -463,7 +435,7 @@ func (w *symbolWorker) matchFile(i symbolFile) {
463
435
}
464
436
465
437
inWorkspace := false
466
- for _ , root := range w . roots {
438
+ for _ , root := range roots {
467
439
if strings .HasPrefix (string (i .uri ), root ) {
468
440
inWorkspace = true
469
441
break
@@ -484,7 +456,7 @@ func (w *symbolWorker) matchFile(i symbolFile) {
484
456
}
485
457
score *= 1.0 - depth * depthFactor
486
458
487
- if w . ss .tooLow (score ) {
459
+ if store .tooLow (score ) {
488
460
continue
489
461
}
490
462
@@ -496,7 +468,7 @@ func (w *symbolWorker) matchFile(i symbolFile) {
496
468
rng : sym .Range ,
497
469
container : i .md .PackagePath (),
498
470
}
499
- w . ss .store (si )
471
+ store .store (si )
500
472
}
501
473
}
502
474
0 commit comments