//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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 BuildServerIntegration
@_spi(SourceKitLSP) import BuildServerProtocol
import Dispatch
import Foundation
import IndexStoreDB
@_spi(SourceKitLSP) package import LanguageServerProtocol
@_spi(SourceKitLSP) import LanguageServerProtocolExtensions
@_spi(SourceKitLSP) package import LanguageServerProtocolTransport
@_spi(SourceKitLSP) import SKLogging
package import SKOptions
import SemanticIndex
import SourceKitD
import SwiftExtensions
import Synchronization
package import ToolchainRegistry
@_spi(SourceKitLSP) package import ToolsProtocolsSwiftExtensions

import struct TSCBasic.AbsolutePath
import protocol TSCBasic.FileSystem

/// Disambiguate LanguageServerProtocol.Language and IndexstoreDB.Language
package typealias Language = LanguageServerProtocol.Language

/// The SourceKit-LSP server.
///
/// This is the client-facing language server implementation, providing indexing, multiple-toolchain
/// and cross-language support. Requests may be dispatched to language-specific services or handled
/// centrally, but this is transparent to the client.
package actor SourceKitLSPServer {
  package let messageHandlingHelper = QueueBasedMessageHandlerHelper(
    signpostLoggingCategory: "message-handling",
    createLoggingScope: true
  )

  package let messageHandlingQueue = AsyncQueue<MessageHandlingDependencyTracker>()

  /// The queue on which we keep track of `inProgressTextDocumentRequests` to ensure updates to
  /// `inProgressTextDocumentRequests` are handled in order.
  package let textDocumentTrackingQueue = AsyncQueue<Serial>()

  /// The queue on which all modifications of `workspaceForUri` happen. This means that the value of
  /// `workspacesAndIsImplicit` and `workspaceForUri` can't change while executing a closure on `workspaceQueue`.
  private let workspaceQueue = AsyncQueue<Serial>()

  /// The connection to the editor.
  nonisolated let client: LegacyNameFallbackConnection

  /// Set to `true` after the `SourceKitLSPServer` has send the reply to the `InitializeRequest`.
  ///
  /// Initialization can be awaited using `waitUntilInitialized`.
  private var initialized: Bool = false

  private let _options: Mutex<SourceKitLSPOptions>
  nonisolated package var options: SourceKitLSPOptions {
    _options.withLock { $0 }
  }

  package let hooks: Hooks

  let toolchainRegistry: ToolchainRegistry

  package var capabilityRegistry: CapabilityRegistry?

  let languageServiceRegistry: LanguageServiceRegistry

  package nonisolated let documentManager = DocumentManager()

  /// The `TaskScheduler` that schedules all background indexing tasks.
  ///
  /// Shared process-wide to ensure the scheduled index operations across multiple workspaces don't exceed the maximum
  /// number of processor cores that the user allocated to background indexing.
  private let indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>

  /// Implicitly unwrapped optional so we can create an `IndexProgressManager` that has a weak reference to
  /// `SourceKitLSPServer`.
  /// `nonisolated(unsafe)` because `indexProgressManager` will not be modified after it is assigned from the
  /// initializer.
  private(set) nonisolated(unsafe) var indexProgressManager: IndexProgressManager!

  /// Implicitly unwrapped optional so we can create an `SharedWorkDoneProgressManager` that has a weak reference to
  /// `SourceKitLSPServer`.
  /// `nonisolated(unsafe)` because `sourcekitdCrashedWorkDoneProgress` will not be modified after it is assigned from
  /// the initializer.
  nonisolated(unsafe) package private(set) var sourcekitdCrashedWorkDoneProgress: SharedWorkDoneProgressManager!

  /// Implicitly unwrapped optional so we can create an `EntryPointManager` that has a weak reference to
  /// `SourceKitLSPServer`.
  /// `nonisolated(unsafe)` because `entryPointManager` will not be modified after it is assigned from the
  /// initializer.
  private(set) nonisolated(unsafe) var entryPointManager: EntryPointManager!

  /// Stores which workspace the given URI has been opened in.
  ///
  /// - Important: Must only be modified from `workspaceQueue`. This means that the value of `workspaceForUri`
  ///   can't change while executing an operation on `workspaceQueue`.
  private var workspaceForUri: [DocumentURI: WeakWorkspace] = [:]

  /// The open workspaces.
  ///
  /// Implicit workspaces are workspaces that weren't actually specified by the client during initialization or by a
  /// `didChangeWorkspaceFolders` request. Instead, they were opened by sourcekit-lsp because a file could not be
  /// handled by any of the open workspaces but one of the file's parent directories had handling capabilities for it.
  ///
  /// - Important: Must only be modified from `workspaceQueue`. This means that the value of `workspacesAndIsImplicit`
  ///   can't change while executing an operation on `workspaceQueue`.
  private var workspacesAndIsImplicit: [(workspace: Workspace, isImplicit: Bool)] = [] {
    didSet {
      self.scheduleUpdateOfUriToWorkspace()
    }
  }

  var workspaces: [Workspace] {
    return workspacesAndIsImplicit.map(\.workspace)
  }

  package func setWorkspaces(_ newValue: [(workspace: Workspace, isImplicit: Bool)]) {
    workspaceQueue.async {
      self.workspacesAndIsImplicit = newValue
    }
  }

  /// For all currently handled text document requests a mapping from the document to the corresponding request ID and
  /// the method of the request (ie. the value of `TextDocumentRequest.method`).
  private var inProgressTextDocumentRequests: [DocumentURI: [(id: RequestID, requestMethod: String)]] = [:]

  var onExit: () -> Void

  /// The files that we asked the client to watch.
  private var watchers: Set<FileSystemWatcher> = []

  private static func maxConcurrentIndexingTasksByPriority(
    isIndexingPaused: Bool,
    options: SourceKitLSPOptions
  ) -> [(priority: TaskPriority, maxConcurrentTasks: Int)] {
    // Use `processorCount` instead of `activeProcessorCount` here because `activeProcessorCount` may be decreased due
    // to thermal throttling. We don't want to consistently limit the concurrent indexing tasks if SourceKit-LSP was
    // launched during a period of thermal throttling.
    let processorCount = ProcessInfo.processInfo.processorCount
    let lowPriorityCores =
      if isIndexingPaused {
        0
      } else {
        max(
          Int(options.indexOrDefault.maxCoresPercentageToUseForBackgroundIndexingOrDefault * Double(processorCount)),
          1
        )
      }
    return [
      (TaskPriority.medium, processorCount),
      (TaskPriority.low, lowPriorityCores),
    ]
  }

  /// Creates a language server for the given client.
  package init(
    client: any Connection,
    toolchainRegistry: ToolchainRegistry,
    languageServerRegistry: LanguageServiceRegistry,
    options: SourceKitLSPOptions,
    hooks: Hooks,
    onExit: @escaping () -> Void = {}
  ) {
    self.toolchainRegistry = toolchainRegistry
    self.languageServiceRegistry = languageServerRegistry
    self._options = Mutex(options)
    self.hooks = hooks
    self.onExit = onExit

    self.client = LegacyNameFallbackConnection(client, legacyNames: MessageRegistry.lspLegacyNames)
    self.indexTaskScheduler = TaskScheduler(
      maxConcurrentTasksByPriority: Self.maxConcurrentIndexingTasksByPriority(isIndexingPaused: false, options: options)
    )
    self.indexProgressManager = nil
    self.indexProgressManager = IndexProgressManager(sourceKitLSPServer: self)
    self.sourcekitdCrashedWorkDoneProgress = SharedWorkDoneProgressManager(
      sourceKitLSPServer: self,
      tokenPrefix: "sourcekitd-crashed",
      title: "SourceKit-LSP: Restoring functionality",
      message: "Please run 'sourcekit-lsp diagnose' to file an issue"
    )
    self.entryPointManager = EntryPointManager(sourceKitLSPServer: self)
  }

  /// Await until the server has send the reply to the initialize request.
  package func waitUntilInitialized() async {
    // The polling of `initialized` is not perfect but it should be OK, because
    //  - In almost all cases the server should already be initialized.
    //  - If it's not initialized, we expect initialization to finish fairly quickly. Even if initialization takes 5s
    //    this only results in 50 polls, which is acceptable.
    // Alternative solutions that signal via an async sequence seem overkill here.
    while !initialized {
      do {
        try await Task.sleep(for: .seconds(0.1))
      } catch {
        break
      }
    }
  }

  /// Search through all the parent directories of `uri` and check if any of these directories contain a workspace that
  /// can be handled by with a build server.
  ///
  /// The search will not consider any directory that is not a child of any of the directories in `rootUris`. This
  /// prevents us from picking up a workspace that is outside of the folders that the user opened.
  private func findImplicitWorkspace(for uri: DocumentURI) async -> Workspace? {
    guard var url = uri.fileURL?.deletingLastPathComponent() else {
      return nil
    }

    // Roots of opened workspaces - only consider explicit here (all implicit must necessarily be subdirectories of
    // the explicit workspace roots)
    let workspaceRoots = workspacesAndIsImplicit.filter { !$0.isImplicit }.compactMap { $0.workspace.rootUri?.fileURL }

    // We want to skip creating another workspace if any existing already has the same config path. This could happen if
    // an existing workspace hasn't reloaded after a new file was added to it (and thus that build server needs to be
    // reloaded).
    let configPaths = await workspacesAndIsImplicit.asyncCompactMap {
      await $0.workspace.buildServerManager.configPath
    }

    while url.pathComponents.count > 1 && workspaceRoots.contains(where: { $0.isPrefix(of: url) }) {
      defer {
        url.deleteLastPathComponent()
      }

      let uri = DocumentURI(url)
      let options = SourceKitLSPOptions.merging(base: self.options, workspaceFolder: uri)

      // Some build servers consider paths outside of the folder (eg. BSP has settings in the home directory). If we
      // allowed those paths, then the very first folder that the file is in would always be its own build server - so
      // skip them in that case.
      guard
        let buildServerSpec = determineBuildServer(
          forWorkspaceFolder: uri,
          onlyConsiderRoot: true,
          options: options,
          hooks: hooks.buildServerHooks
        )
      else {
        continue
      }
      if configPaths.contains(buildServerSpec.configPath) {
        continue
      }

      // No existing workspace matches this root - create one.
      guard
        let workspace = await orLog(
          "Creating implicit workspace",
          { try await createWorkspace(workspaceFolder: uri, options: options, buildServerSpec: buildServerSpec) }
        )
      else {
        continue
      }
      return workspace
    }
    return nil
  }

  package func workspaceForDocument(uri: DocumentURI) async -> Workspace? {
    let uri = uri.buildSettingsFile
    if let cachedWorkspace = self.workspaceForUri[uri]?.value {
      return cachedWorkspace
    }

    return await self.workspaceQueue.async {
      await self.computeWorkspaceForDocument(uri: uri)
    }.valuePropagatingCancellation
  }

  /// This method must be executed on `workspaceQueue` to ensure that the file handling capabilities of the
  /// workspaces don't change during the computation. Otherwise, we could run into a race condition like the following:
  ///  1. We don't have an entry for file `a.swift` in `workspaceForUri` and start the computation
  ///  2. We find that the first workspace in `self.workspaces` can handle this file.
  ///  3. During the `await ... .fileHandlingCapability` for a second workspace the file handling capabilities for the
  ///    first workspace change, meaning it can no longer handle the document. This resets `workspaceForUri`
  ///    assuming that the URI to workspace relation will get re-computed.
  ///  4. But we then set `workspaceForUri[uri]` to the workspace found in step (2), caching an out-of-date result.
  ///
  /// Furthermore, the computation of the workspace for a URI can create a new implicit workspace, which modifies
  /// `workspacesAndIsImplicit` and which must only be modified on `workspaceQueue`.
  ///
  /// - Important: Must only be invoked from `workspaceQueue`.
  private func computeWorkspaceForDocument(uri: DocumentURI) async -> Workspace? {
    // Pick the workspace with the best FileHandlingCapability for this file.
    // If there is a tie, use the workspace that occurred first in the list.
    var bestWorkspace = await self.workspaces.asyncFirst {
      await $0.buildServerManager.canHandle(uri)
    }
    if bestWorkspace == nil {
      // We weren't able to handle the document with any of the known workspaces. See if any of the document's parent
      // directories contain a workspace that might be able to handle the document
      if let workspace = await self.findImplicitWorkspace(for: uri) {
        logger.log("Opening implicit workspace at \(workspace.rootUri.forLogging) to handle \(uri.forLogging)")
        self.workspacesAndIsImplicit.append((workspace: workspace, isImplicit: true))
        bestWorkspace = workspace
      }
    }
    let workspace = bestWorkspace ?? self.workspaces.first
    self.workspaceForUri[uri] = WeakWorkspace(workspace)
    return workspace
  }

  /// Check that the entries in `workspaceForUri` are still up-to-date after workspaces might have changed.
  ///
  /// For any entries that are not up-to-date, close the document in the old workspace and open it in the new document.
  ///
  /// This method returns immediately and schedules the check in the background as a global configuration change.
  /// Requests may still be served by their old workspace until this configuration change is executed by
  /// `SourceKitLSPServer`.
  private func scheduleUpdateOfUriToWorkspace() {
    messageHandlingQueue.async(priority: .low, metadata: .globalConfigurationChange) {
      logger.info("Updating URI to workspace")
      // For each document that has moved to a different workspace, close it in
      // the old workspace and open it in the new workspace.
      for docUri in self.documentManager.openDocuments {
        await self.workspaceQueue.async {
          let oldWorkspace = self.workspaceForUri[docUri]?.value
          let newWorkspace = await self.computeWorkspaceForDocument(uri: docUri)
          guard newWorkspace !== oldWorkspace else {
            return  // Nothing to do, workspace didn't change for this document
          }
          guard let snapshot = try? self.documentManager.latestSnapshot(docUri) else {
            return
          }
          if let oldWorkspace = oldWorkspace {
            await self.closeDocument(
              DidCloseTextDocumentNotification(
                textDocument: TextDocumentIdentifier(docUri)
              ),
              workspace: oldWorkspace
            )
          }
          logger.info(
            "Changing workspace of \(docUri.forLogging) from \(oldWorkspace?.rootUri?.forLogging) to \(newWorkspace?.rootUri?.forLogging)"
          )
          self.workspaceForUri[docUri] = WeakWorkspace(newWorkspace)
          if let newWorkspace = newWorkspace {
            await self.openDocument(
              DidOpenTextDocumentNotification(
                textDocument: TextDocumentItem(
                  uri: docUri,
                  language: snapshot.language,
                  version: snapshot.version,
                  text: snapshot.text
                )
              ),
              workspace: newWorkspace
            )
          }
        }.valuePropagatingCancellation
      }
      // `indexProgressManager` iterates over all workspaces in the SourceKitLSPServer. Modifying workspaces might thus
      // update the index progress status.
      self.indexProgressManager.indexProgressStatusDidChange()
    }
  }

  /// Execute `notificationHandler` with the request as well as the workspace
  /// and language that handle this document.
  private func withLanguageServiceAndWorkspace<NotificationType: TextDocumentNotification>(
    for notification: NotificationType,
    notificationHandler: @escaping (NotificationType, any LanguageService) async -> Void
  ) async {
    let doc = notification.textDocument.uri
    guard let workspace = await self.workspaceForDocument(uri: doc) else {
      return
    }

    // This should be created as soon as we receive an open call, even if the document
    // isn't yet ready.
    for languageService in workspace.languageServices(forOpenDocument: doc) {
      await notificationHandler(notification, languageService)
    }
  }

  private func handleRequest<RequestType: TextDocumentRequest>(
    for request: RequestAndReply<RequestType>,
    requestHandler:
      @Sendable @escaping (
        RequestType, Workspace, any LanguageService
      ) async throws ->
      RequestType.Response
  ) async {
    await request.reply {
      let request = request.params
      let doc = request.textDocument.uri
      guard let workspace = await self.workspaceForDocument(uri: request.textDocument.uri) else {
        throw ResponseError.workspaceNotOpen(request.textDocument.uri)
      }
      let languageServices = workspace.languageServices(forOpenDocument: doc)
      if languageServices.isEmpty {
        throw ResponseError.unknown("No language service for '\(request.textDocument.uri)' found")
      }
      // Return the results from the first language service that doesn't throw a `requestNotImplemented` error.
      for languageService in languageServices {
        do {
          return try await requestHandler(request, workspace, languageService)
        } catch let error as ResponseError where error.code == .requestNotImplemented {
          continue
        }
      }
      throw ResponseError.unknown("No language service implements \(type(of: request).method)")
    }
  }

  /// Send the given notification to the editor.
  package nonisolated func sendNotificationToClient(_ notification: some NotificationType) {
    client.send(notification)
  }

  /// Send the given request to the editor.
  package func sendRequestToClient<R: RequestType>(_ request: R) async throws -> R.Response {
    return try await client.send(request)
  }

  /// After the language service has crashed, send `DidOpenTextDocumentNotification`s to a newly instantiated language service for previously open documents.
  package func reopenDocuments(for languageService: any LanguageService) async {
    for documentUri in self.documentManager.openDocuments {
      guard let workspace = await self.workspaceForDocument(uri: documentUri) else {
        continue
      }
      guard workspace.languageServices(forOpenDocument: documentUri).contains(where: { $0 === languageService }) else {
        continue
      }
      guard let snapshot = try? self.documentManager.latestSnapshot(documentUri) else {
        // The document has been closed since we retrieved its URI. We don't care about it anymore.
        continue
      }

      // Close the document properly in the document manager and build server manager to start with a clean sheet when
      // re-opening it.
      // This closes and re-opens the document in all of its language services, not just the crashed language service
      // but since crashing language services should be rare, this is acceptable.
      let closeNotification = DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(documentUri))
      await self.closeDocument(closeNotification, workspace: workspace)

      let textDocument = TextDocumentItem(
        uri: documentUri,
        language: snapshot.language,
        version: snapshot.version,
        text: snapshot.text
      )
      await self.openDocument(DidOpenTextDocumentNotification(textDocument: textDocument), workspace: workspace)
    }
  }

}

