mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
266 lines
11 KiB
Swift
266 lines
11 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
@_spi(SourceKitLSP) package import LanguageServerProtocol
|
|
@_spi(SourceKitLSP) import LanguageServerProtocolExtensions
|
|
@_spi(SourceKitLSP) import LanguageServerProtocolTransport
|
|
@_spi(SourceKitLSP) import SKLogging
|
|
import SwiftExtensions
|
|
@_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions
|
|
|
|
/// A lightweight way of describing tasks that are created from handling LSP
|
|
/// requests or notifications for the purpose of dependency tracking.
|
|
package enum MessageHandlingDependencyTracker: QueueBasedMessageHandlerDependencyTracker {
|
|
/// A task that changes the global configuration of sourcekit-lsp in any way.
|
|
///
|
|
/// No other tasks must execute simultaneously with this task since they
|
|
/// might be relying on this task to take effect.
|
|
case globalConfigurationChange
|
|
|
|
/// A request that depends on the state of all documents.
|
|
///
|
|
/// These requests wait for `documentUpdate` tasks for all documents to finish before being executed.
|
|
///
|
|
/// Requests that only read the semantic index and are not affected by changes to the in-memory file contents should
|
|
/// `freestanding` requests.
|
|
case workspaceRequest
|
|
|
|
/// Changes the contents of the document with the given URI.
|
|
///
|
|
/// Any other updates or requests to this document must wait for the
|
|
/// document update to finish before being executed
|
|
case documentUpdate(DocumentURI)
|
|
|
|
/// A request that concerns one document.
|
|
///
|
|
/// Any updates to this document must be processed before the document
|
|
/// request can be handled. Multiple requests to the same document can be
|
|
/// handled simultaneously.
|
|
case documentRequest(DocumentURI)
|
|
|
|
/// A request that doesn't have any dependencies other than global
|
|
/// configuration changes.
|
|
case freestanding
|
|
|
|
/// Whether this request needs to finish before `other` can start executing.
|
|
package func isDependency(of other: MessageHandlingDependencyTracker) -> Bool {
|
|
switch (self, other) {
|
|
// globalConfigurationChange
|
|
case (.globalConfigurationChange, _): return true
|
|
case (_, .globalConfigurationChange): return true
|
|
|
|
// workspaceRequest
|
|
case (.workspaceRequest, .workspaceRequest): return false
|
|
case (.documentUpdate, .workspaceRequest): return true
|
|
case (.workspaceRequest, .documentUpdate): return true
|
|
case (.workspaceRequest, .documentRequest): return false
|
|
case (.documentRequest, .workspaceRequest): return false
|
|
|
|
// documentUpdate
|
|
case (.documentUpdate(let selfUri), .documentUpdate(let otherUri)):
|
|
return selfUri == otherUri
|
|
case (.documentUpdate(let selfUri), .documentRequest(let otherUri)):
|
|
return selfUri.buildSettingsFile == otherUri.buildSettingsFile
|
|
case (.documentRequest(let selfUri), .documentUpdate(let otherUri)):
|
|
return selfUri.buildSettingsFile == otherUri.buildSettingsFile
|
|
|
|
// documentRequest
|
|
case (.documentRequest, .documentRequest):
|
|
return false
|
|
|
|
// freestanding
|
|
case (.freestanding, _):
|
|
return false
|
|
case (_, .freestanding):
|
|
return false
|
|
}
|
|
}
|
|
|
|
package init(_ notification: some NotificationType) {
|
|
switch notification {
|
|
case is CancelRequestNotification:
|
|
self = .freestanding
|
|
case is CancelWorkDoneProgressNotification:
|
|
self = .freestanding
|
|
case is DidChangeActiveDocumentNotification:
|
|
// The notification doesn't change behavior in an observable way, so we can treat it as freestanding.
|
|
self = .freestanding
|
|
case is DidChangeConfigurationNotification:
|
|
self = .globalConfigurationChange
|
|
case let notification as DidChangeNotebookDocumentNotification:
|
|
self = .documentUpdate(notification.notebookDocument.uri)
|
|
case let notification as DidChangeTextDocumentNotification:
|
|
self = .documentUpdate(notification.textDocument.uri)
|
|
case is DidChangeWatchedFilesNotification:
|
|
// Technically, the watched files notification can change the response of any other request (eg. because a target
|
|
// needs to be re-prepared). But treating it as a `globalConfiguration` inserts a lot of barriers in request
|
|
// handling and significantly prevents parallelism. Since many editors batch file change notifications already,
|
|
// they might have delayed the file change notification even more, which is equivalent to handling the
|
|
// notification a little later inside SourceKit-LSP. Thus, treating it as `freestanding` should be acceptable.
|
|
self = .freestanding
|
|
case is DidChangeWorkspaceFoldersNotification:
|
|
self = .globalConfigurationChange
|
|
case let notification as DidCloseNotebookDocumentNotification:
|
|
self = .documentUpdate(notification.notebookDocument.uri)
|
|
case let notification as DidCloseTextDocumentNotification:
|
|
self = .documentUpdate(notification.textDocument.uri)
|
|
case is DidCreateFilesNotification:
|
|
self = .freestanding
|
|
case is DidDeleteFilesNotification:
|
|
self = .freestanding
|
|
case let notification as DidOpenNotebookDocumentNotification:
|
|
self = .documentUpdate(notification.notebookDocument.uri)
|
|
case let notification as DidOpenTextDocumentNotification:
|
|
self = .documentUpdate(notification.textDocument.uri)
|
|
case is DidRenameFilesNotification:
|
|
self = .freestanding
|
|
case let notification as DidSaveNotebookDocumentNotification:
|
|
self = .documentUpdate(notification.notebookDocument.uri)
|
|
case let notification as DidSaveTextDocumentNotification:
|
|
self = .documentUpdate(notification.textDocument.uri)
|
|
case is ExitNotification:
|
|
self = .globalConfigurationChange
|
|
case is InitializedNotification:
|
|
self = .globalConfigurationChange
|
|
case is LogMessageNotification:
|
|
self = .freestanding
|
|
case is LogTraceNotification:
|
|
self = .freestanding
|
|
case is PublishDiagnosticsNotification:
|
|
self = .freestanding
|
|
case let notification as ReopenTextDocumentNotification:
|
|
self = .documentUpdate(notification.textDocument.uri)
|
|
case is SetTraceNotification:
|
|
// `$/setTrace` changes a global configuration setting but it doesn't affect the result of any other request. To
|
|
// avoid blocking other requests on a `$/setTrace` notification the client might send during launch, we treat it
|
|
// as a freestanding message.
|
|
// Also, we don't do anything with this notification at the moment, so it doesn't matter.
|
|
self = .freestanding
|
|
case is ShowMessageNotification:
|
|
self = .freestanding
|
|
case let notification as WillSaveTextDocumentNotification:
|
|
self = .documentUpdate(notification.textDocument.uri)
|
|
case is WorkDoneProgress:
|
|
self = .freestanding
|
|
default:
|
|
logger.error(
|
|
"""
|
|
Unknown notification \(type(of: notification)). Treating as a freestanding notification. \
|
|
This might lead to out-of-order request handling
|
|
"""
|
|
)
|
|
self = .freestanding
|
|
}
|
|
}
|
|
|
|
package init(_ request: some RequestType) {
|
|
switch request {
|
|
case is ApplyEditRequest:
|
|
self = .freestanding
|
|
case is CallHierarchyIncomingCallsRequest:
|
|
self = .freestanding
|
|
case is CallHierarchyOutgoingCallsRequest:
|
|
self = .freestanding
|
|
case is CodeActionResolveRequest:
|
|
self = .freestanding
|
|
case is CodeLensRefreshRequest:
|
|
self = .freestanding
|
|
case is CodeLensResolveRequest:
|
|
self = .freestanding
|
|
case is CompletionItemResolveRequest:
|
|
self = .freestanding
|
|
case is CreateWorkDoneProgressRequest:
|
|
self = .freestanding
|
|
case is DiagnosticsRefreshRequest:
|
|
self = .freestanding
|
|
case is DocumentLinkResolveRequest:
|
|
self = .freestanding
|
|
case let request as ExecuteCommandRequest:
|
|
if let uri = request.textDocument?.uri {
|
|
self = .documentRequest(uri)
|
|
} else {
|
|
self = .freestanding
|
|
}
|
|
case let request as GetReferenceDocumentRequest:
|
|
self = .documentRequest(request.uri)
|
|
case is InitializeRequest:
|
|
self = .globalConfigurationChange
|
|
case is InlayHintRefreshRequest:
|
|
self = .freestanding
|
|
case is InlayHintResolveRequest:
|
|
self = .freestanding
|
|
case is InlineValueRefreshRequest:
|
|
self = .freestanding
|
|
case is IsIndexingRequest:
|
|
self = .freestanding
|
|
case is OutputPathsRequest:
|
|
self = .freestanding
|
|
case is RenameRequest:
|
|
// Rename might touch multiple files. Make it a global configuration change so that edits to all files that might
|
|
// be affected have been processed.
|
|
self = .globalConfigurationChange
|
|
case is RegisterCapabilityRequest:
|
|
self = .globalConfigurationChange
|
|
case is SetOptionsRequest:
|
|
// The request does not modify any global state in an observable way, so we can treat it as a freestanding
|
|
// request.
|
|
self = .freestanding
|
|
case is ShowMessageRequest:
|
|
self = .freestanding
|
|
case is ShutdownRequest:
|
|
self = .globalConfigurationChange
|
|
case is SourceKitOptionsRequest:
|
|
self = .freestanding
|
|
case is SynchronizeRequest:
|
|
self = .globalConfigurationChange
|
|
case is TriggerReindexRequest:
|
|
self = .globalConfigurationChange
|
|
case is TypeHierarchySubtypesRequest:
|
|
self = .freestanding
|
|
case is TypeHierarchySupertypesRequest:
|
|
self = .freestanding
|
|
case is UnregisterCapabilityRequest:
|
|
self = .globalConfigurationChange
|
|
case is WillCreateFilesRequest:
|
|
self = .freestanding
|
|
case is WillDeleteFilesRequest:
|
|
self = .freestanding
|
|
case is WillRenameFilesRequest:
|
|
self = .freestanding
|
|
case is WorkspaceDiagnosticsRequest:
|
|
self = .freestanding
|
|
case is WorkspaceFoldersRequest:
|
|
self = .freestanding
|
|
case is WorkspaceSemanticTokensRefreshRequest:
|
|
self = .freestanding
|
|
case is WorkspaceSymbolResolveRequest:
|
|
self = .freestanding
|
|
case is WorkspaceSymbolsRequest:
|
|
self = .freestanding
|
|
case is WorkspaceTestsRequest:
|
|
self = .workspaceRequest
|
|
case is WorkspacePlaygroundsRequest:
|
|
self = .workspaceRequest
|
|
case let request as any TextDocumentRequest:
|
|
self = .documentRequest(request.textDocument.uri)
|
|
default:
|
|
logger.error(
|
|
"""
|
|
Unknown request \(type(of: request)). Treating as a freestanding request. \
|
|
This might lead to out-of-order request handling
|
|
"""
|
|
)
|
|
self = .freestanding
|
|
}
|
|
}
|
|
}
|