1- import  {  inject ,  injectable  }  from  'inversify' ; 
1+ import  {  inject ,  injectable ,   postConstruct  }  from  'inversify' ; 
22import  URI  from  '@theia/core/lib/common/uri' ; 
33import  {  FileNode ,  FileTreeModel  }  from  '@theia/filesystem/lib/browser' ; 
44import  {  FileService  }  from  '@theia/filesystem/lib/browser/file-service' ; 
55import  {  ConfigService  }  from  '../../../common/protocol' ; 
66import  {  SketchbookTree  }  from  './sketchbook-tree' ; 
77import  {  ArduinoPreferences  }  from  '../../arduino-preferences' ; 
8- import  {  SelectableTreeNode ,  TreeNode  }  from  '@theia/core/lib/browser/tree' ; 
8+ import  { 
9+   CompositeTreeNode , 
10+   ExpandableTreeNode , 
11+   SelectableTreeNode , 
12+   TreeNode , 
13+ }  from  '@theia/core/lib/browser/tree' ; 
914import  {  SketchbookCommands  }  from  './sketchbook-commands' ; 
1015import  {  OpenerService ,  open  }  from  '@theia/core/lib/browser' ; 
1116import  {  SketchesServiceClientImpl  }  from  '../../../common/protocol/sketches-service-client-impl' ; 
1217import  {  CommandRegistry  }  from  '@theia/core/lib/common/command' ; 
18+ import  {  WorkspaceService  }  from  '@theia/workspace/lib/browser/workspace-service' ; 
19+ import  {  FrontendApplicationStateService  }  from  '@theia/core/lib/browser/frontend-application-state' ; 
20+ import  {  ProgressService  }  from  '@theia/core/lib/common/progress-service' ; 
21+ import  { 
22+   WorkspaceNode , 
23+   WorkspaceRootNode , 
24+ }  from  '@theia/navigator/lib/browser/navigator-tree' ; 
25+ import  {  Deferred  }  from  '@theia/core/lib/common/promise-util' ; 
26+ import  {  Disposable  }  from  '@theia/core/lib/common/disposable' ; 
1327
1428@injectable ( ) 
1529export  class  SketchbookTreeModel  extends  FileTreeModel  { 
@@ -31,14 +45,217 @@ export class SketchbookTreeModel extends FileTreeModel {
3145  @inject ( SketchesServiceClientImpl ) 
3246  protected  readonly  sketchServiceClient : SketchesServiceClientImpl ; 
3347
34-   async  updateRoot ( ) : Promise < void >  { 
35-     const  config  =  await  this . configService . getConfiguration ( ) ; 
36-     const  fileStat  =  await  this . fileService . resolve ( 
37-       new  URI ( config . sketchDirUri ) 
48+   @inject ( SketchbookTree )  protected  readonly  tree : SketchbookTree ; 
49+   @inject ( WorkspaceService ) 
50+   protected  readonly  workspaceService : WorkspaceService ; 
51+   @inject ( FrontendApplicationStateService ) 
52+   protected  readonly  applicationState : FrontendApplicationStateService ; 
53+ 
54+   @inject ( ProgressService ) 
55+   protected  readonly  progressService : ProgressService ; 
56+ 
57+   @postConstruct ( ) 
58+   protected  init ( ) : void   { 
59+     super . init ( ) ; 
60+     this . reportBusyProgress ( ) ; 
61+     this . initializeRoot ( ) ; 
62+   } 
63+ 
64+   protected  readonly  pendingBusyProgress  =  new  Map < string ,  Deferred < void > > ( ) ; 
65+   protected  reportBusyProgress ( ) : void   { 
66+     this . toDispose . push ( 
67+       this . onDidChangeBusy ( ( node )  =>  { 
68+         const  pending  =  this . pendingBusyProgress . get ( node . id ) ; 
69+         if  ( pending )  { 
70+           if  ( ! node . busy )  { 
71+             pending . resolve ( ) ; 
72+             this . pendingBusyProgress . delete ( node . id ) ; 
73+           } 
74+           return ; 
75+         } 
76+         if  ( node . busy )  { 
77+           const  progress  =  new  Deferred < void > ( ) ; 
78+           this . pendingBusyProgress . set ( node . id ,  progress ) ; 
79+           this . progressService . withProgress ( 
80+             '' , 
81+             'explorer' , 
82+             ( )  =>  progress . promise 
83+           ) ; 
84+         } 
85+       } ) 
86+     ) ; 
87+     this . toDispose . push ( 
88+       Disposable . create ( ( )  =>  { 
89+         for  ( const  pending  of  this . pendingBusyProgress . values ( ) )  { 
90+           pending . resolve ( ) ; 
91+         } 
92+         this . pendingBusyProgress . clear ( ) ; 
93+       } ) 
94+     ) ; 
95+   } 
96+ 
97+   protected  async  initializeRoot ( ) : Promise < void >  { 
98+     await  Promise . all ( [ 
99+       this . applicationState . reachedState ( 'initialized_layout' ) , 
100+       this . workspaceService . roots , 
101+     ] ) ; 
102+     await  this . updateRoot ( ) ; 
103+     if  ( this . toDispose . disposed )  { 
104+       return ; 
105+     } 
106+     this . toDispose . push ( 
107+       this . workspaceService . onWorkspaceChanged ( ( )  =>  this . updateRoot ( ) ) 
38108    ) ; 
39-     const  showAllFiles  = 
40-       this . arduinoPreferences [ 'arduino.sketchbook.showAllFiles' ] ; 
41-     this . tree . root  =  SketchbookTree . RootNode . create ( fileStat ,  showAllFiles ) ; 
109+     this . toDispose . push ( 
110+       this . workspaceService . onWorkspaceLocationChanged ( ( )  =>  this . updateRoot ( ) ) 
111+     ) ; 
112+     this . toDispose . push ( 
113+       this . arduinoPreferences . onPreferenceChanged ( ( {  preferenceName } )  =>  { 
114+         if  ( preferenceName  ===  'arduino.sketchbook.showAllFiles' )  { 
115+           this . updateRoot ( ) ; 
116+         } 
117+       } ) 
118+     ) ; 
119+ 
120+     if  ( this . selectedNodes . length )  { 
121+       return ; 
122+     } 
123+     const  root  =  this . root ; 
124+     if  ( CompositeTreeNode . is ( root )  &&  root . children . length  ===  1 )  { 
125+       const  child  =  root . children [ 0 ] ; 
126+       if  ( 
127+         SelectableTreeNode . is ( child )  && 
128+         ! child . selected  && 
129+         ExpandableTreeNode . is ( child ) 
130+       )  { 
131+         this . selectNode ( child ) ; 
132+         this . expandNode ( child ) ; 
133+       } 
134+     } 
135+   } 
136+ 
137+   previewNode ( node : TreeNode ) : void   { 
138+     if  ( FileNode . is ( node ) )  { 
139+       open ( this . openerService ,  node . uri ,  { 
140+         mode : 'reveal' , 
141+         preview : true , 
142+       } ) ; 
143+     } 
144+   } 
145+ 
146+   * getNodesByUri ( uri : URI ) : IterableIterator < TreeNode >  { 
147+     const  workspace  =  this . root ; 
148+     if  ( WorkspaceNode . is ( workspace ) )  { 
149+       for  ( const  root  of  workspace . children )  { 
150+         const  id  =  this . tree . createId ( root ,  uri ) ; 
151+         const  node  =  this . getNode ( id ) ; 
152+         if  ( node )  { 
153+           yield  node ; 
154+         } 
155+       } 
156+     } 
157+   } 
158+ 
159+   public  async  updateRoot ( ) : Promise < void >  { 
160+     this . root  =  await  this . createRoot ( ) ; 
161+   } 
162+ 
163+   protected  async  createRoot ( ) : Promise < TreeNode  |  undefined >  { 
164+     const  config  =  await  this . configService . getConfiguration ( ) ; 
165+     const  stat  =  await  this . fileService . resolve ( new  URI ( config . sketchDirUri ) ) ; 
166+ 
167+     if  ( this . workspaceService . opened )  { 
168+       const  isMulti  =  stat  ? ! stat . isDirectory  : false ; 
169+       const  workspaceNode  =  isMulti 
170+         ? this . createMultipleRootNode ( ) 
171+         : WorkspaceNode . createRoot ( ) ; 
172+       workspaceNode . children . push ( 
173+         await  this . tree . createWorkspaceRoot ( stat ,  workspaceNode ) 
174+       ) ; 
175+ 
176+       return  workspaceNode ; 
177+     } 
178+   } 
179+ 
180+   /** 
181+    * Create multiple root node used to display 
182+    * the multiple root workspace name. 
183+    * 
184+    * @returns  `WorkspaceNode` 
185+    */ 
186+   protected  createMultipleRootNode ( ) : WorkspaceNode  { 
187+     const  workspace  =  this . workspaceService . workspace ; 
188+     let  name  =  workspace  ? workspace . resource . path . name  : 'untitled' ; 
189+     name  +=  ' (Workspace)' ; 
190+     return  WorkspaceNode . createRoot ( name ) ; 
191+   } 
192+ 
193+   /** 
194+    * Move the given source file or directory to the given target directory. 
195+    */ 
196+   async  move ( source : TreeNode ,  target : TreeNode ) : Promise < URI  |  undefined >  { 
197+     if  ( source . parent  &&  WorkspaceRootNode . is ( source ) )  { 
198+       // do not support moving a root folder 
199+       return  undefined ; 
200+     } 
201+     return  super . move ( source ,  target ) ; 
202+   } 
203+ 
204+   /** 
205+    * Reveals node in the navigator by given file uri. 
206+    * 
207+    * @param  uri uri to file which should be revealed in the navigator 
208+    * @returns  file tree node if the file with given uri was revealed, undefined otherwise 
209+    */ 
210+   async  revealFile ( uri : URI ) : Promise < TreeNode  |  undefined >  { 
211+     if  ( ! uri . path . isAbsolute )  { 
212+       return  undefined ; 
213+     } 
214+     let  node  =  this . getNodeClosestToRootByUri ( uri ) ; 
215+ 
216+     // success stop condition 
217+     // we have to reach workspace root because expanded node could be inside collapsed one 
218+     if  ( WorkspaceRootNode . is ( node ) )  { 
219+       if  ( ExpandableTreeNode . is ( node ) )  { 
220+         if  ( ! node . expanded )  { 
221+           node  =  await  this . expandNode ( node ) ; 
222+         } 
223+         return  node ; 
224+       } 
225+       // shouldn't happen, root node is always directory, i.e. expandable 
226+       return  undefined ; 
227+     } 
228+ 
229+     // fail stop condition 
230+     if  ( uri . path . isRoot )  { 
231+       // file system root is reached but workspace root wasn't found, it means that 
232+       // given uri is not in workspace root folder or points to not existing file. 
233+       return  undefined ; 
234+     } 
235+ 
236+     if  ( await  this . revealFile ( uri . parent ) )  { 
237+       if  ( node  ===  undefined )  { 
238+         // get node if it wasn't mounted into navigator tree before expansion 
239+         node  =  this . getNodeClosestToRootByUri ( uri ) ; 
240+       } 
241+       if  ( ExpandableTreeNode . is ( node )  &&  ! node . expanded )  { 
242+         node  =  await  this . expandNode ( node ) ; 
243+       } 
244+       return  node ; 
245+     } 
246+     return  undefined ; 
247+   } 
248+ 
249+   protected  getNodeClosestToRootByUri ( uri : URI ) : TreeNode  |  undefined  { 
250+     const  nodes  =  [ ...this . getNodesByUri ( uri ) ] ; 
251+     return  nodes . length  >  0 
252+       ? nodes . reduce ( 
253+           ( 
254+             node1 , 
255+             node2  // return the node closest to the workspace root 
256+           )  =>  ( node1 . id . length  >=  node2 . id . length  ? node1  : node2 ) 
257+         ) 
258+       : undefined ; 
42259  } 
43260
44261  // selectNode gets called when the user single-clicks on an item 
0 commit comments