Skip to content

Commit 36e5673

Browse files
authored
Merge pull request #98 from Trzyipolkostkicukru/cukr/document-symbol
Implement document symbol request
2 parents d571212 + 125cf2d commit 36e5673

File tree

10 files changed

+887
-5
lines changed

10 files changed

+887
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ SourceKit-LSP is still in early development, so you may run into rough edges wit
120120
| Formatting || |
121121
| Folding || |
122122
| Syntax Highlighting || Not currently part of LSP. |
123-
| Document Symbols | | |
123+
| Document Symbols | | |
124124

125125

126126
### Caveats

Sources/LanguageServerProtocol/ClientCapabilities.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
189189
/// Whether the client supports dynamic registaration of this request.
190190
public var dynamicRegistration: Bool? = nil
191191

192-
/// Capabilities specific to `SignatureInformation`.
192+
/// Capabilities specific to `SymbolKind`.
193193
public struct SymbolKind: Hashable, Codable {
194194

195195
/// The symbol kind values that the client can support.
@@ -201,6 +201,9 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
201201
}
202202

203203
public var symbolKind: SymbolKind? = nil
204+
205+
public init() {
206+
}
204207
}
205208

206209
/// Capabilities specific to the `textDocument/codeAction` request.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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 symbols to display in the document outline.
14+
///
15+
/// This is used to provide list of all symbols in the document and display inside which
16+
/// type or function the cursor is currently in.
17+
///
18+
/// Servers that provide document highlights should set the `documentSymbolProvider` server
19+
/// capability.
20+
///
21+
/// - Parameters:
22+
/// - textDocument: The document in which to lookup the symbol location.
23+
///
24+
/// - Returns: An array of document symbols, if any.
25+
public struct DocumentSymbolRequest: TextDocumentRequest, Hashable {
26+
public static let method: String = "textDocument/documentSymbol"
27+
public typealias Response = [DocumentSymbol]?
28+
29+
/// The document in which to lookup the symbol location.
30+
public var textDocument: TextDocumentIdentifier
31+
32+
public init(textDocument: TextDocumentIdentifier) {
33+
self.textDocument = textDocument
34+
}
35+
}
36+
37+
/// Represents programming constructs like variables, classes, interfaces etc. that appear
38+
/// in a document. Document symbols can be hierarchical and they have two ranges: one that encloses
39+
/// its definition and one that points to its most interesting range, e.g. the range of an identifier.
40+
public struct DocumentSymbol: Hashable, Codable, ResponseType {
41+
42+
/// The name of this symbol. Will be displayed in the user interface and therefore must not be
43+
/// an empty string or a string only consisting of white spaces.
44+
var name: String
45+
46+
/// More detail for this symbol, e.g the signature of a function.
47+
var detail: String?
48+
49+
/// The kind of this symbol.
50+
var kind: SymbolKind
51+
52+
/// Indicates if this symbol is deprecated.
53+
var deprecated: Bool?
54+
55+
/// The range enclosing this symbol not including leading/trailing whitespace but everything else
56+
/// like comments. This information is typically used to determine if the clients cursor is
57+
/// inside the symbol to reveal in the symbol in the UI.
58+
var range: PositionRange
59+
60+
/// The range that should be selected and revealed when this symbol is being picked,
61+
/// e.g the name of a function.
62+
///
63+
/// Must be contained by the `range`.
64+
var selectionRange: PositionRange
65+
66+
/// Children of this symbol, e.g. properties of a class.
67+
var children: [DocumentSymbol]?
68+
69+
public init(
70+
name: String,
71+
detail: String?,
72+
kind: SymbolKind,
73+
deprecated: Bool?,
74+
range: PositionRange,
75+
selectionRange: PositionRange,
76+
children: [DocumentSymbol]?)
77+
{
78+
self.name = name
79+
self.detail = detail
80+
self.kind = kind
81+
self.deprecated = deprecated
82+
self.range = range
83+
self.selectionRange = selectionRange
84+
self.children = children
85+
}
86+
}

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public let builtinRequests: [_RequestType.Type] = [
3030
DocumentRangeFormatting.self,
3131
DocumentOnTypeFormatting.self,
3232
FoldingRangeRequest.self,
33+
DocumentSymbolRequest.self,
3334

3435
// MARK: LSP Extension Requests
3536

Sources/LanguageServerProtocol/ServerCapabilities.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public struct ServerCapabilities: Codable, Hashable {
4242
/// Whether the server provides "textDocument/foldingRange".
4343
public var foldingRangeProvider: Bool?
4444

45+
/// Whether the server provides "textDocument/documentSymbol"
46+
public var documentSymbolProvider: Bool?
47+
4548
// TODO: fill-in the rest.
4649

4750
public init(
@@ -54,7 +57,8 @@ public struct ServerCapabilities: Codable, Hashable {
5457
documentFormattingProvider: Bool? = nil,
5558
documentRangeFormattingProvider: Bool? = nil,
5659
documentOnTypeFormattingProvider: DocumentOnTypeFormattingOptions? = nil,
57-
foldingRangeProvider: Bool? = nil
60+
foldingRangeProvider: Bool? = nil,
61+
documentSymbolProvider: Bool? = nil
5862
)
5963
{
6064
self.textDocumentSync = textDocumentSync
@@ -67,6 +71,7 @@ public struct ServerCapabilities: Codable, Hashable {
6771
self.documentRangeFormattingProvider = documentRangeFormattingProvider
6872
self.documentOnTypeFormattingProvider = documentOnTypeFormattingProvider
6973
self.foldingRangeProvider = foldingRangeProvider
74+
self.documentSymbolProvider = documentSymbolProvider
7075
}
7176

7277
public init(from decoder: Decoder) throws {
@@ -75,6 +80,7 @@ public struct ServerCapabilities: Codable, Hashable {
7580
self.hoverProvider = try container.decodeIfPresent(Bool.self, forKey: .hoverProvider)
7681
self.definitionProvider = try container.decodeIfPresent(Bool.self, forKey: .definitionProvider)
7782
self.foldingRangeProvider = try container.decodeIfPresent(Bool.self, forKey: .foldingRangeProvider)
83+
self.documentSymbolProvider = try container.decodeIfPresent(Bool.self, forKey: .documentSymbolProvider)
7884

7985
if let textDocumentSync = try? container.decode(TextDocumentSyncOptions.self, forKey: .textDocumentSync) {
8086
self.textDocumentSync = textDocumentSync

Sources/SourceKit/SourceKitServer.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public final class SourceKitServer: LanguageServer {
7676
registerWorkspaceRequest(SourceKitServer.documentSymbolHighlight)
7777
registerWorkspaceRequest(SourceKitServer.foldingRange)
7878
registerWorkspaceRequest(SourceKitServer.symbolInfo)
79+
registerWorkspaceRequest(SourceKitServer.documentSymbol)
7980
}
8081

8182
func registerWorkspaceRequest<R>(
@@ -258,7 +259,8 @@ extension SourceKitServer {
258259
definitionProvider: true,
259260
referencesProvider: true,
260261
documentHighlightProvider: true,
261-
foldingRangeProvider: true
262+
foldingRangeProvider: true,
263+
documentSymbolProvider: true
262264
)))
263265
}
264266

@@ -340,6 +342,10 @@ extension SourceKitServer {
340342
toolchainTextDocumentRequest(req, workspace: workspace, fallback: nil)
341343
}
342344

345+
func documentSymbol(_ req: Request<DocumentSymbolRequest>, workspace: Workspace) {
346+
toolchainTextDocumentRequest(req, workspace: workspace, fallback: nil)
347+
}
348+
343349
func definition(_ req: Request<DefinitionRequest>, workspace: Workspace) {
344350
// FIXME: sending yourself a request isn't very convenient
345351

Sources/SourceKit/sourcekitd/SwiftLanguageServer.swift

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public final class SwiftLanguageServer: LanguageServer {
6363
_register(SwiftLanguageServer.documentSymbolHighlight)
6464
_register(SwiftLanguageServer.foldingRange)
6565
_register(SwiftLanguageServer.symbolInfo)
66+
_register(SwiftLanguageServer.documentSymbol)
6667
}
6768

6869
func getDiagnostic(_ diag: SKResponseDictionary, for snapshot: DocumentSnapshot) -> Diagnostic? {
@@ -191,7 +192,8 @@ extension SwiftLanguageServer {
191192
definitionProvider: nil,
192193
referencesProvider: nil,
193194
documentHighlightProvider: true,
194-
foldingRangeProvider: true
195+
foldingRangeProvider: true,
196+
documentSymbolProvider: true
195197
)))
196198
}
197199

@@ -471,6 +473,86 @@ extension SwiftLanguageServer {
471473
}
472474
}
473475

476+
func documentSymbol(_ req: Request<DocumentSymbolRequest>) {
477+
guard let snapshot = documentManager.latestSnapshot(req.params.textDocument.url) else {
478+
log("failed to find snapshot for url \(req.params.textDocument.url)")
479+
req.reply(nil)
480+
return
481+
}
482+
483+
let skreq = SKRequestDictionary(sourcekitd: sourcekitd)
484+
skreq[keys.request] = requests.editor_open
485+
skreq[keys.name] = "DocumentSymbols:" + snapshot.document.url.path
486+
skreq[keys.sourcetext] = snapshot.text
487+
skreq[keys.syntactic_only] = 1
488+
489+
let handle = sourcekitd.send(skreq) { [weak self] result in
490+
guard let self = self else { return }
491+
guard let dict = result.success else {
492+
req.reply(.failure(result.failure!))
493+
return
494+
}
495+
guard let results: SKResponseArray = dict[self.keys.substructure] else {
496+
return req.reply([])
497+
}
498+
499+
func documentSymbol(value: SKResponseDictionary) -> DocumentSymbol? {
500+
guard let name: String = value[self.keys.name],
501+
let uid: sourcekitd_uid_t = value[self.keys.kind],
502+
let kind: SymbolKind = uid.asSymbolKind(self.values),
503+
let offset: Int = value[self.keys.offset],
504+
let start: Position = snapshot.positionOf(utf8Offset: offset),
505+
let length: Int = value[self.keys.length],
506+
let end: Position = snapshot.positionOf(utf8Offset: offset + length) else {
507+
return nil
508+
}
509+
510+
let range = PositionRange(start..<end)
511+
let selectionRange: PositionRange
512+
if let nameOffset: Int = value[self.keys.nameoffset],
513+
let nameStart: Position = snapshot.positionOf(utf8Offset: nameOffset),
514+
let nameLength: Int = value[self.keys.namelength],
515+
let nameEnd: Position = snapshot.positionOf(utf8Offset: nameOffset + nameLength) {
516+
selectionRange = PositionRange(nameStart..<nameEnd)
517+
} else {
518+
selectionRange = range
519+
}
520+
521+
let children: [DocumentSymbol]?
522+
if let substructure: SKResponseArray = value[self.keys.substructure] {
523+
children = documentSymbols(array: substructure)
524+
} else {
525+
children = nil
526+
}
527+
return DocumentSymbol(name: name,
528+
detail: nil,
529+
kind: kind,
530+
deprecated: nil,
531+
range: range,
532+
selectionRange: selectionRange,
533+
children: children)
534+
}
535+
536+
func documentSymbols(array: SKResponseArray) -> [DocumentSymbol] {
537+
var result: [DocumentSymbol] = []
538+
array.forEach { (i: Int, value: SKResponseDictionary) in
539+
if let documentSymbol = documentSymbol(value: value) {
540+
result.append(documentSymbol)
541+
} else if let substructure: SKResponseArray = value[self.keys.substructure] {
542+
result += documentSymbols(array: substructure)
543+
}
544+
return true
545+
}
546+
return result
547+
}
548+
549+
req.reply(documentSymbols(array: results))
550+
}
551+
// FIXME: cancellation
552+
_ = handle
553+
return
554+
}
555+
474556
func documentSymbolHighlight(_ req: Request<DocumentHighlightRequest>) {
475557

476558
guard let snapshot = documentManager.latestSnapshot(req.params.textDocument.url) else {
@@ -758,4 +840,39 @@ extension sourcekitd_uid_t {
758840
return nil
759841
}
760842
}
843+
844+
func asSymbolKind(_ vals: sourcekitd_values) -> SymbolKind? {
845+
switch self {
846+
case vals.decl_class:
847+
return .class
848+
case vals.decl_function_method_instance,
849+
vals.decl_function_method_static,
850+
vals.decl_function_method_class:
851+
return .method
852+
case vals.decl_var_instance,
853+
vals.decl_var_static,
854+
vals.decl_var_class:
855+
return .property
856+
case vals.decl_enum:
857+
return .enum
858+
case vals.decl_enumelement:
859+
return .enumMember
860+
case vals.decl_protocol:
861+
return .interface
862+
case vals.decl_function_free:
863+
return .function
864+
case vals.decl_var_global,
865+
vals.decl_var_local:
866+
return .variable
867+
case vals.decl_struct:
868+
return .struct
869+
case vals.decl_generic_type_param:
870+
return .typeParameter
871+
case vals.decl_extension:
872+
// There are no extensions in LSP, so I return something vaguely similar
873+
return .namespace
874+
default:
875+
return nil
876+
}
877+
}
761878
}

Sources/SourceKit/sourcekitd/SwiftSourceKitFramework.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ struct sourcekitd_keys {
218218
let bodyoffset: sourcekitd_uid_t
219219
let bodylength: sourcekitd_uid_t
220220
let syntaxmap: sourcekitd_uid_t
221+
let namelength: sourcekitd_uid_t
222+
let nameoffset: sourcekitd_uid_t
221223

222224
init(api: sourcekitd_functions_t) {
223225
request = api.uid_get_from_cstr("key.request")!
@@ -246,6 +248,8 @@ struct sourcekitd_keys {
246248
bodyoffset = api.uid_get_from_cstr("key.bodyoffset")!
247249
bodylength = api.uid_get_from_cstr("key.bodylength")!
248250
syntaxmap = api.uid_get_from_cstr("key.syntaxmap")!
251+
namelength = api.uid_get_from_cstr("key.namelength")!
252+
nameoffset = api.uid_get_from_cstr("key.nameoffset")!
249253
}
250254
}
251255

0 commit comments

Comments
 (0)