File: InProcessSourceKitLSPClient.swift

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (147 lines) | stat: -rw-r--r-- 5,631 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import BuildSystemIntegration
public import Foundation
public import LanguageServerProtocol
import LanguageServerProtocolExtensions
import SKLogging
package import SKOptions
package import SourceKitLSP
import SwiftExtensions
import TSCExtensions
package import ToolchainRegistry

import struct TSCBasic.AbsolutePath

/// Launches a `SourceKitLSPServer` in-process and allows sending messages to it.
public final class InProcessSourceKitLSPClient: Sendable {
  private let server: SourceKitLSPServer

  private let nextRequestID = AtomicUInt32(initialValue: 0)

  public convenience init(
    toolchainPath: URL?,
    capabilities: ClientCapabilities = ClientCapabilities(),
    workspaceFolders: [WorkspaceFolder],
    messageHandler: any MessageHandler
  ) async throws {
    try await self.init(
      toolchainRegistry: ToolchainRegistry(installPath: toolchainPath),
      options: SourceKitLSPOptions(),
      capabilities: capabilities,
      workspaceFolders: workspaceFolders,
      messageHandler: messageHandler
    )
  }

  /// Create a new `SourceKitLSPServer`. An `InitializeRequest` is automatically sent to the server.
  ///
  /// `messageHandler` handles notifications and requests sent from the SourceKit-LSP server to the client.
  package init(
    toolchainRegistry: ToolchainRegistry,
    options: SourceKitLSPOptions = SourceKitLSPOptions(),
    hooks: Hooks = Hooks(),
    capabilities: ClientCapabilities = ClientCapabilities(),
    workspaceFolders: [WorkspaceFolder],
    messageHandler: any MessageHandler
  ) async throws {
    let serverToClientConnection = LocalConnection(receiverName: "client")
    self.server = SourceKitLSPServer(
      client: serverToClientConnection,
      toolchainRegistry: toolchainRegistry,
      options: options,
      hooks: hooks,
      onExit: {
        serverToClientConnection.close()
      }
    )
    serverToClientConnection.start(handler: messageHandler)
    _ = try await self.send(
      InitializeRequest(
        processId: nil,
        rootPath: nil,
        rootURI: nil,
        initializationOptions: nil,
        capabilities: capabilities,
        trace: .off,
        workspaceFolders: workspaceFolders
      )
    )
  }

  /// Send the request to `server` and return the request result.
  ///
  /// - Important: Because this is an async function, Swift concurrency makes no guarantees about the execution ordering
  ///   of this request with regard to other requests to the server. If execution of requests in a particular order is
  ///   necessary and the response of the request is not awaited, use the version of the function that takes a
  ///   completion handler
  public func send<R: RequestType>(_ request: R) async throws -> R.Response {
    let requestId = ThreadSafeBox<RequestID?>(initialValue: nil)
    return try await withTaskCancellationHandler {
      return try await withCheckedThrowingContinuation { continuation in
        if Task.isCancelled {
          // Check if the task has been cancelled before we send the request to LSP to avoid any kind of work if
          // possible.
          return continuation.resume(throwing: CancellationError())
        }
        requestId.value = self.send(request) {
          continuation.resume(with: $0)
        }
        if Task.isCancelled, let requestId = requestId.takeValue() {
          // The task might have been cancelled after the above cancellation check but before `requestId` was assigned
          // a value. To cover that case, check for cancellation here again. Note that we won't cancel twice from here
          // and the `onCancel` handler because we take the request ID out of the `ThreadSafeBox` before sending the
          // `CancelRequestNotification`.
          self.send(CancelRequestNotification(id: requestId))
        }
      }
    } onCancel: {
      if let requestId = requestId.takeValue() {
        self.send(CancelRequestNotification(id: requestId))
      }
    }
  }

  /// Send the request to `server` and return the request result via a completion handler.
  @discardableResult
  public func send<R: RequestType>(
    _ request: R,
    reply: @Sendable @escaping (LSPResult<R.Response>) -> Void
  ) -> RequestID {
    let requestID = RequestID.string("sk-\(Int(nextRequestID.fetchAndIncrement()))")
    server.handle(request, id: requestID, reply: reply)
    return requestID
  }

  /// Send the request to `server` and return the request result via a completion handler.
  ///
  /// The request ID must not start with `sk-` to avoid conflicting with the request IDs that are created by
  /// `send(:reply:)`.
  public func send<R: RequestType>(
    _ request: R,
    id: RequestID,
    reply: @Sendable @escaping (LSPResult<R.Response>) -> Void
  ) {
    if case .string(let string) = id {
      if string.starts(with: "sk-") {
        logger.fault("Manually specified request ID must not have reserved prefix 'sk-'")
      }
    }
    server.handle(request, id: id, reply: reply)
  }

  /// Send the notification to `server`.
  public func send(_ notification: some NotificationType) {
    server.handle(notification)
  }
}