Skip to content

Commit 8c15e27

Browse files
use a helper document in cursorInfo()
1 parent 909e639 commit 8c15e27

16 files changed

+140
-70
lines changed

Sources/BuildSystemIntegration/FileBuildSettings.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,15 @@ package struct FileBuildSettings: Equatable, Sendable {
5555
///
5656
/// This patches the arguments by searching for the argument corresponding to
5757
/// `originalFile` and replacing it.
58-
func patching(newFile: DocumentURI, originalFile: DocumentURI) -> FileBuildSettings {
58+
package func patching(newFile: DocumentURI, originalFile: DocumentURI) -> FileBuildSettings {
59+
return patching(newFile: newFile.pseudoPath, originalFile: originalFile)
60+
}
61+
62+
/// Return arguments suitable for use by `newFile`.
63+
///
64+
/// This patches the arguments by searching for the argument corresponding to
65+
/// `originalFile` and replacing it.
66+
package func patching(newFile: String, originalFile: DocumentURI) -> FileBuildSettings {
5967
var arguments = self.compilerArguments
6068
// URL.lastPathComponent is only set for file URLs but we want to also infer a file extension for non-file URLs like
6169
// untitled:file.cpp
@@ -66,7 +74,7 @@ package struct FileBuildSettings: Equatable, Sendable {
6674
// the file system.
6775
$0.hasSuffix(basename) && originalFile.pseudoPath.hasSuffix($0)
6876
}) {
69-
arguments[index] = newFile.pseudoPath
77+
arguments[index] = newFile
7078
// The `-x<lang>` flag needs to be before the possible `-c <header file>`
7179
// argument in order for Clang to respect it. If there is a pre-existing `-x`
7280
// flag though, Clang will honor that one instead since it comes after.

Sources/DocCDocumentation/DocCDocumentationManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ package struct DocCDocumentationManager: Sendable {
159159
) async throws -> DoccDocumentationResponse {
160160
// Make inputs consumable by DocC
161161
var externalIDsToConvert: [String]? = nil
162-
var overridingDocumentationComments = [String: [String]]()
162+
var overridingDocumentationComments: [String: [String]] = [:]
163163
if let symbolUSR {
164164
externalIDsToConvert = [symbolUSR]
165165
if let overrideDocComments {

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,11 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
425425

426426
return DocumentPositions(markers: markers, textWithoutMarkers: textWithoutMarkers)
427427
}
428+
429+
/// Closes the document with the given URI.
430+
package func closeDocument(uri: DocumentURI) {
431+
self.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(uri)))
432+
}
428433
}
429434

430435
// MARK: - DocumentPositions

Sources/SourceKitLSP/Documentation/DoccDocumentationHandler.swift

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

1313
#if canImport(DocCDocumentation)
14+
import BuildSystemIntegration
1415
import DocCDocumentation
1516
import Foundation
1617
@preconcurrency import IndexStoreDB

Sources/SourceKitLSP/Rename.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ extension SwiftLanguageService {
355355
let req = sourcekitd.dictionary([
356356
keys.request: sourcekitd.requests.nameTranslation,
357357
keys.sourceFile: snapshot.uri.pseudoPath,
358-
keys.compilerArgs: await self.buildSettings(for: snapshot.uri, fallbackAfterTimeout: false)?.compilerArgs
358+
keys.compilerArgs: await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: false)?.compilerArgs
359359
as [SKDRequestValue]?,
360360
keys.offset: snapshot.utf8Offset(of: snapshot.position(of: symbolLocation)),
361361
keys.nameKind: sourcekitd.values.nameSwift,
@@ -407,7 +407,7 @@ extension SwiftLanguageService {
407407
let req = sourcekitd.dictionary([
408408
keys.request: sourcekitd.requests.nameTranslation,
409409
keys.sourceFile: snapshot.uri.pseudoPath,
410-
keys.compilerArgs: await self.buildSettings(for: snapshot.uri, fallbackAfterTimeout: false)?.compilerArgs
410+
keys.compilerArgs: await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: false)?.compilerArgs
411411
as [SKDRequestValue]?,
412412
keys.offset: snapshot.utf8Offset(of: snapshot.position(of: symbolLocation)),
413413
keys.nameKind: sourcekitd.values.nameObjc,

Sources/SourceKitLSP/Swift/CodeCompletion.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension SwiftLanguageService {
2323
let completionPos = await adjustPositionToStartOfIdentifier(req.position, in: snapshot)
2424
let filterText = String(snapshot.text[snapshot.index(of: completionPos)..<snapshot.index(of: req.position)])
2525

26-
let buildSettings = await buildSettings(for: snapshot.uri, fallbackAfterTimeout: false)
26+
let compileCommand = await compileCommand(for: snapshot.uri, fallbackAfterTimeout: false)
2727

2828
let inferredIndentationWidth = BasicFormat.inferIndentation(of: await syntaxTreeManager.syntaxTree(for: snapshot))
2929

@@ -34,7 +34,7 @@ extension SwiftLanguageService {
3434
indentationWidth: inferredIndentationWidth,
3535
completionPosition: completionPos,
3636
cursorPosition: req.position,
37-
compileCommand: buildSettings,
37+
compileCommand: compileCommand,
3838
clientCapabilities: capabilityRegistry.clientCapabilities,
3939
filterText: filterText
4040
)

Sources/SourceKitLSP/Swift/CursorInfo.swift

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

13+
import BuildSystemIntegration
1314
import Csourcekitd
1415
import LanguageServerProtocol
1516
import SKLogging
@@ -121,13 +122,62 @@ extension CursorInfoError: CustomStringConvertible {
121122
}
122123

123124
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+
124170
/// Provides detailed information about a symbol under the cursor, if any.
125171
///
126172
/// Wraps the information returned by sourcekitd's `cursor_info` request, such as symbol name,
127173
/// USR, and declaration location. This request does minimal processing of the result.
128174
///
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+
///
129179
/// - Parameters:
130-
/// - url: Document URI in which to perform the request. Must be an open document.
180+
/// - url: Document URI in which to perform the request.
131181
/// - range: The position range within the document to lookup the symbol at.
132182
/// - includeSymbolGraph: Whether or not to ask sourcekitd for the complete symbol graph.
133183
/// - fallbackSettingsAfterTimeout: Whether fallback build settings should be used for the cursor info request if no
@@ -139,46 +189,46 @@ extension SwiftLanguageService {
139189
fallbackSettingsAfterTimeout: Bool,
140190
additionalParameters appendAdditionalParameters: ((SKDRequestDictionary) -> Void)? = nil
141191
) async throws -> (cursorInfo: [CursorInfo], refactorActions: [SemanticRefactorCommand], symbolGraph: String?) {
142-
let documentManager = try self.documentManager
143-
let snapshot = try await self.latestSnapshotOrDisk(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.buildSettings(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)
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)
168232
}
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)
183233
}
184234
}

Sources/SourceKitLSP/Swift/DoccDocumentation.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#if canImport(DocCDocumentation)
14+
import BuildSystemIntegration
1415
import DocCDocumentation
1516
import Foundation
1617
package import LanguageServerProtocol
@@ -125,9 +126,7 @@ fileprivate struct DocumentableSymbol {
125126
}
126127
}
127128
}
128-
}
129129

130-
fileprivate extension DocumentableSymbol {
131130
static func findNearestSymbol(syntaxTree: SourceFileSyntax, position: AbsolutePosition) -> DocumentableSymbol? {
132131
guard let token = syntaxTree.token(at: position) else {
133132
return nil

Sources/SourceKitLSP/Swift/GeneratedInterfaceManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ actor GeneratedInterfaceManager {
119119
keys.groupName: document.groupName,
120120
keys.name: document.sourcekitdDocumentName,
121121
keys.synthesizedExtension: 1,
122-
keys.compilerArgs: await swiftLanguageService.buildSettings(for: try document.uri, fallbackAfterTimeout: false)?
122+
keys.compilerArgs: await swiftLanguageService.compileCommand(for: try document.uri, fallbackAfterTimeout: false)?
123123
.compilerArgs as [SKDRequestValue]?,
124124
])
125125

Sources/SourceKitLSP/Swift/MacroExpansion.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,16 @@ actor MacroExpansionManager {
8888
}
8989

9090
let snapshot = try await swiftLanguageService.latestSnapshot(for: uri)
91-
let buildSettings = await swiftLanguageService.buildSettings(for: uri, fallbackAfterTimeout: false)
91+
let compileCommand = await swiftLanguageService.compileCommand(for: uri, fallbackAfterTimeout: false)
9292

9393
if let cacheEntry = cache.first(where: {
94-
$0.snapshotID == snapshot.id && $0.range == range && $0.buildSettings == buildSettings
94+
$0.snapshotID == snapshot.id && $0.range == range && $0.buildSettings == compileCommand
9595
}) {
9696
return cacheEntry.value
9797
}
98-
let macroExpansions = try await macroExpansionsImpl(in: snapshot, at: range, buildSettings: buildSettings)
98+
let macroExpansions = try await macroExpansionsImpl(in: snapshot, at: range, buildSettings: compileCommand)
9999
cache.insert(
100-
CacheEntry(snapshot: snapshot, range: range, buildSettings: buildSettings, value: macroExpansions),
100+
CacheEntry(snapshot: snapshot, range: range, buildSettings: compileCommand, value: macroExpansions),
101101
at: 0
102102
)
103103

0 commit comments

Comments
 (0)