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,210 @@ 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+ } )
3894 ) ;
39- const showAllFiles =
40- this . arduinoPreferences [ 'arduino.sketchbook.showAllFiles' ] ;
41- this . tree . root = SketchbookTree . RootNode . create ( fileStat , showAllFiles ) ;
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 ( ) )
108+ ) ;
109+ this . toDispose . push (
110+ this . workspaceService . onWorkspaceLocationChanged ( ( ) => this . updateRoot ( ) )
111+ ) ;
112+ if ( this . selectedNodes . length ) {
113+ return ;
114+ }
115+ const root = this . root ;
116+ if ( CompositeTreeNode . is ( root ) && root . children . length === 1 ) {
117+ const child = root . children [ 0 ] ;
118+ if (
119+ SelectableTreeNode . is ( child ) &&
120+ ! child . selected &&
121+ ExpandableTreeNode . is ( child )
122+ ) {
123+ this . selectNode ( child ) ;
124+ this . expandNode ( child ) ;
125+ }
126+ }
127+ }
128+
129+ previewNode ( node : TreeNode ) : void {
130+ if ( FileNode . is ( node ) ) {
131+ open ( this . openerService , node . uri , {
132+ mode : 'reveal' ,
133+ preview : true ,
134+ } ) ;
135+ }
136+ }
137+
138+ * getNodesByUri ( uri : URI ) : IterableIterator < TreeNode > {
139+ const workspace = this . root ;
140+ if ( WorkspaceNode . is ( workspace ) ) {
141+ for ( const root of workspace . children ) {
142+ const id = this . tree . createId ( root , uri ) ;
143+ const node = this . getNode ( id ) ;
144+ if ( node ) {
145+ yield node ;
146+ }
147+ }
148+ }
149+ }
150+
151+ public async updateRoot ( ) : Promise < void > {
152+ this . root = await this . createRoot ( ) ;
153+ }
154+
155+ protected async createRoot ( ) : Promise < TreeNode | undefined > {
156+ const config = await this . configService . getConfiguration ( ) ;
157+ const stat = await this . fileService . resolve ( new URI ( config . sketchDirUri ) ) ;
158+
159+ if ( this . workspaceService . opened ) {
160+ const isMulti = stat ? ! stat . isDirectory : false ;
161+ const workspaceNode = isMulti
162+ ? this . createMultipleRootNode ( )
163+ : WorkspaceNode . createRoot ( ) ;
164+
165+ workspaceNode . children . push (
166+ await this . tree . createWorkspaceRoot ( stat , workspaceNode )
167+ ) ;
168+
169+ return workspaceNode ;
170+ }
171+ }
172+
173+ /**
174+ * Create multiple root node used to display
175+ * the multiple root workspace name.
176+ *
177+ * @returns `WorkspaceNode`
178+ */
179+ protected createMultipleRootNode ( ) : WorkspaceNode {
180+ const workspace = this . workspaceService . workspace ;
181+ let name = workspace ? workspace . resource . path . name : 'untitled' ;
182+ name += ' (Workspace)' ;
183+ return WorkspaceNode . createRoot ( name ) ;
184+ }
185+
186+ /**
187+ * Move the given source file or directory to the given target directory.
188+ */
189+ async move ( source : TreeNode , target : TreeNode ) : Promise < URI | undefined > {
190+ if ( source . parent && WorkspaceRootNode . is ( source ) ) {
191+ // do not support moving a root folder
192+ return undefined ;
193+ }
194+ return super . move ( source , target ) ;
195+ }
196+
197+ /**
198+ * Reveals node in the navigator by given file uri.
199+ *
200+ * @param uri uri to file which should be revealed in the navigator
201+ * @returns file tree node if the file with given uri was revealed, undefined otherwise
202+ */
203+ async revealFile ( uri : URI ) : Promise < TreeNode | undefined > {
204+ if ( ! uri . path . isAbsolute ) {
205+ return undefined ;
206+ }
207+ let node = this . getNodeClosestToRootByUri ( uri ) ;
208+
209+ // success stop condition
210+ // we have to reach workspace root because expanded node could be inside collapsed one
211+ if ( WorkspaceRootNode . is ( node ) ) {
212+ if ( ExpandableTreeNode . is ( node ) ) {
213+ if ( ! node . expanded ) {
214+ node = await this . expandNode ( node ) ;
215+ }
216+ return node ;
217+ }
218+ // shouldn't happen, root node is always directory, i.e. expandable
219+ return undefined ;
220+ }
221+
222+ // fail stop condition
223+ if ( uri . path . isRoot ) {
224+ // file system root is reached but workspace root wasn't found, it means that
225+ // given uri is not in workspace root folder or points to not existing file.
226+ return undefined ;
227+ }
228+
229+ if ( await this . revealFile ( uri . parent ) ) {
230+ if ( node === undefined ) {
231+ // get node if it wasn't mounted into navigator tree before expansion
232+ node = this . getNodeClosestToRootByUri ( uri ) ;
233+ }
234+ if ( ExpandableTreeNode . is ( node ) && ! node . expanded ) {
235+ node = await this . expandNode ( node ) ;
236+ }
237+ return node ;
238+ }
239+ return undefined ;
240+ }
241+
242+ protected getNodeClosestToRootByUri ( uri : URI ) : TreeNode | undefined {
243+ const nodes = [ ...this . getNodesByUri ( uri ) ] ;
244+ return nodes . length > 0
245+ ? nodes . reduce (
246+ (
247+ node1 ,
248+ node2 // return the node closest to the workspace root
249+ ) => ( node1 . id . length >= node2 . id . length ? node1 : node2 )
250+ )
251+ : undefined ;
42252 }
43253
44254 // selectNode gets called when the user single-clicks on an item
0 commit comments