// MARK: - MessageHandler

extension SourceKitLSPServer: QueueBasedMessageHandler {
  private enum ImplicitTextDocumentRequestCancellationReason {
    case documentChanged
    case documentClosed
  }

  package nonisolated func didReceive(notification: some NotificationType) {
    let textDocumentUri: DocumentURI
    let cancellationReason: ImplicitTextDocumentRequestCancellationReason
    switch notification {
    case let params as DidChangeTextDocumentNotification:
      textDocumentUri = params.textDocument.uri
      cancellationReason = .documentChanged
    case let params as DidCloseTextDocumentNotification:
      textDocumentUri = params.textDocument.uri
      cancellationReason = .documentClosed
    default:
      return
    }
    textDocumentTrackingQueue.async(priority: .high) {
      await self.cancelTextDocumentRequests(for: textDocumentUri, reason: cancellationReason)
    }
  }

  /// Cancel all in-progress text document requests for the given document.
  ///
  /// As a user makes an edit to a file, these requests are most likely no longer relevant. It also makes sure that a
  /// long-running sourcekitd request can't block the entire language server if the client does not cancel all requests.
  /// For example, consider the following sequence of requests:
  ///  - `textDocument/semanticTokens/full` for document A
  ///  - `textDocument/didChange` for document A
  ///  - `textDocument/formatting` for document A
  ///
  /// If the editor is not cancelling the semantic tokens request on edit (like VS Code does), then the `didChange`
  /// notification is blocked on the semantic tokens request finishing. Hence, we also can't run the
  /// `textDocument/formatting` request. Cancelling the semantic tokens on the edit fixes the issue.
  ///
  /// This method is a no-op if `cancelTextDocumentRequestsOnEditAndClose` is disabled.
  ///
  /// - Important: Should be invoked on `textDocumentTrackingQueue` to ensure that new text document requests are
  ///   registered before a notification that triggers cancellation might come in.
  private func cancelTextDocumentRequests(for uri: DocumentURI, reason: ImplicitTextDocumentRequestCancellationReason) {
    let staleRequestSupport = self.capabilityRegistry?.clientCapabilities.general?.staleRequestSupport
    for (requestID, requestMethod) in self.inProgressTextDocumentRequests[uri, default: []] {
      // Implicitly cancel text document requests if:
      //  - We have enabled implicit text document request cancellation in the SourceKit options
      //  - The client has indicated that it does not cancel stale requests. Defaults to `true` because this is an
      //    option introduced in LSP 3.17 and most clients cancel requests diligently without setting
      //    `staleRequestSupport.cancel = true`
      //  - `staleRequestSupport.retryOnContentModified` contains this request method. Documentation for this behavior
      //    is very limited but it appears that if a request is included in that array, the client (VS Code in
      //    particular) expects to receive a `ContentModified` response when the LSP server detects an edit, in which
      //    case it will re-run the request with the new file contents.
      guard
        self.options.cancelTextDocumentRequestsOnEditAndCloseOrDefault
          || !(staleRequestSupport?.cancel ?? true)
          || staleRequestSupport?.retryOnContentModified.contains(requestMethod) ?? false
      else {
        continue
      }

      if reason == .documentChanged && requestMethod == CompletionRequest.method {
        // As the user types, we filter the code completion results. Cancelling the completion request on every
        // keystroke means that we will never build the initial list of completion results for this code
        // completion session if building that list takes longer than the user's typing cadence (eg. for global
        // completions) and we will thus not show any completions.
        continue
      }
      logger.info("Implicitly cancelling request \(requestID)")
      self.messageHandlingHelper.cancelRequest(id: requestID, error: .contentModified)
    }
  }

  package func handle(notification: some NotificationType) async {
    logger.log("Received notification: \(notification.forLogging)")

    switch notification {
    case let notification as DidChangeActiveDocumentNotification:
      await self.didChangeActiveDocument(notification)
    case let notification as DidChangeTextDocumentNotification:
      await self.changeDocument(notification)
    case let notification as DidChangeWorkspaceFoldersNotification:
      await self.didChangeWorkspaceFolders(notification)
    case let notification as DidCloseTextDocumentNotification:
      await self.closeDocument(notification)
    case let notification as DidChangeWatchedFilesNotification:
      await self.didChangeWatchedFiles(notification)
    case let notification as DidOpenTextDocumentNotification:
      await self.openDocument(notification)
    case let notification as DidSaveTextDocumentNotification:
      await self.withLanguageServiceAndWorkspace(for: notification, notificationHandler: self.didSaveDocument)
    case let notification as InitializedNotification:
      self.clientInitialized(notification)
    case let notification as ExitNotification:
      await self.exit(notification)
    case let notification as ReopenTextDocumentNotification:
      await self.reopenDocument(notification)
    case let notification as WillSaveTextDocumentNotification:
      await self.withLanguageServiceAndWorkspace(for: notification, notificationHandler: self.willSaveDocument)
    // IMPORTANT: When adding a new entry to this switch, also add it to the `MessageHandlingDependencyTracker` initializer.
    default:
      logger.error("Ignoring unknown notification \(type(of: notification).method)")
    }
  }

  package nonisolated func didReceive(request: some RequestType, id: RequestID) {
    guard let request = request as? any TextDocumentRequest else {
      return
    }
    textDocumentTrackingQueue.async(priority: .background) {
      await self.registerInProgressTextDocumentRequest(request, id: id)
    }
  }

  /// - Important: Should be invoked on `textDocumentTrackingQueue` to ensure that new text document requests are
  ///   registered before a notification that triggers cancellation might come in.
  private func registerInProgressTextDocumentRequest<T: TextDocumentRequest>(_ request: T, id: RequestID) {
    self.inProgressTextDocumentRequests[request.textDocument.uri, default: []].append((id: id, requestMethod: T.method))
  }

  package func handle<Request: RequestType>(
    request params: Request,
    id: RequestID,
    reply: @Sendable @escaping (Result<Request.Response, any Error>) -> Void
  ) async {
    defer {
      if let request = params as? any TextDocumentRequest {
        textDocumentTrackingQueue.async(priority: .background) {
          self.inProgressTextDocumentRequests[request.textDocument.uri, default: []].removeAll { $0.id == id }
        }
      }
    }

    await self.hooks.preHandleRequest?(params)

    let startDate = Date()

    let request = RequestAndReply(params) { result in
      reply(result)
      let endDate = Date()
      Task {
        switch result {
        case .success(let response):
          logger.log(
            """
            Succeeded (took \(endDate.timeIntervalSince(startDate) * 1000, privacy: .public)ms)
            \(Request.method, privacy: .public)
            \(response.forLogging)
            """
          )
        case .failure(let error):
          logger.log(
            """
            Failed (took \(endDate.timeIntervalSince(startDate) * 1000, privacy: .public)ms)
            \(Request.method, privacy: .public)(\(id, privacy: .public))
            \(error.forLogging, privacy: .private)
            """
          )
        }
      }
    }

    logger.log("Received request \(id, privacy: .public): \(params.forLogging)")

    if let textDocumentRequest = params as? any TextDocumentRequest {
      await self.clientInteractedWithDocument(textDocumentRequest.textDocument.uri)
    }

    switch request {
    case let request as RequestAndReply<CallHierarchyIncomingCallsRequest>:
      await request.reply { try await incomingCalls(request.params) }
    case let request as RequestAndReply<CallHierarchyOutgoingCallsRequest>:
      await request.reply { try await outgoingCalls(request.params) }
    case let request as RequestAndReply<CallHierarchyPrepareRequest>:
      await self.handleRequest(for: request, requestHandler: self.prepareCallHierarchy)
    case let request as RequestAndReply<CodeActionRequest>:
      await self.handleRequest(for: request, requestHandler: self.codeAction)
    case let request as RequestAndReply<CodeActionResolveRequest>:
      await request.reply { try await codeActionResolve(request.params) }
    case let request as RequestAndReply<CodeLensRequest>:
      await self.handleRequest(for: request, requestHandler: self.codeLens)
    case let request as RequestAndReply<ColorPresentationRequest>:
      await self.handleRequest(for: request, requestHandler: self.colorPresentation)
    case let request as RequestAndReply<CompletionRequest>:
      await self.handleRequest(for: request, requestHandler: self.completion)
    case let request as RequestAndReply<CompletionItemResolveRequest>:
      await request.reply { try await completionItemResolve(request: request.params) }
    case let request as RequestAndReply<SignatureHelpRequest>:
      await self.handleRequest(for: request, requestHandler: self.signatureHelp)
    case let request as RequestAndReply<DeclarationRequest>:
      await self.handleRequest(for: request, requestHandler: self.declaration)
    case let request as RequestAndReply<DefinitionRequest>:
      await self.handleRequest(for: request, requestHandler: self.definition)
    case let request as RequestAndReply<TypeDefinitionRequest>:
      await self.handleRequest(for: request, requestHandler: self.typeDefinition)
    case let request as RequestAndReply<DoccDocumentationRequest>:
      await self.handleRequest(for: request, requestHandler: self.doccDocumentation)
    case let request as RequestAndReply<DocumentColorRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentColor)
    case let request as RequestAndReply<DocumentDiagnosticsRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentDiagnostic)
    case let request as RequestAndReply<DocumentFormattingRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentFormatting)
    case let request as RequestAndReply<DocumentRangeFormattingRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentRangeFormatting)
    case let request as RequestAndReply<DocumentOnTypeFormattingRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentOnTypeFormatting)
    case let request as RequestAndReply<DocumentHighlightRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentSymbolHighlight)
    case let request as RequestAndReply<DocumentSemanticTokensDeltaRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentSemanticTokensDelta)
    case let request as RequestAndReply<DocumentSemanticTokensRangeRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentSemanticTokensRange)
    case let request as RequestAndReply<DocumentSemanticTokensRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentSemanticTokens)
    case let request as RequestAndReply<DocumentSymbolRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentSymbol)
    case let request as RequestAndReply<DocumentTestsRequest>:
      await self.handleRequest(for: request, requestHandler: self.documentTests)
    case let request as RequestAndReply<ExecuteCommandRequest>:
      await request.reply { try await executeCommand(request.params) }
    case let request as RequestAndReply<FoldingRangeRequest>:
      await self.handleRequest(for: request, requestHandler: self.foldingRange)
    case let request as RequestAndReply<SelectionRangeRequest>:
      await self.handleRequest(for: request, requestHandler: self.selectionRange)
    case let request as RequestAndReply<GetReferenceDocumentRequest>:
      await request.reply { try await getReferenceDocument(request.params) }
    case let request as RequestAndReply<HoverRequest>:
      await self.handleRequest(for: request, requestHandler: self.hover)
    case let request as RequestAndReply<ImplementationRequest>:
      await self.handleRequest(for: request, requestHandler: self.implementation)
    case let request as RequestAndReply<IndexedRenameRequest>:
      await self.handleRequest(for: request, requestHandler: self.indexedRename)
    case let request as RequestAndReply<InitializeRequest>:
      await request.reply { try await initialize(request.params) }
      // Only set `initialized` to `true` after we have sent the response to the initialize request to the client.
      initialized = true
    case let request as RequestAndReply<InlayHintRequest>:
      await self.handleRequest(for: request, requestHandler: self.inlayHint)
    case let request as RequestAndReply<InlayHintResolveRequest>:
      await request.reply { try await inlayHintResolve(request: request.params) }
    case let request as RequestAndReply<IsIndexingRequest>:
      await request.reply { try await self.isIndexing(request.params) }
    case let request as RequestAndReply<OutputPathsRequest>:
      await request.reply { try await outputPaths(request.params) }
    case let request as RequestAndReply<PrepareRenameRequest>:
      await self.handleRequest(for: request, requestHandler: self.prepareRename)
    case let request as RequestAndReply<ReferencesRequest>:
      await self.handleRequest(for: request, requestHandler: self.references)
    case let request as RequestAndReply<RenameRequest>:
      await request.reply { try await rename(request.params) }
    case let request as RequestAndReply<SetOptionsRequest>:
      await request.reply { try await self.setBackgroundIndexingPaused(request.params) }
    case let request as RequestAndReply<SourceKitOptionsRequest>:
      await request.reply { try await sourceKitOptions(request.params) }
    case let request as RequestAndReply<ShutdownRequest>:
      await request.reply { try await shutdown(request.params) }
    case let request as RequestAndReply<SymbolInfoRequest>:
      await self.handleRequest(for: request, requestHandler: self.symbolInfo)
    case let request as RequestAndReply<SynchronizeRequest>:
      await request.reply { try await synchronize(request.params) }
    case let request as RequestAndReply<TriggerReindexRequest>:
      await request.reply { try await triggerReindex(request.params) }
    case let request as RequestAndReply<TypeHierarchyPrepareRequest>:
      await self.handleRequest(for: request, requestHandler: self.prepareTypeHierarchy)
    case let request as RequestAndReply<TypeHierarchySubtypesRequest>:
      await request.reply { try await subtypes(request.params) }
    case let request as RequestAndReply<TypeHierarchySupertypesRequest>:
      await request.reply { try await supertypes(request.params) }
    case let request as RequestAndReply<WorkspaceSymbolNamesRequest>:
      await request.reply { try await workspaceSymbolNames(request.params) }
    case let request as RequestAndReply<WorkspaceSymbolInfoRequest>:
      await request.reply { try await workspaceSymbolInfo(request.params) }
    case let request as RequestAndReply<WorkspaceSymbolResolveRequest>:
      await request.reply { try await workspaceSymbolResolve(request.params) }
    case let request as RequestAndReply<WorkspaceSymbolsRequest>:
      await request.reply { try await workspaceSymbols(request.params) }
    case let request as RequestAndReply<WorkspaceTestsRequest>:
      await request.reply { try await workspaceTests(request.params) }
    case let request as RequestAndReply<WorkspacePlaygroundsRequest>:
      await request.reply { try await workspacePlaygrounds(request.params) }
    // IMPORTANT: When adding a new entry to this switch, also add it to the `MessageHandlingDependencyTracker` initializer.
    default:
      await request.reply { throw ResponseError.methodNotFound(Request.method) }
    }
  }
}

