Skip to content

Add workspace/symbols support #118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/LanguageServerProtocol/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public let builtinRequests: [_RequestType.Type] = [
WorkspaceFoldersRequest.self,
CompletionRequest.self,
HoverRequest.self,
WorkspaceSymbolsRequest.self,
DefinitionRequest.self,
ReferencesRequest.self,
DocumentHighlightRequest.self,
Expand Down
8 changes: 7 additions & 1 deletion Sources/LanguageServerProtocol/ServerCapabilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public struct ServerCapabilities: Codable, Hashable {
/// Whether the server provides "textDocument/codeAction".
public var codeActionProvider: CodeActionServerCapabilities?

/// The server provides workspace symbol support.
public var workspaceSymbolProvider: Bool?

// TODO: fill-in the rest.

public init(
Expand All @@ -66,7 +69,8 @@ public struct ServerCapabilities: Codable, Hashable {
foldingRangeProvider: Bool? = nil,
documentSymbolProvider: Bool? = nil,
colorProvider: Bool? = nil,
codeActionProvider: CodeActionServerCapabilities? = nil
codeActionProvider: CodeActionServerCapabilities? = nil,
workspaceSymbolProvider: Bool? = nil
)
{
self.textDocumentSync = textDocumentSync
Expand All @@ -82,6 +86,7 @@ public struct ServerCapabilities: Codable, Hashable {
self.documentSymbolProvider = documentSymbolProvider
self.colorProvider = colorProvider
self.codeActionProvider = codeActionProvider
self.workspaceSymbolProvider = workspaceSymbolProvider
}

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

if let textDocumentSync = try? container.decode(TextDocumentSyncOptions.self, forKey: .textDocumentSync) {
self.textDocumentSync = textDocumentSync
Expand Down
58 changes: 58 additions & 0 deletions Sources/LanguageServerProtocol/WorkspaceSymbols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

/// Request for all symbols that match a certain query string.
///
/// This request looks up the canonical occurence of each symbol which has a name that contains the query string.
/// The list of symbol information is returned
///
/// Servers that provide workspace symbol queries should set the `workspaceSymbolProvider` server capability.
///
/// - Parameters:
/// - query: The string that should be looked for in symbols of the workspace.
///
/// - Returns: Information about each symbol with a name that contains the query string
public struct WorkspaceSymbolsRequest: RequestType, Hashable {

public static let method: String = "workspace/symbol"
public typealias Response = [SymbolInformation]?

/// The document in which to lookup the symbol location.
public var query: String

public init(query: String) {
self.query = query
}
}

public struct SymbolInformation: Hashable, ResponseType {
public var name: String

public var detail: String?

public var kind: SymbolKind

public var deprecated: Bool

public var location: Location

public var containerName: String?

public init(name: String, detail: String?, kind: SymbolKind, deprecated: Bool, location: Location, containerName: String?) {
self.name = name
self.detail = detail
self.kind = kind
self.deprecated = deprecated
self.location = location
self.containerName = containerName
}
}
45 changes: 44 additions & 1 deletion Sources/SourceKit/SourceKitServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public final class SourceKitServer: LanguageServer {
registerWorkspaceNotfication(SourceKitServer.didSaveDocument)
registerWorkspaceRequest(SourceKitServer.completion)
registerWorkspaceRequest(SourceKitServer.hover)
registerWorkspaceRequest(SourceKitServer.workspaceSymbols)
registerWorkspaceRequest(SourceKitServer.definition)
registerWorkspaceRequest(SourceKitServer.references)
registerWorkspaceRequest(SourceKitServer.documentSymbolHighlight)
Expand Down Expand Up @@ -269,7 +270,8 @@ extension SourceKitServer {
clientCapabilities: req.params.capabilities.textDocument?.codeAction,
codeActionOptions: CodeActionOptions(codeActionKinds: nil),
supportsCodeActions: false // TODO: Turn it on after a provider is implemented.
)
),
workspaceSymbolProvider: true
)))
}

Expand Down Expand Up @@ -338,6 +340,47 @@ extension SourceKitServer {
toolchainTextDocumentRequest(req, workspace: workspace, fallback: nil)
}

