PadXcode-Daemon/Daemon/LSPProxy.swift

71 lines
3 KiB
Swift
Raw Normal View History

2026-04-12 00:42:51 -07:00
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 }
2026-04-12 10:16:21 -07:00
for ws in clients where !ws.isClosed { let w = ws; Task { try? await w.send(text) } }
2026-04-12 00:42:51 -07:00
}
}
private func broadcastStatus(_ s: String) {
2026-04-12 10:16:21 -07:00
for ws in clients where !ws.isClosed { let w = ws; let msg = #"{"padxcode_status":"\#(s)"}"#; Task { try? await w.send(msg) } }
2026-04-12 00:42:51 -07:00
}
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..<sr.lowerBound]
guard let hs = String(data: hd, encoding: .utf8),
let ls = hs.components(separatedBy: "\r\n").first(where: { $0.hasPrefix("Content-Length:") })?
.components(separatedBy: ":").last?.trimmingCharacters(in: .whitespaces),
let len = Int(ls) else { break }
let bs = sr.upperBound
let be = rem.index(bs, offsetBy: len, limitedBy: rem.endIndex) ?? rem.endIndex
results.append(Data(rem[bs..<be])); rem = rem[be...]
}
return results
}
}