extension SourceKitLSPServer {
  nonisolated package func logMessageToIndexLog(
    message: String,
    type: WindowMessageType,
    structure: LanguageServerProtocol.StructuredLogKind?
  ) {
    self.sendNotificationToClient(
      LogMessageNotification(
        type: type,
        message: message,
        logName: "SourceKit-LSP: Indexing",
        structure: structure
      ).representingTaskIDUsingEmojiPrefixIfNecessary(options: options)
    )
  }

  func fileHandlingCapabilityChanged() {
    logger.log("Scheduling update of URI to workspace because file handling capability of a workspace changed")
    self.scheduleUpdateOfUriToWorkspace()
  }
}

// MARK: - Request and notification handling

extension SourceKitLSPServer {

  // MARK: - General

  /// Creates a workspace at the given `uri`.
  ///
  /// A workspace does not necessarily have any build server attached to it, in which case `buildServerSpec` may be
  /// `nil` - consider eg. a top level workspace folder with multiple SwiftPM projects inside it.
  private func createWorkspace(
    workspaceFolder: DocumentURI,
    options: SourceKitLSPOptions,
    buildServerSpec: BuildServerSpec?
  ) async throws -> Workspace {
    guard let capabilityRegistry = capabilityRegistry else {
      struct NoCapabilityRegistryError: Error {}
      logger.log("Cannot open workspace before server is initialized")
      throw NoCapabilityRegistryError()
    }

    logger.log("Creating workspace at \(workspaceFolder.forLogging)")
    logger.logFullObjectInMultipleLogMessages(header: "Workspace options", options.loggingProxy)

    let workspace = await Workspace(
      sourceKitLSPServer: self,
      documentManager: self.documentManager,
      rootUri: workspaceFolder,
      capabilityRegistry: capabilityRegistry,
      buildServerSpec: buildServerSpec,
      toolchainRegistry: self.toolchainRegistry,
      options: options,
      hooks: hooks,
      languageServiceRegistry: languageServiceRegistry,
      indexTaskScheduler: indexTaskScheduler
    )
    return workspace
  }

  /// Determines the build server for the given workspace folder and creates a `Workspace` that uses this inferred build
  /// system.
  private func createWorkspaceWithInferredBuildServer(workspaceFolder: DocumentURI) async throws -> Workspace {
    let options = SourceKitLSPOptions.merging(base: self.options, workspaceFolder: workspaceFolder)
    let buildServerSpec = determineBuildServer(
      forWorkspaceFolder: workspaceFolder,
      onlyConsiderRoot: false,
      options: options,
      hooks: hooks.buildServerHooks
    )
    return try await self.createWorkspace(
      workspaceFolder: workspaceFolder,
      options: options,
      buildServerSpec: buildServerSpec
    )
  }

  func initialize(_ req: InitializeRequest) async throws -> InitializeResult {
    logger.logFullObjectInMultipleLogMessages(header: "Initialize request", AnyRequestType(request: req))
    // If the client can handle `PeekDocumentsRequest`, they can enable the
    // experimental client capability `"workspace/peekDocuments"` through the `req.capabilities.experimental`.
    //
    // The below is a workaround for the vscode-swift extension since it cannot set client capabilities.
    // It passes "workspace/peekDocuments" through the `initializationOptions`.
    var clientCapabilities = req.capabilities
    if case .dictionary(let initializationOptions) = req.initializationOptions {
      let experimentalClientCapabilities = [
        PeekDocumentsRequest.method,
        GetReferenceDocumentRequest.method,
        DidChangeActiveDocumentNotification.method,
      ]
      for capabilityName in experimentalClientCapabilities {
        // Some clients still pass these under their legacy method names, so accept
        // either the current name or the legacy name from `initializationOptions`.
        let experimentalCapability =
          initializationOptions[capabilityName]
          ?? MessageRegistry.lspLegacyNames[capabilityName].flatMap { initializationOptions[$0] }
        guard let experimentalCapability else {
          continue
        }
        var experimentalCapabilities: [String: LSPAny] =
          if case .dictionary(let experimentalCapabilities) = clientCapabilities.experimental {
            experimentalCapabilities
          } else {
            [:]
          }
        experimentalCapabilities[capabilityName] = experimentalCapability
        clientCapabilities.experimental = .dictionary(experimentalCapabilities)
      }

      // The client announces what CodeLenses it supports, and the LSP will only return
      // ones found in the supportedCommands dictionary.
      if let codeLens = initializationOptions["textDocument/codeLens"],
        case let .dictionary(codeLensConfig) = codeLens,
        case let .dictionary(supportedCommands) = codeLensConfig["supportedCommands"]
      {
        let commandMap = supportedCommands.compactMap { (key, value) in
          if case let .string(clientCommand) = value {
            return (SupportedCodeLensCommand(rawValue: key), clientCommand)
          }
          return nil
        }

        clientCapabilities.textDocument?.codeLens?.supportedCommands = Dictionary(uniqueKeysWithValues: commandMap)
      }
    }

    capabilityRegistry = CapabilityRegistry(clientCapabilities: clientCapabilities)

    let initializeOptions = SourceKitLSPOptions(fromLSPAny: req.initializationOptions)
    _options.withLock { options in
      options = SourceKitLSPOptions.merging(base: options, override: initializeOptions)
    }

    logger.log("Initialized SourceKit-LSP")
    logger.logFullObjectInMultipleLogMessages(header: "Global options", options.loggingProxy)

    await workspaceQueue.async { [hooks] in
      if let workspaceFolders = req.workspaceFolders {
        self.workspacesAndIsImplicit += await workspaceFolders.asyncCompactMap { workspaceFolder in
          await orLog("Creating workspace from workspaceFolders") {
            return (
              workspace: try await self.createWorkspaceWithInferredBuildServer(workspaceFolder: workspaceFolder.uri),
              isImplicit: false
            )
          }
        }
      } else if let uri = req.rootURI {
        await orLog("Creating workspace from rootURI") {
          self.workspacesAndIsImplicit.append(
            (workspace: try await self.createWorkspaceWithInferredBuildServer(workspaceFolder: uri), isImplicit: false)
          )
        }
      } else if let path = req.rootPath {
        let uri = DocumentURI(URL(fileURLWithPath: path))
        await orLog("Creating workspace from rootPath") {
          self.workspacesAndIsImplicit.append(
            (workspace: try await self.createWorkspaceWithInferredBuildServer(workspaceFolder: uri), isImplicit: false)
          )
        }
      }

      if self.workspaces.isEmpty {
        logger.error("No workspace found")

        let options = self.options
        let workspace = await Workspace(
          sourceKitLSPServer: self,
          documentManager: self.documentManager,
          rootUri: req.rootURI,
          capabilityRegistry: self.capabilityRegistry!,
          buildServerSpec: nil,
          toolchainRegistry: self.toolchainRegistry,
          options: options,
          hooks: hooks,
          languageServiceRegistry: self.languageServiceRegistry,
          indexTaskScheduler: self.indexTaskScheduler
        )

        self.workspacesAndIsImplicit.append((workspace: workspace, isImplicit: false))
      }
    }.value

    assert(!self.workspaces.isEmpty)

    do {  // Setup EntryPointManager.
      let onWorkspaceTestsChanged =
        capabilityRegistry!.clientHasWorkspaceTestsRefreshSupport
        ? { @Sendable [weak self] in
          _ = Task { try await self?.sendRequestToClient(WorkspaceTestsRefreshRequest()) }
        } : nil
      let onWorkspacePlaygroundsChanged =
        capabilityRegistry!.clientHasWorkspacePlaygroundsRefreshSupport
        ? { @Sendable [weak self] in
          _ = Task { try await self?.sendRequestToClient(WorkspacePlaygroundsRefreshRequest()) }
        } : nil
      await entryPointManager.setCallbacks(
        onWorkspaceTestsChanged: onWorkspaceTestsChanged,
        onWorkspacePlaygroundsChanged: onWorkspacePlaygroundsChanged
      )
    }

    let result = InitializeResult(
      capabilities: await self.serverCapabilities(
        for: req.capabilities,
        registry: self.capabilityRegistry!,
        options: options
      )
    )
    logger.logFullObjectInMultipleLogMessages(header: "Initialize response", AnyRequestType(request: req))
    return result
  }

  func serverCapabilities(
    for client: ClientCapabilities,
    registry: CapabilityRegistry,
    options: SourceKitLSPOptions
  ) async -> ServerCapabilities {
    let completionOptions =
      await registry.clientHasDynamicCompletionRegistration
      ? nil
      : LanguageServerProtocol.CompletionOptions(
        resolveProvider: true,
        triggerCharacters: [".", "("]
      )

    let signatureHelpOptions =
      await registry.clientHasDynamicSignatureHelpRegistration
      ? nil
      : LanguageServerProtocol.SignatureHelpOptions(
        triggerCharacters: ["(", "["],
        // We retrigger on `:` as it's potentially after an argument label which can change the active parameter or signature.
        retriggerCharacters: [",", ":"]
      )

    let onTypeFormattingOptions =
      options.hasExperimentalFeature(.onTypeFormatting)
      ? DocumentOnTypeFormattingOptions(triggerCharacters: ["\n", "\r\n", "\r", "{", "}", ";", ".", ":", "#"])
      : nil

    let foldingRangeOptions =
      await registry.clientHasDynamicFoldingRangeRegistration
      ? nil
      : ValueOrBool<TextDocumentAndStaticRegistrationOptions>.bool(true)

    let inlayHintOptions =
      await registry.clientHasDynamicInlayHintRegistration
      ? nil
      : ValueOrBool.value(InlayHintOptions(resolveProvider: true))

    let semanticTokensOptions =
      await registry.clientHasDynamicSemanticTokensRegistration
      ? nil
      : SemanticTokensOptions(
        legend: SemanticTokensLegend.sourceKitLSPLegend,
        range: .bool(true),
        full: .bool(true)
      )

    let executeCommandOptions =
      await registry.clientHasDynamicExecuteCommandRegistration
      ? nil
      : ExecuteCommandOptions(commands: languageServiceRegistry.languageServices.flatMap { $0.type.builtInCommands })

    var experimentalCapabilities: [String: LSPAny] = [:]
    // Add both the current and legacy method names so old clients still discover the capability.
    func addCapabilities(_ method: String, _ value: LSPAny) {
      experimentalCapabilities[method] = value
      if let legacy = MessageRegistry.lspLegacyNames[method] { experimentalCapabilities[legacy] = value }
    }
    addCapabilities(WorkspaceTestsRequest.method, ["version": 2])
    addCapabilities(WorkspaceTestsRefreshRequest.method, ["version": 1])
    addCapabilities(DocumentTestsRequest.method, ["version": 2])
    addCapabilities(DoccDocumentationRequest.method, ["version": 1])
    addCapabilities(TriggerReindexRequest.method, ["version": 1])
    addCapabilities(GetReferenceDocumentRequest.method, ["version": 1])
    addCapabilities(DidChangeActiveDocumentNotification.method, ["version": 1])
    addCapabilities(SynchronizeRequest.method, ["version": 1])
    addCapabilities(WorkspaceSymbolNamesRequest.method, ["version": 1])
    addCapabilities(WorkspaceSymbolInfoRequest.method, ["version": 1])
    if let toolchain = await toolchainRegistry.preferredToolchain(containing: [\.swiftc]), toolchain.swiftPlay != nil {
      addCapabilities(WorkspacePlaygroundsRefreshRequest.method, ["version": 1])
      addCapabilities(WorkspacePlaygroundsRequest.method, ["version": 1])
    }
    for (key, value) in languageServiceRegistry.languageServices.flatMap({ $0.type.experimentalCapabilities }) {
      if let existingValue = experimentalCapabilities[key] {
        logger.error(
          "Conflicting experimental capabilities for \(key): \(existingValue.forLogging) vs \(value.forLogging)"
        )
      }
      experimentalCapabilities[key] = value
    }

    return ServerCapabilities(
      textDocumentSync: .options(
        TextDocumentSyncOptions(
          openClose: true,
          change: .incremental
        )
      ),
      hoverProvider: .bool(true),
      completionProvider: completionOptions,
      signatureHelpProvider: signatureHelpOptions,
      definitionProvider: .bool(true),
      typeDefinitionProvider: .bool(true),
      implementationProvider: .bool(true),
      referencesProvider: .bool(true),
      documentHighlightProvider: .bool(true),
      documentSymbolProvider: .bool(true),
      workspaceSymbolProvider: .value(WorkspaceSymbolOptions(resolveProvider: true)),
      codeActionProvider: .value(
        CodeActionServerCapabilities(
          clientCapabilities: client.textDocument?.codeAction,
          codeActionOptions: CodeActionOptions(codeActionKinds: nil, resolveProvider: true),
          supportsCodeActions: true
        )
      ),
      codeLensProvider: CodeLensOptions(),
      documentFormattingProvider: .value(DocumentFormattingOptions(workDoneProgress: false)),
      documentRangeFormattingProvider: .value(DocumentRangeFormattingOptions(workDoneProgress: false)),
      documentOnTypeFormattingProvider: onTypeFormattingOptions,
      renameProvider: .value(RenameOptions(prepareProvider: true)),
      colorProvider: .bool(true),
      foldingRangeProvider: foldingRangeOptions,
      declarationProvider: .bool(true),
      executeCommandProvider: executeCommandOptions,
      workspace: WorkspaceServerCapabilities(
        workspaceFolders: .init(
          supported: true,
          changeNotifications: .bool(true)
        )
      ),
      callHierarchyProvider: .bool(true),
      typeHierarchyProvider: .bool(true),
      semanticTokensProvider: semanticTokensOptions,
      inlayHintProvider: inlayHintOptions,
      selectionRangeProvider: .bool(true),
      experimental: .dictionary(experimentalCapabilities)
    )
  }

