1// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers 2// 3// SPDX-License-Identifier: MPL-2.0 4 5import Foundation 6import JSONRPC 7import LanguageServer 8import LanguageServerProtocol 9import Logging 10import PterodactylBuild 11import PterodactylSyntax 12import TSCBasic 13import llbuild2fx 14 15final class EventHandler { 16 private let connection: JSONRPCClientConnection 17 private let buildEngine: FXEngine 18 private let casContext: TSCUtility.Context 19 private let casClient: LLBCASFSClient 20 21 var storedBlobs: [DocumentUri: (blobId: LLBDataID, version: Int?)] = [:] 22 23 init(connection: JSONRPCClientConnection) { 24 self.connection = connection 25 26 let group = LLBMakeDefaultDispatchGroup() 27 let db = LLBInMemoryCASDatabase(group: group) 28 let functionCache = FXInMemoryFunctionCache(group: group) 29 let executor = FXLocalExecutor() 30 self.buildEngine = FXEngine(group: group, db: db, functionCache: functionCache, executor: executor) 31 self.casClient = LLBCASFSClient(db) 32 self.casContext = Context() 33 } 34 35 func storeBlob(text: String, uri: DocumentUri, version: Int?) async throws -> LLBDataID { 36 if let stored = storedBlobs[uri], let version, let storedVersion = stored.version, storedVersion >= version { 37 return stored.blobId 38 } 39 40 let blobId: LLBDataID = try await casClient.store(LLBByteBuffer(string: text), casContext).get() 41 storedBlobs[uri] = (blobId: blobId, version: version) 42 return blobId 43 } 44 45 func publishLiveDiagnostics(blobId: LLBDataID, uri: DocumentUri, version: Int?) async throws { 46 let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: blobId), casContext).get() 47 let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: blobId), casContext).get() 48 let diagnostics = parseResult.diagnostics.map { $0.lspDiagnostic(lineMap: lineMap) } 49 let publishParams = PublishDiagnosticsParams(uri: uri, version: version, diagnostics: diagnostics) 50 try await connection.sendNotification(.textDocumentPublishDiagnostics(publishParams)) 51 } 52} 53 54 55extension EventHandler: LanguageServer.EventHandler { 56 var textDocumentSinkOptions: TextDocumentSyncOptions { 57 TextDocumentSyncOptions( 58 openClose: true, 59 change: .full, 60 61 save: .optionA(false) 62 ) 63 } 64 65 var completionOptions: CompletionOptions { 66 CompletionOptions( 67 workDoneProgress: false, 68 triggerCharacters: [], // TODO 69 allCommitCharacters: nil, 70 resolveProvider: false, 71 completionItem: nil 72 ) 73 } 74 75 var semanticTokensLegend: SemanticTokensLegend { 76 SemanticTokensLegend(tokenTypes: SemanticTokenTypes.allStrings, tokenModifiers: SemanticTokenModifiers.allStrings) 77 } 78 79 var semanticTokensOptions: SemanticTokensOptions { 80 SemanticTokensOptions(legend: semanticTokensLegend, full: .optionB(SemanticTokensClientCapabilities.Requests.Full(delta: false))) 81 } 82 83 func initialize(id: JSONId, params: InitializeParams) async -> Response<InitializationResponse> { 84 Logger.shared.debug("Received initialize request") 85 var serverCapabilities = ServerCapabilities() 86 serverCapabilities.textDocumentSync = .optionA(textDocumentSinkOptions) 87 serverCapabilities.completionProvider = completionOptions 88 serverCapabilities.hoverProvider = .optionA(false) 89 serverCapabilities.semanticTokensProvider = .optionA(semanticTokensOptions) 90 serverCapabilities.foldingRangeProvider = .optionA(true) 91 let response = InitializationResponse( 92 capabilities: serverCapabilities, 93 serverInfo: nil 94 ) 95 return .success(response) 96 } 97 98 func textDocumentDidChange(_ params: DidChangeTextDocumentParams) async { 99 guard let text = params.contentChanges.first?.text else { return } 100 do { 101 let blobId: LLBDataID = try await storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version) 102 try await publishLiveDiagnostics(blobId: blobId, uri: params.textDocument.uri, version: params.textDocument.version) 103 } catch {} 104 } 105 106 func textDocumentDidOpen(_ params: DidOpenTextDocumentParams) async { 107 let text = params.textDocument.text 108 do { 109 let blobId: LLBDataID = try await storeBlob(text: text, uri: params.textDocument.uri, version: params.textDocument.version) 110 try await publishLiveDiagnostics(blobId: blobId, uri: params.textDocument.uri, version: params.textDocument.version) 111 } catch {} 112 } 113 114 func semanticTokensFull(id: JSONId, params: SemanticTokensParams) async -> Response<SemanticTokensResponse> { 115 guard let storedBlob = storedBlobs[params.textDocument.uri] else { return .success(nil) } 116 117 do { 118 let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: storedBlob.blobId), casContext).get() 119 let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: storedBlob.blobId), casContext).get() 120 let cursor = SyntaxCursor(lineMap: lineMap, node: .tree(parseResult.tree), utf16Offset: 0) 121 return .success(SemanticTokensResponse(SemanticTokens(resultId: nil, tokens: cursor.semanticTokens))) 122 } catch { 123 return .success(nil) 124 } 125 } 126 127 func foldingRange(id: JSONId, params: FoldingRangeParams) async -> Response<FoldingRangeResponse> { 128 guard let storedBlob = storedBlobs[params.textDocument.uri] else { return .success(nil) } 129 130 do { 131 let lineMap = try await buildEngine.build(key: Keys.Blob.GetLineMap(blobId: storedBlob.blobId), casContext).get() 132 let parseResult = try await buildEngine.build(key: Keys.Blob.ParseDocument(blobId: storedBlob.blobId), casContext).get() 133 let cursor = SyntaxCursor(lineMap: lineMap, node: .tree(parseResult.tree), utf16Offset: 0) 134 return .success(FoldingRangeResponse(cursor.foldingRanges)) 135 } catch { 136 return .success(nil) 137 } 138 } 139 140 141 func typeHierarchySupertypes( 142 id: JSONRPC.JSONId, 143 params: TypeHierarchySupertypesParams 144 ) async -> Response<TypeHierarchySupertypesResponse> { 145 .success(nil) 146 } 147 148 func typeHierarchySubtypes( 149 id: JSONId, 150 params: TypeHierarchySubtypesParams 151 ) async -> Response<TypeHierarchySubtypesResponse> { 152 .success(nil) 153 } 154 155 func internalError(_ error: any Error) async { 156 Logger.shared.error("Received error: \(error)") 157 } 158 159 func initialized(_ params: InitializedParams) async { 160 161 } 162 163 func exit() async { 164 Logger.shared.info("Received exit notification") 165 Foundation.exit(0) 166 } 167 168 func diagnostics(id: JSONId, params: DocumentDiagnosticParams) async -> Response<DocumentDiagnosticReport> { 169 return .success(.init(kind: .unchanged)) 170 } 171 172 173 174 func textDocumentDidClose(_ params: DidCloseTextDocumentParams) async { 175 176 } 177 178 func textDocumentWillSave(_ params: WillSaveTextDocumentParams) async { 179 180 } 181 182 func textDocumentDidSave(_ params: DidSaveTextDocumentParams) async { 183 184 } 185 186 func protocolCancelRequest(_ params: CancelParams) async { 187 188 } 189 190 func protocolSetTrace(_ params: SetTraceParams) async { 191 192 } 193 194 func workspaceDidChangeWatchedFiles(_ params: DidChangeWatchedFilesParams) async { 195 196 } 197 198 func windowWorkDoneProgressCancel(_ params: WorkDoneProgressCancelParams) async { 199 200 } 201 202 func workspaceDidChangeWorkspaceFolders(_ params: DidChangeWorkspaceFoldersParams) async { 203 204 } 205 206 func workspaceDidChangeConfiguration(_ params: DidChangeConfigurationParams) async { 207 208 } 209 210 func workspaceDidCreateFiles(_ params: CreateFilesParams) async { 211 212 } 213 214 func workspaceDidRenameFiles(_ params: RenameFilesParams) async { 215 216 } 217 218 func workspaceDidDeleteFiles(_ params: DeleteFilesParams) async { 219 220 } 221 222 func shutdown(id: JSONId) async {} 223 func workspaceInlayHintRefresh(id: JSONId) async {} 224 func workspaceExecuteCommand(id: JSONId, params: ExecuteCommandParams) async -> Response<LSPAny?> { .success(nil) } 225 func workspaceWillCreateFiles(id: JSONId, params: CreateFilesParams) async -> Response<WorkspaceEdit?> { .success(nil) } 226 func workspaceWillRenameFiles(id: JSONId, params: RenameFilesParams) async -> Response<WorkspaceEdit?> { .success(nil) } 227 func workspaceWillDeleteFiles(id: JSONId, params: DeleteFilesParams) async -> Response<WorkspaceEdit?> { .success(nil) } 228 func workspaceSymbol(id: JSONId, params: WorkspaceSymbolParams) async -> Response<WorkspaceSymbolResponse> { .success(nil) } 229 // func workspaceSymbolResolve(id: JSONId, params: WorkspaceSymbol) async -> Response<WorkspaceSymbol> { .success(nil) } 230 func textDocumentWillSaveWaitUntil(id: JSONId, params: WillSaveTextDocumentParams) async -> Response<[TextEdit]?> { .success(nil) } 231 func completion(id: JSONId, params: CompletionParams) async -> Response<CompletionResponse> { 232 .success(CompletionResponse(.optionB(.init(isIncomplete: false, items: [])))) 233 } 234 // func completionItemResolve(id: JSONId, params: CompletionItem) async -> Response<CompletionItem> { .success(nil) } 235 func hover(id: JSONId, params: TextDocumentPositionParams) async -> Response<HoverResponse> { .success(nil) } 236 func signatureHelp(id: JSONId, params: TextDocumentPositionParams) async -> Response<SignatureHelpResponse> { .success(nil) } 237 func declaration(id: JSONId, params: TextDocumentPositionParams) async -> Response<DeclarationResponse> { .success(nil) } 238 func definition(id: JSONId, params: TextDocumentPositionParams) async -> Response<DefinitionResponse> { .success(nil) } 239 func typeDefinition(id: JSONId, params: TextDocumentPositionParams) async -> Response<TypeDefinitionResponse> { .success(nil) } 240 func implementation(id: JSONId, params: TextDocumentPositionParams) async -> Response<ImplementationResponse> { .success(nil) } 241 func documentHighlight(id: JSONId, params: DocumentHighlightParams) async -> Response<DocumentHighlightResponse> { .success(nil) } 242 func documentSymbol(id: JSONId, params: DocumentSymbolParams) async -> Response<DocumentSymbolResponse> { .success(nil) } 243 func codeAction(id: JSONId, params: CodeActionParams) async -> Response<CodeActionResponse> { .success(nil) } 244 // func codeActionResolve(id: JSONId, params: CodeAction) async -> Response<CodeAction> { .success(nil) } 245 func codeLens(id: JSONId, params: CodeLensParams) async -> Response<CodeLensResponse> { .success(nil) } 246 // func codeLensResolve(id: JSONId, params: CodeLens) async -> Response<CodeLens> { .success(nil) } 247 func selectionRange(id: JSONId, params: SelectionRangeParams) async -> Response<SelectionRangeResponse> { .success(nil) } 248 func linkedEditingRange(id: JSONId, params: LinkedEditingRangeParams) async -> Response<LinkedEditingRangeResponse> { .success(nil) } 249 func prepareCallHierarchy(id: JSONId, params: CallHierarchyPrepareParams) async -> Response<CallHierarchyPrepareResponse> { .success(nil) } 250 func prepareRename(id: JSONId, params: PrepareRenameParams) async -> Response<PrepareRenameResponse> { .success(nil) } 251 func prepareTypeHeirarchy(id: JSONId, params: TypeHierarchyPrepareParams) async -> Response<PrepareTypeHeirarchyResponse> { .success(nil) } 252 func rename(id: JSONId, params: RenameParams) async -> Response<RenameResponse> { .success(nil) } 253 func inlayHint(id: JSONId, params: InlayHintParams) async -> Response<InlayHintResponse> { .success(nil) } 254 func inlayHintResolve(id: JSONId, params: InlayHint) async -> Response<InlayHintResponse> { .success(nil) } 255 func documentLink(id: JSONId, params: DocumentLinkParams) async -> Response<DocumentLinkResponse> { .success(nil) } 256 // func documentLinkResolve(id: JSONId, params: DocumentLink) async -> Response<DocumentLink> { .success(nil) } 257 // func documentColor(id: JSONId, params: DocumentColorParams) async -> Response<DocumentColorResponse> { .success(nil) } 258 // func colorPresentation(id: JSONId, params: ColorPresentationParams) async -> Response<ColorPresentationResponse> { .success(nil) } 259 func formatting(id: JSONId, params: DocumentFormattingParams) async -> Response<FormattingResult> { .success(nil) } 260 func rangeFormatting(id: JSONId, params: DocumentRangeFormattingParams) async -> Response<FormattingResult> { .success(nil) } 261 func onTypeFormatting(id: JSONId, params: DocumentOnTypeFormattingParams) async -> Response<FormattingResult> { .success(nil) } 262 func references(id: JSONId, params: ReferenceParams) async -> Response<ReferenceResponse> { .success(nil) } 263 func moniker(id: JSONId, params: MonikerParams) async -> Response<MonikerResponse> { .success(nil) } 264 func semanticTokensFullDelta(id: JSONId, params: SemanticTokensDeltaParams) async -> Response<SemanticTokensDeltaResponse> { .success(nil) } 265 func semanticTokensRange(id: JSONId, params: SemanticTokensRangeParams) async -> Response<SemanticTokensResponse> { .success(nil) } 266 func callHierarchyIncomingCalls(id: JSONId, params: CallHierarchyIncomingCallsParams) async -> Response<CallHierarchyIncomingCallsResponse> { .success(nil) } 267 func callHierarchyOutgoingCalls(id: JSONId, params: CallHierarchyOutgoingCallsParams) async -> Response<CallHierarchyOutgoingCallsResponse> { .success(nil) } 268 func custom(id: JSONId, method: String, params: LSPAny) async -> Response<LSPAny> { .success(nil) } 269}