1
- import { inject , injectable } from 'inversify' ;
1
+ import { inject , injectable , postConstruct } from 'inversify' ;
2
2
import URI from '@theia/core/lib/common/uri' ;
3
3
import { FileNode , FileTreeModel } from '@theia/filesystem/lib/browser' ;
4
4
import { FileService } from '@theia/filesystem/lib/browser/file-service' ;
5
5
import { ConfigService } from '../../../common/protocol' ;
6
6
import { SketchbookTree } from './sketchbook-tree' ;
7
7
import { 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' ;
9
14
import { SketchbookCommands } from './sketchbook-commands' ;
10
15
import { OpenerService , open } from '@theia/core/lib/browser' ;
11
16
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl' ;
12
17
import { 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' ;
13
27
14
28
@injectable ( )
15
29
export class SketchbookTreeModel extends FileTreeModel {
@@ -31,14 +45,210 @@ export class SketchbookTreeModel extends FileTreeModel {
31
45
@inject ( SketchesServiceClientImpl )
32
46
protected readonly sketchServiceClient : SketchesServiceClientImpl ;
33
47
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
+ } )
38
94
) ;
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 ;
42
252
}
43
253
44
254
// selectNode gets called when the user single-clicks on an item
0 commit comments