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,209 @@ 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+ workspaceNode . children . push (
165+ await this . tree . createWorkspaceRoot ( stat , workspaceNode )
166+ ) ;
167+
168+ return workspaceNode ;
169+ }
170+ }
171+
172+ /**
173+ * Create multiple root node used to display
174+ * the multiple root workspace name.
175+ *
176+ * @returns `WorkspaceNode`
177+ */
178+ protected createMultipleRootNode ( ) : WorkspaceNode {
179+ const workspace = this . workspaceService . workspace ;
180+ let name = workspace ? workspace . resource . path . name : 'untitled' ;
181+ name += ' (Workspace)' ;
182+ return WorkspaceNode . createRoot ( name ) ;
183+ }
184+
185+ /**
186+ * Move the given source file or directory to the given target directory.
187+ */
188+ async move ( source : TreeNode , target : TreeNode ) : Promise < URI | undefined > {
189+ if ( source . parent && WorkspaceRootNode . is ( source ) ) {
190+ // do not support moving a root folder
191+ return undefined ;
192+ }
193+ return super . move ( source , target ) ;
194+ }
195+
196+ /**
197+ * Reveals node in the navigator by given file uri.
198+ *
199+ * @param uri uri to file which should be revealed in the navigator
200+ * @returns file tree node if the file with given uri was revealed, undefined otherwise
201+ */
202+ async revealFile ( uri : URI ) : Promise < TreeNode | undefined > {
203+ if ( ! uri . path . isAbsolute ) {
204+ return undefined ;
205+ }
206+ let node = this . getNodeClosestToRootByUri ( uri ) ;
207+
208+ // success stop condition
209+ // we have to reach workspace root because expanded node could be inside collapsed one
210+ if ( WorkspaceRootNode . is ( node ) ) {
211+ if ( ExpandableTreeNode . is ( node ) ) {
212+ if ( ! node . expanded ) {
213+ node = await this . expandNode ( node ) ;
214+ }
215+ return node ;
216+ }
217+ // shouldn't happen, root node is always directory, i.e. expandable
218+ return undefined ;
219+ }
220+
221+ // fail stop condition
222+ if ( uri . path . isRoot ) {
223+ // file system root is reached but workspace root wasn't found, it means that
224+ // given uri is not in workspace root folder or points to not existing file.
225+ return undefined ;
226+ }
227+
228+ if ( await this . revealFile ( uri . parent ) ) {
229+ if ( node === undefined ) {
230+ // get node if it wasn't mounted into navigator tree before expansion
231+ node = this . getNodeClosestToRootByUri ( uri ) ;
232+ }
233+ if ( ExpandableTreeNode . is ( node ) && ! node . expanded ) {
234+ node = await this . expandNode ( node ) ;
235+ }
236+ return node ;
237+ }
238+ return undefined ;
239+ }
240+
241+ protected getNodeClosestToRootByUri ( uri : URI ) : TreeNode | undefined {
242+ const nodes = [ ...this . getNodesByUri ( uri ) ] ;
243+ return nodes . length > 0
244+ ? nodes . reduce (
245+ (
246+ node1 ,
247+ node2 // return the node closest to the workspace root
248+ ) => ( node1 . id . length >= node2 . id . length ? node1 : node2 )
249+ )
250+ : undefined ;
42251 }
43252
44253 // selectNode gets called when the user single-clicks on an item
0 commit comments