// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers // // SPDX-License-Identifier: MPL-2.0 import Foundation import JSONRPC import LanguageServer import LanguageServerProtocol import Logging import PterodactylBuild import PterodactylSyntax import TSCBasic import llbuild2fx final class EventHandler { private let connection: JSONRPCClientConnection private let buildEngine: FXEngine private let casContext: TSCUtility.Context private let casClient: LLBCASFSClient var storedBlobs: [DocumentUri: (blobId: LLBDataID, version: Int?)] = [:] init(connection: JSONRPCClientConnection) { self.connection = connection let group = LLBMakeDefaultDispatchGroup() let db = LLBInMemoryCASDatabase(group: group) let functionCache = FXInMemoryFunctionCache(group: group) let executor = FXLocalExecutor() self.buildEngine = FXEngine(group: group, db: db, functionCache: functionCache, executor: executor) self.casClient = LLBCASFSClient(db) self.casContext = Context() } func storeBlob(text: String, uri: DocumentUri, version: Int?) async throws -> LLBDataID { if let stored = storedBlobs[uri], let version, let storedVersion = stored.version, storedVersion >= version { return stored.blobId } let blobId: LLBDataID = try await casClient.store(LLBByteBuffer(string: text), casContext).get() storedBlobs[uri] = (blobId: blobId, version: version) return blobId } func publishLiveDiagnostics(blobId: LLBDataID, uri: DocumentUri, version: Int?) async throws { let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: blobId), casContext).get() let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: blobId), casContext).get() let diagnostics = parseResult.diagnostics.map { $0.lspDiagnostic(lineMap: lineMap) } let publishParams = PublishDiagnosticsParams(uri: uri, version: version, diagnostics: diagnostics) try await connection.sendNotification(.textDocumentPublishDiagnostics(publishParams)) } } extension EventHandler: LanguageServer.EventHandler { var textDocumentSinkOptions: TextDocumentSyncOptions { TextDocumentSyncOptions( openClose: true, change: .full, save: .optionA(false) ) } var completionOptions: CompletionOptions { CompletionOptions( workDoneProgress: false, triggerCharacters: [], // TODO allCommitCharacters: nil, resolveProvider: false, completionItem: nil ) } var semanticTokensLegend: SemanticTokensLegend { SemanticTokensLegend(tokenTypes: SemanticTokenTypes.allStrings, tokenModifiers: SemanticTokenModifiers.allStrings) } var semanticTokensOptions: SemanticTokensOptions { SemanticTokensOptions(legend: semanticTokensLegend, full: .optionB(SemanticTokensClientCapabilities.Requests.Full(delta: false))) } func initialize(id: JSONId, params: InitializeParams) async -> Response { Logger.shared.debug("Received initialize request") var serverCapabilities = ServerCapabilities() serverCapabilities.textDocumentSync = .optionA(textDocumentSinkOptions) serverCapabilities.completionProvider = completionOptions serverCapabilities.hoverProvider = .optionA(false) serverCapabilities.semanticTokensProvider = .optionA(semanticTokensOptions) serverCapabilities.foldingRangeProvider = .optionA(true) let response = InitializationResponse( capabilities: serverCapabilities, serverInfo: nil ) return .success(response) } func textDocumentDidChange(_ params: DidChangeTextDocumentParams) async { guard let text = params.contentChanges.first?.text else { return } do { let blobId: LLBDataID = try await storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version) try await publishLiveDiagnostics(blobId: blobId, uri: params.textDocument.uri, version: params.textDocument.version) } catch {} } func textDocumentDidOpen(_ params: DidOpenTextDocumentParams) async { let text = params.textDocument.text do { let blobId: LLBDataID = try await storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version) try await publishLiveDiagnostics(blobId: blobId, uri: params.textDocument.uri, version: params.textDocument.version) } catch {} } func semanticTokensFull(id: JSONId, params: SemanticTokensParams) async -> Response { guard let storedBlob = storedBlobs[params.textDocument.uri] else { return .success(nil) } do { let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: storedBlob.blobId), casContext).get() let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: storedBlob.blobId), casContext).get() let cursor = SyntaxCursor(lineMap: lineMap, node: .tree(parseResult.tree), utf16Offset: 0) return .success(SemanticTokensResponse(SemanticTokens(resultId: nil, tokens: cursor.semanticTokens))) } catch { return .success(nil) } } func foldingRange(id: JSONId, params: FoldingRangeParams) async -> Response { guard let storedBlob = storedBlobs[params.textDocument.uri] else { return .success(nil) } do { let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: storedBlob.blobId), casContext).get() let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: storedBlob.blobId), casContext).get() let cursor = SyntaxCursor(lineMap: lineMap, node: .tree(parseResult.tree), utf16Offset: 0) return .success(FoldingRangeResponse(cursor.foldingRanges)) } catch { return .success(nil) } } func typeHierarchySupertypes( id: JSONRPC.JSONId, params: TypeHierarchySupertypesParams ) async -> Response { .success(nil) } func typeHierarchySubtypes( id: JSONId, params: TypeHierarchySubtypesParams ) async -> Response { .success(nil) } func internalError(_ error: any Error) async { Logger.shared.error("Received error: \(error)") } func initialized(_ params: InitializedParams) async { } func exit() async { Logger.shared.info("Received exit notification") Foundation.exit(0) } func diagnostics(id: JSONId, params: DocumentDiagnosticParams) async -> Response { return .success(.init(kind: .unchanged)) } func textDocumentDidClose(_ params: DidCloseTextDocumentParams) async { } func textDocumentWillSave(_ params: WillSaveTextDocumentParams) async { } func textDocumentDidSave(_ params: DidSaveTextDocumentParams) async { } func protocolCancelRequest(_ params: CancelParams) async { } func protocolSetTrace(_ params: SetTraceParams) async { } func workspaceDidChangeWatchedFiles(_ params: DidChangeWatchedFilesParams) async { } func windowWorkDoneProgressCancel(_ params: WorkDoneProgressCancelParams) async { } func workspaceDidChangeWorkspaceFolders(_ params: DidChangeWorkspaceFoldersParams) async { } func workspaceDidChangeConfiguration(_ params: DidChangeConfigurationParams) async { } func workspaceDidCreateFiles(_ params: CreateFilesParams) async { } func workspaceDidRenameFiles(_ params: RenameFilesParams) async { } func workspaceDidDeleteFiles(_ params: DeleteFilesParams) async { } func shutdown(id: JSONId) async {} func workspaceInlayHintRefresh(id: JSONId) async {} func workspaceExecuteCommand(id: JSONId, params: ExecuteCommandParams) async -> Response { .success(nil) } func workspaceWillCreateFiles(id: JSONId, params: CreateFilesParams) async -> Response { .success(nil) } func workspaceWillRenameFiles(id: JSONId, params: RenameFilesParams) async -> Response { .success(nil) } func workspaceWillDeleteFiles(id: JSONId, params: DeleteFilesParams) async -> Response { .success(nil) } func workspaceSymbol(id: JSONId, params: WorkspaceSymbolParams) async -> Response { .success(nil) } // func workspaceSymbolResolve(id: JSONId, params: WorkspaceSymbol) async -> Response { .success(nil) } func textDocumentWillSaveWaitUntil(id: JSONId, params: WillSaveTextDocumentParams) async -> Response<[TextEdit]?> { .success(nil) } func completion(id: JSONId, params: CompletionParams) async -> Response { .success(CompletionResponse(.optionB(.init(isIncomplete: false, items: [])))) } // func completionItemResolve(id: JSONId, params: CompletionItem) async -> Response { .success(nil) } func hover(id: JSONId, params: TextDocumentPositionParams) async -> Response { .success(nil) } func signatureHelp(id: JSONId, params: TextDocumentPositionParams) async -> Response { .success(nil) } func declaration(id: JSONId, params: TextDocumentPositionParams) async -> Response { .success(nil) } func definition(id: JSONId, params: TextDocumentPositionParams) async -> Response { .success(nil) } func typeDefinition(id: JSONId, params: TextDocumentPositionParams) async -> Response { .success(nil) } func implementation(id: JSONId, params: TextDocumentPositionParams) async -> Response { .success(nil) } func documentHighlight(id: JSONId, params: DocumentHighlightParams) async -> Response { .success(nil) } func documentSymbol(id: JSONId, params: DocumentSymbolParams) async -> Response { .success(nil) } func codeAction(id: JSONId, params: CodeActionParams) async -> Response { .success(nil) } // func codeActionResolve(id: JSONId, params: CodeAction) async -> Response { .success(nil) } func codeLens(id: JSONId, params: CodeLensParams) async -> Response { .success(nil) } // func codeLensResolve(id: JSONId, params: CodeLens) async -> Response { .success(nil) } func selectionRange(id: JSONId, params: SelectionRangeParams) async -> Response { .success(nil) } func linkedEditingRange(id: JSONId, params: LinkedEditingRangeParams) async -> Response { .success(nil) } func prepareCallHierarchy(id: JSONId, params: CallHierarchyPrepareParams) async -> Response { .success(nil) } func prepareRename(id: JSONId, params: PrepareRenameParams) async -> Response { .success(nil) } func prepareTypeHeirarchy(id: JSONId, params: TypeHierarchyPrepareParams) async -> Response { .success(nil) } func rename(id: JSONId, params: RenameParams) async -> Response { .success(nil) } func inlayHint(id: JSONId, params: InlayHintParams) async -> Response { .success(nil) } func inlayHintResolve(id: JSONId, params: InlayHint) async -> Response { .success(nil) } func documentLink(id: JSONId, params: DocumentLinkParams) async -> Response { .success(nil) } // func documentLinkResolve(id: JSONId, params: DocumentLink) async -> Response { .success(nil) } // func documentColor(id: JSONId, params: DocumentColorParams) async -> Response { .success(nil) } // func colorPresentation(id: JSONId, params: ColorPresentationParams) async -> Response { .success(nil) } func formatting(id: JSONId, params: DocumentFormattingParams) async -> Response { .success(nil) } func rangeFormatting(id: JSONId, params: DocumentRangeFormattingParams) async -> Response { .success(nil) } func onTypeFormatting(id: JSONId, params: DocumentOnTypeFormattingParams) async -> Response { .success(nil) } func references(id: JSONId, params: ReferenceParams) async -> Response { .success(nil) } func moniker(id: JSONId, params: MonikerParams) async -> Response { .success(nil) } func semanticTokensFullDelta(id: JSONId, params: SemanticTokensDeltaParams) async -> Response { .success(nil) } func semanticTokensRange(id: JSONId, params: SemanticTokensRangeParams) async -> Response { .success(nil) } func callHierarchyIncomingCalls(id: JSONId, params: CallHierarchyIncomingCallsParams) async -> Response { .success(nil) } func callHierarchyOutgoingCalls(id: JSONId, params: CallHierarchyOutgoingCallsParams) async -> Response { .success(nil) } func custom(id: JSONId, method: String, params: LSPAny) async -> Response { .success(nil) } }