Skip to content

Commit 16e4f83

Browse files
authored
Merge pull request swiftlang#1534 from ahoppen/configuration-improvements
Allow specification of SourceKitLSPOptions in the initialize request and look for SourceKit-LSP options in `$XDG_CONFIG_HOME/sourcekit-lsp`
2 parents 6b88cfe + 2c21284 commit 16e4f83

File tree

8 files changed

+95
-6
lines changed

8 files changed

+95
-6
lines changed

Documentation/Configuration File.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
`.sourcekit-lsp/config.json` configuration files can be used to modify the behavior of SourceKit-LSP in various ways. The following locations are checked. Settings in later configuration files override settings in earlier configuration files
44
- `~/.sourcekit-lsp/config.json`
55
- On macOS: `~/Library/Application Support/org.swift.sourcekit-lsp/config.json` from the various `Library` folders on the system
6-
- If the `XDG_CONFIG_HOME` environment variable is set: `$XDG_CONFIG_HOME/org.swift.sourcekit-lsp/config.json`
6+
- If the `XDG_CONFIG_HOME` environment variable is set: `$XDG_CONFIG_HOME/sourcekit-lsp/config.json`
7+
- Initialization options passed in the initialize request
78
- A `.sourcekit-lsp/config.json` file in a workspace’s root
89

910
The structure of the file is currently not guaranteed to be stable. Options may be removed or renamed.

Sources/SKCore/SourceKitLSPOptions.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import Foundation
1414
import LSPLogging
15-
import SKCore
15+
import LanguageServerProtocol
1616
import SKSupport
1717

1818
import struct TSCBasic.AbsolutePath
@@ -250,6 +250,21 @@ public struct SourceKitLSPOptions: Sendable, Codable {
250250
self.workDoneProgressDebounceDuration = workDoneProgressDebounceDuration
251251
}
252252