func findWorkspaceSymbols(matching: String) -> [SymbolOccurrence] {
var symbolOccurenceResults: [SymbolOccurrence] = []
workspace?.index?.forEachCanonicalSymbolOccurrence(
containing: matching,
anchorStart: false,
anchorEnd: false,
subsequence: true,
ignoreCase: true
) {symbol in
if !symbol.location.isSystem && !symbol.roles.contains(.accessorOf) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what roles should be included. I've excluded accessorOf because otherwise getters and setters of variables were returned in addition to the variable itself.

symbolOccurenceResults.append(symbol)
}
return true
}
return symbolOccurenceResults
}

func workspaceSymbols(_ req: Request<WorkspaceSymbolsRequest>, workspace: Workspace) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much of the other functionality seems to be done in SwiftLanguageServer, but I think I need to do this here to have access to index. Is that correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this is the right place for it. Language-agnostic functionality belongs here, which is why we also implement e.g. jump to definition here.

let symbols = findWorkspaceSymbols(
matching: req.params.query
).map({symbolOccurrence -> SymbolInformation in
let symbolPosition = Position(
line: symbolOccurrence.location.line - 1, // 1-based -> 0-based
// FIXME: we need to convert the utf8/utf16 column, which may require reading the file!
utf16index: symbolOccurrence.location.utf8Column - 1)

let symbolLocation = Location(
url: URL(fileURLWithPath: symbolOccurrence.location.path),
range: Range(symbolPosition))
// TODO: symbol kind, detail, and containerName should be added
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like some guidance on how to get kind, detail, and containerName.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • kind: We have a symbol kind in the index that we should expose on class Symbol. Then we can convert it to an LSP SymbolKind here.

  • detail: This isn't a field on SymbolInformation AFAICT. Did you mix it up with DocumentSymbol?

  • containerName: Ah didn't realize we'd need this. For this we would need to expose the SymbolOccurrence's relations in the index. The C++ version of this is SymbolOccurrence::getRelations, we just need to thread it through the C and Swift interfaces. Since you already have the canonical occurrence, you would find the related symbol that has relation role childOf and use that symbol's name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct that detail came from DocumentSymbol.

for containerName, it looks like I will have to pass an ArrayPointer to Swift, which I can't find a good example of. Maybe I could use unsafeBufferPointer as mentioned here, but it looks like that involves copying which I think we want to avoid. is there a better way to do this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying should be fine, since the number of relations for a single occurrence will generally be small, and we can do the copy lazily only when you ask for related symbols. Here's a sketch of the C API I would start with. We can always optimize this later if needed.

typedef void *indexstoredb_symbol_relation_t;

...

INDEXSTOREDB_PUBLIC uint64_t
indexstoredb_symbol_relation_get_roles(_Nonnull  indexstoredb_symbol_relation_t);

INDEXSTOREDB_PUBLIC indexstoredb_symbol_t
indexstoredb_symbol_relation_get_symbol(_Nonnull indexstoredb_symbol_relation_t);

/// The relations are owned by the occurrence and shall not be used after the occurrence is freed.
INDEXSTOREDB_PUBLIC bool
indexstoredb_symbol_occurrence_relations(_Nonnull indexstoredb_occurrence_t,
                      bool(^applier)(indexstoredb_symbol_relation_t));

On the Swift side, this can lazily create an array from the occurrence.

return SymbolInformation(
name: symbolOccurrence.symbol.name,
detail: nil,
kind: .variable,
deprecated: false,
location: symbolLocation,
containerName: nil)
})
req.reply(symbols)
}

/// Forwards a SymbolInfoRequest to the appropriate toolchain service for this document.
func symbolInfo(_ req: Request<SymbolInfoRequest>, workspace: Workspace) {
toolchainTextDocumentRequest(req, workspace: workspace, fallback: [])
Expand Down
3 changes: 2 additions & 1 deletion Tests/LanguageServerProtocolJSONRPCTests/CodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ final class CodingTests: XCTestCase {
referencesProvider: nil,
documentHighlightProvider: nil,
foldingRangeProvider: nil,
codeActionProvider: nil)), id: .number(2), json: """
codeActionProvider: nil,
workspaceSymbolProvider: nil)), id: .number(2), json: """
{
"id" : 2,
"jsonrpc" : "2.0",
Expand Down