Skip to content

Commit 4fb5323

Browse files
add cursorInfoFromDisk() to SwiftLanguageService
1 parent 3610593 commit 4fb5323

File tree

5 files changed

+174
-94
lines changed

5 files changed

+174
-94
lines changed

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ target_sources(SourceKitLSP PRIVATE
4444
Swift/CodeCompletionSession.swift
4545
Swift/CommentXML.swift
4646
Swift/CursorInfo.swift
47+
Swift/CursorInfoFromDisk.swift
4748
Swift/Diagnostic.swift
4849
Swift/DiagnosticReportManager.swift
4950
Swift/DocumentFormatting.swift

Sources/SourceKitLSP/Documentation/DoccDocumentationHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ extension DocumentationLanguageService {
8181
)
8282
}
8383
let position = symbolSnapshot.position(of: symbolOccurrence.location)
84-
let cursorInfo = try await languageService.cursorInfo(
84+
let cursorInfo = try await languageService.cursorInfoFromDisk(
8585
symbolOccurrence.location.documentUri,
8686
position..<position,
8787
includeSymbolGraph: true,

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 41 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import BuildSystemIntegration
1413
import Csourcekitd
1514
import LanguageServerProtocol
1615
import SKLogging
@@ -122,62 +121,13 @@ extension CursorInfoError: CustomStringConvertible {
122121
}
123122

124123
extension SwiftLanguageService {
125-
fileprivate func openHelperDocument(_ snapshot: DocumentSnapshot) async throws {
126-
let skreq = sourcekitd.dictionary([
127-
keys.request: self.requests.editorOpen,
128-
keys.name: snapshot.uri.pseudoPath,
129-
keys.sourceText: snapshot.text,
130-
keys.syntacticOnly: 1,
131-
])
132-
_ = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
133-
}
134-
135-
fileprivate func closeHelperDocument(_ snapshot: DocumentSnapshot) async {
136-
let skreq = sourcekitd.dictionary([
137-
keys.request: requests.editorClose,
138-
keys.name: snapshot.uri.pseudoPath,
139-
keys.cancelBuilds: 0,
140-
])
141-
_ = await orLog("Close helper document for cursor info") {
142-
try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
143-
}
144-
}
145-
146-
/// Ensures that the snapshot is open in sourcekitd before calling body(_:).
147-
///
148-
/// - Parameters:
149-
/// - uri: The URI of the document to be opened.
150-
/// - body: A closure that accepts the DocumentSnapshot as a parameter.
151-
fileprivate func withSnapshot<Result>(
152-
uri: DocumentURI,
153-
_ body: (_ snapshot: DocumentSnapshot) async throws -> Result
154-
) async throws -> Result where Result: Sendable {
155-
let documentManager = try self.documentManager
156-
let snapshot = try await self.latestSnapshotOrDisk(for: uri)
157-
158-
// If the document is not opened in the DocumentManager then send an open
159-
// request to sourcekitd.
160-
guard documentManager.openDocuments.contains(uri) else {
161-
try await openHelperDocument(snapshot)
162-
defer {
163-
Task { await closeHelperDocument(snapshot) }
164-
}
165-
return try await body(snapshot)
166-
}
167-
return try await body(snapshot)
168-
}
169-
170124
/// Provides detailed information about a symbol under the cursor, if any.
171125
///
172126
/// Wraps the information returned by sourcekitd's `cursor_info` request, such as symbol name,
173127
/// USR, and declaration location. This request does minimal processing of the result.
174128
///
175-
/// Ensures that the document is actually open in sourcekitd before sending the request. If not,
176-
/// then the document will be opened for the duration of the request so that sourcekitd does not
177-
/// have to read from disk.
178-
///
179129
/// - Parameters:
180-
/// - url: Document URI in which to perform the request.
130+
/// - url: Document URI in which to perform the request. Must be an open document.
181131
/// - range: The position range within the document to lookup the symbol at.
182132
/// - includeSymbolGraph: Whether or not to ask sourcekitd for the complete symbol graph.
183133
/// - fallbackSettingsAfterTimeout: Whether fallback build settings should be used for the cursor info request if no
@@ -189,46 +139,46 @@ extension SwiftLanguageService {
189139
fallbackSettingsAfterTimeout: Bool,
190140
additionalParameters appendAdditionalParameters: ((SKDRequestDictionary) -> Void)? = nil
191141
) async throws -> (cursorInfo: [CursorInfo], refactorActions: [SemanticRefactorCommand], symbolGraph: String?) {
192-
try await withSnapshot(uri: uri) { snapshot in
193-
let documentManager = try self.documentManager
194-
let offsetRange = snapshot.utf8OffsetRange(of: range)
195-
196-
let keys = self.keys
197-
198-
let skreq = sourcekitd.dictionary([
199-
keys.request: requests.cursorInfo,
200-
keys.cancelOnSubsequentRequest: 0,
201-
keys.offset: offsetRange.lowerBound,
202-
keys.length: offsetRange.upperBound != offsetRange.lowerBound ? offsetRange.count : nil,
203-
keys.sourceFile: snapshot.uri.sourcekitdSourceFile,
204-
keys.primaryFile: snapshot.uri.primaryFile?.pseudoPath,
205-
keys.retrieveSymbolGraph: includeSymbolGraph ? 1 : 0,
206-
keys.compilerArgs: await self.compileCommand(for: uri, fallbackAfterTimeout: fallbackSettingsAfterTimeout)?
207-
.compilerArgs as [SKDRequestValue]?,
208-
])
209-
210-
appendAdditionalParameters?(skreq)
211-
212-
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
213-
214-
var cursorInfoResults: [CursorInfo] = []
215-
if let cursorInfo = CursorInfo(dict, documentManager: documentManager, sourcekitd: sourcekitd) {
216-
cursorInfoResults.append(cursorInfo)
217-
}
218-
cursorInfoResults +=
219-
dict[keys.secondarySymbols]?
220-
.compactMap { CursorInfo($0, documentManager: documentManager, sourcekitd: sourcekitd) } ?? []
221-
let refactorActions =
222-
[SemanticRefactorCommand](
223-
array: dict[keys.refactorActions],
224-
range: range,
225-
textDocument: TextDocumentIdentifier(uri),
226-
keys,
227-
self.sourcekitd.api
228-
) ?? []
229-
let symbolGraph: String? = dict[keys.symbolGraph]
230-
231-
return (cursorInfoResults, refactorActions, symbolGraph)
142+
let documentManager = try self.documentManager
143+
let snapshot = try await self.latestSnapshot(for: uri)
144+
145+
let offsetRange = snapshot.utf8OffsetRange(of: range)
146+
147+
let keys = self.keys
148+
149+
let skreq = sourcekitd.dictionary([
150+
keys.request: requests.cursorInfo,
151+
keys.cancelOnSubsequentRequest: 0,
152+
keys.offset: offsetRange.lowerBound,
153+
keys.length: offsetRange.upperBound != offsetRange.lowerBound ? offsetRange.count : nil,
154+
keys.sourceFile: snapshot.uri.sourcekitdSourceFile,
155+
keys.primaryFile: snapshot.uri.primaryFile?.pseudoPath,
156+
keys.retrieveSymbolGraph: includeSymbolGraph ? 1 : 0,
157+
keys.compilerArgs: await self.compileCommand(for: uri, fallbackAfterTimeout: fallbackSettingsAfterTimeout)?
158+
.compilerArgs as [SKDRequestValue]?,
159+
])
160+
161+
appendAdditionalParameters?(skreq)
162+
163+
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
164+
165+
var cursorInfoResults: [CursorInfo] = []
166+
if let cursorInfo = CursorInfo(dict, documentManager: documentManager, sourcekitd: sourcekitd) {
167+
cursorInfoResults.append(cursorInfo)
232168
}
169+
cursorInfoResults +=
170+
dict[keys.secondarySymbols]?
171+
.compactMap { CursorInfo($0, documentManager: documentManager, sourcekitd: sourcekitd) } ?? []
172+
let refactorActions =
173+
[SemanticRefactorCommand](
174+
array: dict[keys.refactorActions],
175+
range: range,
176+
textDocument: TextDocumentIdentifier(uri),
177+
keys,
178+
self.sourcekitd.api
179+
) ?? []
180+
let symbolGraph: String? = dict[keys.symbolGraph]
181+
182+
return (cursorInfoResults, refactorActions, symbolGraph)
233183
}
234184
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
import BuildSystemIntegration
14+
import Csourcekitd
15+
import Foundation
16+
import LanguageServerProtocol
17+
import SKLogging
18+
import SourceKitD
19+
20+
extension SwiftLanguageService {
21+
fileprivate func openHelperDocument(_ snapshot: DocumentSnapshot) async throws -> String {
22+
let sourceFile = "\(UUID().uuidString):\(snapshot.uri.pseudoPath)"
23+
let skreq = sourcekitd.dictionary([
24+
keys.request: self.requests.editorOpen,
25+
keys.name: sourceFile,
26+
keys.sourceText: snapshot.text,
27+
keys.syntacticOnly: 1,
28+
])
29+
_ = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
30+
return sourceFile
31+
}
32+
33+
fileprivate func closeHelperDocument(_ sourceFile: String, _ snapshot: DocumentSnapshot) async {
34+
let skreq = sourcekitd.dictionary([
35+
keys.request: requests.editorClose,
36+
keys.name: sourceFile,
37+
keys.cancelBuilds: 0,
38+
])
39+
_ = await orLog("Close helper document \"\(sourceFile)\" for cursorInfoFromDisk()") {
40+
try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
41+
}
42+
}
43+
44+
/// Ensures that the snapshot is open in sourcekitd before calling body(_:).
45+
///
46+
/// - Parameters:
47+
/// - uri: The URI of the document to be opened.
48+
/// - body: A closure that accepts the DocumentSnapshot as a parameter.
49+
fileprivate func withSnapshotFromDisk<Result>(
50+
prefix: String,
51+
uri: DocumentURI,
52+
_ body: (_ sourceFile: String, _ primaryFile: String?, _ snapshot: DocumentSnapshot) async throws -> Result
53+
) async throws -> Result where Result: Sendable {
54+
let snapshot = try await self.snapshotFromDisk(for: uri)
55+
let sourceFile = try await openHelperDocument(snapshot)
56+
defer {
57+
Task { await closeHelperDocument(sourceFile, snapshot) }
58+
}
59+
var primaryFile = snapshot.uri.primaryFile?.pseudoPath
60+
if primaryFile == snapshot.uri.pseudoPath {
61+
primaryFile = sourceFile
62+
}
63+
return try await body(sourceFile, primaryFile, snapshot)
64+
}
65+
66+
/// Provides detailed information about a symbol under the cursor, if any.
67+
///
68+
/// Wraps the information returned by sourcekitd's `cursor_info` request, such as symbol name,
69+
/// USR, and declaration location. This request does minimal processing of the result.
70+
///
71+
/// Always uses the document contents on disk regardless of whether or not it is currently open
72+
/// in SourceKit-LSP.
73+
///
74+
/// - Parameters:
75+
/// - url: Document URI in which to perform the request.
76+
/// - range: The position range within the document to lookup the symbol at.
77+
/// - includeSymbolGraph: Whether or not to ask sourcekitd for the complete symbol graph.
78+
/// - fallbackSettingsAfterTimeout: Whether fallback build settings should be used for the cursor info request if no
79+
/// build settings can be retrieved within a timeout.
80+
func cursorInfoFromDisk(
81+
_ uri: DocumentURI,
82+
_ range: Range<Position>,
83+
includeSymbolGraph: Bool = false,
84+
fallbackSettingsAfterTimeout: Bool,
85+
additionalParameters appendAdditionalParameters: ((SKDRequestDictionary) -> Void)? = nil
86+
) async throws -> (cursorInfo: [CursorInfo], refactorActions: [SemanticRefactorCommand], symbolGraph: String?) {
87+
try await withSnapshotFromDisk(prefix: UUID().uuidString, uri: uri) { sourceFile, primaryFile, snapshot in
88+
let documentManager = try self.documentManager
89+
let offsetRange = snapshot.utf8OffsetRange(of: range)
90+
91+
let keys = self.keys
92+
93+
let skreq = sourcekitd.dictionary([
94+
keys.request: requests.cursorInfo,
95+
keys.cancelOnSubsequentRequest: 0,
96+
keys.offset: offsetRange.lowerBound,
97+
keys.length: offsetRange.upperBound != offsetRange.lowerBound ? offsetRange.count : nil,
98+
keys.sourceFile: sourceFile,
99+
keys.primaryFile: primaryFile,
100+
keys.retrieveSymbolGraph: includeSymbolGraph ? 1 : 0,
101+
keys.compilerArgs: await self.buildSettings(for: uri, fallbackAfterTimeout: fallbackSettingsAfterTimeout)?
102+
.patching(newFile: sourceFile, originalFile: uri).compilerArguments as [SKDRequestValue]?,
103+
])
104+
105+
appendAdditionalParameters?(skreq)
106+
107+
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
108+
109+
var cursorInfoResults: [CursorInfo] = []
110+
if let cursorInfo = CursorInfo(dict, documentManager: documentManager, sourcekitd: sourcekitd) {
111+
cursorInfoResults.append(cursorInfo)
112+
}
113+
cursorInfoResults +=
114+
dict[keys.secondarySymbols]?
115+
.compactMap { CursorInfo($0, documentManager: documentManager, sourcekitd: sourcekitd) } ?? []
116+
let refactorActions =
117+
[SemanticRefactorCommand](
118+
array: dict[keys.refactorActions],
119+
range: range,
120+
textDocument: TextDocumentIdentifier(uri),
121+
keys,
122+
self.sourcekitd.api
123+
) ?? []
124+
let symbolGraph: String? = dict[keys.symbolGraph]
125+
126+
return (cursorInfoResults, refactorActions, symbolGraph)
127+
}
128+
}
129+
}

Sources/SourceKitLSP/Swift/SwiftLanguageService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,15 +279,15 @@ package actor SwiftLanguageService: LanguageService, Sendable {
279279
}
280280
}
281281

282-
func latestSnapshotOrDisk(for uri: DocumentURI) async throws -> DocumentSnapshot {
282+
func snapshotFromDisk(for uri: DocumentURI) async throws -> DocumentSnapshot {
283283
switch try? ReferenceDocumentURL(from: uri) {
284284
case .macroExpansion(let data):
285285
let content = try await self.macroExpansionManager.macroExpansion(for: data)
286286
return DocumentSnapshot(uri: uri, language: .swift, version: 0, lineTable: LineTable(content))
287287
case .generatedInterface(let data):
288288
return try await self.generatedInterfaceManager.snapshot(of: data)
289289
case nil:
290-
guard let snapshot = try documentManager.latestSnapshotOrDisk(uri, language: .swift) else {
290+
guard let snapshot = try DocumentSnapshot(withContentsFromDisk: uri, language: .swift) else {
291291
throw ResponseError.unknown("Failed to find snapshot for '\(uri)'")
292292
}
293293
return snapshot

0 commit comments

Comments
 (0)