Skip to content

Move ClangLanguageService to its own module #2228

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 4 commits into from
Aug 11, 2025
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
23 changes: 23 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var targets: [Target] = [
dependencies: [
"BuildServerIntegration",
"Diagnose",
"InProcessClient",
"LanguageServerProtocol",
"LanguageServerProtocolExtensions",
"LanguageServerProtocolJSONRPC",
Expand Down Expand Up @@ -126,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(
Expand Down Expand Up @@ -240,6 +262,7 @@ var targets: [Target] = [
name: "InProcessClient",
dependencies: [
"BuildServerIntegration",
"ClangLanguageService",
"LanguageServerProtocol",
"SKLogging",
"SKOptions",
Expand Down
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions Sources/ClangLanguageService/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@

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 SourceKitLSP
package import SwiftExtensions
package import SwiftSyntax
import TSCExtensions
import ToolchainRegistry
package import ToolchainRegistry

#if canImport(DocCDocumentation)
import DocCDocumentation
Expand All @@ -38,7 +39,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
Expand Down Expand Up @@ -144,12 +145,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)
}

Expand Down Expand Up @@ -244,7 +245,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:
Expand All @@ -267,7 +268,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<R: RequestType>(
package nonisolated func handle<R: RequestType>(
_ params: R,
id: RequestID,
reply: @Sendable @escaping (LSPResult<R.Response>) -> Void
Expand Down Expand Up @@ -316,7 +317,7 @@ actor ClangLanguageService: LanguageService, MessageHandler {
return nil
}

func crash() {
package func crash() {
clangdProcess?.terminateImmediately()
}
}
Expand Down Expand Up @@ -366,7 +367,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

Expand Down Expand Up @@ -417,7 +418,7 @@ extension ClangLanguageService {
clangd.send(notification)
}

func reopenDocument(_ notification: ReopenTextDocumentNotification) {}
package func reopenDocument(_ notification: ReopenTextDocumentNotification) {}

package func changeDocument(
_ notification: DidChangeTextDocumentNotification,
Expand Down Expand Up @@ -481,49 +482,51 @@ 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")
}

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

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
}
Expand All @@ -533,7 +536,7 @@ extension ClangLanguageService {
return response
}

func documentSemanticTokensDelta(
package func documentSemanticTokensDelta(
_ req: DocumentSemanticTokensDeltaRequest
) async throws -> DocumentSemanticTokensDeltaResponse? {
guard var response = try await forwardRequestToClangd(req) else {
Expand All @@ -558,7 +561,7 @@ extension ClangLanguageService {
return response
}

func documentSemanticTokensRange(
package func documentSemanticTokensRange(
_ req: DocumentSemanticTokensRangeRequest
) async throws -> DocumentSemanticTokensResponse? {
guard var response = try await forwardRequestToClangd(req) else {
Expand All @@ -570,49 +573,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?,
Expand All @@ -621,19 +624,86 @@ 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")
}

package 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)
}

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,
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`.
Expand Down
Loading