  package func registerCapabilities(
    for server: ServerCapabilities,
    languages: [Language],
    registry: CapabilityRegistry
  ) async {
    // IMPORTANT: When adding new capabilities here, also add the value of that capability in `SwiftLanguageService`
    // to SourceKitLSPServer.serverCapabilities. That way the capabilities get registered for all languages in case the
    // client does not support dynamic capability registration.

    if let completionOptions = server.completionProvider {
      await registry.registerCompletionIfNeeded(options: completionOptions, for: languages, server: self)
    }
    if let signatureHelpOptions = server.signatureHelpProvider {
      await registry.registerSignatureHelpIfNeeded(options: signatureHelpOptions, for: languages, server: self)
    }
    if server.foldingRangeProvider?.isSupported == true {
      await registry.registerFoldingRangeIfNeeded(options: FoldingRangeOptions(), for: languages, server: self)
    }
    if let semanticTokensOptions = server.semanticTokensProvider {
      await registry.registerSemanticTokensIfNeeded(options: semanticTokensOptions, for: languages, server: self)
    }
    if let inlayHintProvider = server.inlayHintProvider, inlayHintProvider.isSupported {
      let options: InlayHintOptions
      switch inlayHintProvider {
      case .bool(true):
        options = InlayHintOptions()
      case .bool(false):
        return
      case .value(let opts):
        options = opts
      }
      await registry.registerInlayHintIfNeeded(options: options, for: languages, server: self)
    }
    // We use the registration for the diagnostics provider to decide whether to enable pull-diagnostics (see comment
    // on `CapabilityRegistry.clientSupportPullDiagnostics`.
    // Thus, we can't statically register this capability in the server options. We need the client's reply to decide
    // whether it supports pull diagnostics.
    if let diagnosticOptions = server.diagnosticProvider {
      await registry.registerDiagnosticIfNeeded(options: diagnosticOptions, for: languages, server: self)
    }
    if let commandOptions = server.executeCommandProvider {
      await registry.registerExecuteCommandIfNeeded(commands: commandOptions.commands, server: self)
    }
  }

  func clientInitialized(_: InitializedNotification) {
    // Nothing to do.
  }

  /// The server is about to exit, and the server should flush any buffered state.
  ///
  /// The server shall not be used to handle more requests (other than possibly
  /// `shutdown` and `exit`) and should attempt to flush any buffered state
  /// immediately, such as sending index changes to disk.
  ///
  ///  - Note: this method should be safe to call multiple times, since we want to be resilient against multiple
  //     possible shutdown sequences, including pipe failure.
  package func prepareForExit() async {
    // We are shutting down / closing all workspaces and language services, so clear the arrays caching them.
    let workspaces = await self.workspaceQueue.async {
      let workspaces = self.workspaces
      self.workspacesAndIsImplicit = []
      return workspaces
    }.valuePropagatingCancellation

    // Concurrently shut all things down.
    async let taskSchedulerShutdown = self.indexTaskScheduler.shutDown()
    async let workspaceShutdown = workspaces.concurrentForEach { await $0.shutdown() }
    _ = await (taskSchedulerShutdown, workspaceShutdown)

    // Make sure we emit all pending log messages. When we're not using `NonDarwinLogger` this is a no-op.
    await NonDarwinLogger.flush()
  }

  func shutdown(_ request: ShutdownRequest) async throws -> ShutdownRequest.Response {
    await prepareForExit()

    // Wait for all services to shut down before sending the shutdown response.
    // Otherwise we might terminate sourcekit-lsp while it still has open
    // connections to the toolchain servers, which could send messages to
    // sourcekit-lsp while it is being deallocated, causing crashes.
    return ShutdownRequest.Response()
  }

  func exit(_ notification: ExitNotification) async {
    // Should have been called in shutdown, but allow misbehaving clients.
    await prepareForExit()

    // Call onExit only once, and hop off queue to allow the handler to call us back.
    self.onExit()
  }

  /// Start watching for changes with the given patterns.
  func watchFiles(_ fileWatchers: [FileSystemWatcher]) async {
    await self.waitUntilInitialized()
    if fileWatchers.allSatisfy({ self.watchers.contains($0) }) {
      // All watchers already registered. Nothing to do.
      return
    }
    self.watchers.formUnion(fileWatchers)
    await self.capabilityRegistry?.registerDidChangeWatchedFiles(
      watchers: self.watchers.sorted { $0.globPattern < $1.globPattern },
      server: self
    )
  }

  func isIndexing(_ request: IsIndexingRequest) async throws -> IsIndexingResponse {
    guard self.options.hasExperimentalFeature(.isIndexingRequest) else {
      throw ResponseError.unknown("\(IsIndexingRequest.method) indexing is an experimental request")
    }
    let isIndexing =
      await workspaces
      .asyncCompactMap { await $0.semanticIndexManager }
      .asyncContains { await $0.progressStatus != .upToDate }
    return IsIndexingResponse(indexing: isIndexing)
  }

  // MARK: - Text synchronization

  func openDocument(_ notification: DidOpenTextDocumentNotification) async {
    let uri = notification.textDocument.uri
    guard let workspace = await workspaceForDocument(uri: uri) else {
      logger.error(
        "Received open notification for file '\(uri.forLogging)' without a corresponding workspace, ignoring..."
      )
      return
    }
    await openDocument(notification, workspace: workspace)
    await self.clientInteractedWithDocument(uri)
  }

  private func openDocument(_ notification: DidOpenTextDocumentNotification, workspace: Workspace) async {
    // Immediately open the document even if the build server isn't ready. This is important since
    // we check that the document is open when we receive messages from the build server.
    let snapshot = orLog("Opening document") {
      try documentManager.open(
        notification.textDocument.uri,
        language: notification.textDocument.language,
        version: notification.textDocument.version,
        text: notification.textDocument.text
      )
    }
    guard let snapshot else {
      // Already logged failure
      return
    }

    let textDocument = notification.textDocument
    let uri = textDocument.uri
    let language = textDocument.language

    let languageServices = await workspace.languageServices(for: uri, language)

    if languageServices.isEmpty {
      // If we can't create a service, this document is unsupported and we can bail here.
      return
    }

    workspace.setLanguageServices(forOpenDocument: uri, languageServices)

    await workspace.buildServerManager.registerForChangeNotifications(for: uri, language: language)

    // If the document is ready, we can immediately send the notification.
    for languageService in languageServices {
      await languageService.openDocument(notification, snapshot: snapshot)
    }
  }

  func closeDocument(_ notification: DidCloseTextDocumentNotification) async {
    let uri = notification.textDocument.uri
    guard let workspace = await workspaceForDocument(uri: uri) else {
      logger.error(
        "Received close notification for file '\(uri.forLogging)' without a corresponding workspace, ignoring..."
      )
      return
    }
    await self.closeDocument(notification, workspace: workspace)
  }

  func reopenDocument(_ notification: ReopenTextDocumentNotification) async {
    let uri = notification.textDocument.uri
    guard let workspace = await workspaceForDocument(uri: uri) else {
      logger.error(
        "Received reopen notification for file '\(uri.forLogging)' without a corresponding workspace, ignoring..."
      )
      return
    }

    for languageService in workspace.languageServices(forOpenDocument: uri) {
      await languageService.reopenDocument(notification)
    }
  }

  func closeDocument(_ notification: DidCloseTextDocumentNotification, workspace: Workspace) async {
    // Immediately close the document. We need to be sure to clear our pending work queue in case
    // the build server still isn't ready.
    orLog("failed to close document", level: .error) {
      try documentManager.close(notification.textDocument.uri)
    }

    let uri = notification.textDocument.uri

    await workspace.buildServerManager.unregisterForChangeNotifications(for: uri)

    for languageService in workspace.languageServices(forOpenDocument: uri) {
      await languageService.closeDocument(notification)
    }

    workspace.removeLanguageServices(forOpenDocument: uri)

    workspaceQueue.async {
      self.workspaceForUri[notification.textDocument.uri] = nil
    }
  }

  func changeDocument(_ notification: DidChangeTextDocumentNotification) async {
    let uri = notification.textDocument.uri

    guard let workspace = await workspaceForDocument(uri: uri) else {
      logger.error(
        "Received change notification for file '\(uri.forLogging)' without a corresponding workspace, ignoring..."
      )
      return
    }
    await self.clientInteractedWithDocument(uri)

    // If the document is ready, we can handle the change right now.
    let editResult = orLog("Editing document") {
      try documentManager.edit(
        notification.textDocument.uri,
        newVersion: notification.textDocument.version,
        edits: notification.contentChanges
      )
    }
    guard let (preEditSnapshot, postEditSnapshot, edits) = editResult else {
      // Already logged failure
      return
    }
    for languageService in workspace.languageServices(forOpenDocument: uri) {
      await languageService.changeDocument(
        notification,
        preEditSnapshot: preEditSnapshot,
        postEditSnapshot: postEditSnapshot,
        edits: edits
      )
    }
  }

  /// If the client doesn't support the `window/didChangeActiveDocument` notification, when the client interacts with a
  /// document, infer that this is the currently active document and poke preparation of its target. We don't want to
  /// wait for the preparation to finish because that would cause too big a delay.
  ///
  /// In practice, while the user is working on a file, we'll get a text document request for it on a regular basis,
  /// which prepares the files. For files that are open but aren't being worked on (eg. a different tab), we don't
  /// get requests, ensuring that we don't unnecessarily prepare them.
  func clientInteractedWithDocument(_ uri: DocumentURI) async {
    if self.capabilityRegistry?.clientHasExperimentalCapability(DidChangeActiveDocumentNotification.method) ?? false {
      // The client actively notifies us about the currently active document, so we shouldn't infer the active document
      // based on other requests that the client sends us.
      return
    }
    await self.didChangeActiveDocument(
      DidChangeActiveDocumentNotification(textDocument: TextDocumentIdentifier(uri))
    )
  }

  func didChangeActiveDocument(_ notification: DidChangeActiveDocumentNotification) async {
    guard let activeDocument = notification.textDocument?.uri else {
      // The client no longer has a SourceKit-LSP document open. Mark in-progress preparations as irrelevant to cancel
      // them if they haven't started yet.
      for workspace in workspaces {
        await workspace.semanticIndexManager?.markPreparationForEditorFunctionalityTaskAsIrrelevant()
      }
      return
    }
    let documentWorkspace = await self.workspaceForDocument(uri: activeDocument)
    for workspace in workspaces {
      if workspace === documentWorkspace {
        await workspace.semanticIndexManager?.schedulePreparationForEditorFunctionality(of: activeDocument)
      } else {
        await workspace.semanticIndexManager?.markPreparationForEditorFunctionalityTaskAsIrrelevant()
      }
    }
  }

  func willSaveDocument(
    _ notification: WillSaveTextDocumentNotification,
    languageService: any LanguageService
  ) async {
    await languageService.willSaveDocument(notification)
  }

  func didSaveDocument(
    _ notification: DidSaveTextDocumentNotification,
    languageService: any LanguageService
  ) async {
    await languageService.didSaveDocument(notification)
  }

  func didChangeWorkspaceFolders(_ notification: DidChangeWorkspaceFoldersNotification) async {
    // There is a theoretical race condition here: While we await in this function,
    // the open documents or workspaces could have changed. Because of this,
    // we might close a document in a workspace that is no longer responsible
    // for it.
    // In practice, it is fine: sourcekit-lsp will not handle any new messages
    // while we are executing this function and thus there's no risk of
    // documents or workspaces changing. To hit the race condition, you need
    // to invoke the API of `SourceKitLSPServer` directly and open documents
    // while this function is executing. Even in such an API use case, hitting
    // that race condition seems very unlikely.
    var preChangeWorkspaces: [DocumentURI: Workspace] = [:]
    for docUri in self.documentManager.openDocuments {
      preChangeWorkspaces[docUri] = await self.workspaceForDocument(uri: docUri)
    }
    // Capture the workspaces that will be removed so we can shut down their services after.
    var removedWorkspaces: [Workspace] = []
    await workspaceQueue.async {
      if let removed = notification.event.removed {
        var entries = self.workspacesAndIsImplicit
        let firstIndexToRemove = entries.partition { entry in
          // Close all implicit workspaces as well because we could have opened a new explicit workspace that now contains
          // files from a previous implicit workspace.
          entry.isImplicit || removed.contains { $0.uri == entry.workspace.rootUri }
        }
        self.workspacesAndIsImplicit = Array(entries[..<firstIndexToRemove])
        removedWorkspaces = Array(entries[firstIndexToRemove...]).map(\.workspace)
      }
      if let added = notification.event.added {
        let newWorkspaces = await added.asyncCompactMap { workspaceFolder in
          await orLog("Creating workspace after workspace folder change") {
            try await self.createWorkspaceWithInferredBuildServer(workspaceFolder: workspaceFolder.uri)
          }
        }
        self.workspacesAndIsImplicit += newWorkspaces.map { (workspace: $0, isImplicit: false) }
      }
    }.value

    // Shut down orphaned workspaces in a background task to avoid blocking other requests.
    if !removedWorkspaces.isEmpty {
      Task {
        await removedWorkspaces.concurrentForEach { await $0.shutdown() }
      }
    }
  }

  func didChangeWatchedFiles(_ notification: DidChangeWatchedFilesNotification) async {
    // We can't make any assumptions about which file changes a particular build
    // system is interested in. Just because it doesn't have build settings for
    // a file doesn't mean a file can't affect the build server's build settings
    // (e.g. Package.swift doesn't have build settings but affects build
    // settings). Inform the build server about all file changes.
    await workspaces.concurrentForEach { await $0.filesDidChange(notification.changes) }

    // Schedule updating entry point cache.
    await entryPointManager.refresh()
  }

  func setBackgroundIndexingPaused(_ request: SetOptionsRequest) async throws -> VoidResponse {
    guard self.options.hasExperimentalFeature(.setOptionsRequest) else {
      throw ResponseError.unknown("\(SetOptionsRequest.method) indexing is an experimental request")
    }
    if let backgroundIndexingPaused = request.backgroundIndexingPaused {
      await self.indexTaskScheduler.setMaxConcurrentTasksByPriority(
        Self.maxConcurrentIndexingTasksByPriority(isIndexingPaused: backgroundIndexingPaused, options: self.options)
      )
    }

    return VoidResponse()
  }

