Skip to content

Commit 2d806c8

Browse files
committed
Add workspace/symbols support
https://bugs.swift.org/browse/SR-10806
1 parent 8482476 commit 2d806c8

File tree

5 files changed

+164
-3
lines changed

5 files changed

+164
-3
lines changed

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public let builtinRequests: [_RequestType.Type] = [
2323
WorkspaceFoldersRequest.self,
2424
CompletionRequest.self,
2525
HoverRequest.self,
26+
WorkspaceSymbolsRequest.self,
2627
DefinitionRequest.self,
2728
ReferencesRequest.self,
2829
DocumentHighlightRequest.self,

Sources/LanguageServerProtocol/ServerCapabilities.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ public struct ServerCapabilities: Codable, Hashable {
5151
/// Whether the server provides "textDocument/codeAction".
5252
public var codeActionProvider: CodeActionServerCapabilities?
5353

54+
/// The server provides workspace symbol support.
55+
public var workspaceSymbolProvider: Bool?
56+
5457
// TODO: fill-in the rest.
5558

5659
public init(
@@ -66,7 +69,8 @@ public struct ServerCapabilities: Codable, Hashable {
6669
foldingRangeProvider: Bool? = nil,
6770
documentSymbolProvider: Bool? = nil,
6871
colorProvider: Bool? = nil,
69-
codeActionProvider: CodeActionServerCapabilities? = nil
72+
codeActionProvider: CodeActionServerCapabilities? = nil,
73+
workspaceSymbolProvider: Bool? = nil
7074
)
7175
{
7276
self.textDocumentSync = textDocumentSync
@@ -82,6 +86,7 @@ public struct ServerCapabilities: Codable, Hashable {
8286
self.documentSymbolProvider = documentSymbolProvider
8387
self.colorProvider = colorProvider
8488
self.codeActionProvider = codeActionProvider
89+
self.workspaceSymbolProvider = workspaceSymbolProvider
8590
}
8691

8792
public init(from decoder: Decoder) throws {
@@ -93,6 +98,7 @@ public struct ServerCapabilities: Codable, Hashable {
9398
self.documentSymbolProvider = try container.decodeIfPresent(Bool.self, forKey: .documentSymbolProvider)
9499
self.colorProvider = try container.decodeIfPresent(Bool.self, forKey: .colorProvider)
95100
self.codeActionProvider = try container.decodeIfPresent(CodeActionServerCapabilities.self, forKey: .codeActionProvider)
101+
self.workspaceSymbolProvider = try container.decodeIfPresent(Bool.self, forKey: .workspaceSymbolProvider)
96102

97103
if let textDocumentSync = try? container.decode(TextDocumentSyncOptions.self, forKey: .textDocumentSync) {
98104
self.textDocumentSync = textDocumentSync
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Request for all symbols that match a certain query string.
14+
///
15+
/// This request looks up the canonical occurence of each symbol which has a name that contains the query string.
16+
/// The list of symbol information is returned
17+
///
18+
/// Servers that provide workspace symbol queries should set the `workspaceSymbolProvider` server capability.
19+
///
20+
/// - Parameters:
21+
/// - query: The string that should be looked for in symbols of the workspace.
22+
///
23+
/// - Returns: Information about each symbol with a name that contains the query string
24+
public struct WorkspaceSymbolsRequest: RequestType, Hashable {
25+
26+
public static let method: String = "workspace/symbol"
27+
public typealias Response = [SymbolInformation]?
28+
29+
/// The document in which to lookup the symbol location.
30+
public var query: String
31+
32+
public init(query: String) {
33+
self.query = query
34+
}
35+
}
36+
37+
public struct SymbolInformation: Hashable, ResponseType {
38+
public var name: String
39+
40+
public var kind: SymbolKind
41+
42+
public var deprecated: Bool
43+
44+
public var location: Location
45+
46+
public var containerName: String?
47+
48+
public init(name: String, kind: SymbolKind, deprecated: Bool, location: Location, containerName: String?) {
49+
self.name = name
50+
self.kind = kind
51+
self.deprecated = deprecated
52+
self.location = location
53+
self.containerName = containerName
54+
}
55+
}

Sources/SourceKit/SourceKitServer.swift

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public final class SourceKitServer: LanguageServer {
7171
registerWorkspaceNotfication(SourceKitServer.didSaveDocument)
7272
registerWorkspaceRequest(SourceKitServer.completion)
7373
registerWorkspaceRequest(SourceKitServer.hover)
74+
registerWorkspaceRequest(SourceKitServer.workspaceSymbols)
7475
registerWorkspaceRequest(SourceKitServer.definition)
7576
registerWorkspaceRequest(SourceKitServer.references)
7677
registerWorkspaceRequest(SourceKitServer.documentSymbolHighlight)
@@ -269,7 +270,8 @@ extension SourceKitServer {
269270
clientCapabilities: req.params.capabilities.textDocument?.codeAction,
270271
codeActionOptions: CodeActionOptions(codeActionKinds: nil),
271272
supportsCodeActions: false // TODO: Turn it on after a provider is implemented.
272-
)
273+
),
274+
workspaceSymbolProvider: true
273275
)))
274276
}
275277

@@ -338,6 +340,51 @@ extension SourceKitServer {
338340
toolchainTextDocumentRequest(req, workspace: workspace, fallback: nil)
339341
}
340342

343+
/// Find all symbols in the workspace that include a string in their name.
344+
/// - returns: An array of SymbolOccurrences that match the string.
345+
func findWorkspaceSymbols(matching: String) -> [SymbolOccurrence] {
346+
var symbolOccurenceResults: [SymbolOccurrence] = []
347+
workspace?.index?.forEachCanonicalSymbolOccurrence(
348+
containing: matching,
349+
anchorStart: false,
350+
anchorEnd: false,
351+
subsequence: true,
352+
ignoreCase: true
353+
) {symbol in
354+
if !symbol.location.isSystem && !symbol.roles.contains(.accessorOf) {
355+
symbolOccurenceResults.append(symbol)
356+
}
357+
return true
358+
}
359+
return symbolOccurenceResults
360+
}
361+
362+
/// Handle a workspace/symbols request, returning the SymbolInformation.
363+
/// - returns: An array with SymbolInformation for each matching symbol in the workspace.
364+
func workspaceSymbols(_ req: Request<WorkspaceSymbolsRequest>, workspace: Workspace) {
365+
let symbols = findWorkspaceSymbols(
366+
matching: req.params.query
367+
).map({symbolOccurrence -> SymbolInformation in
368+
let symbolPosition = Position(
369+
line: symbolOccurrence.location.line - 1, // 1-based -> 0-based
370+
// FIXME: we need to convert the utf8/utf16 column, which may require reading the file!
371+
utf16index: symbolOccurrence.location.utf8Column - 1)
372+
373+
let symbolLocation = Location(
374+
url: URL(fileURLWithPath: symbolOccurrence.location.path),
375+
range: Range(symbolPosition))
376+
377+
return SymbolInformation(
378+
name: symbolOccurrence.symbol.name,
379+
kind: symbolOccurrence.symbol.kind.toLspSymbolKind(),
380+
deprecated: false,
381+
location: symbolLocation,
382+
containerName: symbolOccurrence.getContainerName()
383+
)
384+
})
385+
req.reply(symbols)
386+
}
387+
341388
/// Forwards a SymbolInfoRequest to the appropriate toolchain service for this document.
342389
func symbolInfo(_ req: Request<SymbolInfoRequest>, workspace: Workspace) {
343390
toolchainTextDocumentRequest(req, workspace: workspace, fallback: [])
@@ -523,3 +570,54 @@ public func languageService(
523570

524571
public typealias Notification = LanguageServerProtocol.Notification
525572
public typealias Diagnostic = LanguageServerProtocol.Diagnostic
573+
574+
extension IndexSymbolKind {
575+
func toLspSymbolKind() -> SymbolKind {
576+
switch self {
577+
case Self.class:
578+
return .class
579+
case Self.classMethod,
580+
Self.instanceMethod,
581+
Self.staticMethod:
582+
return .method
583+
case Self.instanceProperty,
584+
Self.staticProperty,
585+
Self.classProperty:
586+
return .property
587+
case Self.enum:
588+
return .enum
589+
case Self.enumConstant: // Unsure about this one
590+
return .enumMember
591+
case Self.protocol:
592+
return .interface
593+
case Self.function,
594+
Self.conversionFunction: // assuming conversion function is function
595+
return .function
596+
case Self.variable:
597+
return .variable
598+
case Self.struct:
599+
return .struct
600+
case Self.parameter:
601+
return .typeParameter
602+
case Self.parameter:
603+
return .typeParameter
604+
605+
default:
606+
return .null
607+
}
608+
}
609+
}
610+
611+
extension SymbolOccurrence {
612+
/// Get the name of the symbol that is a parent of this symbol, if one exists
613+
func getContainerName() -> String? {
614+
var foundChildRelation: SymbolRelation?
615+
self.forEachRelation { relation in
616+
if relation.roles.contains(.childOf) {
617+
foundChildRelation = relation
618+
}
619+
return false
620+
}
621+
return foundChildRelation?.symbol.name
622+
}
623+
}

Tests/LanguageServerProtocolJSONRPCTests/CodingTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ final class CodingTests: XCTestCase {
8585
referencesProvider: nil,
8686
documentHighlightProvider: nil,
8787
foldingRangeProvider: nil,
88-
codeActionProvider: nil)), id: .number(2), json: """
88+
codeActionProvider: nil,
89+
workspaceSymbolProvider: nil)), id: .number(2), json: """
8990
{
9091
"id" : 2,
9192
"jsonrpc" : "2.0",

0 commit comments

Comments
 (0)