/* * Copyright 2025 the original author or authors. *

* Licensed under the Moderne Source Available License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://docs.moderne.io/licensing/moderne-source-available-license *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as rpc from "vscode-jsonrpc/node"; import {Recipe} from "../../recipe"; import {Cursor, Tree} from "../../tree"; import {TreeVisitor} from "../../visitor"; import {MarkersKind, SearchResult} from "../../markers"; import {Visit} from "./visit"; import {ExecutionContext} from "../../execution"; import {DATA_TABLE_STORE, DataTableStore} from "../../data-table"; import {withMetrics} from "./metrics"; export interface BatchVisitItem { visitor: string visitorOptions?: Map } export interface BatchVisitResult { modified: boolean deleted: boolean hasNewMessages: boolean searchResultIds: string[] } export interface BatchVisitResponse { results: BatchVisitResult[] } export interface BatchVisitRequest { sourceFileType: string treeId: string p: string cursor?: string[] visitors: BatchVisitItem[] } async function collectSearchResultIds(tree: Tree | null | undefined): Promise> { const ids = new Set(); if (!tree) return ids; await new class extends TreeVisitor> { protected async visitMarker(marker: any, ctx: Set): Promise { if (marker && marker.kind === MarkersKind.SearchResult) { ctx.add((marker as SearchResult).id.toString()); } return super.visitMarker(marker, ctx); } }().visit(tree, ids); return ids; } export class BatchVisit { static handle(connection: rpc.MessageConnection, localObjects: Map, preparedRecipes: Map, recipeCursors: WeakMap, getObject: (id: string, sourceFileType?: string) => any, captureRefCheckpoint: (treeId: string) => void, getCursor: (cursorIds: string[] | undefined, sourceFileType?: string) => Promise, dataTableStore: () => DataTableStore | undefined, metricsCsv?: string): void { connection.onRequest( new rpc.RequestType("BatchVisit"), withMetrics( "BatchVisit", metricsCsv, (_context) => async (request) => { const p = await getObject(request.p, undefined); const store = dataTableStore(); if (store && p instanceof ExecutionContext) { p.messages[DATA_TABLE_STORE] = store; } captureRefCheckpoint(request.treeId); let tree: Tree = await getObject(request.treeId, request.sourceFileType); const cursor = await getCursor(request.cursor, request.sourceFileType); const results: BatchVisitResult[] = []; const knownIds = await collectSearchResultIds(tree); for (const item of request.visitors) { // Instantiate and run visitor const visitor = await Visit.instantiateVisitor( {visitor: item.visitor, visitorOptions: item.visitorOptions}, preparedRecipes, recipeCursors, p); // Snapshot ctx message keys so we can flag whether // the visitor put anything new into the context. const preKeys = new Set( Reflect.ownKeys(p.messages) as (string | symbol)[] ); const after = await visitor.visit(tree, p, cursor); const modified = after !== tree; const deleted = after == null; let hasNewMessages = false; for (const k of Reflect.ownKeys(p.messages)) { if (!preKeys.has(k)) { hasNewMessages = true; break; } } // Diff SearchResult IDs against the running set let searchResultIds: string[]; if (deleted) { searchResultIds = []; } else { const afterIds = await collectSearchResultIds(after); searchResultIds = [...afterIds].filter(id => !knownIds.has(id)); for (const id of searchResultIds) knownIds.add(id); } results.push({modified, deleted, hasNewMessages, searchResultIds}); if (deleted) { localObjects.delete(request.treeId); break; } if (modified) { tree = after; } } // Store final tree in localObjects if (tree != null) { localObjects.set(tree.id.toString(), tree); if (tree.id.toString() !== request.treeId) { localObjects.set(request.treeId, tree); } } return {results}; } ) ); } }