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