import Vapor import Foundation actor LSPProxy { static let shared = LSPProxy() private var process: Process? private var stdinPipe: Pipe? private var clients: [WebSocket] = [] func start(projectPath: String) throws { guard process == nil else { return } let p = Process() p.executableURL = URL(fileURLWithPath: "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp") p.currentDirectoryURL = URL(fileURLWithPath: projectPath) let inPipe = Pipe(); let outPipe = Pipe(); let errPipe = Pipe() p.standardInput = inPipe; p.standardOutput = outPipe; p.standardError = errPipe stdinPipe = inPipe outPipe.fileHandleForReading.readabilityHandler = { [weak self] h in let d = h.availableData; guard !d.isEmpty else { return } Task { await self?.broadcastToClients(d) } } p.terminationHandler = { [weak self] _ in Task { await self?.handleTermination() } } try p.run(); process = p } func stop() { process?.terminate(); process = nil; stdinPipe = nil } private func handleTermination() { process = nil; broadcastStatus("lsp_terminated") } func addClient(_ ws: WebSocket) { clients.append(ws) } func removeClient(_ ws: WebSocket) { clients.removeAll { $0 === ws } } func forwardToLSP(_ message: Data) { guard let pipe = stdinPipe else { return } let header = "Content-Length: \(message.count)\r\n\r\n" if let hd = header.data(using: .utf8) { pipe.fileHandleForWriting.write(hd) pipe.fileHandleForWriting.write(message) } } private func broadcastToClients(_ data: Data) { let messages = extractJSONBodies(from: data) for msg in messages { guard let text = String(data: msg, encoding: .utf8) else { continue } for ws in clients where !ws.isClosed { ws.send(text) } } } private func broadcastStatus(_ s: String) { for ws in clients where !ws.isClosed { ws.send(#"{"padxcode_status":"\#(s)"}"#) } } private func extractJSONBodies(from data: Data) -> [Data] { var results: [Data] = []; var rem = data let sep = Data([0x0D,0x0A,0x0D,0x0A]) while !rem.isEmpty { guard let sr = rem.range(of: sep) else { break } let hd = rem[rem.startIndex..