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
|
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#if compiler(>=6)
public import ArgumentParser
import Foundation
import InProcessClient
import LanguageServerProtocol
import LanguageServerProtocolExtensions
import SKOptions
import SourceKitLSP
import SwiftExtensions
import ToolchainRegistry
import struct TSCBasic.AbsolutePath
import class TSCBasic.Process
import class TSCUtility.PercentProgressAnimation
#else
import ArgumentParser
import Foundation
import InProcessClient
import LanguageServerProtocol
import LanguageServerProtocolExtensions
import SKOptions
import SourceKitLSP
import SwiftExtensions
import ToolchainRegistry
import struct TSCBasic.AbsolutePath
import class TSCBasic.Process
import class TSCUtility.PercentProgressAnimation
#endif
private actor IndexLogMessageHandler: MessageHandler {
var hasSeenError: Bool = false
/// Queue to ensure that we don't have two interleaving `print` calls.
let queue = AsyncQueue<Serial>()
nonisolated func handle(_ notification: some NotificationType) {
if let notification = notification as? LogMessageNotification {
queue.async {
await self.handle(notification)
}
}
}
func handle(_ notification: LogMessageNotification) {
self.hasSeenError = notification.type == .warning
print(notification.message)
}
nonisolated func handle<Request: RequestType>(
_ request: Request,
id: RequestID,
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
) {
reply(.failure(.methodNotFound(Request.method)))
}
}
package struct IndexCommand: AsyncParsableCommand {
package static let configuration: CommandConfiguration = CommandConfiguration(
commandName: "index",
abstract: "Index a project and print all the processes executed for it as well as their outputs"
)
@Option(
name: .customLong("toolchain"),
help: """
The toolchain used to reduce the sourcekitd issue. \
If not specified, the toolchain is found in the same way that sourcekit-lsp finds it
"""
)
var toolchainOverride: String?
@Option(
name: .customLong("experimental-index-feature"),
help: """
Enable an experimental sourcekit-lsp feature.
Available features are: \(ExperimentalFeature.allCases.map(\.rawValue).joined(separator: ", "))
"""
)
var experimentalFeatures: [ExperimentalFeature] = []
@Option(help: "The path to the project that should be indexed")
var project: String
package init() {}
package func run() async throws {
let options = SourceKitLSPOptions(
backgroundIndexing: true,
experimentalFeatures: Set(experimentalFeatures)
)
let installPath =
if let toolchainOverride, let toolchain = Toolchain(URL(fileURLWithPath: toolchainOverride)) {
toolchain.path
} else {
Bundle.main.bundleURL
}
let messageHandler = IndexLogMessageHandler()
let inProcessClient = try await InProcessSourceKitLSPClient(
toolchainPath: installPath,
options: options,
workspaceFolders: [WorkspaceFolder(uri: DocumentURI(URL(fileURLWithPath: project)))],
messageHandler: messageHandler
)
let start = ContinuousClock.now
_ = try await inProcessClient.send(PollIndexRequest())
print("Indexing finished in \(start.duration(to: .now))")
if await messageHandler.hasSeenError {
throw ExitCode(1)
}
}
}
fileprivate extension SourceKitLSPServer {
func handle<R: RequestType>(_ request: R, requestID: RequestID) async throws -> R.Response {
return try await withCheckedThrowingContinuation { continuation in
self.handle(request, id: requestID) { result in
continuation.resume(with: result)
}
}
}
}
extension ExperimentalFeature: ArgumentParser.ExpressibleByArgument {}
|