| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:collection'; |
| |
| import 'package:analysis_server/lsp_protocol/protocol_generated.dart'; |
| import 'package:analysis_server/lsp_protocol/protocol_special.dart'; |
| import 'package:analysis_server/src/lsp/handlers/handler_document_symbols.dart' |
| show defaultSupportedSymbolKinds; |
| import 'package:analysis_server/src/lsp/handlers/handlers.dart'; |
| import 'package:analysis_server/src/lsp/lsp_analysis_server.dart'; |
| import 'package:analysis_server/src/lsp/mapping.dart'; |
| import 'package:analyzer/src/dart/analysis/search.dart' as search; |
| |
| class WorkspaceSymbolHandler |
| extends MessageHandler<WorkspaceSymbolParams, List<SymbolInformation>> { |
| WorkspaceSymbolHandler(LspAnalysisServer server) : super(server); |
| Method get handlesMessage => Method.workspace_symbol; |
| |
| @override |
| LspJsonHandler<WorkspaceSymbolParams> get jsonHandler => |
| WorkspaceSymbolParams.jsonHandler; |
| |
| Future<ErrorOr<List<SymbolInformation>>> handle( |
| WorkspaceSymbolParams params) async { |
| // Respond to empty queries with an empty list. The spec says this should |
| // be non-empty, however VS Code's client sends empty requests (but then |
| // appears to not render the results we supply anyway). |
| final query = params?.query ?? ''; |
| if (query == '') { |
| return success([]); |
| } |
| |
| final symbolCapabilities = server?.clientCapabilities?.workspace?.symbol; |
| |
| final clientSupportedSymbolKinds = |
| symbolCapabilities?.symbolKind?.valueSet != null |
| ? new HashSet<SymbolKind>.of(symbolCapabilities.symbolKind.valueSet) |
| : defaultSupportedSymbolKinds; |
| |
| // Convert the string input into a case-insensitive regex that has wildcards |
| // between every character and at start/end to allow for fuzzy matching. |
| final fuzzyQuery = query.split('').map(RegExp.escape).join('.*'); |
| final partialFuzzyQuery = '.*$fuzzyQuery.*'; |
| final regex = new RegExp(partialFuzzyQuery, caseSensitive: false); |
| |
| // Cap the number of results we'll return because short queries may match |
| // huge numbers on large projects. |
| var remainingResults = 500; |
| |
| final declarations = <search.Declaration>[]; |
| final filePathsHashSet = new LinkedHashSet<String>(); |
| for (var driver in server.driverMap.values) { |
| final driverResults = await driver.search |
| .declarations(regex, remainingResults, filePathsHashSet); |
| declarations.addAll(driverResults); |
| remainingResults -= driverResults.length; |
| } |
| |
| // Convert the file paths to something we can quickly index into since |
| // we'll be looking things up by index a lot. |
| final filePaths = filePathsHashSet.toList(); |
| |
| // Map the results to SymbolInformations and flatten the list of lists. |
| final symbols = declarations |
| .map((declaration) => _asSymbolInformation( |
| declaration, |
| clientSupportedSymbolKinds, |
| filePaths, |
| )) |
| .toList(); |
| |
| return success(symbols); |
| } |
| |
| SymbolInformation _asSymbolInformation( |
| search.Declaration declaration, |
| HashSet<SymbolKind> clientSupportedSymbolKinds, |
| List<String> filePaths, |
| ) { |
| final filePath = filePaths[declaration.fileIndex]; |
| |
| final kind = declarationKindToSymbolKind( |
| clientSupportedSymbolKinds, |
| declaration.kind, |
| ); |
| final range = toRange( |
| declaration.lineInfo, |
| declaration.codeOffset, |
| declaration.codeLength, |
| ); |
| final location = new Location( |
| Uri.file(filePath).toString(), |
| range, |
| ); |
| |
| return new SymbolInformation( |
| declaration.name, |
| kind, |
| null, // We don't have easy access to isDeprecated here. |
| location, |
| declaration.className ?? declaration.mixinName); |
| } |
| } |