Skip to content

Allow specification of SourceKitLSPOptions in the initialize request and look for SourceKit-LSP options in $XDG_CONFIG_HOME/sourcekit-lsp #1534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Documentation/Configuration File.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
`.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
- `~/.sourcekit-lsp/config.json`
- On macOS: `~/Library/Application Support/org.swift.sourcekit-lsp/config.json` from the various `Library` folders on the system
- If the `XDG_CONFIG_HOME` environment variable is set: `$XDG_CONFIG_HOME/org.swift.sourcekit-lsp/config.json`
- If the `XDG_CONFIG_HOME` environment variable is set: `$XDG_CONFIG_HOME/sourcekit-lsp/config.json`
- Initialization options passed in the initialize request
- A `.sourcekit-lsp/config.json` file in a workspace’s root

The structure of the file is currently not guaranteed to be stable. Options may be removed or renamed.
Expand Down
17 changes: 16 additions & 1 deletion Sources/SKCore/SourceKitLSPOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import Foundation
import LSPLogging
import SKCore
import LanguageServerProtocol
import SKSupport

import struct TSCBasic.AbsolutePath
Expand Down Expand Up @@ -200,6 +200,21 @@ public struct SourceKitLSPOptions: Sendable, Codable {
self.workDoneProgressDebounce = workDoneProgressDebounce
}

public init?(fromLSPAny lspAny: LSPAny?) throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this type to LSP so that clients can also construct it (though for that to actually be true we probably need to split LSP out of this repo, but still... seems like a reasonable place for it to live)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started moving it to the LanguageServerProtocol module and then decided against it. I can’t really tell why but it didn’t feel at home there.

guard let lspAny else {
return nil
}
let jsonEncoded = try JSONEncoder().encode(lspAny)
self = try JSONDecoder().decode(Self.self, from: jsonEncoded)
}

public var asLSPAny: LSPAny {
get throws {
let jsonEncoded = try JSONEncoder().encode(self)
return try JSONDecoder().decode(LSPAny.self, from: jsonEncoded)
}
}

public init?(path: URL?) {
guard let path, let contents = try? String(contentsOf: path, encoding: .utf8) else {
return nil
Expand Down
2 changes: 2 additions & 0 deletions Sources/SKTestSupport/MultiFileTestProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public class MultiFileTestProject {
public init(
files: [RelativeFileLocation: String],
workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] },
initializationOptions: LSPAny? = nil,
capabilities: ClientCapabilities = ClientCapabilities(),
options: SourceKitLSPOptions = .testDefault(),
testHooks: TestHooks = TestHooks(),
Expand Down Expand Up @@ -118,6 +119,7 @@ public class MultiFileTestProject {
self.testClient = try await TestSourceKitLSPClient(
options: options,
testHooks: testHooks,
initializationOptions: initializationOptions,
capabilities: capabilities,
usePullDiagnostics: usePullDiagnostics,
enableBackgroundIndexing: enableBackgroundIndexing,
Expand Down
2 changes: 2 additions & 0 deletions Sources/SKTestSupport/SwiftPMTestProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ public class SwiftPMTestProject: MultiFileTestProject {
files: [RelativeFileLocation: String],
manifest: String = SwiftPMTestProject.defaultPackageManifest,
workspaces: (URL) async throws -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] },
initializationOptions: LSPAny? = nil,
capabilities: ClientCapabilities = ClientCapabilities(),
options: SourceKitLSPOptions = .testDefault(),
testHooks: TestHooks = TestHooks(),
Expand Down Expand Up @@ -190,6 +191,7 @@ public class SwiftPMTestProject: MultiFileTestProject {
try await super.init(
files: filesByPath,
workspaces: workspaces,
initializationOptions: initializationOptions,
capabilities: capabilities,
options: options,
testHooks: testHooks,
Expand Down
9 changes: 7 additions & 2 deletions Sources/SourceKitLSP/SourceKitLSPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public actor SourceKitLSPServer {
/// This ensures that we only inform the user about background indexing not being supported for these projects once.
private var didSendBackgroundIndexingNotSupportedNotification = false

let options: SourceKitLSPOptions
var options: SourceKitLSPOptions

let testHooks: TestHooks

Expand Down Expand Up @@ -959,6 +959,10 @@ extension SourceKitLSPServer {

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

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

let options = self.options
let workspace = await Workspace(
documentManager: self.documentManager,
rootUri: req.rootURI,
capabilityRegistry: self.capabilityRegistry!,
toolchainRegistry: self.toolchainRegistry,
options: self.options,
options: options,
testHooks: testHooks,
underlyingBuildSystem: nil,
index: nil,
Expand Down
2 changes: 1 addition & 1 deletion Sources/SourceKitLSP/Swift/SwiftLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ extension SwiftLanguageService {
var canInlineMacro = false

let showMacroExpansionsIsEnabled =
self.sourceKitLSPServer?.options.hasExperimentalFeature(.showMacroExpansions) ?? false
await self.sourceKitLSPServer?.options.hasExperimentalFeature(.showMacroExpansions) ?? false

var refactorActions = cursorInfoResponse.refactorActions.compactMap {
let lspCommand = $0.asCommand()
Expand Down
2 changes: 1 addition & 1 deletion Sources/sourcekit-lsp/SourceKitLSP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ struct SourceKitLSP: AsyncParsableCommand {
override: SourceKitLSPOptions(
path:
URL(fileURLWithPath: xdgConfigHome)
.appendingPathComponent("org.swift.sourcekit-lsp")
.appendingPathComponent("sourcekit-lsp")
.appendingPathComponent("config.json")
)
)
Expand Down
64 changes: 64 additions & 0 deletions Tests/SourceKitLSPTests/WorkspaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -861,4 +861,68 @@ final class WorkspaceTests: XCTestCase {
}
XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"])
}

func testOptionsInInitializeRequest() async throws {
let project = try await SwiftPMTestProject(
files: [
"Test.swift": """
func test() {
#if TEST
let x: String = 1
#endif
}
"""
],
initializationOptions: SourceKitLSPOptions(
swiftPM: SourceKitLSPOptions.SwiftPMOptions(swiftCompilerFlags: ["-D", "TEST"])
).asLSPAny
)

let (uri, _) = try project.openDocument("Test.swift")
let diagnostics = try await project.testClient.send(
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
)
guard case .full(let diagnostics) = diagnostics else {
XCTFail("Expected full diagnostics")
return
}
XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"])
}

func testWorkspaceOptionsOverrideGlobalOptions() async throws {
let project = try await SwiftPMTestProject(
files: [
"/.sourcekit-lsp/config.json": """
{
"swiftPM": {
"swiftCompilerFlags": ["-D", "TEST"]
}
}
""",
"Test.swift": """
func test() {
#if TEST
let x: String = 1
#endif
#if OTHER
let x: String = 1.0
#endif
}
""",
],
initializationOptions: SourceKitLSPOptions(
swiftPM: SourceKitLSPOptions.SwiftPMOptions(swiftCompilerFlags: ["-D", "OTHER"])
).asLSPAny
)

let (uri, _) = try project.openDocument("Test.swift")
let diagnostics = try await project.testClient.send(
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
)
guard case .full(let diagnostics) = diagnostics else {
XCTFail("Expected full diagnostics")
return
}
XCTAssertEqual(diagnostics.items.map(\.message), ["Cannot convert value of type 'Int' to specified type 'String'"])
}
}