Skip to content

Commit af726db

Browse files
committed
wip
1 parent e10f0f1 commit af726db

File tree

4 files changed

+286
-55
lines changed

4 files changed

+286
-55
lines changed

arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-model.ts

+218-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1-
import { inject, injectable } from 'inversify';
1+
import { inject, injectable, postConstruct } from 'inversify';
22
import URI from '@theia/core/lib/common/uri';
33
import { FileNode, FileTreeModel } from '@theia/filesystem/lib/browser';
44
import { FileService } from '@theia/filesystem/lib/browser/file-service';
55
import { ConfigService } from '../../../common/protocol';
66
import { SketchbookTree } from './sketchbook-tree';
77
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';
914
import { SketchbookCommands } from './sketchbook-commands';
1015
import { OpenerService, open } from '@theia/core/lib/browser';
1116
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
1217
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';
1327

1428
@injectable()
1529
export 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

arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts

+31-46
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,48 @@
11
import { inject, injectable } from 'inversify';
22
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
33
import { Command } from '@theia/core/lib/common/command';
4-
import { TreeNode, CompositeTreeNode } from '@theia/core/lib/browser/tree';
5-
import {
6-
DirNode,
7-
FileStatNode,
8-
FileTree,
9-
} from '@theia/filesystem/lib/browser/file-tree';
4+
import { TreeNode } from '@theia/core/lib/browser/tree';
5+
import { DirNode } from '@theia/filesystem/lib/browser/file-tree';
106
import { SketchesService } from '../../../common/protocol';
117
import { FileStat } from '@theia/filesystem/lib/common/files';
128
import { SketchbookCommands } from './sketchbook-commands';
9+
import { FileNavigatorTree } from '@theia/navigator/lib/browser/navigator-tree';
1310

1411
@injectable()
15-
export class SketchbookTree extends FileTree {
12+
export class SketchbookTree extends FileNavigatorTree {
1613
@inject(LabelProvider)
1714
protected readonly labelProvider: LabelProvider;
1815

1916
@inject(SketchesService)
2017
protected readonly sketchesService: SketchesService;
2118

22-
async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
23-
if (!FileStatNode.is(parent)) {
24-
return super.resolveChildren(parent);
25-
}
26-
const { root } = this;
27-
if (!root) {
28-
return [];
29-
}
30-
if (!SketchbookTree.RootNode.is(root)) {
31-
return [];
32-
}
33-
const children = (
34-
await Promise.all(
35-
(
36-
await super.resolveChildren(parent)
37-
).map((node) => this.maybeDecorateNode(node, root.showAllFiles))
38-
)
39-
).filter((node) => {
40-
// filter out hidden nodes
41-
if (DirNode.is(node) || FileStatNode.is(node)) {
42-
return node.fileStat.name.indexOf('.') !== 0;
43-
}
44-
return true;
45-
});
46-
if (SketchbookTree.RootNode.is(parent)) {
47-
return children
48-
.filter(DirNode.is)
49-
.filter(
50-
(node) =>
51-
['libraries', 'hardware'].indexOf(
52-
this.labelProvider.getName(node)
53-
) === -1
54-
);
55-
}
56-
if (SketchbookTree.SketchDirNode.is(parent)) {
57-
return children.filter(FileStatNode.is);
58-
}
59-
return children;
60-
}
19+
// async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
20+
21+
// if (!FileStatNode.is(parent)) {
22+
// return super.resolveChildren(parent);
23+
// }
24+
// const { root } = this;
25+
// if (!root) {
26+
// return [];
27+
// }
28+
// if (!SketchbookTree.RootNode.is(root)) {
29+
// return [];
30+
// }
31+
// const children = (await Promise.all((await super.resolveChildren(parent)).map(node => this.maybeDecorateNode(node, root.showAllFiles)))).filter(node => {
32+
// // filter out hidden nodes
33+
// if (DirNode.is(node) || FileStatNode.is(node)) {
34+
// return node.fileStat.name.indexOf('.') !== 0
35+
// }
36+
// return true;
37+
// });
38+
// if (SketchbookTree.RootNode.is(parent)) {
39+
// return children.filter(DirNode.is).filter(node => ['libraries', 'hardware'].indexOf(this.labelProvider.getName(node)) === -1);
40+
// }
41+
// if (SketchbookTree.SketchDirNode.is(parent)) {
42+
// return children.filter(FileStatNode.is);
43+
// }
44+
// return children;
45+
// }
6146

6247
protected async maybeDecorateNode(
6348
node: TreeNode,

arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts

+33
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import { SketchbookCommands } from './sketchbook-commands';
1414
import { WorkspaceService } from '../../theia/workspace/workspace-service';
1515
import {
1616
ContextMenuRenderer,
17+
Navigatable,
1718
RenderContextMenuOptions,
19+
SelectableTreeNode,
20+
Widget,
1821
} from '@theia/core/lib/browser';
1922
import {
2023
Disposable,
@@ -76,6 +79,10 @@ export class SketchbookWidgetContribution
7679
}
7780

7881
onStart(): void {
82+
this.shell.currentChanged.connect(() =>
83+
this.onCurrentWidgetChangedHandler()
84+
);
85+
7986
this.arduinoPreferences.onPreferenceChanged(({ preferenceName }) => {
8087
if (preferenceName === 'arduino.sketchbook.showAllFiles') {
8188
this.mainMenuManager.update();
@@ -187,4 +194,30 @@ export class SketchbookWidgetContribution
187194
order: '0',
188195
});
189196
}
197+
198+
/**
199+
* Reveals and selects node in the file navigator to which given widget is related.
200+
* Does nothing if given widget undefined or doesn't have related resource.
201+
*
202+
* @param widget widget file resource of which should be revealed and selected
203+
*/
204+
async selectWidgetFileNode(widget: Widget | undefined): Promise<void> {
205+
if (Navigatable.is(widget)) {
206+
const resourceUri = widget.getResourceUri();
207+
if (resourceUri) {
208+
const { model } = (await this.widget).getTreeWidget();
209+
const node = await model.revealFile(resourceUri);
210+
if (SelectableTreeNode.is(node)) {
211+
model.selectNode(node);
212+
}
213+
}
214+
}
215+
}
216+
217+
protected onCurrentWidgetChangedHandler(): void {
218+
debugger;
219+
this.selectWidgetFileNode(this.shell.currentWidget);
220+
// if (this.fileNavigatorPreferences['explorer.autoReveal']) {
221+
// }
222+
}
190223
}

0 commit comments

Comments
 (0)