253+
public init?(fromLSPAny lspAny: LSPAny?) throws {
254+
guard let lspAny else {
255+
return nil
256+
}
257+
let jsonEncoded = try JSONEncoder().encode(lspAny)
258+
self = try JSONDecoder().decode(Self.self, from: jsonEncoded)
259+
}
260+
261+
public var asLSPAny: LSPAny {
262+
get throws {
263+
let jsonEncoded = try JSONEncoder().encode(self)
264+
return try JSONDecoder().decode(LSPAny.self, from: jsonEncoded)
265+
}
266+
}
267+
253268
public init?(path: URL?) {
254269
guard let path, let contents = try? String(contentsOf: path, encoding: .utf8) else {
255270
return nil

Sources/SKTestSupport/MultiFileTestProject.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public class MultiFileTestProject {
8080
public init(
8181
files: [RelativeFileLocation: String],
8282
workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] },
83+
initializationOptions: LSPAny? = nil,
8384
capabilities: ClientCapabilities = ClientCapabilities(),
8485
options: SourceKitLSPOptions = .testDefault(),
8586
testHooks: TestHooks = TestHooks(),
@@ -118,6 +119,7 @@ public class MultiFileTestProject {
118119
self.testClient = try await TestSourceKitLSPClient(
119120
options: options,
120121
testHooks: testHooks,
122+
initializationOptions: initializationOptions,
121123
capabilities: capabilities,
122124
usePullDiagnostics: usePullDiagnostics,
123125
enableBackgroundIndexing: enableBackgroundIndexing,

Sources/SKTestSupport/SwiftPMTestProject.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ public class SwiftPMTestProject: MultiFileTestProject {
150150
files: [RelativeFileLocation: String],
151151
manifest: String = SwiftPMTestProject.defaultPackageManifest,
152152
workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] },
153+
initializationOptions: LSPAny? = nil,
153154
capabilities: ClientCapabilities = ClientCapabilities(),
154155
options: SourceKitLSPOptions = .testDefault(),
155156
testHooks: TestHooks = TestHooks(),
@@ -190,6 +191,7 @@ public class SwiftPMTestProject: MultiFileTestProject {
190191
try await super.init(
191192
files: filesByPath,
192193
workspaces: workspaces,
194+
initializationOptions: initializationOptions,
193195
capabilities: capabilities,
194196
options: options,
195197
testHooks: testHooks,

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public actor SourceKitLSPServer {
104104
/// This ensures that we only inform the user about background indexing not being supported for these projects once.
105105
private var didSendBackgroundIndexingNotSupportedNotification = false
106106

107-
let options: SourceKitLSPOptions
107+
var options: SourceKitLSPOptions
108108

109109
let testHooks: TestHooks
110110

@@ -959,6 +959,10 @@ extension SourceKitLSPServer {
959959

960960
func initialize(_ req: InitializeRequest) async throws -> InitializeResult {
961961
capabilityRegistry = CapabilityRegistry(clientCapabilities: req.capabilities)
962+
self.options = SourceKitLSPOptions.merging(
963+
base: self.options,
964+
override: orLog("Parsing SourceKitLSPOptions", { try SourceKitLSPOptions(fromLSPAny: req.initializationOptions) })
965+
)
962966

963967
await workspaceQueue.async { [testHooks] in
964968
if let workspaceFolders = req.workspaceFolders {
@@ -983,12 +987,13 @@ extension SourceKitLSPServer {
983987
if self.workspaces.isEmpty {
984988
logger.error("No workspace found")
985989

990+
let options = self.options
986991
let workspace = await Workspace(
987992
documentManager: self.documentManager,
988993
rootUri: req.rootURI,
989994
capabilityRegistry: self.capabilityRegistry!,
990995
toolchainRegistry: self.toolchainRegistry,
991-
options: self.options,
996+
options: options,
992997
testHooks: testHooks,
993998
underlyingBuildSystem: nil,
994999
index: nil,

Sources/SourceKitLSP/Swift/SwiftLanguageService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,7 @@ extension SwiftLanguageService {
807807
var canInlineMacro = false
808808

809809
let showMacroExpansionsIsEnabled =
810-
self.sourceKitLSPServer?.options.hasExperimentalFeature(.showMacroExpansions) ?? false
810+
await self.sourceKitLSPServer?.options.hasExperimentalFeature(.showMacroExpansions) ?? false
811811

812812
var refactorActions = cursorInfoResponse.refactorActions.compactMap {
813813
let lspCommand = $0.asCommand()

Sources/sourcekit-lsp/SourceKitLSP.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ struct SourceKitLSP: AsyncParsableCommand {
264264
override: SourceKitLSPOptions(
265265
path:
266266
URL(fileURLWithPath: xdgConfigHome)
267-
.appendingPathComponent("org.swift.sourcekit-lsp")
267+
.appendingPathComponent("sourcekit-lsp")
268268
.appendingPathComponent("config.json")
269269
)
270270
)

Tests/SourceKitLSPTests/WorkspaceTests.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,4 +861,68 @@ final class WorkspaceTests: XCTestCase {
861861
}
862862
XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"])
863863
}
864+
865+
func testOptionsInInitializeRequest() async throws {
866+
let project = try await SwiftPMTestProject(
867+
files: [
868+
"Test.swift": """
869+
func test() {
870+
#if TEST
871+
let x: String = 1
872+
#endif
873+
}
874+
"""
875+
],
876+
initializationOptions: SourceKitLSPOptions(
877+
swiftPM: SourceKitLSPOptions.SwiftPMOptions(swiftCompilerFlags: ["-D", "TEST"])
878+
).asLSPAny
879+
)
880+
881+
let (uri, _) = try project.openDocument("Test.swift")
882+
let diagnostics = try await project.testClient.send(
883+
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
884+
)
885+
guard case .full(let diagnostics) = diagnostics else {
886+
XCTFail("Expected full diagnostics")
887+
return
888+
}
889+
XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"])
890+
}
891+
892+
func testWorkspaceOptionsOverrideGlobalOptions() async throws {
893+
let project = try await SwiftPMTestProject(
894+
files: [
895+
"/.sourcekit-lsp/config.json": """
896+
{
897+
"swiftPM": {
898+
"swiftCompilerFlags": ["-D", "TEST"]
899+
}
900+
}
901+
""",
902+
"Test.swift": """
903+
func test() {
904+
#if TEST
905+
let x: String = 1
906+
#endif
907+
#if OTHER
908+
let x: String = 1.0
909+
#endif
910+
}
911+
""",
912+
],
913+
initializationOptions: SourceKitLSPOptions(
914+
swiftPM: SourceKitLSPOptions.SwiftPMOptions(swiftCompilerFlags: ["-D", "OTHER"])
915+
).asLSPAny
916+
)
917+
918+
let (uri, _) = try project.openDocument("Test.swift")
919+
let diagnostics = try await project.testClient.send(
920+
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
921+
)
922+
guard case .full(let diagnostics) = diagnostics else {
923+
XCTFail("Expected full diagnostics")
924+
return
925+
}
926+
XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"])
927+
}
864928
}

0 commit comments

Comments
 (0)