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,217 @@ 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
+ } )
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 ( ) )
38
108
) ;
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 ;
42
259
}
43
260
44
261
// selectNode gets called when the user single-clicks on an item
0 commit comments