1- import { commands , EventEmitter , Event , ExtensionContext , window , Memento , Uri , QuickPickItem } from "vscode" ;
2- import { SpringNode , StereotypedNode } from "./nodes" ;
1+ import { commands , EventEmitter , Event , ExtensionContext , window , Memento , QuickPickItem } from "vscode" ;
2+ import { StereotypedNode } from "./nodes" ;
33import { ExtensionAPI } from "../api" ;
4- import * as ls from 'vscode-languageserver-protocol' ;
5-
64
75const SPRING_STRUCTURE_CMD = "sts/spring-boot/structure" ;
86
7+ interface StructureCommandParams {
8+ updateMetadata : boolean ;
9+ groups ?: Record < string , string [ ] > ;
10+ affectedProjects ?: string [ ] ;
11+ }
12+
913export class StructureManager {
1014
11- private _rootElements : Thenable < SpringNode [ ] >
12- private _onDidChange : EventEmitter < SpringNode | undefined > = new EventEmitter < SpringNode | undefined > ( ) ;
15+ private _rootElementsRequest : Thenable < StereotypedNode [ ] >
16+ private _rootElements : StereotypedNode [ ] = [ ] ;
17+ private _onDidChange = new EventEmitter < undefined | StereotypedNode | StereotypedNode [ ] > ( ) ;
1318 private workspaceState : Memento ;
1419
1520 constructor ( context : ExtensionContext , api : ExtensionAPI ) {
1621 this . workspaceState = context . workspaceState ;
1722 context . subscriptions . push ( commands . registerCommand ( "vscode-spring-boot.structure.refresh" , ( ) => this . refresh ( true ) ) ) ;
18- context . subscriptions . push ( commands . registerCommand ( "vscode-spring-boot.structure.openReference" , ( node ) => {
19- const reference = node ?. getReferenceValue ( ) as ls . Location ; ;
23+ context . subscriptions . push ( commands . registerCommand ( "vscode-spring-boot.structure.openReference" , ( node : StereotypedNode ) => {
24+ const reference = node ?. referenceValue ;
2025 if ( reference ) {
2126 const location = api . client . protocol2CodeConverter . asLocation ( reference )
2227 window . showTextDocument ( location . uri , { selection : location . range } ) ;
2328 }
2429 } ) ) ;
2530
2631 context . subscriptions . push ( commands . registerCommand ( "vscode-spring-boot.structure.grouping" , async ( node : StereotypedNode ) => {
27- const projectName = node . getProjectId ( ) ;
32+ const projectName = node . projectId ;
2833 const groups = await commands . executeCommand < Groups > ( "sts/spring-boot/structure/groups" , projectName ) ;
2934 const initialGroups : string [ ] | undefined = this . getVisibleGroups ( projectName ) ;
3035 const items = ( groups ?. groups || [ ] ) . map ( g => ( {
@@ -45,38 +50,81 @@ export class StructureManager {
4550 }
4651 } ) ) ;
4752
48- context . subscriptions . push ( api . getSpringIndex ( ) . onSpringIndexUpdated ( e => this . refresh ( false ) ) ) ;
53+ context . subscriptions . push ( api . getSpringIndex ( ) . onSpringIndexUpdated ( indexUpdateDetails => this . refresh ( false , indexUpdateDetails . affectedProjects ) ) ) ;
4954
5055 }
5156
52- get rootElements ( ) : Thenable < SpringNode [ ] > {
53- return this . _rootElements ;
57+ get rootElements ( ) : Thenable < StereotypedNode [ ] > {
58+ return this . _rootElementsRequest ;
5459 }
5560
56- refresh ( updateMetadata : boolean ) : void {
57- this . _rootElements = commands . executeCommand ( SPRING_STRUCTURE_CMD ,
58- {
59- "updateMetadata" : updateMetadata ,
60- "groups" : this . getGroupings ( )
61- } ) . then ( json => {
61+ // Serves 2 purposes: non UI trigerred refresh as a result of the index update and a UI trigerred refresh
62+ // The UI trigerred refresh needs to preceed with an event fired such that tree view would kick off a new promise getting all new root elements and would show progress while promise is being resolved.
63+ // The index update typically would have a list of projects for which index has changed then the refresh can be silent with letting the tree know about new data once it is computed
64+ // If the index update event doesn't have a list of project then this is an edge case for which we'd show the preogress and treat it like UI trigerred refresh
65+ refresh ( updateMetadata : boolean , affectedProjects ?: string [ ] ) : void {
66+ const isPartialLoad = ! ! ( affectedProjects && affectedProjects . length ) ;
67+ // Notify the tree to get the children to trigger "loading" bar in the view???
68+ const params = {
69+ updateMetadata,
70+ affectedProjects,
71+ groups : this . getGroupings ( ) ,
72+ } as StructureCommandParams ;
73+ this . _rootElementsRequest = commands . executeCommand ( SPRING_STRUCTURE_CMD , params ) . then ( json => {
6274 const nodes = this . parseArray ( json ) ;
63- this . _onDidChange . fire ( undefined ) ;
64- return nodes ;
75+ if ( isPartialLoad ) {
76+ const newNodes = [ ] as StereotypedNode [ ] ;
77+ const nodesMap = { } as Record < string , StereotypedNode > ;
78+ affectedProjects . forEach ( projectName => nodesMap [ projectName ] = nodes . find ( n => n . projectId === projectName ) ) ;
79+ // merge old and newly fetched stereotype root nodes
80+ let onlyMutations = true ;
81+ this . _rootElements . forEach ( n => {
82+ if ( nodesMap . hasOwnProperty ( n . projectId ) ) {
83+ const newN = nodesMap [ n . projectId ] ;
84+ delete nodesMap [ n . projectId ] ;
85+ if ( newN ) {
86+ newNodes . push ( newN ) ;
87+ } else {
88+ // element removed
89+ onlyMutations = false ;
90+ }
91+ } else {
92+ newNodes . push ( n ) ;
93+ }
94+ } ) ;
95+ if ( Object . values ( nodesMap ) . length ) {
96+ // elements added
97+ onlyMutations = false ;
98+ Object . values ( nodesMap ) . filter ( n => ! ! n ) . forEach ( n => newNodes . push ( n ) ) ;
99+ }
100+ this . _rootElements = newNodes ;
101+ // TODO: Partial tree refresh didn't wowrk for restbucks it remains either without children or without the full text label
102+ // (test with `spring-restbucks` project in a workspace with other boot projects, i.e. demo, spring-petclinic)
103+ this . _onDidChange . fire ( /*onlyMutations ? nodes : */ undefined ) ;
104+ } else {
105+ this . _rootElements = nodes ;
106+ // No need to fire another event to update the UI since there is an event fired before referesh is trigerred to reference the new promise
107+ }
108+ return this . _rootElements ;
65109 } ) ;
110+ if ( ! isPartialLoad ) {
111+ // Fire an event for full reload to have a progress bar while the promise above is resolved
112+ this . _onDidChange . fire ( undefined ) ;
113+ }
66114 }
67115
68- private parseNode ( json : any , parent ?: SpringNode ) : SpringNode | undefined {
116+ private parseNode ( json : any , parent ?: StereotypedNode ) : StereotypedNode | undefined {
69117 const node = new StereotypedNode ( json as LsStereoTypedNode , [ ] , parent ) ;
70118 // Parse children after creating the node so we can pass it as parent
71119 node . children . push ( ...this . parseArray ( json . children , node ) ) ;
72120 return node ;
73121 }
74122
75- private parseArray ( json : any , parent ?: SpringNode ) : SpringNode [ ] {
123+ private parseArray ( json : any , parent ?: StereotypedNode ) : StereotypedNode [ ] {
76124 return Array . isArray ( json ) ? ( json as [ ] ) . map ( j => this . parseNode ( j , parent ) ) . filter ( e => ! ! e ) : [ ] ;
77125 }
78126
79- public get onDidChange ( ) : Event < SpringNode | undefined > {
127+ public get onDidChange ( ) : Event < undefined | StereotypedNode | StereotypedNode [ ] > {
80128 return this . _onDidChange . event ;
81129 }
82130
0 commit comments