@@ -199,6 +199,7 @@ export class TestElementsTreeView extends TreeViewBase<TestElementsTreeItem> {
199199 try {
200200 await this . refreshResourceAvailabilityFromWorkspace ( ) ;
201201 await this . updateAllParentMarkings ( ) ;
202+ this . _onDidChangeTreeData . fire ( undefined ) ;
202203 } catch ( error ) {
203204 this . logger . error (
204205 "[TestElementsTreeView] Error during debounced resource availability refresh:" ,
@@ -272,6 +273,124 @@ export class TestElementsTreeView extends TreeViewBase<TestElementsTreeItem> {
272273 }
273274 }
274275
276+ /**
277+ * Ensure Language Server readiness for availability/icon checks.
278+ */
279+ private async ensureLanguageServerReadyForAvailabilityChecks ( ) : Promise < void > {
280+ if ( isLanguageServerRunning ( ) ) {
281+ return ;
282+ }
283+
284+ const cfgExists = await hasLsConfig ( ) ;
285+ if ( ! cfgExists ) {
286+ this . logger . trace ( "[TestElementsTreeView] No LS config present; proceeding with availability checks." ) ;
287+ return ;
288+ }
289+
290+ try {
291+ await updateOrRestartLS ( ) ;
292+ await waitForLanguageServerReady ( 5000 , 100 ) ;
293+ } catch {
294+ this . logger . trace ( "[TestElementsTreeView] LS not ready, proceeding with availability checks." ) ;
295+ }
296+ }
297+
298+ private isResourceSubdivision ( item : TestElementsTreeItem ) : boolean {
299+ if ( item . data . testElementType !== TestElementType . Subdivision || item . data . isVirtual ) {
300+ return false ;
301+ }
302+ return ResourceFileService . hasResourceMarker ( item . data . hierarchicalName || item . data . displayName || "" ) ;
303+ }
304+
305+ private collectSubdivisionItems (
306+ items : TestElementsTreeItem [ ] ,
307+ options : {
308+ onlyVisible : boolean ;
309+ filter ?: ( item : TestElementsTreeItem ) => boolean ;
310+ }
311+ ) : TestElementsTreeItem [ ] {
312+ const subdivisionItems : TestElementsTreeItem [ ] = [ ] ;
313+ const { onlyVisible, filter } = options ;
314+
315+ const collect = ( currentItems : TestElementsTreeItem [ ] ) => {
316+ for ( const item of currentItems ) {
317+ const isExpanded = item . collapsibleState === vscode . TreeItemCollapsibleState . Expanded ;
318+ const shouldRecurse = ! onlyVisible || isExpanded ;
319+
320+ if ( item . data . testElementType === TestElementType . Subdivision ) {
321+ const passesVisibility = ! onlyVisible || isExpanded ;
322+ const passesFilter = filter ? filter ( item ) : true ;
323+ if ( passesVisibility && passesFilter ) {
324+ subdivisionItems . push ( item ) ;
325+ }
326+ }
327+
328+ if ( item . children && shouldRecurse ) {
329+ collect ( item . children as TestElementsTreeItem [ ] ) ;
330+ }
331+ }
332+ } ;
333+ collect ( items ) ;
334+ return subdivisionItems ;
335+ }
336+
337+ private async updateSubdivisionAvailability (
338+ subdivisionItems : TestElementsTreeItem [ ] ,
339+ options : {
340+ updateParentMarkingOnAvailableResource : boolean ;
341+ }
342+ ) : Promise < void > {
343+ await this . ensureLanguageServerReadyForAvailabilityChecks ( ) ;
344+
345+ // Process file checks in batches to yield to UI thread
346+ const BATCH_SIZE = 20 ;
347+ for ( let i = 0 ; i < subdivisionItems . length ; i += BATCH_SIZE ) {
348+ const batch = subdivisionItems . slice ( i , i + BATCH_SIZE ) ;
349+ await Promise . all (
350+ batch . map ( async ( subdivisionItem ) => {
351+ try {
352+ if ( subdivisionItem . data . isVirtual ) {
353+ return ;
354+ }
355+
356+ const hierarchicalName = subdivisionItem . data . hierarchicalName ;
357+ if ( ! hierarchicalName ) {
358+ return ;
359+ }
360+
361+ const isResourceFile = ResourceFileService . hasResourceMarker ( hierarchicalName ) ;
362+ const cleanName = this . removeResourceMarkersFromHierarchicalName ( hierarchicalName ) . trim ( ) ;
363+ let resourcePath = await this . resourceFileService . constructAbsolutePath ( cleanName ) ;
364+
365+ if ( ! resourcePath ) {
366+ return ;
367+ }
368+
369+ if ( isResourceFile && ! resourcePath . endsWith ( ".resource" ) ) {
370+ resourcePath += ".resource" ;
371+ }
372+
373+ const exists = await this . resourceFileService . pathExists ( resourcePath ) ;
374+ subdivisionItem . updateLocalAvailability ( exists , resourcePath ) ;
375+
376+ if ( options . updateParentMarkingOnAvailableResource && exists && isResourceFile ) {
377+ await this . updateParentSubdivisionMarking ( subdivisionItem ) ;
378+ }
379+ } catch ( error ) {
380+ this . logger . error (
381+ `[TestElementsTreeView] Error updating subdivision availability for tree item ${ subdivisionItem . label } :` ,
382+ error
383+ ) ;
384+ }
385+ } )
386+ ) ;
387+
388+ if ( i + BATCH_SIZE < subdivisionItems . length ) {
389+ await new Promise ( ( resolve ) => setImmediate ( resolve ) ) ;
390+ }
391+ }
392+ }
393+
275394 /**
276395 * Handler for resource file related operations.
277396 * @param config The configuration object defining the operation to perform.
@@ -556,9 +675,9 @@ export class TestElementsTreeView extends TreeViewBase<TestElementsTreeItem> {
556675 this . stateManager . setLoading ( false ) ;
557676 ( this as any ) . updateTreeViewMessage ( ) ;
558677
678+ // Publish new data immediately, then update availability/marking in the background.
559679 this . _onDidChangeTreeData . fire ( undefined ) ;
560- // Only check visible items initially
561- await this . updateSubdivisionIcons ( newRootItems , true ) ;
680+ void this . runPostFetchAvailabilityUpdates ( newRootItems ) ;
562681
563682 const loadTime = Date . now ( ) - startTime ;
564683 this . logger . debug (
@@ -646,6 +765,10 @@ export class TestElementsTreeView extends TreeViewBase<TestElementsTreeItem> {
646765 // Remaining items will be checked when expanded
647766 await this . updateSubdivisionIcons ( this . rootItems , true ) ;
648767
768+ // Compute availability for all resource subdivisions and recompute parent markings
769+ await this . updateResourceSubdivisionAvailability ( this . rootItems ) ;
770+ await this . updateAllParentMarkings ( ) ;
771+
649772 // Set the last data fetch timestamp to prevent infinite loading
650773 // This is important even for empty results to prevent the tree from continuously trying to load data
651774 ( this as any ) . _lastDataFetch = Date . now ( ) ;
@@ -739,10 +862,8 @@ export class TestElementsTreeView extends TreeViewBase<TestElementsTreeItem> {
739862 this . rootItems = rootTestElementItems ;
740863 ( this as any ) . _lastDataFetch = Date . now ( ) ;
741864
742- // Async icon updates for visible items only
743- this . updateSubdivisionIcons ( rootTestElementItems , true ) . then ( ( ) => {
744- this . _onDidChangeTreeData . fire ( undefined ) ;
745- } ) ;
865+ // Run availability/icon updates in the background
866+ void this . runPostFetchAvailabilityUpdates ( rootTestElementItems ) ;
746867
747868 return rootTestElementItems ;
748869 } catch ( error ) {
@@ -751,88 +872,52 @@ export class TestElementsTreeView extends TreeViewBase<TestElementsTreeItem> {
751872 }
752873 }
753874
875+ /**
876+ * Post-fetch background updates:
877+ * - refresh visible subdivision availability
878+ * - compute availability for all resource subdivisions (even under collapsed branches)
879+ * - recompute parent markings
880+ * Always triggers a final tree refresh.
881+ */
882+ private async runPostFetchAvailabilityUpdates ( rootItems : TestElementsTreeItem [ ] ) : Promise < void > {
883+ try {
884+ await this . updateSubdivisionIcons ( rootItems , true ) ;
885+ await this . updateResourceSubdivisionAvailability ( rootItems ) ;
886+ await this . updateAllParentMarkings ( ) ;
887+ } catch ( error ) {
888+ this . logger . error ( "[TestElementsTreeView] Error during post-fetch availability updates:" , error ) ;
889+ } finally {
890+ this . _onDidChangeTreeData . fire ( undefined ) ;
891+ }
892+ }
893+
894+ /**
895+ * Makes sure local availability is computed for all resource subdivisions in the tree.
896+ * This is required so parent marking/icon state is correct even when resource subdivisions
897+ * are under collapsed branches (i.e., not "visible" yet).
898+ */
899+ private async updateResourceSubdivisionAvailability ( items : TestElementsTreeItem [ ] ) : Promise < void > {
900+ const resourceSubdivisionItems = this . collectSubdivisionItems ( items , {
901+ onlyVisible : false ,
902+ filter : ( item ) => this . isResourceSubdivision ( item )
903+ } ) ;
904+ await this . updateSubdivisionAvailability ( resourceSubdivisionItems , {
905+ // Parent marking is recomputed in a separate full pass (updateAllParentMarkings)
906+ updateParentMarkingOnAvailableResource : false
907+ } ) ;
908+ }
909+
754910 /**
755911 * Updates all subdivision icons by checking for their existence on the local file system
756912 * @param items Array of tree items to process
757913 * @param onlyVisible If true, only checks visible/expanded items to save performance
758914 * @returns Promise that resolves when all icon updates are complete
759915 */
760916 private async updateSubdivisionIcons ( items : TestElementsTreeItem [ ] , onlyVisible : boolean = false ) : Promise < void > {
761- // The python regex processing is done in language server via testbench_ls.get_resource_directory_subdivision_index command.
762- // Language server initialization should be awaited here to prevent error logs caused by this command call.
763- if ( ! isLanguageServerRunning ( ) ) {
764- const cfgExists = await hasLsConfig ( ) ;
765- if ( cfgExists ) {
766- try {
767- await updateOrRestartLS ( ) ;
768- await waitForLanguageServerReady ( 5000 , 100 ) ;
769- } catch {
770- this . logger . trace ( "[TestElementsTreeView] LS not ready, proceeding with icon updates." ) ;
771- }
772- } else {
773- this . logger . trace ( "[TestElementsTreeView] No LS config present; proceeding with icon updates." ) ;
774- }
775- }
776-
777- const subdivisionItems : TestElementsTreeItem [ ] = [ ] ;
778- const collectSubdivisions = ( currentItems : TestElementsTreeItem [ ] , checkExpanded : boolean ) => {
779- for ( const item of currentItems ) {
780- if ( item . data . testElementType === TestElementType . Subdivision ) {
781- if ( ! checkExpanded || item . collapsibleState === vscode . TreeItemCollapsibleState . Expanded ) {
782- subdivisionItems . push ( item ) ;
783- }
784- }
785- if (
786- item . children &&
787- ( ! checkExpanded || item . collapsibleState === vscode . TreeItemCollapsibleState . Expanded )
788- ) {
789- collectSubdivisions ( item . children as TestElementsTreeItem [ ] , checkExpanded ) ;
790- }
791- }
792- } ;
793- collectSubdivisions ( items , onlyVisible ) ;
794-
795- // Process file checks in batches to yield to UI thread
796- const BATCH_SIZE = 20 ;
797- for ( let i = 0 ; i < subdivisionItems . length ; i += BATCH_SIZE ) {
798- const batch = subdivisionItems . slice ( i , i + BATCH_SIZE ) ;
799- await Promise . all (
800- batch . map ( async ( subdivisionItem ) => {
801- try {
802- if ( subdivisionItem . data . isVirtual ) {
803- return ;
804- }
805- const hierarchicalName = subdivisionItem . data . hierarchicalName ;
806- if ( hierarchicalName ) {
807- const isResourceFile = ResourceFileService . hasResourceMarker ( hierarchicalName ) ;
808- const cleanName = this . removeResourceMarkersFromHierarchicalName ( hierarchicalName ) . trim ( ) ;
809- let resourcePath = await this . resourceFileService . constructAbsolutePath ( cleanName ) ;
810-
811- if ( resourcePath ) {
812- if ( isResourceFile && ! resourcePath . endsWith ( ".resource" ) ) {
813- resourcePath += ".resource" ;
814- }
815- const resourcePathExists = await this . resourceFileService . pathExists ( resourcePath ) ;
816- subdivisionItem . updateLocalAvailability ( resourcePathExists , resourcePath ) ;
817-
818- if ( resourcePathExists ) {
819- await this . updateParentSubdivisionMarking ( subdivisionItem ) ;
820- }
821- }
822- }
823- } catch ( error ) {
824- this . logger . error (
825- `[TestElementsTreeView] Error updating subdivision icon for tree item ${ subdivisionItem . label } :` ,
826- error
827- ) ;
828- }
829- } )
830- ) ;
831- // Yield to UI thread between batches to keep UI responsive
832- if ( i + BATCH_SIZE < subdivisionItems . length ) {
833- await new Promise ( ( resolve ) => setImmediate ( resolve ) ) ;
834- }
835- }
917+ const subdivisionItems = this . collectSubdivisionItems ( items , { onlyVisible } ) ;
918+ await this . updateSubdivisionAvailability ( subdivisionItems , {
919+ updateParentMarkingOnAvailableResource : true
920+ } ) ;
836921 }
837922
838923 /**
0 commit comments