  func sourceKitOptions(_ request: SourceKitOptionsRequest) async throws -> SourceKitOptionsResponse {
    guard options.hasExperimentalFeature(.sourceKitOptionsRequest) else {
      throw ResponseError.unknown("\(SourceKitOptionsRequest.method) is an experimental request")
    }
    let uri = request.textDocument.uri
    guard let workspace = await self.workspaceForDocument(uri: uri) else {
      throw ResponseError.workspaceNotOpen(uri)
    }

    let target: BuildTargetIdentifier?
    if let requestedTarget = request.target {
      target = BuildTargetIdentifier(uri: requestedTarget)
    } else if let canonicalTarget = await workspace.buildServerManager.canonicalTarget(for: uri) {
      target = canonicalTarget
    } else {
      target = nil
    }
    let didPrepareTarget: Bool?
    if request.prepareTarget, let target, let semanticIndexManager = await workspace.semanticIndexManager {
      didPrepareTarget = await semanticIndexManager.prepareTargetsForSourceKitOptions(target: target)
    } else {
      didPrepareTarget = nil
    }
    let buildSettings = await workspace.buildServerManager.buildSettingsInferredFromMainFile(
      for: request.textDocument.uri,
      target: target,
      language: nil,
      fallbackAfterTimeout: request.allowFallbackSettings
    )
    guard let buildSettings else {
      throw ResponseError.unknown("Unable to determine build settings")
    }
    return SourceKitOptionsResponse(
      compilerArguments: buildSettings.compilerArguments,
      workingDirectory: buildSettings.workingDirectory,
      kind: buildSettings.isFallback ? .fallback : .normal,
      didPrepareTarget: didPrepareTarget,
      data: buildSettings.data
    )
  }

  func outputPaths(_ request: OutputPathsRequest) async throws -> OutputPathsResponse {
    guard options.hasExperimentalFeature(.outputPathsRequest) else {
      throw ResponseError.unknown("\(OutputPathsRequest.method) is an experimental request")
    }
    guard let workspace = self.workspaces.first(where: { $0.rootUri == request.workspace }) else {
      throw ResponseError.unknown("No workspace with URI \(request.workspace.forLogging) found")
    }
    guard await workspace.buildServerManager.initializationData?.outputPathsProvider ?? false else {
      throw ResponseError.unknown("Build server for \(request.workspace.forLogging) does not support output paths")
    }
    let outputPaths = try await workspace.buildServerManager.outputPaths(in: [
      BuildTargetIdentifier(uri: request.target)
    ])
    return OutputPathsResponse(outputPaths: outputPaths)
  }

  // MARK: - Language features

