From c83061e341cf94d27feb32b311ad64de69982e41 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 15 Jul 2025 14:14:37 +0200 Subject: [PATCH 1/4] Remove reference to `ClangLanguageService` in Rename.swift --- .../Clang/ClangLanguageService.swift | 63 ++++++++++++ Sources/SourceKitLSP/Rename.swift | 99 ++++--------------- 2 files changed, 80 insertions(+), 82 deletions(-) diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift index 823278161..7425917b7 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift @@ -634,6 +634,69 @@ extension ClangLanguageService { func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse { throw ResponseError.unknown("unsupported method") } + + func rename(_ renameRequest: RenameRequest) async throws -> (edits: WorkspaceEdit, usr: String?) { + async let edits = forwardRequestToClangd(renameRequest) + let symbolInfoRequest = SymbolInfoRequest( + textDocument: renameRequest.textDocument, + position: renameRequest.position + ) + let symbolDetail = try await forwardRequestToClangd(symbolInfoRequest).only + return (try await edits ?? WorkspaceEdit(), symbolDetail?.usr) + } + + func editsToRename( + locations renameLocations: [RenameLocation], + in snapshot: DocumentSnapshot, + oldName oldCrossLanguageName: CrossLanguageName, + newName newCrossLanguageName: CrossLanguageName + ) async throws -> [TextEdit] { + let positions = [ + snapshot.uri: renameLocations.compactMap { snapshot.position(of: $0) } + ] + guard + let oldName = oldCrossLanguageName.clangName, + let newName = newCrossLanguageName.clangName + else { + throw ResponseError.unknown( + "Failed to rename \(snapshot.uri.forLogging) because the clang name for rename is unknown" + ) + } + let request = IndexedRenameRequest( + textDocument: TextDocumentIdentifier(snapshot.uri), + oldName: oldName, + newName: newName, + positions: positions + ) + do { + let edits = try await forwardRequestToClangd(request) + return edits?.changes?[snapshot.uri] ?? [] + } catch { + logger.error("Failed to get indexed rename edits: \(error.forLogging)") + return [] + } + } + + package func prepareRename( + _ request: PrepareRenameRequest + ) async throws -> (prepareRename: PrepareRenameResponse, usr: String?)? { + guard let prepareRename = try await forwardRequestToClangd(request) else { + return nil + } + let symbolInfo = try await forwardRequestToClangd( + SymbolInfoRequest(textDocument: request.textDocument, position: request.position) + ) + return (prepareRename, symbolInfo.only?.usr) + } + + package func editsToRenameParametersInFunctionBody( + snapshot: DocumentSnapshot, + renameLocation: RenameLocation, + newName: CrossLanguageName + ) async -> [TextEdit] { + // When renaming a clang function name, we don't need to rename any references to the arguments. + return [] + } } /// Clang build settings derived from a `FileBuildSettingsChange`. diff --git a/Sources/SourceKitLSP/Rename.swift b/Sources/SourceKitLSP/Rename.swift index f566b277a..1b4384c3e 100644 --- a/Sources/SourceKitLSP/Rename.swift +++ b/Sources/SourceKitLSP/Rename.swift @@ -14,6 +14,7 @@ import Csourcekitd import Foundation import IndexStoreDB package import LanguageServerProtocol +import LanguageServerProtocolExtensions import SKLogging import SKUtilities import SemanticIndex @@ -451,11 +452,11 @@ extension SwiftLanguageService { package struct CrossLanguageName: Sendable { /// The name of the symbol in clang languages or `nil` if the symbol is defined in Swift, doesn't have any references /// from clang languages and thus hasn't been translated. - fileprivate let clangName: String? + package let clangName: String? /// The name of the symbol in Swift or `nil` if the symbol is defined in clang, doesn't have any references from /// Swift and thus hasn't been translated. - fileprivate let swiftName: String? + package let swiftName: String? fileprivate var compoundSwiftName: CompoundDeclName? { if let swiftName { @@ -465,10 +466,10 @@ package struct CrossLanguageName: Sendable { } /// the language that the symbol is defined in. - fileprivate let definitionLanguage: Language + package let definitionLanguage: Language /// The name of the symbol in the language that it is defined in. - var definitionName: String? { + package var definitionName: String? { switch definitionLanguage { case .c, .cpp, .objective_c, .objective_cpp: return clangName @@ -576,20 +577,10 @@ extension SourceKitLSPServer { } let definitionDocumentUri = definitionOccurrence.location.documentUri - guard - let definitionLanguageService = await self.languageService( - for: definitionDocumentUri, - definitionLanguage, - in: workspace - ) - else { - throw ResponseError.unknown("Failed to get language service for the document defining \(usr)") - } - let definitionName = overrideName ?? definitionSymbol.name - switch definitionLanguageService { - case is ClangLanguageService: + switch definitionLanguage.semanticKind { + case .clang: let swiftName: String? if let swiftReference = await getReferenceFromSwift(usr: usr, index: index, workspace: workspace) { let isObjectiveCSelector = definitionLanguage == .objective_c && definitionSymbol.kind.isMethod @@ -604,7 +595,16 @@ extension SourceKitLSPServer { swiftName = nil } return CrossLanguageName(clangName: definitionName, swiftName: swiftName, definitionLanguage: definitionLanguage) - case let swiftLanguageService as SwiftLanguageService: + case .swift: + guard + let swiftLanguageService = await self.languageService( + for: definitionDocumentUri, + definitionLanguage, + in: workspace + ) as? SwiftLanguageService + else { + throw ResponseError.unknown("Failed to get language service for the document defining \(usr)") + } // Continue iteration if the symbol provider is not clang. // If we terminate early by returning `false` from the closure, `forEachSymbolOccurrence` returns `true`, // indicating that we have found a reference from clang. @@ -1358,71 +1358,6 @@ extension SwiftLanguageService { // MARK: - Clang -extension ClangLanguageService { - func rename(_ renameRequest: RenameRequest) async throws -> (edits: WorkspaceEdit, usr: String?) { - async let edits = forwardRequestToClangd(renameRequest) - let symbolInfoRequest = SymbolInfoRequest( - textDocument: renameRequest.textDocument, - position: renameRequest.position - ) - let symbolDetail = try await forwardRequestToClangd(symbolInfoRequest).only - return (try await edits ?? WorkspaceEdit(), symbolDetail?.usr) - } - - func editsToRename( - locations renameLocations: [RenameLocation], - in snapshot: DocumentSnapshot, - oldName oldCrossLanguageName: CrossLanguageName, - newName newCrossLanguageName: CrossLanguageName - ) async throws -> [TextEdit] { - let positions = [ - snapshot.uri: renameLocations.compactMap { snapshot.position(of: $0) } - ] - guard - let oldName = oldCrossLanguageName.clangName, - let newName = newCrossLanguageName.clangName - else { - throw ResponseError.unknown( - "Failed to rename \(snapshot.uri.forLogging) because the clang name for rename is unknown" - ) - } - let request = IndexedRenameRequest( - textDocument: TextDocumentIdentifier(snapshot.uri), - oldName: oldName, - newName: newName, - positions: positions - ) - do { - let edits = try await forwardRequestToClangd(request) - return edits?.changes?[snapshot.uri] ?? [] - } catch { - logger.error("Failed to get indexed rename edits: \(error.forLogging)") - return [] - } - } - - package func prepareRename( - _ request: PrepareRenameRequest - ) async throws -> (prepareRename: PrepareRenameResponse, usr: String?)? { - guard let prepareRename = try await forwardRequestToClangd(request) else { - return nil - } - let symbolInfo = try await forwardRequestToClangd( - SymbolInfoRequest(textDocument: request.textDocument, position: request.position) - ) - return (prepareRename, symbolInfo.only?.usr) - } - - package func editsToRenameParametersInFunctionBody( - snapshot: DocumentSnapshot, - renameLocation: RenameLocation, - newName: CrossLanguageName - ) async -> [TextEdit] { - // When renaming a clang function name, we don't need to rename any references to the arguments. - return [] - } -} - fileprivate extension SyntaxProtocol { /// Returns the parent node and casts it to the specified type. func parent(as syntaxType: S.Type) -> S? { From 4f792e8781dd405eff7911c9c07a3dedba566c10 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 17 Jul 2025 08:47:06 +0200 Subject: [PATCH 2/4] Register language services in a dynamic registry --- .../InProcessSourceKitLSPClient.swift | 1 + .../TestSourceKitLSPClient.swift | 1 + Sources/SourceKitLSP/CMakeLists.txt | 2 +- Sources/SourceKitLSP/LanguageServerType.swift | 54 ------------------- .../LanguageServiceRegistry.swift | 43 +++++++++++++++ Sources/SourceKitLSP/SourceKitLSPServer.swift | 24 ++++++--- Sources/sourcekit-lsp/SourceKitLSP.swift | 1 + 7 files changed, 65 insertions(+), 61 deletions(-) delete mode 100644 Sources/SourceKitLSP/LanguageServerType.swift create mode 100644 Sources/SourceKitLSP/LanguageServiceRegistry.swift diff --git a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift index fefdd2332..6e7dea57c 100644 --- a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift +++ b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift @@ -60,6 +60,7 @@ public final class InProcessSourceKitLSPClient: Sendable { self.server = SourceKitLSPServer( client: serverToClientConnection, toolchainRegistry: toolchainRegistry, + languageServerRegistry: LanguageServiceRegistry(), options: options, hooks: hooks, onExit: { diff --git a/Sources/SKTestSupport/TestSourceKitLSPClient.swift b/Sources/SKTestSupport/TestSourceKitLSPClient.swift index 5a062fe8e..db1631f26 100644 --- a/Sources/SKTestSupport/TestSourceKitLSPClient.swift +++ b/Sources/SKTestSupport/TestSourceKitLSPClient.swift @@ -164,6 +164,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { server = SourceKitLSPServer( client: serverToClientConnection, toolchainRegistry: toolchainRegistry, + languageServerRegistry: LanguageServiceRegistry(), options: options, hooks: hooks, onExit: { diff --git a/Sources/SourceKitLSP/CMakeLists.txt b/Sources/SourceKitLSP/CMakeLists.txt index c1aa45d28..0b8d808a0 100644 --- a/Sources/SourceKitLSP/CMakeLists.txt +++ b/Sources/SourceKitLSP/CMakeLists.txt @@ -6,7 +6,7 @@ add_library(SourceKitLSP STATIC Hooks.swift IndexProgressManager.swift IndexStoreDB+MainFilesProvider.swift - LanguageServerType.swift + LanguageServiceRegistry.swift LanguageService.swift LogMessageNotification+representingStructureUsingEmojiPrefixIfNecessary.swift MessageHandlingDependencyTracker.swift diff --git a/Sources/SourceKitLSP/LanguageServerType.swift b/Sources/SourceKitLSP/LanguageServerType.swift deleted file mode 100644 index 41796139f..000000000 --- a/Sources/SourceKitLSP/LanguageServerType.swift +++ /dev/null @@ -1,54 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import IndexStoreDB -import LanguageServerProtocol - -/// Exhaustive enumeration of all toolchain language servers known to SourceKit-LSP. -enum LanguageServerType: Hashable { - case clangd - case swift - case documentation - - init?(language: Language) { - switch language { - case .c, .cpp, .objective_c, .objective_cpp: - self = .clangd - case .swift: - self = .swift - case .markdown, .tutorial: - self = .documentation - default: - return nil - } - } - - init?(symbolProvider: SymbolProviderKind?) { - switch symbolProvider { - case .clang: self = .clangd - case .swift: self = .swift - case nil: return nil - } - } - - /// The `LanguageService` class used to provide functionality for this language class. - var serverType: LanguageService.Type { - switch self { - case .clangd: - return ClangLanguageService.self - case .swift: - return SwiftLanguageService.self - case .documentation: - return DocumentationLanguageService.self - } - } -} diff --git a/Sources/SourceKitLSP/LanguageServiceRegistry.swift b/Sources/SourceKitLSP/LanguageServiceRegistry.swift new file mode 100644 index 000000000..b5fec5238 --- /dev/null +++ b/Sources/SourceKitLSP/LanguageServiceRegistry.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import IndexStoreDB +import LanguageServerProtocol +import SKLogging + +/// Registry in which conformers to `LanguageService` can be registered to server semantic functionality for a set of +/// languages. +package struct LanguageServiceRegistry { + private var byLanguage: [Language: LanguageService.Type] = [:] + + package init() { + self.register(ClangLanguageService.self, for: [.c, .cpp, .objective_c, .objective_cpp]) + self.register(SwiftLanguageService.self, for: [.swift]) + self.register(DocumentationLanguageService.self, for: [.markdown, .tutorial]) + } + + private mutating func register(_ languageService: LanguageService.Type, for languages: [Language]) { + for language in languages { + if let existingLanguageService = byLanguage[language] { + logger.fault( + "Cannot register \(languageService) for \(language, privacy: .public) because \(existingLanguageService) is already registered" + ) + continue + } + byLanguage[language] = languageService + } + } + + func languageService(for language: Language) -> LanguageService.Type? { + return byLanguage[language] + } +} diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index a0a40c371..68a0ebadc 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -35,6 +35,14 @@ import DocCDocumentation /// Disambiguate LanguageServerProtocol.Language and IndexstoreDB.Language package typealias Language = LanguageServerProtocol.Language +struct LanguageServiceIdentifier: Hashable { + private let identifier: String + + init(type: LanguageService.Type) { + self.identifier = String(reflecting: type) + } +} + /// The SourceKit-LSP server. /// /// This is the client-facing language server implementation, providing indexing, multiple-toolchain @@ -75,7 +83,9 @@ package actor SourceKitLSPServer { package var capabilityRegistry: CapabilityRegistry? - var languageServices: [LanguageServerType: [LanguageService]] = [:] + private let languageServiceRegistry: LanguageServiceRegistry + + var languageServices: [LanguageServiceIdentifier: [LanguageService]] = [:] package let documentManager = DocumentManager() @@ -160,11 +170,13 @@ package actor SourceKitLSPServer { package init( client: Connection, toolchainRegistry: ToolchainRegistry, + languageServerRegistry: LanguageServiceRegistry, options: SourceKitLSPOptions, hooks: Hooks, onExit: @escaping () -> Void = {} ) { self.toolchainRegistry = toolchainRegistry + self.languageServiceRegistry = languageServerRegistry self._options = ThreadSafeBox(initialValue: options) self.hooks = hooks self.onExit = onExit @@ -450,11 +462,11 @@ package actor SourceKitLSPServer { /// If a language service of type `serverType` that can handle `workspace` using the given toolchain has already been /// started, return it, otherwise return `nil`. private func existingLanguageService( - _ serverType: LanguageServerType, + _ serverType: LanguageService.Type, toolchain: Toolchain, workspace: Workspace ) -> LanguageService? { - for languageService in languageServices[serverType, default: []] { + for languageService in languageServices[LanguageServiceIdentifier(type: serverType), default: []] { if languageService.canHandle(workspace: workspace, toolchain: toolchain) { return languageService } @@ -467,7 +479,7 @@ package actor SourceKitLSPServer { _ language: Language, in workspace: Workspace ) async -> LanguageService? { - guard let serverType = LanguageServerType(language: language) else { + guard let serverType = languageServiceRegistry.languageService(for: language) else { logger.error("Unable to infer language server type for language '\(language)'") return nil } @@ -478,7 +490,7 @@ package actor SourceKitLSPServer { // Start a new service. return await orLog("failed to start language service", level: .error) { [options = workspace.options, hooks] in - let service = try await serverType.serverType.init( + let service = try await serverType.init( sourceKitLSPServer: self, toolchain: toolchain, options: options, @@ -538,7 +550,7 @@ package actor SourceKitLSPServer { return concurrentlyInitializedService } - languageServices[serverType, default: []].append(service) + languageServices[LanguageServiceIdentifier(type: serverType), default: []].append(service) return service } } diff --git a/Sources/sourcekit-lsp/SourceKitLSP.swift b/Sources/sourcekit-lsp/SourceKitLSP.swift index 737856e6a..db5ea2d56 100644 --- a/Sources/sourcekit-lsp/SourceKitLSP.swift +++ b/Sources/sourcekit-lsp/SourceKitLSP.swift @@ -294,6 +294,7 @@ struct SourceKitLSP: AsyncParsableCommand { let server = SourceKitLSPServer( client: clientConnection, toolchainRegistry: ToolchainRegistry(installPath: Bundle.main.bundleURL), + languageServerRegistry: LanguageServiceRegistry(), options: globalConfigurationOptions, hooks: Hooks(), onExit: { From c692a83f439ff9918794665c00291f9c1943304f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 17 Jul 2025 08:48:12 +0200 Subject: [PATCH 3/4] Construct `LanguageServiceRegistry` outside of `SourceKitLSP` module --- Package.swift | 1 + Sources/InProcessClient/CMakeLists.txt | 3 +- .../InProcessSourceKitLSPClient.swift | 2 +- ...viceRegistry+staticallyKnownServices.swift | 13 +++ .../TestSourceKitLSPClient.swift | 2 +- .../Clang/ClangLanguageService.swift | 84 ++++++++++--------- .../LanguageServiceRegistry.swift | 8 +- Sources/SourceKitLSP/TestDiscovery.swift | 6 -- Sources/sourcekit-lsp/CMakeLists.txt | 1 + Sources/sourcekit-lsp/SourceKitLSP.swift | 3 +- 10 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift diff --git a/Package.swift b/Package.swift index 34a8548fe..6cd711d31 100644 --- a/Package.swift +++ b/Package.swift @@ -39,6 +39,7 @@ var targets: [Target] = [ dependencies: [ "BuildServerIntegration", "Diagnose", + "InProcessClient", "LanguageServerProtocol", "LanguageServerProtocolExtensions", "LanguageServerProtocolJSONRPC", diff --git a/Sources/InProcessClient/CMakeLists.txt b/Sources/InProcessClient/CMakeLists.txt index 1b5db8d5b..58ed80dce 100644 --- a/Sources/InProcessClient/CMakeLists.txt +++ b/Sources/InProcessClient/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(InProcessClient STATIC - InProcessSourceKitLSPClient.swift) + InProcessSourceKitLSPClient.swift + LanguageServiceRegistry+staticallyKnownServices.swift) set_target_properties(InProcessClient PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) diff --git a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift index 6e7dea57c..928461ef1 100644 --- a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift +++ b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift @@ -60,7 +60,7 @@ public final class InProcessSourceKitLSPClient: Sendable { self.server = SourceKitLSPServer( client: serverToClientConnection, toolchainRegistry: toolchainRegistry, - languageServerRegistry: LanguageServiceRegistry(), + languageServerRegistry: .staticallyKnownServices, options: options, hooks: hooks, onExit: { diff --git a/Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift b/Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift new file mode 100644 index 000000000..443cbbacc --- /dev/null +++ b/Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift @@ -0,0 +1,13 @@ +import LanguageServerProtocol +package import SourceKitLSP + +extension LanguageServiceRegistry { + /// All types conforming to `LanguageService` that are known at compile time. + package static let staticallyKnownServices = { + var registry = LanguageServiceRegistry() + registry.register(ClangLanguageService.self, for: [.c, .cpp, .objective_c, .objective_cpp]) + registry.register(SwiftLanguageService.self, for: [.swift]) + registry.register(DocumentationLanguageService.self, for: [.markdown, .tutorial]) + return registry + }() +} diff --git a/Sources/SKTestSupport/TestSourceKitLSPClient.swift b/Sources/SKTestSupport/TestSourceKitLSPClient.swift index db1631f26..24bf44101 100644 --- a/Sources/SKTestSupport/TestSourceKitLSPClient.swift +++ b/Sources/SKTestSupport/TestSourceKitLSPClient.swift @@ -164,7 +164,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { server = SourceKitLSPServer( client: serverToClientConnection, toolchainRegistry: toolchainRegistry, - languageServerRegistry: LanguageServiceRegistry(), + languageServerRegistry: .staticallyKnownServices, options: options, hooks: hooks, onExit: { diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift index 7425917b7..8b322744d 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift @@ -12,15 +12,15 @@ import BuildServerIntegration import Foundation -import LanguageServerProtocol +package import LanguageServerProtocol import LanguageServerProtocolExtensions import LanguageServerProtocolJSONRPC import SKLogging -import SKOptions -import SwiftExtensions -import SwiftSyntax +package import SKOptions +package import SwiftExtensions +package import SwiftSyntax import TSCExtensions -import ToolchainRegistry +package import ToolchainRegistry #if canImport(DocCDocumentation) import DocCDocumentation @@ -38,7 +38,7 @@ import WinSDK /// ``ClangLanguageServerShim`` conforms to ``MessageHandler`` to receive /// requests and notifications **from** clangd, not from the editor, and it will /// forward these requests and notifications to the editor. -actor ClangLanguageService: LanguageService, MessageHandler { +package actor ClangLanguageService: LanguageService, MessageHandler { /// The queue on which all messages that originate from clangd are handled. /// /// These are requests and notifications sent *from* clangd, not replies from @@ -144,12 +144,12 @@ actor ClangLanguageService: LanguageService, MessageHandler { return ClangBuildSettings(settings, clangPath: clangPath) } - nonisolated func canHandle(workspace: Workspace, toolchain: Toolchain) -> Bool { + package nonisolated func canHandle(workspace: Workspace, toolchain: Toolchain) -> Bool { // We launch different clangd instance for each workspace because clangd doesn't have multi-root workspace support. return workspace === self.workspace.value && self.clangdPath == toolchain.clangd } - func addStateChangeHandler(handler: @escaping (LanguageServerState, LanguageServerState) -> Void) { + package func addStateChangeHandler(handler: @escaping (LanguageServerState, LanguageServerState) -> Void) { self.stateChangeHandlers.append(handler) } @@ -244,7 +244,7 @@ actor ClangLanguageService: LanguageService, MessageHandler { /// sending a notification that's intended for the editor. /// /// We should either handle it ourselves or forward it to the editor. - nonisolated func handle(_ params: some NotificationType) { + package nonisolated func handle(_ params: some NotificationType) { logger.info( """ Received notification from clangd: @@ -267,7 +267,7 @@ actor ClangLanguageService: LanguageService, MessageHandler { /// sending a notification that's intended for the editor. /// /// We should either handle it ourselves or forward it to the client. - nonisolated func handle( + package nonisolated func handle( _ params: R, id: RequestID, reply: @Sendable @escaping (LSPResult) -> Void @@ -316,7 +316,7 @@ actor ClangLanguageService: LanguageService, MessageHandler { return nil } - func crash() { + package func crash() { clangdProcess?.terminateImmediately() } } @@ -366,7 +366,7 @@ extension ClangLanguageService { extension ClangLanguageService { - func initialize(_ initialize: InitializeRequest) async throws -> InitializeResult { + package func initialize(_ initialize: InitializeRequest) async throws -> InitializeResult { // Store the initialize request so we can replay it in case clangd crashes self.initializeRequest = initialize @@ -417,7 +417,7 @@ extension ClangLanguageService { clangd.send(notification) } - func reopenDocument(_ notification: ReopenTextDocumentNotification) {} + package func reopenDocument(_ notification: ReopenTextDocumentNotification) {} package func changeDocument( _ notification: DidChangeTextDocumentNotification, @@ -481,20 +481,20 @@ extension ClangLanguageService { return try await forwardRequestToClangd(req) } - func completion(_ req: CompletionRequest) async throws -> CompletionList { + package func completion(_ req: CompletionRequest) async throws -> CompletionList { return try await forwardRequestToClangd(req) } - func completionItemResolve(_ req: CompletionItemResolveRequest) async throws -> CompletionItem { + package func completionItemResolve(_ req: CompletionItemResolveRequest) async throws -> CompletionItem { return try await forwardRequestToClangd(req) } - func hover(_ req: HoverRequest) async throws -> HoverResponse? { + package func hover(_ req: HoverRequest) async throws -> HoverResponse? { return try await forwardRequestToClangd(req) } #if canImport(DocCDocumentation) - func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse { + package func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse { guard let sourceKitLSPServer else { throw ResponseError.unknown("Connection to the editor closed") } @@ -504,26 +504,28 @@ extension ClangLanguageService { } #endif - func symbolInfo(_ req: SymbolInfoRequest) async throws -> [SymbolDetails] { + package func symbolInfo(_ req: SymbolInfoRequest) async throws -> [SymbolDetails] { return try await forwardRequestToClangd(req) } - func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? { + package func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? { return try await forwardRequestToClangd(req) } - func documentSymbol(_ req: DocumentSymbolRequest) async throws -> DocumentSymbolResponse? { + package func documentSymbol(_ req: DocumentSymbolRequest) async throws -> DocumentSymbolResponse? { return try await forwardRequestToClangd(req) } - func documentColor(_ req: DocumentColorRequest) async throws -> [ColorInformation] { + package func documentColor(_ req: DocumentColorRequest) async throws -> [ColorInformation] { guard self.capabilities?.colorProvider?.isSupported ?? false else { return [] } return try await forwardRequestToClangd(req) } - func documentSemanticTokens(_ req: DocumentSemanticTokensRequest) async throws -> DocumentSemanticTokensResponse? { + package func documentSemanticTokens( + _ req: DocumentSemanticTokensRequest + ) async throws -> DocumentSemanticTokensResponse? { guard var response = try await forwardRequestToClangd(req) else { return nil } @@ -533,7 +535,7 @@ extension ClangLanguageService { return response } - func documentSemanticTokensDelta( + package func documentSemanticTokensDelta( _ req: DocumentSemanticTokensDeltaRequest ) async throws -> DocumentSemanticTokensDeltaResponse? { guard var response = try await forwardRequestToClangd(req) else { @@ -558,7 +560,7 @@ extension ClangLanguageService { return response } - func documentSemanticTokensRange( + package func documentSemanticTokensRange( _ req: DocumentSemanticTokensRangeRequest ) async throws -> DocumentSemanticTokensResponse? { guard var response = try await forwardRequestToClangd(req) else { @@ -570,49 +572,49 @@ extension ClangLanguageService { return response } - func colorPresentation(_ req: ColorPresentationRequest) async throws -> [ColorPresentation] { + package func colorPresentation(_ req: ColorPresentationRequest) async throws -> [ColorPresentation] { guard self.capabilities?.colorProvider?.isSupported ?? false else { return [] } return try await forwardRequestToClangd(req) } - func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]? { + package func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]? { return try await forwardRequestToClangd(req) } - func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]? { + package func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]? { return try await forwardRequestToClangd(req) } - func documentOnTypeFormatting(_ req: DocumentOnTypeFormattingRequest) async throws -> [TextEdit]? { + package func documentOnTypeFormatting(_ req: DocumentOnTypeFormattingRequest) async throws -> [TextEdit]? { return try await forwardRequestToClangd(req) } - func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse? { + package func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse? { return try await forwardRequestToClangd(req) } - func inlayHint(_ req: InlayHintRequest) async throws -> [InlayHint] { + package func inlayHint(_ req: InlayHintRequest) async throws -> [InlayHint] { return try await forwardRequestToClangd(req) } - func documentDiagnostic(_ req: DocumentDiagnosticsRequest) async throws -> DocumentDiagnosticReport { + package func documentDiagnostic(_ req: DocumentDiagnosticsRequest) async throws -> DocumentDiagnosticReport { return try await forwardRequestToClangd(req) } - func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens] { + package func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens] { return try await forwardRequestToClangd(req) ?? [] } - func foldingRange(_ req: FoldingRangeRequest) async throws -> [FoldingRange]? { + package func foldingRange(_ req: FoldingRangeRequest) async throws -> [FoldingRange]? { guard self.capabilities?.foldingRangeProvider?.isSupported ?? false else { return nil } return try await forwardRequestToClangd(req) } - func openGeneratedInterface( + package func openGeneratedInterface( document: DocumentURI, moduleName: String, groupName: String?, @@ -621,21 +623,21 @@ extension ClangLanguageService { throw ResponseError.unknown("unsupported method") } - func indexedRename(_ request: IndexedRenameRequest) async throws -> WorkspaceEdit? { + package func indexedRename(_ request: IndexedRenameRequest) async throws -> WorkspaceEdit? { return try await forwardRequestToClangd(request) } // MARK: - Other - func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? { + package func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? { return try await forwardRequestToClangd(req) } - func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse { + package func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse { throw ResponseError.unknown("unsupported method") } - func rename(_ renameRequest: RenameRequest) async throws -> (edits: WorkspaceEdit, usr: String?) { + package func rename(_ renameRequest: RenameRequest) async throws -> (edits: WorkspaceEdit, usr: String?) { async let edits = forwardRequestToClangd(renameRequest) let symbolInfoRequest = SymbolInfoRequest( textDocument: renameRequest.textDocument, @@ -645,7 +647,11 @@ extension ClangLanguageService { return (try await edits ?? WorkspaceEdit(), symbolDetail?.usr) } - func editsToRename( + package func syntacticDocumentTests(for uri: DocumentURI, in workspace: Workspace) async -> [AnnotatedTestItem]? { + return nil + } + + package func editsToRename( locations renameLocations: [RenameLocation], in snapshot: DocumentSnapshot, oldName oldCrossLanguageName: CrossLanguageName, diff --git a/Sources/SourceKitLSP/LanguageServiceRegistry.swift b/Sources/SourceKitLSP/LanguageServiceRegistry.swift index b5fec5238..c931f707b 100644 --- a/Sources/SourceKitLSP/LanguageServiceRegistry.swift +++ b/Sources/SourceKitLSP/LanguageServiceRegistry.swift @@ -19,13 +19,9 @@ import SKLogging package struct LanguageServiceRegistry { private var byLanguage: [Language: LanguageService.Type] = [:] - package init() { - self.register(ClangLanguageService.self, for: [.c, .cpp, .objective_c, .objective_cpp]) - self.register(SwiftLanguageService.self, for: [.swift]) - self.register(DocumentationLanguageService.self, for: [.markdown, .tutorial]) - } + package init() {} - private mutating func register(_ languageService: LanguageService.Type, for languages: [Language]) { + package mutating func register(_ languageService: LanguageService.Type, for languages: [Language]) { for language in languages { if let existingLanguageService = byLanguage[language] { logger.fault( diff --git a/Sources/SourceKitLSP/TestDiscovery.swift b/Sources/SourceKitLSP/TestDiscovery.swift index 07a7731d4..943eb29fc 100644 --- a/Sources/SourceKitLSP/TestDiscovery.swift +++ b/Sources/SourceKitLSP/TestDiscovery.swift @@ -623,9 +623,3 @@ extension SwiftLanguageService { return (xctestSymbols + swiftTestingSymbols).sorted { $0.testItem.location < $1.testItem.location } } } - -extension ClangLanguageService { - package func syntacticDocumentTests(for uri: DocumentURI, in workspace: Workspace) async -> [AnnotatedTestItem]? { - return nil - } -} diff --git a/Sources/sourcekit-lsp/CMakeLists.txt b/Sources/sourcekit-lsp/CMakeLists.txt index 03a058f0c..b5d4baf59 100644 --- a/Sources/sourcekit-lsp/CMakeLists.txt +++ b/Sources/sourcekit-lsp/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(sourcekit-lsp target_link_libraries(sourcekit-lsp PRIVATE BuildServerIntegration Diagnose + InProcessClient LanguageServerProtocol LanguageServerProtocolExtensions LanguageServerProtocolJSONRPC diff --git a/Sources/sourcekit-lsp/SourceKitLSP.swift b/Sources/sourcekit-lsp/SourceKitLSP.swift index db5ea2d56..71e74ed00 100644 --- a/Sources/sourcekit-lsp/SourceKitLSP.swift +++ b/Sources/sourcekit-lsp/SourceKitLSP.swift @@ -16,6 +16,7 @@ import Csourcekitd // Not needed here, but fixes debugging... import Diagnose import Dispatch import Foundation +import InProcessClient import LanguageServerProtocol import LanguageServerProtocolExtensions import LanguageServerProtocolJSONRPC @@ -294,7 +295,7 @@ struct SourceKitLSP: AsyncParsableCommand { let server = SourceKitLSPServer( client: clientConnection, toolchainRegistry: ToolchainRegistry(installPath: Bundle.main.bundleURL), - languageServerRegistry: LanguageServiceRegistry(), + languageServerRegistry: .staticallyKnownServices, options: globalConfigurationOptions, hooks: Hooks(), onExit: { From 634ec92921ce0d4342e0789f6d599b9b114c47e0 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sat, 2 Aug 2025 10:16:59 +0200 Subject: [PATCH 4/4] Move ClangLanguageService to its own module --- Package.swift | 22 ++ Sources/CMakeLists.txt | 1 + Sources/ClangLanguageService/CMakeLists.txt | 25 ++ .../ClangLanguageService.swift | 3 +- .../SemanticTokenTranslator.swift | 0 Sources/InProcessClient/CMakeLists.txt | 1 + ...viceRegistry+staticallyKnownServices.swift | 13 + Sources/SourceKitLSP/CMakeLists.txt | 5 +- ...DocumentSnapshot+PositionConversions.swift | 296 ++++++++++++++++++ ...anticTokensLegend+SourceKitLSPLegend.swift | 2 +- Sources/SourceKitLSP/SourceKitLSPServer.swift | 4 +- .../Swift/SwiftLanguageService.swift | 275 ---------------- Sources/SourceKitLSP/Workspace.swift | 6 +- 13 files changed, 367 insertions(+), 286 deletions(-) create mode 100644 Sources/ClangLanguageService/CMakeLists.txt rename Sources/{SourceKitLSP/Clang => ClangLanguageService}/ClangLanguageService.swift (99%) rename Sources/{SourceKitLSP/Clang => ClangLanguageService}/SemanticTokenTranslator.swift (100%) create mode 100644 Sources/SourceKitLSP/DocumentSnapshot+PositionConversions.swift diff --git a/Package.swift b/Package.swift index 6cd711d31..af07489dd 100644 --- a/Package.swift +++ b/Package.swift @@ -127,6 +127,27 @@ var targets: [Target] = [ dependencies: [] ), + // MARK: ClangLanguageService + + .target( + name: "ClangLanguageService", + dependencies: [ + "BuildServerIntegration", + "DocCDocumentation", + "LanguageServerProtocol", + "LanguageServerProtocolExtensions", + "LanguageServerProtocolJSONRPC", + "SKLogging", + "SKOptions", + "SourceKitLSP", + "SwiftExtensions", + "ToolchainRegistry", + "TSCExtensions", + ] + swiftSyntaxDependencies(["SwiftSyntax"]), + exclude: ["CMakeLists.txt"], + swiftSettings: globalSwiftSettings + ), + // MARK: CompletionScoring .target( @@ -241,6 +262,7 @@ var targets: [Target] = [ name: "InProcessClient", dependencies: [ "BuildServerIntegration", + "ClangLanguageService", "LanguageServerProtocol", "SKLogging", "SKOptions", diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index 95b2b3749..6d532df1f 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(BuildServerProtocol) add_subdirectory(BuildServerIntegration) add_subdirectory(CAtomics) add_subdirectory(CCompletionScoring) +add_subdirectory(ClangLanguageService) add_subdirectory(CompletionScoring) add_subdirectory(Csourcekitd) add_subdirectory(Diagnose) diff --git a/Sources/ClangLanguageService/CMakeLists.txt b/Sources/ClangLanguageService/CMakeLists.txt new file mode 100644 index 000000000..8ea42c567 --- /dev/null +++ b/Sources/ClangLanguageService/CMakeLists.txt @@ -0,0 +1,25 @@ +add_library(ClangLanguageService STATIC + ClangLanguageService.swift + SemanticTokenTranslator.swift +) + +set_target_properties(ClangLanguageService PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY} +) + +target_link_libraries(ClangLanguageService PUBLIC + LanguageServerProtocol + SKOptions + SourceKitLSP + SwiftExtensions + ToolchainRegistry + SwiftSyntax::SwiftSyntax +) + +target_link_libraries(ClangLanguageService PRIVATE + BuildServerIntegration + LanguageServerProtocolExtensions + LanguageServerProtocolJSONRPC + SKLogging + TSCExtensions +) diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/ClangLanguageService/ClangLanguageService.swift similarity index 99% rename from Sources/SourceKitLSP/Clang/ClangLanguageService.swift rename to Sources/ClangLanguageService/ClangLanguageService.swift index 8b322744d..b61466168 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift +++ b/Sources/ClangLanguageService/ClangLanguageService.swift @@ -17,6 +17,7 @@ import LanguageServerProtocolExtensions import LanguageServerProtocolJSONRPC import SKLogging package import SKOptions +package import SourceKitLSP package import SwiftExtensions package import SwiftSyntax import TSCExtensions @@ -499,7 +500,7 @@ extension ClangLanguageService { throw ResponseError.unknown("Connection to the editor closed") } - let snapshot = try sourceKitLSPServer.documentManager.latestSnapshot(req.textDocument.uri) + let snapshot = try await sourceKitLSPServer.documentManager.latestSnapshot(req.textDocument.uri) throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language)) } #endif diff --git a/Sources/SourceKitLSP/Clang/SemanticTokenTranslator.swift b/Sources/ClangLanguageService/SemanticTokenTranslator.swift similarity index 100% rename from Sources/SourceKitLSP/Clang/SemanticTokenTranslator.swift rename to Sources/ClangLanguageService/SemanticTokenTranslator.swift diff --git a/Sources/InProcessClient/CMakeLists.txt b/Sources/InProcessClient/CMakeLists.txt index 58ed80dce..dd9399d36 100644 --- a/Sources/InProcessClient/CMakeLists.txt +++ b/Sources/InProcessClient/CMakeLists.txt @@ -7,6 +7,7 @@ set_target_properties(InProcessClient PROPERTIES target_link_libraries(InProcessClient PUBLIC BuildServerIntegration + ClangLanguageService LanguageServerProtocol SKLogging SKOptions diff --git a/Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift b/Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift index 443cbbacc..a5a44d44f 100644 --- a/Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift +++ b/Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift @@ -1,3 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ClangLanguageService import LanguageServerProtocol package import SourceKitLSP diff --git a/Sources/SourceKitLSP/CMakeLists.txt b/Sources/SourceKitLSP/CMakeLists.txt index 0b8d808a0..23026f4b2 100644 --- a/Sources/SourceKitLSP/CMakeLists.txt +++ b/Sources/SourceKitLSP/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(SourceKitLSP STATIC CapabilityRegistry.swift DocumentManager.swift DocumentSnapshot+FromFileContents.swift + DocumentSnapshot+PositionConversions.swift Hooks.swift IndexProgressManager.swift IndexStoreDB+MainFilesProvider.swift @@ -21,10 +22,6 @@ add_library(SourceKitLSP STATIC TextEdit+IsNoop.swift Workspace.swift ) -target_sources(SourceKitLSP PRIVATE - Clang/ClangLanguageService.swift - Clang/SemanticTokenTranslator.swift -) target_sources(SourceKitLSP PRIVATE Documentation/DocCDocumentationHandler.swift Documentation/DocumentationLanguageService.swift diff --git a/Sources/SourceKitLSP/DocumentSnapshot+PositionConversions.swift b/Sources/SourceKitLSP/DocumentSnapshot+PositionConversions.swift new file mode 100644 index 000000000..a4be6a9bc --- /dev/null +++ b/Sources/SourceKitLSP/DocumentSnapshot+PositionConversions.swift @@ -0,0 +1,296 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +package import IndexStoreDB +package import LanguageServerProtocol +import SKLogging +import SKUtilities +package import SwiftSyntax + +extension DocumentSnapshot { + + // MARK: String.Index <-> Raw UTF-8 + + /// Converts the given UTF-8 offset to `String.Index`. + /// + /// If the offset is out-of-bounds of the snapshot, returns the closest valid index and logs a fault containing the + /// file and line of the caller (from `callerFile` and `callerLine`). + package func indexOf(utf8Offset: Int, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> String.Index { + guard utf8Offset >= 0 else { + logger.fault( + """ + UTF-8 offset \(utf8Offset) is negative while converting it to String.Index \ + (\(callerFile, privacy: .public):\(callerLine, privacy: .public)) + """ + ) + return text.startIndex + } + guard let index = text.utf8.index(text.startIndex, offsetBy: utf8Offset, limitedBy: text.endIndex) else { + logger.fault( + """ + UTF-8 offset \(utf8Offset) is past end of file while converting it to String.Index \ + (\(callerFile, privacy: .public):\(callerLine, privacy: .public)) + """ + ) + return text.endIndex + } + return index + } + + // MARK: Position <-> Raw UTF-8 offset + + /// Converts the given UTF-16-based line:column position to the UTF-8 offset of that position within the source file. + /// + /// If `position` does not refer to a valid position with in the snapshot, returns the offset of the closest valid + /// position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). + package func utf8Offset(of position: Position, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> Int { + return lineTable.utf8OffsetOf( + line: position.line, + utf16Column: position.utf16index, + callerFile: callerFile, + callerLine: callerLine + ) + } + + /// Converts the given UTF-8 offset to a UTF-16-based line:column position. + /// + /// If the offset is after the end of the snapshot, returns `nil` and logs a fault containing the file and line of + /// the caller (from `callerFile` and `callerLine`). + package func positionOf(utf8Offset: Int, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> Position { + let (line, utf16Column) = lineTable.lineAndUTF16ColumnOf( + utf8Offset: utf8Offset, + callerFile: callerFile, + callerLine: callerLine + ) + return Position(line: line, utf16index: utf16Column) + } + + /// Converts the given UTF-16 based line:column range to a UTF-8 based offset range. + /// + /// If the bounds of the range do not refer to a valid positions with in the snapshot, this function adjusts them to + /// the closest valid positions and logs a fault containing the file and line of the caller (from `callerFile` and + /// `callerLine`). + package func utf8OffsetRange( + of range: Range, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> Range { + let startOffset = utf8Offset(of: range.lowerBound, callerFile: callerFile, callerLine: callerLine) + let endOffset = utf8Offset(of: range.upperBound, callerFile: callerFile, callerLine: callerLine) + return startOffset.. String.Index + + /// Converts the given UTF-16-based `line:column` position to a `String.Index`. + /// + /// If `position` does not refer to a valid position with in the snapshot, returns the index of the closest valid + /// position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). + package func index( + of position: Position, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> String.Index { + return lineTable.stringIndexOf( + line: position.line, + utf16Column: position.utf16index, + callerFile: callerFile, + callerLine: callerLine + ) + } + + /// Converts the given UTF-16-based `line:column` range to a `String.Index` range. + /// + /// If the bounds of the range do not refer to a valid positions with in the snapshot, this function adjusts them to + /// the closest valid positions and logs a fault containing the file and line of the caller (from `callerFile` and + /// `callerLine`). + package func indexRange( + of range: Range, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> Range { + return self.index(of: range.lowerBound).. Position { + let utf16Column = lineTable.utf16ColumnAt( + line: zeroBasedLine, + utf8Column: utf8Column, + callerFile: callerFile, + callerLine: callerLine + ) + return Position(line: zeroBasedLine, utf16index: utf16Column) + } + + /// Converts the given `String.Index` to a UTF-16-based line:column position. + package func position(of index: String.Index, fromLine: Int = 0) -> Position { + let (line, utf16Column) = lineTable.lineAndUTF16ColumnOf(index, fromLine: fromLine) + return Position(line: line, utf16index: utf16Column) + } + + // MARK: Position <-> AbsolutePosition + + /// Converts the given UTF-8-offset-based `AbsolutePosition` to a UTF-16-based line:column. + /// + /// If the `AbsolutePosition` out of bounds of the source file, returns the closest valid position and logs a fault + /// containing the file and line of the caller (from `callerFile` and `callerLine`). + package func position( + of position: AbsolutePosition, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> Position { + return positionOf(utf8Offset: position.utf8Offset, callerFile: callerFile, callerLine: callerLine) + } + + /// Converts the given UTF-16-based line:column `Position` to a UTF-8-offset-based `AbsolutePosition`. + /// + /// If the UTF-16 based line:column pair does not refer to a valid position within the snapshot, returns the closest + /// valid position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). + package func absolutePosition( + of position: Position, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> AbsolutePosition { + let offset = utf8Offset(of: position, callerFile: callerFile, callerLine: callerLine) + return AbsolutePosition(utf8Offset: offset) + } + + /// Converts the lower and upper bound of the given UTF-8-offset-based `AbsolutePosition` range to a UTF-16-based + /// line:column range for use in LSP. + /// + /// If the bounds of the range do not refer to a valid positions with in the snapshot, this function adjusts them to + /// the closest valid positions and logs a fault containing the file and line of the caller (from `callerFile` and + /// `callerLine`). + package func absolutePositionRange( + of range: Range, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> Range { + let lowerBound = self.position(of: range.lowerBound, callerFile: callerFile, callerLine: callerLine) + let upperBound = self.position(of: range.upperBound, callerFile: callerFile, callerLine: callerLine) + return lowerBound.. Range { + let lowerBound = self.position(of: node.position, callerFile: callerFile, callerLine: callerLine) + let upperBound = self.position(of: node.endPosition, callerFile: callerFile, callerLine: callerLine) + return lowerBound.., + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> Range { + let utf8OffsetRange = utf8OffsetRange(of: range, callerFile: callerFile, callerLine: callerLine) + return Range( + position: AbsolutePosition(utf8Offset: utf8OffsetRange.startIndex), + length: SourceLength(utf8Length: utf8OffsetRange.count) + ) + } + + // MARK: Position <-> RenameLocation + + /// Converts the given UTF-8-based line:column `RenamedLocation` to a UTF-16-based line:column `Position`. + /// + /// If the UTF-8 based line:column pair does not refer to a valid position within the snapshot, returns the closest + /// valid position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). + package func position( + of renameLocation: RenameLocation, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> Position { + return positionOf( + zeroBasedLine: renameLocation.line - 1, + utf8Column: renameLocation.utf8Column - 1, + callerFile: callerFile, + callerLine: callerLine + ) + } + + // MARK: Position <-> SourceKitDPosition + + func sourcekitdPosition( + of position: Position, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> SourceKitDPosition { + let utf8Column = lineTable.utf8ColumnAt( + line: position.line, + utf16Column: position.utf16index, + callerFile: callerFile, + callerLine: callerLine + ) + // FIXME: Introduce new type for UTF-8 based positions + return SourceKitDPosition(line: position.line + 1, utf8Column: utf8Column + 1) + } + + // MAR: Position <-> SymbolLocation + + /// Converts the given UTF-8-offset-based `SymbolLocation` to a UTF-16-based line:column `Position`. + /// + /// If the UTF-8 offset is out-of-bounds of the snapshot, returns the closest valid position and logs a fault + /// containing the file and line of the caller (from `callerFile` and `callerLine`). + package func position( + of symbolLocation: SymbolLocation, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> Position { + return positionOf( + zeroBasedLine: symbolLocation.line - 1, + utf8Column: symbolLocation.utf8Column - 1, + callerFile: callerFile, + callerLine: callerLine + ) + } + + // MARK: AbsolutePosition <-> RenameLocation + + /// Converts the given UTF-8-based line:column `RenamedLocation` to a UTF-8-offset-based `AbsolutePosition`. + /// + /// If the UTF-8 based line:column pair does not refer to a valid position within the snapshot, returns the offset of + /// the closest valid position and logs a fault containing the file and line of the caller (from `callerFile` and + /// `callerLine`). + package func absolutePosition( + of renameLocation: RenameLocation, + callerFile: StaticString = #fileID, + callerLine: UInt = #line + ) -> AbsolutePosition { + let utf8Offset = lineTable.utf8OffsetOf( + line: renameLocation.line - 1, + utf8Column: renameLocation.utf8Column - 1, + callerFile: callerFile, + callerLine: callerLine + ) + return AbsolutePosition(utf8Offset: utf8Offset) + } +} diff --git a/Sources/SourceKitLSP/SemanticTokensLegend+SourceKitLSPLegend.swift b/Sources/SourceKitLSP/SemanticTokensLegend+SourceKitLSPLegend.swift index d86ee908c..21dc91c0c 100644 --- a/Sources/SourceKitLSP/SemanticTokensLegend+SourceKitLSPLegend.swift +++ b/Sources/SourceKitLSP/SemanticTokensLegend+SourceKitLSPLegend.swift @@ -24,7 +24,7 @@ extension SemanticTokenTypes { extension SemanticTokensLegend { /// The semantic tokens legend that is used between SourceKit-LSP and the editor. - static let sourceKitLSPLegend = SemanticTokensLegend( + package static let sourceKitLSPLegend = SemanticTokensLegend( tokenTypes: SemanticTokenTypes.all.map(\.name), tokenModifiers: SemanticTokenModifiers.all.compactMap(\.name) ) diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 68a0ebadc..f793f554c 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -77,7 +77,7 @@ package actor SourceKitLSPServer { _options.value } - let hooks: Hooks + package let hooks: Hooks let toolchainRegistry: ToolchainRegistry @@ -432,7 +432,7 @@ package actor SourceKitLSPServer { } /// After the language service has crashed, send `DidOpenTextDocumentNotification`s to a newly instantiated language service for previously open documents. - func reopenDocuments(for languageService: LanguageService) async { + package func reopenDocuments(for languageService: LanguageService) async { for documentUri in self.documentManager.openDocuments { guard let workspace = await self.workspaceForDocument(uri: documentUri) else { continue diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift index dfc0438f4..e54fc7d02 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageService.swift @@ -1189,281 +1189,6 @@ struct SourceKitDPosition { public var utf8Column: Int } -extension DocumentSnapshot { - - // MARK: String.Index <-> Raw UTF-8 - - /// Converts the given UTF-8 offset to `String.Index`. - /// - /// If the offset is out-of-bounds of the snapshot, returns the closest valid index and logs a fault containing the - /// file and line of the caller (from `callerFile` and `callerLine`). - func indexOf(utf8Offset: Int, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> String.Index { - guard utf8Offset >= 0 else { - logger.fault( - """ - UTF-8 offset \(utf8Offset) is negative while converting it to String.Index \ - (\(callerFile, privacy: .public):\(callerLine, privacy: .public)) - """ - ) - return text.startIndex - } - guard let index = text.utf8.index(text.startIndex, offsetBy: utf8Offset, limitedBy: text.endIndex) else { - logger.fault( - """ - UTF-8 offset \(utf8Offset) is past end of file while converting it to String.Index \ - (\(callerFile, privacy: .public):\(callerLine, privacy: .public)) - """ - ) - return text.endIndex - } - return index - } - - // MARK: Position <-> Raw UTF-8 offset - - /// Converts the given UTF-16-based line:column position to the UTF-8 offset of that position within the source file. - /// - /// If `position` does not refer to a valid position with in the snapshot, returns the offset of the closest valid - /// position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). - func utf8Offset(of position: Position, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> Int { - return lineTable.utf8OffsetOf( - line: position.line, - utf16Column: position.utf16index, - callerFile: callerFile, - callerLine: callerLine - ) - } - - /// Converts the given UTF-8 offset to a UTF-16-based line:column position. - /// - /// If the offset is after the end of the snapshot, returns `nil` and logs a fault containing the file and line of - /// the caller (from `callerFile` and `callerLine`). - func positionOf(utf8Offset: Int, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> Position { - let (line, utf16Column) = lineTable.lineAndUTF16ColumnOf( - utf8Offset: utf8Offset, - callerFile: callerFile, - callerLine: callerLine - ) - return Position(line: line, utf16index: utf16Column) - } - - /// Converts the given UTF-16 based line:column range to a UTF-8 based offset range. - /// - /// If the bounds of the range do not refer to a valid positions with in the snapshot, this function adjusts them to - /// the closest valid positions and logs a fault containing the file and line of the caller (from `callerFile` and - /// `callerLine`). - func utf8OffsetRange( - of range: Range, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> Range { - let startOffset = utf8Offset(of: range.lowerBound, callerFile: callerFile, callerLine: callerLine) - let endOffset = utf8Offset(of: range.upperBound, callerFile: callerFile, callerLine: callerLine) - return startOffset.. String.Index - - /// Converts the given UTF-16-based `line:column` position to a `String.Index`. - /// - /// If `position` does not refer to a valid position with in the snapshot, returns the index of the closest valid - /// position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). - func index(of position: Position, callerFile: StaticString = #fileID, callerLine: UInt = #line) -> String.Index { - return lineTable.stringIndexOf( - line: position.line, - utf16Column: position.utf16index, - callerFile: callerFile, - callerLine: callerLine - ) - } - - /// Converts the given UTF-16-based `line:column` range to a `String.Index` range. - /// - /// If the bounds of the range do not refer to a valid positions with in the snapshot, this function adjusts them to - /// the closest valid positions and logs a fault containing the file and line of the caller (from `callerFile` and - /// `callerLine`). - func indexRange( - of range: Range, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> Range { - return self.index(of: range.lowerBound).. Position { - let utf16Column = lineTable.utf16ColumnAt( - line: zeroBasedLine, - utf8Column: utf8Column, - callerFile: callerFile, - callerLine: callerLine - ) - return Position(line: zeroBasedLine, utf16index: utf16Column) - } - - /// Converts the given `String.Index` to a UTF-16-based line:column position. - func position(of index: String.Index, fromLine: Int = 0) -> Position { - let (line, utf16Column) = lineTable.lineAndUTF16ColumnOf(index, fromLine: fromLine) - return Position(line: line, utf16index: utf16Column) - } - - // MARK: Position <-> AbsolutePosition - - /// Converts the given UTF-8-offset-based `AbsolutePosition` to a UTF-16-based line:column. - /// - /// If the `AbsolutePosition` out of bounds of the source file, returns the closest valid position and logs a fault - /// containing the file and line of the caller (from `callerFile` and `callerLine`). - func position( - of position: AbsolutePosition, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> Position { - return positionOf(utf8Offset: position.utf8Offset, callerFile: callerFile, callerLine: callerLine) - } - - /// Converts the given UTF-16-based line:column `Position` to a UTF-8-offset-based `AbsolutePosition`. - /// - /// If the UTF-16 based line:column pair does not refer to a valid position within the snapshot, returns the closest - /// valid position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). - func absolutePosition( - of position: Position, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> AbsolutePosition { - let offset = utf8Offset(of: position, callerFile: callerFile, callerLine: callerLine) - return AbsolutePosition(utf8Offset: offset) - } - - /// Converts the lower and upper bound of the given UTF-8-offset-based `AbsolutePosition` range to a UTF-16-based - /// line:column range for use in LSP. - /// - /// If the bounds of the range do not refer to a valid positions with in the snapshot, this function adjusts them to - /// the closest valid positions and logs a fault containing the file and line of the caller (from `callerFile` and - /// `callerLine`). - func absolutePositionRange( - of range: Range, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> Range { - let lowerBound = self.position(of: range.lowerBound, callerFile: callerFile, callerLine: callerLine) - let upperBound = self.position(of: range.upperBound, callerFile: callerFile, callerLine: callerLine) - return lowerBound.. Range { - let lowerBound = self.position(of: node.position, callerFile: callerFile, callerLine: callerLine) - let upperBound = self.position(of: node.endPosition, callerFile: callerFile, callerLine: callerLine) - return lowerBound.., - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> Range { - let utf8OffsetRange = utf8OffsetRange(of: range, callerFile: callerFile, callerLine: callerLine) - return Range( - position: AbsolutePosition(utf8Offset: utf8OffsetRange.startIndex), - length: SourceLength(utf8Length: utf8OffsetRange.count) - ) - } - - // MARK: Position <-> RenameLocation - - /// Converts the given UTF-8-based line:column `RenamedLocation` to a UTF-16-based line:column `Position`. - /// - /// If the UTF-8 based line:column pair does not refer to a valid position within the snapshot, returns the closest - /// valid position and logs a fault containing the file and line of the caller (from `callerFile` and `callerLine`). - func position( - of renameLocation: RenameLocation, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> Position { - return positionOf( - zeroBasedLine: renameLocation.line - 1, - utf8Column: renameLocation.utf8Column - 1, - callerFile: callerFile, - callerLine: callerLine - ) - } - - // MARK: Position <-> SourceKitDPosition - - func sourcekitdPosition( - of position: Position, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> SourceKitDPosition { - let utf8Column = lineTable.utf8ColumnAt( - line: position.line, - utf16Column: position.utf16index, - callerFile: callerFile, - callerLine: callerLine - ) - // FIXME: Introduce new type for UTF-8 based positions - return SourceKitDPosition(line: position.line + 1, utf8Column: utf8Column + 1) - } - - // MAR: Position <-> SymbolLocation - - /// Converts the given UTF-8-offset-based `SymbolLocation` to a UTF-16-based line:column `Position`. - /// - /// If the UTF-8 offset is out-of-bounds of the snapshot, returns the closest valid position and logs a fault - /// containing the file and line of the caller (from `callerFile` and `callerLine`). - func position( - of symbolLocation: SymbolLocation, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> Position { - return positionOf( - zeroBasedLine: symbolLocation.line - 1, - utf8Column: symbolLocation.utf8Column - 1, - callerFile: callerFile, - callerLine: callerLine - ) - } - - // MARK: AbsolutePosition <-> RenameLocation - - /// Converts the given UTF-8-based line:column `RenamedLocation` to a UTF-8-offset-based `AbsolutePosition`. - /// - /// If the UTF-8 based line:column pair does not refer to a valid position within the snapshot, returns the offset of - /// the closest valid position and logs a fault containing the file and line of the caller (from `callerFile` and - /// `callerLine`). - func absolutePosition( - of renameLocation: RenameLocation, - callerFile: StaticString = #fileID, - callerLine: UInt = #line - ) -> AbsolutePosition { - let utf8Offset = lineTable.utf8OffsetOf( - line: renameLocation.line - 1, - utf8Column: renameLocation.utf8Column - 1, - callerFile: callerFile, - callerLine: callerLine - ) - return AbsolutePosition(utf8Offset: utf8Offset) - } -} - extension sourcekitd_api_uid_t { func isCommentKind(_ vals: sourcekitd_api_values) -> Bool { switch self { diff --git a/Sources/SourceKitLSP/Workspace.swift b/Sources/SourceKitLSP/Workspace.swift index c83d094a3..b1f8ab1ea 100644 --- a/Sources/SourceKitLSP/Workspace.swift +++ b/Sources/SourceKitLSP/Workspace.swift @@ -522,10 +522,10 @@ package final class Workspace: Sendable, BuildServerManagerDelegate { } /// Wrapper around a workspace that isn't being retained. -struct WeakWorkspace { - weak var value: Workspace? +package struct WeakWorkspace { + package weak var value: Workspace? - init(_ value: Workspace? = nil) { + package init(_ value: Workspace? = nil) { self.value = value } }