Skip to content

Commit 81e2688

Browse files
authored
Support for clangd's go-to-definition for #include/#import (swiftlang#247)
* Support for clangd's go-to-definition for header files - By forwarding the request to clangd when it fails to give symbol information, we are able to use its built in go-to-definition support for headers (jump to header file) * Add static tibs test for clangd go-to-#include * Move #include test to SourceKitTests and regenerate linux main * Fix improper escaping of %40 in file URLs * Add URL escaping test * Another attempt to fix broken BuildServerBuildSystemTests test on Linux - URL's `standardizedFileURL` removes trailing slashes from file URLs, but only on Linux
1 parent 7d18d3b commit 81e2688

File tree

12 files changed

+91
-3
lines changed

12 files changed

+91
-3
lines changed

Sources/LanguageServerProtocol/SupportTypes/DocumentURI.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@
1212

1313
import Foundation
1414

15+
/// Standardize the URL.
16+
///
17+
/// This normalizes escape sequences in file URLs, like `%40` --> `@`.
18+
/// But for non-file URLs, has no effect.
19+
fileprivate func standardize(url: URL) -> URL {
20+
guard url.isFileURL else { return url }
21+
22+
// This has the side-effect of removing trailing slashes from file URLs
23+
// on Linux, so we may need to add it back.
24+
let standardized = url.standardizedFileURL
25+
if url.absoluteString.hasSuffix("/") && !standardized.absoluteString.hasSuffix("/") {
26+
return URL(fileURLWithPath: standardized.path, isDirectory: true)
27+
}
28+
return standardized
29+
}
30+
1531
public struct DocumentURI: Codable, Hashable {
1632
/// The URL that store the URIs value
1733
private let storage: URL
@@ -53,7 +69,7 @@ public struct DocumentURI: Codable, Hashable {
5369
guard let url = URL(string: string) else {
5470
fatalError("Failed to construct DocumentURI from '\(string)'")
5571
}
56-
self.init(url)
72+
self.init(standardize(url: url))
5773
}
5874

5975
public init(_ url: URL) {

Sources/SourceKit/SourceKitServer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,8 @@ extension SourceKitServer {
599599
let index = self.workspace?.index
600600
let callback = callbackOnQueue(self.queue) { (result: Result<SymbolInfoRequest.Response, ResponseError>) in
601601
guard let symbols: [SymbolDetails] = result.success ?? nil, let symbol = symbols.first else {
602+
let handled = languageService.definition(req)
603+
guard !handled else { return }
602604
if let error = result.failure {
603605
req.reply(.failure(error))
604606
} else {

Sources/SourceKit/ToolchainLanguageServer.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public protocol ToolchainLanguageServer: AnyObject {
4040
func hover(_ req: Request<HoverRequest>)
4141
func symbolInfo(_ request: Request<SymbolInfoRequest>)
4242

43+
/// Returns true if the `ToolchainLanguageServer` will take ownership of the request.
44+
func definition(_ request: Request<DefinitionRequest>) -> Bool
45+
4346
func documentSymbolHighlight(_ req: Request<DocumentHighlightRequest>)
4447
func foldingRange(_ req: Request<FoldingRangeRequest>)
4548
func documentSymbol(_ req: Request<DocumentSymbolRequest>)

Sources/SourceKit/clangd/ClangLanguageServer.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ extension ClangLanguageServerShim {
141141

142142
// MARK: - Text Document
143143

144+
145+
/// Returns true if the `ToolchainLanguageServer` will take ownership of the request.
146+
public func definition(_ req: Request<DefinitionRequest>) -> Bool {
147+
// We handle it to provide jump-to-header support for #import/#include.
148+
forwardRequest(req, to: clangd)
149+
return true
150+
}
151+
144152
func completion(_ req: Request<CompletionRequest>) {
145153
forwardRequest(req, to: clangd)
146154
}

Sources/SourceKit/sourcekitd/SwiftLanguageServer.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,12 @@ extension SwiftLanguageServer {
426426
return TextEdit(range: textEditRangeStart..<requestPosition, newText: newText)
427427
}
428428

429+
/// Returns true if the `ToolchainLanguageServer` will take ownership of the request.
430+
public func definition(_ request: Request<DefinitionRequest>) -> Bool {
431+
// We don't handle it.
432+
return false
433+
}
434+
429435
public func completion(_ req: Request<CompletionRequest>) {
430436
let keys = self.keys
431437

Tests/INPUTS/BasicCXX/Object.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
struct /*Object*/Object {
2+
int field;
3+
};
4+
5+
struct Object * newObject();

Tests/INPUTS/BasicCXX/main.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include /*Object:include:main*/"Object.h"
2+
3+
int main(int argc, const char *argv[]) {
4+
struct Object *obj = newObject();
5+
return obj->field;
6+
}

Tests/INPUTS/BasicCXX/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "sources": ["main.c"] }

Tests/LanguageServerProtocolTests/LanguageServerProtocolTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,10 @@ final class LanguageServerProtocolTests: XCTestCase {
2626
XCTAssertEqual(Language.objective_cpp.xflag, "objective-c++")
2727
XCTAssertEqual(Language.objective_cpp.xflagHeader, "objective-c++-header")
2828
}
29+
30+
func testURLEscaping() {
31+
let expectedURL = URL(string: "file:///folder/[email protected]")
32+
let doc = DocumentURI(string: "file:///folder/image%403x.png")
33+
XCTAssertEqual(doc.fileURL, expectedURL)
34+
}
2935
}

Tests/SKCoreTests/BuildServerBuildSystemTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ final class BuildServerBuildSystemTests: XCTestCase {
118118
XCTAssertEqual(items[1].target.uri, targets[1].uri)
119119
XCTAssertEqual(items[0].sources[0].uri, DocumentURI(URL(fileURLWithPath: "/path/to/a/file")))
120120
XCTAssertEqual(items[0].sources[0].kind, SourceItemKind.file)
121-
XCTAssertEqual(items[0].sources[1].uri, DocumentURI(URL(fileURLWithPath: "/path/to/a/folder/")))
121+
XCTAssertEqual(items[0].sources[1].uri, DocumentURI(URL(fileURLWithPath: "/path/to/a/folder", isDirectory: true)))
122122
XCTAssertEqual(items[0].sources[1].kind, SourceItemKind.directory)
123123
XCTAssertEqual(items[1].sources[0].uri, DocumentURI(URL(fileURLWithPath: "/path/to/b/file")))
124124
XCTAssertEqual(items[1].sources[0].kind, SourceItemKind.file)
125-
XCTAssertEqual(items[1].sources[1].uri, DocumentURI(URL(fileURLWithPath: "/path/to/b/folder/")))
125+
XCTAssertEqual(items[1].sources[1].uri, DocumentURI(URL(fileURLWithPath: "/path/to/b/folder", isDirectory: true)))
126126
XCTAssertEqual(items[1].sources[1].kind, SourceItemKind.directory)
127127
expectation.fulfill()
128128
case .failure(let error):

Tests/SourceKitTests/SourceKitTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import LanguageServerProtocol
14+
import SKCore
1415
import SKTestSupport
1516
import XCTest
1617

@@ -283,4 +284,37 @@ final class SKTests: XCTestCase {
283284
fatalError("error \(finished) waiting for post-build diagnostics notification")
284285
}
285286
}
287+
288+
func testClangdGoToInclude() throws {
289+
guard let ws = try staticSourceKitTibsWorkspace(name: "BasicCXX") else { return }
290+
guard ToolchainRegistry.shared.default?.clangd != nil else { return }
291+
292+
let mainLoc = ws.testLoc("Object:include:main")
293+
let expectedDoc = ws.testLoc("Object").docIdentifier.uri
294+
let includePosition =
295+
Position(line: mainLoc.position.line, utf16index: mainLoc.utf16Column + 2)
296+
297+
try ws.openDocument(mainLoc.url, language: .c)
298+
299+
let goToInclude = DefinitionRequest(
300+
textDocument: mainLoc.docIdentifier, position: includePosition)
301+
let resp = try! ws.sk.sendSync(goToInclude)
302+
303+
guard let locationsOrLinks = resp else {
304+
XCTFail("No response for go-to-#include")
305+
return
306+
}
307+
switch locationsOrLinks {
308+
case .locations(let locations):
309+
XCTAssert(!locations.isEmpty, "Found no locations for go-to-#include")
310+
if let loc = locations.first {
311+
XCTAssertEqual(loc.uri, expectedDoc)
312+
}
313+
case .locationLinks(let locationLinks):
314+
XCTAssert(!locationLinks.isEmpty, "Found no location links for go-to-#include")
315+
if let link = locationLinks.first {
316+
XCTAssertEqual(link.targetUri, expectedDoc)
317+
}
318+
}
319+
}
286320
}

Tests/SourceKitTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ extension SKTests {
140140
// `swift test --generate-linuxmain`
141141
// to regenerate.
142142
static let __allTests__SKTests = [
143+
("testClangdGoToInclude", testClangdGoToInclude),
143144
("testCodeCompleteSwiftTibs", testCodeCompleteSwiftTibs),
144145
("testDependenciesUpdatedCXXTibs", testDependenciesUpdatedCXXTibs),
145146
("testDependenciesUpdatedSwiftTibs", testDependenciesUpdatedSwiftTibs),

0 commit comments

Comments
 (0)