  func completion(
    _ req: CompletionRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> CompletionList {
    return try await languageService.completion(req)
  }

  func completionItemResolve(
    request: CompletionItemResolveRequest
  ) async throws -> CompletionItem {
    // Swift completion items specify the URI of the item they originate from in the `data`
    guard let uri = ResolveItemData(fromLSPAny: request.item.data)?.uri else {
      return request.item
    }
    guard let workspace = await self.workspaceForDocument(uri: uri) else {
      throw ResponseError.workspaceNotOpen(uri)
    }
    let languageService = try workspace.primaryLanguageService(forOpenDocument: uri)
    return try await languageService.completionItemResolve(request)
  }

  func doccDocumentation(
    _ req: DoccDocumentationRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> DoccDocumentationResponse {
    return try await languageService.doccDocumentation(req)
  }

  func hover(
    _ req: HoverRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> HoverResponse? {
    return try await languageService.hover(req)
  }

  func signatureHelp(
    _ req: SignatureHelpRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> SignatureHelp? {
    return try await languageService.signatureHelp(req)
  }

  /// Handle a workspace/symbolNames request, returning the name list.
  func workspaceSymbolNames(_ req: WorkspaceSymbolNamesRequest) async throws -> WorkspaceSymbolNamesResponse {
    var symbols = await self.workspaces
      .concurrentMap { workspace in
        await orLog("Getting symbol names in workspace") {
          try await workspace.uncheckedIndex?.allSymbolNames() ?? []
        } ?? []
      }
      .flatMap { $0 }
    if !symbols.isSortedAndUnique {
      symbols.sortAndDedupe()
    }
    return WorkspaceSymbolNamesResponse(names: symbols)
  }

  /// Map a `SymbolOccurrence` from the index to a `WorkspaceSymbolItem`, or `nil` if it has no
  /// representable location.
  ///
  /// If `useQualifiedName` is `true` and the symbol has a container, the item's `name` is the fully-qualified
  /// name (e.g. `Foo.bar`) and `containerName` is dropped. This is used for qualified queries so that clients
  /// which filter workspace symbols by matching the query against the item's `name` (e.g. VS Code) keep the
  /// result — the qualified query wouldn't match the bare member name otherwise.
  ///
  /// - Parameter referenceDocumentMainFile: The project file whose build settings are used to open the
  ///   symbol's generated interface. **Passing a non-`nil` value changes the shape of the result**: the
  ///   symbol is returned as a `WorkspaceSymbol` with a `sourcekit-lsp://generated-swift-interface`
  ///   reference-document location (its range resolved lazily via `workspaceSymbol/resolve`) and the USR
  ///   in `data`, instead of a `SymbolInformation` with a plain `file://` location. A non-`nil` value must
  ///   therefore only be passed when the client supports **both** `workspace/getReferenceDocument` and
  ///   `workspaceSymbol/resolve`; enforcing that is the caller's responsibility.
  private nonisolated func workspaceSymbolItem(
    for symbolOccurrence: SymbolOccurrence,
    in index: CheckedIndex,
    copiedFileMap: CopiedFileMap,
    referenceDocumentMainFile: DocumentURI?,
    useQualifiedName: Bool = false
  ) throws -> WorkspaceSymbolItem? {
    let containerNames = try index.containerNames(of: symbolOccurrence)
    let separator =
      switch symbolOccurrence.symbol.language {
      case .cxx, .c, .objc: "::"
      case .swift: "."
      }
    let containerName: String? = containerNames.isEmpty ? nil : containerNames.joined(separator: separator)

    // For qualified queries, put the qualified name in the label (which clients filter against) and drop the
    // now-redundant container name.
    let name: String
    let displayContainerName: String?
    if useQualifiedName, let containerName {
      name = "\(containerName)\(separator)\(symbolOccurrence.symbol.name)"
      displayContainerName = nil
    } else {
      name = symbolOccurrence.symbol.name
      displayContainerName = containerName
    }

    if let referenceDocumentMainFile {
      let (interfaceModuleName, groupName) = Self.splitModuleNameAndGroup(symbolOccurrence.location.moduleName)
      let urlData = GeneratedInterfaceDocumentURLData(
        moduleName: interfaceModuleName,
        groupName: groupName,
        primaryFile: referenceDocumentMainFile
      )
      let usr = symbolOccurrence.symbol.usr
      // Include the interface path and module name in `data` so clients can render the candidate without
      // parsing the opaque location URI.
      let data = SourceKitWorkspaceSymbolData(
        usr: usr,
        interfaceURI: symbolOccurrence.location.uri,
        moduleName: symbolOccurrence.location.moduleName
      )
      return WorkspaceSymbolItem.workspaceSymbol(
        WorkspaceSymbol(
          name: name,
          kind: symbolOccurrence.symbol.kind.asLspSymbolKind(),
          containerName: displayContainerName,
          location: .uri(.init(uri: try urlData.uri)),
          data: data.encodeToLSPAny()
        )
      )
    }

    guard let symbolLocation = symbolOccurrence.location.lspLocation else { return nil }
    let location = symbolLocation.adjusted(for: copiedFileMap)
    return WorkspaceSymbolItem.symbolInformation(
      SymbolInformation(
        name: name,
        kind: symbolOccurrence.symbol.kind.asLspSymbolKind(),
        deprecated: nil,
        location: location,
        containerName: displayContainerName
      )
    )
  }

  /// Split an index module name into its module and optional group components.
  private nonisolated static func splitModuleNameAndGroup(
    _ fullModuleName: String
  ) -> (module: String, group: String?) {
    // A dotted index module name is ambiguous: `Foo.Bar` could be module `Foo` with group `Bar`, or a real
    // submodule named `Foo.Bar`, and SourceKit-LSP can't tell the two apart. In practice only the `Swift`
    // module is divided into groups (and it has no submodules), so only there is the trailing component
    // treated as a group; every other module name is kept whole.
    let swiftModulePrefix = "Swift."
    guard fullModuleName.hasPrefix(swiftModulePrefix) else {
      return (fullModuleName, nil)
    }
    // The index spells a group's levels with `.` (`Swift.Math.Integers`) but sourcekitd expects '/'
    // separated group name.
    let group = fullModuleName.dropFirst(swiftModulePrefix.count).replacing(".", with: "/")
    return ("Swift", String(group))
  }

  /// For each distinct SDK interface (`.swiftinterface`/`.swiftmodule`) among `symbols`, resolve the main
  /// file — a project file that imports the module, found via `mainFiles(containing:)` — whose build
  /// settings are used to open the generated interface. The lookup runs once per interface so a
  /// `workspace/symbol` response with many members of the same module doesn't repeat it. Interfaces with no
  /// main file are omitted, so callers skip those symbols.
  private func generatedInterfaceMainFiles(
    for symbols: [SymbolOccurrence],
    in workspace: Workspace
  ) async throws -> [DocumentURI: DocumentURI] {
    var mainFiles: [DocumentURI: DocumentURI] = [:]
    for symbol in symbols {
      let path = symbol.location.path
      guard path.hasSuffix(".swiftinterface") || path.hasSuffix(".swiftmodule"),
        let interfaceURI = symbol.location.uri,
        mainFiles[interfaceURI] == nil
      else {
        continue
      }
      try Task.checkCancellation()
      let mainFile = await workspace.buildServerManager
        .mainFiles(containing: interfaceURI)
        .sorted(by: { $0.arbitrarySchemeURL.absoluteString < $1.arbitrarySchemeURL.absoluteString })
        .first
      if let mainFile {
        mainFiles[interfaceURI] = mainFile
      }
    }
    return mainFiles
  }

  /// Handle a `workspace/symbolInfo` request.
  ///
  /// For each name in `req.names`, looks up all canonical occurrences in every workspace index and
  /// converts them to `WorkspaceSymbolItem` values:
  /// - Source-file symbols get a `file://` URI with the exact 0-based line/column from the index.
  /// - SDK/stdlib symbols (index location ends in `.swiftinterface` or `.swiftmodule`) get a
  ///   `WorkspaceSymbol` with `location: .uri(file:// URL?module=...)` and the USR in `data`, provided
  ///   the client advertises `workspace.symbol.resolveSupport`. The client should call
  ///   `workspaceSymbol/resolve` to obtain the exact location within the interface.
  ///   Without that capability the raw `file://` URI from the index record is returned instead.
  ///
  /// Every requested name is present as a key in the response, mapping to an empty array when there
  /// are no occurrences.
  func workspaceSymbolInfo(_ req: WorkspaceSymbolInfoRequest) async throws -> WorkspaceSymbolInfoResponse {
    // Emitting a generated-interface reference document requires both that the client can open it and that
    // it will call `workspaceSymbol/resolve` to fill in the range.
    let canUseGeneratedInterfaceReferenceDocument =
      (self.capabilityRegistry?.clientHasWorkspaceGetReferenceDocumentSupport ?? false)
      && (self.capabilityRegistry?.clientSupportsWorkspaceSymbolResolve ?? false)

    let groupedResultPerWorkspace = await workspaces.concurrentMap { workspace -> [String: [WorkspaceSymbolItem]] in
      guard let index = await workspace.index(checkedFor: .deletedFiles) else {
        return [:]
      }
      var occurrencesByName: [String: [SymbolOccurrence]] = [:]
      for name in req.names {
        if Task.isCancelled { return [:] }
        var symbols: [SymbolOccurrence] = []
        _ = orLog("Getting symbol occurrences") {
          try index.forEachCanonicalSymbolOccurrence(byName: name) { symbolOccurrence in
            symbols.append(symbolOccurrence)
            return true
          }
        }
        occurrencesByName[name] = symbols
      }

      var mainFiles: [DocumentURI: DocumentURI] = [:]
      if canUseGeneratedInterfaceReferenceDocument {
        let occurrences = occurrencesByName.values.flatMap { $0 }
        mainFiles =
          await orLog("Resolving generated interface main files") {
            try await self.generatedInterfaceMainFiles(for: occurrences, in: workspace)
          } ?? [:]
      }
      if Task.isCancelled { return [:] }

      var result: [String: [WorkspaceSymbolItem]] = [:]
      let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
      for name in req.names {
        result[name] = (occurrencesByName[name] ?? []).compactMap { symbol in
          orLog("Getting symbol information") {
            try self.workspaceSymbolItem(
              for: symbol,
              in: index,
              copiedFileMap: copiedFileMap,
              referenceDocumentMainFile: symbol.location.uri.flatMap { mainFiles[$0] }
            )
          }
        }
      }
      return result
    }

    try Task.checkCancellation()

    // Flatten the result.
    var result: [WorkspaceSymbolItem] = []
    for name in req.names {
      for grouped in groupedResultPerWorkspace {
        if let items = grouped[name] {
          result.append(contentsOf: items)
        }
      }
    }
    return WorkspaceSymbolInfoResponse(results: result)
  }

  /// Handle a `workspaceSymbol/resolve` request.
  ///
  /// If the symbol has a `location: .uri(sourcekit-lsp://generated-swift-interface?...)` (as emitted by
  /// `workspace/symbol` and `workspace/symbolInfo` for SDK/stdlib symbols), opens the generated Swift
  /// interface, resolves the symbol position using `data["usr"]`, and returns the symbol with the exact
  /// range. Symbols with an already-resolved `location: .location(...)` are returned unchanged.
  func workspaceSymbolResolve(_ req: WorkspaceSymbolResolveRequest) async throws -> WorkspaceSymbol {
    var symbol = req.workspaceSymbol
    guard
      case .uri(let uriOnly) = symbol.location,
      let referenceURL = try? ReferenceDocumentURL(from: uriOnly.uri),
      case .generatedInterface(let urlData) = referenceURL
    else {
      return symbol
    }

    // A USR is always present in practice; this only guards against a malformed `data` payload with an
    // empty USR string, treating it as absent so we don't run a position lookup that can't match.
    let usr = (symbol.sourceKitData?.usr).flatMap { $0.isEmpty ? nil : $0 }
    let buildSettingsFile = urlData.buildSettingsFrom
    guard let workspace = await self.workspaceForDocument(uri: buildSettingsFile) else {
      return symbol
    }
    let languageService = try await workspace.primaryLanguageService(for: buildSettingsFile, .swift)
    let details = await orLog("Opening generated interface in workspaceSymbol/resolve") {
      try await languageService.openGeneratedInterface(
        document: buildSettingsFile,
        moduleName: urlData.moduleName,
        groupName: urlData.groupName,
        symbolUSR: usr
      )
    }
    symbol.location = .location(
      Location(uri: uriOnly.uri, range: Range(details?.position ?? Position(line: 0, utf16index: 0)))
    )
    return symbol
  }

  /// Handle a workspace/symbol request, returning the SymbolInformation.
  /// - returns: An array with SymbolInformation for each matching symbol in the workspace.
  func workspaceSymbols(_ req: WorkspaceSymbolsRequest) async throws -> [WorkspaceSymbolItem]? {
    // Ignore short queries since they are:
    // - noisy and slow, since they can match many symbols
    // - normally unintentional, triggered when the user types slowly or if the editor doesn't
    //   debounce events while the user is typing
    guard req.query.count >= minWorkspaceSymbolPatternLength else {
      return []
    }
    if let qualified = QualifiedWorkspaceSymbolQuery(req.query) {
      return try await qualifiedWorkspaceSymbols(qualified)
    }
    return try await unqualifiedWorkspaceSymbols(query: req.query)
  }

  private func unqualifiedWorkspaceSymbols(query: String) async throws -> [WorkspaceSymbolItem] {
    var items: [WorkspaceSymbolItem] = []
    for workspace in workspaces {
      guard let index = await workspace.index(checkedFor: .deletedFiles) else {
        continue
      }
      let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
      var symbols: [SymbolOccurrence] = []
      try index.forEachCanonicalSymbolOccurrence(
        containing: query,
        anchorStart: false,
        anchorEnd: false,
        subsequence: true,
        ignoreCase: true
      ) { symbol in
        if Task.isCancelled {
          return false
        }
        guard !symbol.location.isSystem && !symbol.roles.contains(.accessorOf) else {
          return true
        }
        symbols.append(symbol)
        return true
      }
      try Task.checkCancellation()
      // `workspace/symbol` filters out system symbols above, so no result points into a generated
      // interface and there is no main file to resolve.
      items += try symbols.sorted(by: <).compactMap {
        try self.workspaceSymbolItem(
          for: $0,
          in: index,
          copiedFileMap: copiedFileMap,
          referenceDocumentMainFile: nil
        )
      }
    }
    return items
  }

  /// Handle a `workspace/symbol` request whose query contains a qualifier separator (`.` or `::`).
  ///
  /// Resolves the container named by the query's container chain and returns the container's members whose
  /// name matches the query's member component.
  /// See `members(ofContainerChain:matching:includeSystemSymbols:in:)`.
  private func qualifiedWorkspaceSymbols(
    _ query: QualifiedWorkspaceSymbolQuery
  ) async throws -> [WorkspaceSymbolItem] {
    // Emitting a generated-interface reference document requires both that the client can open it and that
    // it will call `workspaceSymbol/resolve` to fill in the range. Unlike `unqualifiedWorkspaceSymbols`,
    // qualified queries can match SDK/stdlib members (e.g. `String.count`), so they need main files.
    let canUseGeneratedInterfaceReferenceDocument =
      (self.capabilityRegistry?.clientHasWorkspaceGetReferenceDocumentSupport ?? false)
      && (self.capabilityRegistry?.clientSupportsWorkspaceSymbolResolve ?? false)
    var items: [WorkspaceSymbolItem] = []
    for workspace in workspaces {
      guard let index = await workspace.index(checkedFor: .deletedFiles) else {
        continue
      }
      let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
      // Resolve the container named by the container chain, then take its direct members, keeping those
      // whose name matches `query.member`. An empty member (e.g. the query `Foo.`) lists all members.
      // System members are only useful if we can point at their generated interface.
      let symbols = try self.members(
        ofContainerChain: query.containerChain,
        fuzzyMatching: query.member,
        includeSystemSymbols: canUseGeneratedInterfaceReferenceDocument,
        in: index
      )
      var mainFiles: [DocumentURI: DocumentURI] = [:]
      if canUseGeneratedInterfaceReferenceDocument {
        mainFiles =
          await orLog("Resolving generated interface main files") {
            try await self.generatedInterfaceMainFiles(for: symbols, in: workspace)
          } ?? [:]
      }
      items += try symbols.sorted(by: <).compactMap {
        try self.workspaceSymbolItem(
          for: $0,
          in: index,
          copiedFileMap: copiedFileMap,
          referenceDocumentMainFile: $0.location.uri.flatMap { mainFiles[$0] },
          useQualifiedName: true
        )
      }
    }
    return items
  }

  /// Resolve the container named by `chain` (outer-to-inner) and return its direct members (`childOf`)
  /// whose name fuzzily contains `member`.
  ///
  /// The innermost name in the chain is matched exactly (case-insensitive) and its `ancestors` — the
  /// container chain minus that innermost name — must match its enclosing scopes. `member` is matched as a
  /// case-insensitive subsequence; an empty `member` matches all members. Members declared in extensions
  /// are included. Members from all matching containers are unioned and de-duplicated by USR.
  ///
  /// System (SDK/stdlib) members are only included if `includeSystemSymbols` is `true`. They can only be
  /// navigated to through a generated interface, so callers pass `false` when the client can't open one.
  private nonisolated func members(
    ofContainerChain chain: [String],
    fuzzyMatching member: String,
    includeSystemSymbols: Bool,
    in index: CheckedIndex
  ) throws -> [SymbolOccurrence] {
    guard let containerName = chain.last else {
      throw ResponseError.internalError("\(#function) requires a non-empty chain")
    }

    // Lowercased once here because the enclosing scopes of every candidate container are compared against it.
    let lowercasedAncestors = chain.dropLast().map { $0.lowercased() }

    // Resolve the innermost container(s) by exact name, verifying the outer scope chain.
    //
    // `containerUSRs` holds every resolved container, including system ones, because a system type can be
    // extended from the user's own modules and those members stay reachable even when system symbols are
    // excluded. `containerUSRsForMemberLookup` is the subset whose children are actually walked.
    var containerUSRs: Set<String> = []
    var containerUSRsForMemberLookup: Set<String> = []
    try index.forEachCanonicalSymbolOccurrence(
      containing: containerName,
      anchorStart: true,
      anchorEnd: true,
      subsequence: false,
      ignoreCase: true
    ) { symbol in
      if Task.isCancelled {
        return false
      }
      // Resolving a system namespace (e.g. `std`, or a whole module) would enumerate an entire system scope,
      // so skip those.
      let isSystemNamespace: Bool =
        switch symbol.symbol.kind {
        case .namespace, .namespaceAlias, .module:
          symbol.location.isSystem
        default:
          false
        }
      // Match only the suffix of the enclosing scopes, so a chain may name just the inner scopes, e.g.
      // `Inner` for a container declared as `Outer.Inner`.
      let enclosingScopes = ((try? index.containerNames(of: symbol)) ?? []).suffix(lowercasedAncestors.count)
      guard !isSystemNamespace, enclosingScopes.map({ $0.lowercased() }) == lowercasedAncestors else {
        return true
      }
      containerUSRs.insert(symbol.symbol.usr)
      if includeSystemSymbols || !symbol.location.isSystem {
        containerUSRsForMemberLookup.insert(symbol.symbol.usr)
      }
      return true
    }
    try Task.checkCancellation()

    // Members declared in an extension are `childOf` the extension symbol, not the extended type. A
    // type's occurrences at `extension` sites carry `extendedBy` relations pointing at those extensions,
    // so add the extension USRs to the set of containers whose children we enumerate. Filtering the
    // occurrences by the `.extendedBy` role restricts the lookup to those extension sites instead of
    // returning every reference of the type.
    //
    // The extension site's location distinguishes the user's extensions from the SDK's, so system
    // extensions are skipped before any of their members are enumerated.
    for typeUSR in containerUSRs {
      try Task.checkCancellation()
      for occurrence in try index.occurrences(ofUSR: typeUSR, roles: .extendedBy) {
        guard includeSystemSymbols || !occurrence.location.isSystem else {
          continue
        }
        for relation in occurrence.relations where relation.roles.contains(.extendedBy) {
          containerUSRsForMemberLookup.insert(relation.symbol.usr)
        }
      }
    }

    var members: [SymbolOccurrence] = []
    var seenUSRs: Set<String> = []
    for containerUSR in containerUSRsForMemberLookup {
      try Task.checkCancellation()
      let children = try index.occurrences(relatedToUSR: containerUSR, roles: .childOf)
      for child in children {
        guard
          !child.roles.contains(.accessorOf),
          includeSystemSymbols || !child.location.isSystem,
          child.symbol.name.fuzzilyContains(subsequence: member),
          seenUSRs.insert(child.symbol.usr).inserted
        else {
          continue
        }
        switch child.symbol.language {
        case .c, .cxx, .objc:
          // A C-family symbol can have separate declaration and definition occurrences.
          members.append((try? index.primaryDefinitionOrDeclarationOccurrence(ofUSR: child.symbol.usr)) ?? child)
        case .swift:
          // Swift members have a single declaration site, so use the occurrence directly.
          members.append(child)
        }
      }
    }
    return members
  }

  /// Forwards a SymbolInfoRequest to the appropriate toolchain service for this document.
  func symbolInfo(
    _ req: SymbolInfoRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [SymbolDetails] {
    return try await languageService.symbolInfo(req)
  }

  func documentSymbolHighlight(
    _ req: DocumentHighlightRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [DocumentHighlight]? {
    return try await languageService.documentSymbolHighlight(req)
  }

  func foldingRange(
    _ req: FoldingRangeRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [FoldingRange]? {
    return try await languageService.foldingRange(req)
  }

  func selectionRange(
    _ req: SelectionRangeRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [SelectionRange] {
    return try await languageService.selectionRange(req)
  }

  func documentSymbol(
    _ req: DocumentSymbolRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> DocumentSymbolResponse? {
    return try await languageService.documentSymbol(req)
  }

  func documentColor(
    _ req: DocumentColorRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [ColorInformation] {
    return try await languageService.documentColor(req)
  }

  func documentSemanticTokens(
    _ req: DocumentSemanticTokensRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> DocumentSemanticTokensResponse? {
    return try await languageService.documentSemanticTokens(req)
  }

  func documentSemanticTokensDelta(
    _ req: DocumentSemanticTokensDeltaRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> DocumentSemanticTokensDeltaResponse? {
    return try await languageService.documentSemanticTokensDelta(req)
  }

  func documentSemanticTokensRange(
    _ req: DocumentSemanticTokensRangeRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> DocumentSemanticTokensResponse? {
    return try await languageService.documentSemanticTokensRange(req)
  }

  func documentFormatting(
    _ req: DocumentFormattingRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [TextEdit]? {
    return try await languageService.documentFormatting(req)
  }

  func documentRangeFormatting(
    _ req: DocumentRangeFormattingRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [TextEdit]? {
    return try await languageService.documentRangeFormatting(req)
  }

  func documentOnTypeFormatting(
    _ req: DocumentOnTypeFormattingRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [TextEdit]? {
    return try await languageService.documentOnTypeFormatting(req)
  }

  func colorPresentation(
    _ req: ColorPresentationRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [ColorPresentation] {
    return try await languageService.colorPresentation(req)
  }

  func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? {
    guard let uri = req.textDocument?.uri else {
      logger.error("Attempted to perform executeCommand request without an URL")
      return nil
    }

    let executeCommand = ExecuteCommandRequest(
      command: req.command,
      arguments: req.argumentsWithoutSourceKitMetadata
    )
    guard let workspace = await self.workspaceForDocument(uri: uri) else {
      throw ResponseError.workspaceNotOpen(uri)
    }
    // First, check if we have a language service that explicitly declares support for this command.
    if let languageService = workspace.languageServices(forOpenDocument: uri)
      .first(where: { type(of: $0).builtInCommands.contains(req.command) })
    {
      return try await languageService.executeCommand(executeCommand)
    }
    // Otherwise handle it in the primary language service. This is important to handle eg. commands in clangd, which
    // are not declared as built-in commands.
    return try await workspace.primaryLanguageService(forOpenDocument: uri).executeCommand(executeCommand)
  }

  func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse {
    let buildSettingsUri = try ReferenceDocumentURL(from: req.uri).buildSettingsFile

    guard let workspace = await self.workspaceForDocument(uri: buildSettingsUri) else {
      throw ResponseError.workspaceNotOpen(buildSettingsUri)
    }
    let language: Language

    // The document that provided the build settings might no longer be open, so we need to be able to infer the
    // language by other means as well.
    if let snapshot = try? documentManager.latestSnapshot(buildSettingsUri) {
      language = snapshot.language
    } else if let target = await workspace.buildServerManager.canonicalTarget(for: buildSettingsUri),
      let lang = await workspace.buildServerManager.defaultLanguage(for: buildSettingsUri, in: target)
    {
      language = lang
    } else if let lang = Language(inferredFromFileExtension: buildSettingsUri) {
      language = lang
    } else {
      throw ResponseError.unknown("Unable to infer language for \(buildSettingsUri)")
    }

    return try await workspace.primaryLanguageService(for: buildSettingsUri, language).getReferenceDocument(req)
  }

  func codeAction(
    _ req: CodeActionRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> CodeActionRequestResponse? {
    let response = try await languageService.codeAction(req)
    return req.injectMetadata(toResponse: response)
  }

  func codeActionResolve(_ req: CodeActionResolveRequest) async throws -> CodeAction {
    guard case .dictionary(let dict) = req.codeAction.data,
      let resolveMetadata = CodeActionResolveMetadata(fromLSPDictionary: dict)
    else {
      return req.codeAction
    }

    let uri = resolveMetadata.textDocument.uri

    guard let workspace = await self.workspaceForDocument(uri: uri) else {
      return req.codeAction
    }

    var forwardedReq = req
    forwardedReq.codeAction.data = resolveMetadata.underlyingData

    let languageService = try workspace.primaryLanguageService(forOpenDocument: uri)
    return try await languageService.codeActionResolve(forwardedReq)
  }

  func codeLens(
    _ req: CodeLensRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [CodeLens] {
    return try await languageService.codeLens(req)
  }

  func inlayHint(
    _ req: InlayHintRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [InlayHint] {
    return try await languageService.inlayHint(req)
  }

  func inlayHintResolve(
    request: InlayHintResolveRequest
  ) async throws -> InlayHint {
    guard let uri = ResolveItemData(fromLSPAny: request.inlayHint.data)?.uri else {
      return request.inlayHint
    }
    guard let workspace = await self.workspaceForDocument(uri: uri) else {
      return request.inlayHint
    }
    let languageService = try workspace.primaryLanguageService(forOpenDocument: uri)
    return try await languageService.inlayHintResolve(request)
  }

  func documentDiagnostic(
    _ req: DocumentDiagnosticsRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> DocumentDiagnosticReport {
    return try await languageService.documentDiagnostic(req)
  }

  func declaration(
    _ req: DeclarationRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> LocationsOrLocationLinksResponse? {
    return try await languageService.declaration(req)
  }

  func typeDefinition(
    _ req: TypeDefinitionRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> LocationsOrLocationLinksResponse? {
    return try await languageService.typeDefinition(req)
  }

  /// Returns the result of a `DefinitionRequest` by running a `SymbolInfoRequest`, inspecting
  /// its result and doing index lookups, if necessary.
  ///
  /// In contrast to `definition`, this does not fall back to sending a `DefinitionRequest` to the
  /// toolchain language server.
  private func indexBasedDefinition(
    _ req: DefinitionRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [Location] {
    let symbols = try await languageService.symbolInfo(
      SymbolInfoRequest(
        textDocument: req.textDocument,
        position: req.position
      )
    )

    let canonicalOriginatorLocation = await languageService.canonicalDeclarationPosition(
      of: req.position,
      in: req.textDocument.uri
    )

    // Returns `true` if `location` points to the same declaration that the definition request was initiated from.
    @Sendable func isAtCanonicalOriginatorLocation(_ location: Location) async -> Bool {
      guard location.uri == req.textDocument.uri, let canonicalOriginatorLocation else {
        return false
      }
      return await languageService.canonicalDeclarationPosition(of: location.range.lowerBound, in: location.uri)
        == canonicalOriginatorLocation
    }

    var locations = try await symbols.asyncFlatMap { (symbol) -> [Location] in
      var locations: [Location]
      if let bestLocalDeclaration = symbol.bestLocalDeclaration,
        !(symbol.isDynamic ?? true),
        symbol.usr?.hasPrefix("s:") ?? false /* Swift symbols have USRs starting with s: */
      {
        // If we have a known non-dynamic symbol within Swift, we don't need to do an index lookup.
        // For non-Swift symbols, we need to perform an index lookup because the best local declaration will point to
        // a header file but jump-to-definition should prefer the implementation (there's the declaration request to
        // jump to the function's declaration).
        locations = [bestLocalDeclaration]
      } else {
        let index = await workspace.index(checkedFor: .deletedFiles)
        let definitionResult = try await definitionLocations(
          for: symbol,
          originatorUri: req.textDocument.uri,
          index: index,
          languageService: languageService
        )
        locations = definitionResult.locations
        // For dynamic symbols, also include overridden definitions
        if let index, symbol.isDynamic ?? true, !definitionResult.indexOccurrences.isEmpty {
          lazy var transitiveReceiverUsrs: [String]? = {
            orLog("Determining transitive receiver USRs") {
              if let receiverUsrs = symbol.receiverUsrs {
                return try transitiveSubtypeClosure(
                  ofUsrs: receiverUsrs,
                  index: index
                )
              } else {
                return nil
              }
            }
          }()
          // Use the occurrences already retrieved by definitionLocations to avoid duplicate index lookup
          let overriddenLocations = try definitionResult.indexOccurrences.flatMap { occurrence -> [Location] in
            let overriddenUsrs = try index.occurrences(relatedToUSR: occurrence.symbol.usr, roles: .overrideOf)
              .map(\.symbol.usr)
            let overriddenSymbolDefinitions = try overriddenUsrs.compactMap {
              try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: $0)
            }
            // Only contain overrides that are children of one of the receiver types or their subtypes or extensions.
            return overriddenSymbolDefinitions.filter { override in
              override.relations.contains(where: {
                guard $0.roles.contains(.childOf) else {
                  return false
                }
                if let transitiveReceiverUsrs, !transitiveReceiverUsrs.contains($0.symbol.usr) {
                  return false
                }
                return true
              })
            }.compactMap { $0.location.lspLocation }
          }
          locations += overriddenLocations
        }
      }

      // If the symbol's location is is where we initiated rename from, also show the declarations that the symbol
      // overrides.
      if let location = locations.only,
        let usr = symbol.usr,
        let index = await workspace.index(checkedFor: .deletedFiles),
        await isAtCanonicalOriginatorLocation(location)
      {
        let baseUSRs = try index.occurrences(ofUSR: usr, roles: .overrideOf).flatMap {
          $0.relations.filter { $0.roles.contains(.overrideOf) }.map(\.symbol.usr)
        }
        locations += try baseUSRs.compactMap {
          guard let baseDeclOccurrence = try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: $0) else {
            return nil
          }
          return baseDeclOccurrence.location.lspLocation
        }
      }

      return locations
    }

    // Remove any duplicate locations. We might end up with duplicate locations when performing a definition request
    // on eg. `MyStruct()` when no explicit initializer is declared. In this case we get two symbol infos, one for the
    // declaration of the `MyStruct` type and one for the initializer, which is implicit and thus has the location of
    // the `MyStruct` declaration itself.
    locations = locations.unique

    // Try removing any results that would point back to the location we are currently at. This ensures that eg. in the
    // following case we only show line 2 when performing jump-to-definition on `TestImpl.doThing`.
    //
    // ```
    // protocol TestProtocol {
    //   func doThing()
    // }
    // struct TestImpl: TestProtocol {
    //   func doThing() { }
    // }
    // ```
    //
    // If this would result in no locations, don't apply the filter. This way, performing jump-to-definition in the
    // middle of a function's base name takes us to the base name start, indicating that jump-to-definition was able to
    // resolve the location and didn't fail.
    let nonOriginatorLocations = await locations.asyncFilter { await !isAtCanonicalOriginatorLocation($0) }
    if !nonOriginatorLocations.isEmpty {
      locations = nonOriginatorLocations
    }
    return locations
  }

  func definition(
    _ req: DefinitionRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> LocationsOrLocationLinksResponse? {
    let indexBasedResponse = try await indexBasedDefinition(req, workspace: workspace, languageService: languageService)
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    // If we're unable to handle the definition request using our index, see if the
    // language service can handle it (e.g. clangd can provide AST based definitions).
    // We are on only calling the language service's `definition` function if your index-based lookup failed.
    // If this fallback request fails, its error is usually not very enlightening. For example the
    // `SwiftLanguageService` will always respond with `unsupported method`. Thus, only log such a failure instead of
    // returning it to the client.
    if indexBasedResponse.isEmpty {
      return await orLog("Fallback definition request", level: .info) {
        let result = try await languageService.definition(req)
        return result?.adjusted(for: copiedFileMap)
      }
    }
    let remappedLocations = indexBasedResponse.adjusted(for: copiedFileMap)
    return .locations(remappedLocations)
  }

  func implementation(
    _ req: ImplementationRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> LocationsOrLocationLinksResponse? {
    let symbols = try await languageService.symbolInfo(
      SymbolInfoRequest(
        textDocument: req.textDocument,
        position: req.position
      )
    )
    guard let index = await workspaceForDocument(uri: req.textDocument.uri)?.index(checkedFor: .deletedFiles) else {
      return nil
    }
    let locations = try symbols.flatMap { (symbol) -> [Location] in
      guard let usr = symbol.usr else { return [] }
      var occurrences = try index.occurrences(ofUSR: usr, roles: .baseOf)
      if occurrences.isEmpty {
        occurrences = try index.occurrences(relatedToUSR: usr, roles: .overrideOf)
      }

      return occurrences.compactMap { $0.location.lspLocation }
    }
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    let remappedLocations = locations.adjusted(for: copiedFileMap)
    return .locations(remappedLocations.sorted())
  }

  func references(
    _ req: ReferencesRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [Location] {
    let symbols = try await languageService.symbolInfo(
      SymbolInfoRequest(
        textDocument: req.textDocument,
        position: req.position
      )
    )
    let index = await workspaceForDocument(uri: req.textDocument.uri)?.index(checkedFor: .deletedFiles)
    let indexLocations = try symbols.flatMap { symbol -> [Location] in
      guard let usr = symbol.usr, let index else { return [] }
      logger.info("Finding references for USR \(usr)")
      var roles: SymbolRole = [.reference]
      if req.context.includeDeclaration {
        roles.formUnion([.declaration, .definition])
      }
      return try index.occurrences(ofUSR: usr, roles: roles).compactMap { $0.location.lspLocation }
    }

    var locations = indexLocations

    let hasCurrentFileIndexResults = indexLocations.contains { $0.uri == req.textDocument.uri }

    if !hasCurrentFileIndexResults {
      do {
        let localLocations = try await languageService.localReferences(
          at: req.position,
          in: req.textDocument.uri,
          includeDeclaration: req.context.includeDeclaration
        )
        locations += localLocations
      } catch let error as ResponseError {
        logger.debug("localReferences not supported for this language service")
      } catch {
        logger.error("Unexpected error computing local references: \(String(describing: error))")
      }
    }

    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    let remappedLocations = locations.adjusted(for: copiedFileMap)
    return remappedLocations.unique.sorted()
  }

  private func indexToLSPCallHierarchyItem(
    definition: SymbolOccurrence,
    index: CheckedIndex
  ) throws -> CallHierarchyItem? {
    guard let location = definition.location.lspLocation else {
      return nil
    }
    let name = try index.fullyQualifiedName(of: definition)
    let symbol = definition.symbol
    return CallHierarchyItem(
      name: name,
      kind: symbol.kind.asLspSymbolKind(),
      tags: nil,
      detail: nil,
      uri: location.uri,
      range: location.range,
      selectionRange: location.range,
      // We encode usr and uri for incoming/outgoing call lookups in the implementation-specific data field
      data: HierarchyItemData(uri: location.uri, usr: symbol.usr).encodeToLSPAny()
    )
  }

  func prepareCallHierarchy(
    _ req: CallHierarchyPrepareRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [CallHierarchyItem]? {
    let symbols = try await languageService.symbolInfo(
      SymbolInfoRequest(
        textDocument: req.textDocument,
        position: req.position
      )
    )
    guard let index = await workspaceForDocument(uri: req.textDocument.uri)?.index(checkedFor: .deletedFiles) else {
      return nil
    }
    // For call hierarchy preparation we only locate the definition
    let usrs = symbols.compactMap(\.usr)

    // TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
    func indexToLSPCallHierarchyItem2(
      definition: SymbolOccurrence,
      index: CheckedIndex
    ) throws -> CallHierarchyItem? {
      return try self.indexToLSPCallHierarchyItem(definition: definition, index: index)
    }

    // Only return a single call hierarchy item. Returning multiple doesn't make sense because they will all have the
    // same USR (because we query them by USR) and will thus expand to the exact same call hierarchy.
    var callHierarchyItems: [CallHierarchyItem] = []
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    for usr in usrs {
      guard let definition = try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
        continue
      }
      guard let item = try indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
        continue
      }
      callHierarchyItems.append(item.adjusted(for: copiedFileMap))
    }
    callHierarchyItems.sort(by: { Location(uri: $0.uri, range: $0.range) < Location(uri: $1.uri, range: $1.range) })

    // Ideally, we should show multiple symbols. But VS Code fails to display call hierarchies with multiple root items,
    // failing with `Cannot read properties of undefined (reading 'map')`. Pick the first one.
    return Array(callHierarchyItems.prefix(1))
  }

  func incomingCalls(_ req: CallHierarchyIncomingCallsRequest) async throws -> [CallHierarchyIncomingCall]? {
    guard let data = HierarchyItemData(fromLSPAny: req.item.data),
      let workspace = await self.workspaceForDocument(uri: data.uri),
      let index = await workspace.index(checkedFor: .deletedFiles)
    else {
      return []
    }
    var callableUsrs = [data.usr]
    // Also show calls to the functions that this method overrides. This includes overridden class methods and
    // satisfied protocol requirements.
    callableUsrs += try index.occurrences(ofUSR: data.usr, roles: .overrideOf).flatMap { occurrence in
      occurrence.relations.filter { $0.roles.contains(.overrideOf) }.map(\.symbol.usr)
    }
    // callOccurrences are all the places that any of the USRs in callableUsrs is called.
    // We also load the `calledBy` roles to get the method that contains the reference to this call.
    let callOccurrences = try callableUsrs.flatMap { try index.occurrences(ofUSR: $0, roles: .containedBy) }
      .filter(\.shouldShowInCallHierarchy)

    // Maps functions that call a USR in `callableUSRs` to all the called occurrences of `callableUSRs` within the
    // function. If a function `foo` calls `bar` multiple times, `callersToCalls[foo]` will contain two call
    // `SymbolOccurrence`s.
    // This way, we can group multiple calls to `bar` within `foo` to a single item with multiple `fromRanges`.
    var callersToCalls: [Symbol: [SymbolOccurrence]] = [:]

    for call in callOccurrences {
      // Callers are all `calledBy` relations of a call to a USR in `callableUsrs`, ie. all the functions that contain a
      // call to a USR in callableUSRs. In practice, this should always be a single item.
      let callers = call.relations.filter { $0.roles.contains(.containedBy) }.map(\.symbol)
      for caller in callers {
        callersToCalls[caller, default: []].append(call)
      }
    }

    // TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
    func indexToLSPCallHierarchyItem2(
      definition: SymbolOccurrence,
      index: CheckedIndex
    ) throws -> CallHierarchyItem? {
      return try self.indexToLSPCallHierarchyItem(definition: definition, index: index)
    }

    var calls: [CallHierarchyIncomingCall] = []
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    for (caller, callsList) in callersToCalls {
      // Resolve the caller's definition to find its location
      guard let definition = try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: caller.usr) else {
        continue
      }

      let locations = callsList.compactMap { $0.location.lspLocation }.sorted()
      let remappedLocations = locations.adjusted(for: copiedFileMap)
      guard !remappedLocations.isEmpty else {
        continue
      }

      guard let item = try indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
        continue
      }
      let remappedItem = item.adjusted(for: copiedFileMap)

      calls.append(CallHierarchyIncomingCall(from: remappedItem, fromRanges: remappedLocations.map(\.range)))
    }
    return calls.sorted(by: { $0.from.name < $1.from.name })
  }

  func outgoingCalls(_ req: CallHierarchyOutgoingCallsRequest) async throws -> [CallHierarchyOutgoingCall]? {
    guard let data = HierarchyItemData(fromLSPAny: req.item.data),
      let workspace = await self.workspaceForDocument(uri: data.uri),
      let index = await workspace.index(checkedFor: .deletedFiles)
    else {
      return []
    }

    // TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
    func indexToLSPCallHierarchyItem2(
      definition: SymbolOccurrence,
      index: CheckedIndex
    ) throws -> CallHierarchyItem? {
      return try self.indexToLSPCallHierarchyItem(definition: definition, index: index)
    }

    let callableUsrs = try [data.usr] + index.occurrences(relatedToUSR: data.usr, roles: .accessorOf).map(\.symbol.usr)
    let callOccurrences = try callableUsrs.flatMap { try index.occurrences(relatedToUSR: $0, roles: .containedBy) }
      .filter(\.shouldShowInCallHierarchy)
    var calls: [CallHierarchyOutgoingCall] = []
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    for occurrence in callOccurrences {
      guard occurrence.symbol.kind.isCallable else {
        continue
      }
      guard let location = occurrence.location.lspLocation else {
        continue
      }
      let remappedLocation = location.adjusted(for: copiedFileMap)

      // Resolve the callee's definition to find its location
      guard let definition = try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr) else {
        continue
      }

      guard let item = try indexToLSPCallHierarchyItem2(definition: definition, index: index) else {
        continue
      }
      let remappedItem = item.adjusted(for: copiedFileMap)

      calls.append(CallHierarchyOutgoingCall(to: remappedItem, fromRanges: [remappedLocation.range]))
    }
    return calls.sorted(by: { $0.to.name < $1.to.name })
  }

  private func indexToLSPTypeHierarchyItem(
    definition: SymbolOccurrence,
    moduleName: String?,
    index: CheckedIndex
  ) throws -> TypeHierarchyItem? {
    let name: String
    let detail: String?

    guard let location = definition.location.lspLocation else {
      return nil
    }

    let symbol = definition.symbol
    switch symbol.kind {
    case .extension:
      // Query the conformance added by this extension
      let conformances = try index.occurrences(relatedToUSR: symbol.usr, roles: .baseOf)
      if conformances.isEmpty {
        name = symbol.name
      } else {
        name = "\(symbol.name): \(conformances.map(\.symbol.name).sorted().joined(separator: ", "))"
      }
      // Add the file name and line to the detail string
      if let url = location.uri.fileURL,
        let basename = (try? AbsolutePath(validating: url.filePath))?.basename
      {
        detail = "Extension at \(basename):\(location.range.lowerBound.line + 1)"
      } else if let moduleName = moduleName {
        detail = "Extension in \(moduleName)"
      } else {
        detail = "Extension"
      }
    default:
      name = try index.fullyQualifiedName(of: definition)
      detail = moduleName
    }

    return TypeHierarchyItem(
      name: name,
      kind: symbol.kind.asLspSymbolKind(),
      tags: nil,
      detail: detail,
      uri: location.uri,
      range: location.range,
      selectionRange: location.range,
      // We encode usr and uri for incoming/outgoing type lookups in the implementation-specific data field
      data: HierarchyItemData(uri: location.uri, usr: symbol.usr).encodeToLSPAny()
    )
  }

  func prepareTypeHierarchy(
    _ req: TypeHierarchyPrepareRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [TypeHierarchyItem]? {
    let symbols = try await languageService.symbolInfo(
      SymbolInfoRequest(
        textDocument: req.textDocument,
        position: req.position
      )
    )
    guard !symbols.isEmpty else {
      return nil
    }
    guard let index = await workspaceForDocument(uri: req.textDocument.uri)?.index(checkedFor: .deletedFiles) else {
      return nil
    }
    let usrs = symbols.filter {
      // Only include references to type. For example, we don't want to find the type hierarchy of a constructor when
      // starting the type hierarchy on `Foo()`.
      // Consider a symbol a class if its kind is `nil`, eg. for a symbol returned by clang's SymbolInfo, which
      // doesn't support the `kind` field.
      switch $0.kind {
      case .class, .enum, .interface, .struct, nil: return true
      default: return false
      }
    }.compactMap(\.usr)

    // TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
    func indexToLSPTypeHierarchyItem2(
      definition: SymbolOccurrence,
      moduleName: String?,
      index: CheckedIndex
    ) throws -> TypeHierarchyItem? {
      return try self.indexToLSPTypeHierarchyItem(
        definition: definition,
        moduleName: moduleName,
        index: index
      )
    }

    var typeHierarchyItems: [TypeHierarchyItem] = []
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    for usr in usrs {
      guard let info = try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
        continue
      }
      // Filter symbols based on their kind in the index since the filter on the symbol info response might have
      // returned `nil` for the kind, preventing us from doing any filtering there.
      switch info.symbol.kind {
      case .unknown, .macro, .function, .variable, .field, .enumConstant, .instanceMethod, .classMethod, .staticMethod,
        .instanceProperty, .classProperty, .staticProperty, .constructor, .destructor, .conversionFunction, .parameter,
        .concept, .commentTag:
        continue
      case .module, .namespace, .namespaceAlias, .enum, .struct, .class, .protocol, .extension, .union, .typealias,
        .using:
        break
      }

      guard info.location.lspLocation != nil else {
        continue
      }

      let moduleName = info.location.moduleName
      guard let item = try indexToLSPTypeHierarchyItem2(definition: info, moduleName: moduleName, index: index) else {
        continue
      }
      typeHierarchyItems.append(item.adjusted(for: copiedFileMap))
    }
    typeHierarchyItems.sort(by: { $0.name < $1.name })

    if typeHierarchyItems.isEmpty {
      // When returning an empty array, VS Code fails with the following two errors. Returning `nil` works around those
      // VS Code-internal errors showing up
      //  - MISSING provider
      //  - Cannot read properties of null (reading 'kind')
      return nil
    }
    // Ideally, we should show multiple symbols. But VS Code fails to display type hierarchies with multiple root items,
    // failing with `Cannot read properties of undefined (reading 'map')`. Pick the first one.
    return Array(typeHierarchyItems.prefix(1))
  }

  func supertypes(_ req: TypeHierarchySupertypesRequest) async throws -> [TypeHierarchyItem]? {
    guard let data = HierarchyItemData(fromLSPAny: req.item.data),
      let workspace = await self.workspaceForDocument(uri: data.uri),
      let index = await workspace.index(checkedFor: .deletedFiles)
    else {
      return []
    }

    // Resolve base types
    let baseOccurs = try index.occurrences(relatedToUSR: data.usr, roles: .baseOf)

    // Resolve retroactive conformances via the extensions
    let extensions = try index.occurrences(ofUSR: data.usr, roles: .extendedBy)
    let retroactiveConformanceOccurs = try extensions.flatMap { occurrence -> [SymbolOccurrence] in
      if occurrence.relations.count > 1 {
        // When the occurrence has an `extendedBy` relation, it's an extension declaration. An extension can only extend
        // a single type, so there can only be a single relation here.
        logger.fault("Expected at most extendedBy relation but got \(occurrence.relations.count)")
      }
      guard let related = occurrence.relations.sorted().first else {
        return []
      }
      return try index.occurrences(relatedToUSR: related.symbol.usr, roles: .baseOf)
    }

    // TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
    func indexToLSPTypeHierarchyItem2(
      definition: SymbolOccurrence,
      moduleName: String?,
      index: CheckedIndex
    ) throws -> TypeHierarchyItem? {
      return try self.indexToLSPTypeHierarchyItem(
        definition: definition,
        moduleName: moduleName,
        index: index
      )
    }

    // Convert occurrences to type hierarchy items
    let occurs = baseOccurs + retroactiveConformanceOccurs
    var types: [TypeHierarchyItem] = []
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    for occurrence in occurs {
      // Resolve the supertype's definition to find its location
      guard let definition = try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: occurrence.symbol.usr) else {
        continue
      }

      let moduleName = definition.location.moduleName
      guard let item = try indexToLSPTypeHierarchyItem2(definition: definition, moduleName: moduleName, index: index)
      else {
        continue
      }
      types.append(item.adjusted(for: copiedFileMap))
    }
    return types.sorted(by: { $0.name < $1.name })
  }

  func subtypes(_ req: TypeHierarchySubtypesRequest) async throws -> [TypeHierarchyItem]? {
    guard let data = HierarchyItemData(fromLSPAny: req.item.data),
      let workspace = await self.workspaceForDocument(uri: data.uri),
      let index = await workspace.index(checkedFor: .deletedFiles)
    else {
      return []
    }

    // Resolve child types and extensions
    let occurs = try index.occurrences(ofUSR: data.usr, roles: [.baseOf, .extendedBy])

    // TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
    func indexToLSPTypeHierarchyItem2(
      definition: SymbolOccurrence,
      moduleName: String?,
      index: CheckedIndex
    ) throws -> TypeHierarchyItem? {
      return try self.indexToLSPTypeHierarchyItem(
        definition: definition,
        moduleName: moduleName,
        index: index
      )
    }

    // Convert occurrences to type hierarchy items
    var types: [TypeHierarchyItem] = []
    let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
    for occurrence in occurs {
      if occurrence.relations.count > 1 {
        // An occurrence with a `baseOf` or `extendedBy` relation is an occurrence inside an inheritance clause.
        // Such an occurrence can only be the source of a single type, namely the one that the inheritance clause belongs
        // to.
        logger.fault("Expected at most extendedBy or baseOf relation but got \(occurrence.relations.count)")
      }
      guard let related = occurrence.relations.sorted().first else {
        continue
      }

      // Resolve the subtype's definition to find its location
      guard let definition = try index.primaryDefinitionOrDeclarationOccurrence(ofUSR: related.symbol.usr) else {
        continue
      }

      let moduleName = definition.location.moduleName
      guard let item = try indexToLSPTypeHierarchyItem2(definition: definition, moduleName: moduleName, index: index)
      else {
        continue
      }
      types.append(item.adjusted(for: copiedFileMap))
    }
    return types.sorted { $0.name < $1.name }
  }

  func synchronize(_ req: SynchronizeRequest) async throws -> VoidResponse {
    if req.buildServerUpdates != nil, !self.options.hasExperimentalFeature(.synchronizeForBuildSystemUpdates) {
      throw ResponseError.unknown("\(SynchronizeRequest.method).buildServerUpdates is an experimental request option")
    }
    if req.copyFileMap != nil, !self.options.hasExperimentalFeature(.synchronizeCopyFileMap) {
      throw ResponseError.unknown("\(SynchronizeRequest.method).copyFileMap is an experimental request option")
    }
    for workspace in workspaces {
      await workspace.synchronize(req)
    }
    return VoidResponse()
  }

  func triggerReindex(_ req: TriggerReindexRequest) async throws -> VoidResponse {
    for workspace in workspaces {
      await workspace.semanticIndexManager?.scheduleReindex()
    }
    return VoidResponse()
  }

  func workspaceTests(_ req: WorkspaceTestsRequest) async throws -> [TestItem] {
    if self.capabilityRegistry?.clientHasWorkspaceTestsRefreshSupport ?? false {
      // If the client supports 'workspace/tests/refresh', return the pre-populated tests.
      return await self.entryPointManager.latestWorkspaceTests
    } else {
      // Otherwise, retrieve tests from the current index.
      return await TestDiscovery(sourceKitLSPServer: self).workspaceTests()
    }
  }

  func documentTests(
    _ req: DocumentTestsRequest,
    workspace: Workspace,
    languageService: any LanguageService
  ) async throws -> [TestItem] {
    return try await TestDiscovery(sourceKitLSPServer: self).documentTests(
      req.textDocument.uri,
      workspace: workspace,
      languageService: languageService
    )
  }

  func workspacePlaygrounds(_ req: WorkspacePlaygroundsRequest) async throws -> [Playground] {
    if self.capabilityRegistry?.clientHasWorkspacePlaygroundsRefreshSupport ?? false {
      return await self.entryPointManager.latestPlaygrounds
    } else {
      return await PlaygroundDiscovery(sourceKitLSPServer: self).workspacePlaygrounds()
    }
  }
}

private func languageClass(for language: Language) -> [Language] {
  switch language {
  case .c, .cpp, .objective_c, .objective_cpp:
    return [.c, .cpp, .objective_c, .objective_cpp]
  case .swift:
    return [.swift]
  default:
    return [language]
  }
}

/// Minimum supported pattern length for a `workspace/symbol` request, smaller pattern
/// strings are not queried and instead we return no results.
private let minWorkspaceSymbolPatternLength = 3

/// The maximum number of results to return from a `workspace/symbol` request.
private let maxWorkspaceSymbolResults = 4096

package typealias Diagnostic = LanguageServerProtocol.Diagnostic

fileprivate extension CheckedIndex {
  /// Take the name of containers into account to form a fully-qualified name for the given symbol.
  /// This means that we will form names of nested types and type-qualify methods.
  func fullyQualifiedName(of symbolOccurrence: SymbolOccurrence) throws -> String {
    let symbol = symbolOccurrence.symbol
    let containerNames = try self.containerNames(of: symbolOccurrence)
    guard let containerName = containerNames.last else {
      // No containers, so nothing to do.
      return symbol.name
    }
    switch symbol.language {
    case .objc where symbol.kind == .instanceMethod || symbol.kind == .instanceProperty:
      return "-[\(containerName) \(symbol.name)]"
    case .objc where symbol.kind == .classMethod || symbol.kind == .classProperty:
      return "+[\(containerName) \(symbol.name)]"
    case .cxx, .c, .objc:
      // C shouldn't have container names for call hierarchy and Objective-C should be covered above.
      // Fall back to using the C++ notation using `::`.
      return (containerNames + [symbol.name]).joined(separator: "::")
    case .swift:
      return (containerNames + [symbol.name]).joined(separator: ".")
    }
  }
}

extension IndexSymbolKind {
  func asLspSymbolKind() -> SymbolKind {
    switch self {
    case .class:
      return .class
    case .classMethod, .instanceMethod, .staticMethod:
      return .method
    case .instanceProperty, .staticProperty, .classProperty:
      return .property
    case .enum:
      return .enum
    case .enumConstant:
      return .enumMember
    case .protocol:
      return .interface
    case .function, .conversionFunction:
      return .function
    case .variable:
      return .variable
    case .struct:
      return .struct
    case .parameter:
      return .typeParameter
    case .module, .namespace:
      return .namespace
    case .field:
      return .property
    case .constructor:
      return .constructor
    case .destructor:
      return .null
    case .commentTag, .concept, .extension, .macro, .namespaceAlias, .typealias, .union, .unknown, .using:
      return .null
    }
  }

  var isCallable: Bool {
    switch self {
    case .function, .instanceMethod, .classMethod, .staticMethod, .constructor, .destructor, .conversionFunction:
      return true
    case .unknown, .module, .namespace, .namespaceAlias, .macro, .enum, .struct, .protocol, .extension, .union,
      .typealias, .field, .enumConstant, .parameter, .using, .concept, .commentTag, .variable, .instanceProperty,
      .class, .staticProperty, .classProperty:
      return false
    }
  }
}

fileprivate extension SymbolOccurrence {
  /// Whether this is a call-like occurrence that should be shown in the call hierarchy.
  var shouldShowInCallHierarchy: Bool {
    !roles.intersection([.addressOf, .call, .read, .reference, .write]).isEmpty
  }
}

/// Simple struct for pending notifications/requests, including a cancellation handler.
/// For convenience the notifications/request handlers are type erased via wrapping.
private struct NotificationRequestOperation {
  let operation: () async -> Void
  let cancellationHandler: (() -> Void)?
}

/// Used to queue up notifications and requests for documents which are blocked
/// on build server operations such as fetching build settings.
///
/// Note: This is not thread safe. Must be called from the `SourceKitLSPServer.queue`.
private struct DocumentNotificationRequestQueue {
  fileprivate var queue = [NotificationRequestOperation]()

  /// Add an operation to the end of the queue.
  mutating func add(operation: @escaping () async -> Void, cancellationHandler: (() -> Void)? = nil) {
    queue.append(NotificationRequestOperation(operation: operation, cancellationHandler: cancellationHandler))
  }

  /// Cancel all operations in the queue. No-op for operations without a cancellation
  /// handler.
  mutating func cancelAll() {
    for task in queue {
      if let cancellationHandler = task.cancellationHandler {
        cancellationHandler()
      }
    }
    queue = []
  }
}

/// Returns the USRs of the subtypes of `usrs` as well as their subtypes and extensions, transitively.
private func transitiveSubtypeClosure(ofUsrs usrs: [String], index: CheckedIndex) throws -> [String] {
  var result: [String] = []
  for usr in usrs {
    result.append(usr)
    let directSubtypes = try index.occurrences(ofUSR: usr, roles: [.baseOf, .extendedBy]).flatMap { occurrence in
      occurrence.relations.filter { $0.roles.contains(.baseOf) || $0.roles.contains(.extendedBy) }.map(\.symbol.usr)
    }
    let transitiveSubtypes = try transitiveSubtypeClosure(ofUsrs: directSubtypes, index: index)
    result += transitiveSubtypes
  }
  return result
}

fileprivate extension Sequence where Element: Hashable {
  /// Removes all duplicate elements from the sequence, maintaining order.
  var unique: [Element] {
    var set = Set<Element>()
    return self.filter { set.insert($0).inserted }
  }
}

fileprivate extension URL {
  func isPrefix(of other: URL) -> Bool {
    guard self.pathComponents.count < other.pathComponents.count else {
      return false
    }
    return other.pathComponents[0..<self.pathComponents.count] == self.pathComponents[...]
  }
}
