Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fstasi committed Jul 9, 2021
1 parent e10f0f1 commit af726db
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import { inject, injectable } from 'inversify';
import { inject, injectable, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { FileNode, FileTreeModel } from '@theia/filesystem/lib/browser';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { ConfigService } from '../../../common/protocol';
import { SketchbookTree } from './sketchbook-tree';
import { ArduinoPreferences } from '../../arduino-preferences';
import { SelectableTreeNode, TreeNode } from '@theia/core/lib/browser/tree';
import {
CompositeTreeNode,
ExpandableTreeNode,
SelectableTreeNode,
TreeNode,
} from '@theia/core/lib/browser/tree';
import { SketchbookCommands } from './sketchbook-commands';
import { OpenerService, open } from '@theia/core/lib/browser';
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { ProgressService } from '@theia/core/lib/common/progress-service';
import {
WorkspaceNode,
WorkspaceRootNode,
} from '@theia/navigator/lib/browser/navigator-tree';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { Disposable } from '@theia/core/lib/common/disposable';

@injectable()
export class SketchbookTreeModel extends FileTreeModel {
Expand All @@ -31,14 +45,209 @@ export class SketchbookTreeModel extends FileTreeModel {
@inject(SketchesServiceClientImpl)
protected readonly sketchServiceClient: SketchesServiceClientImpl;

async updateRoot(): Promise<void> {
const config = await this.configService.getConfiguration();
const fileStat = await this.fileService.resolve(
new URI(config.sketchDirUri)
@inject(SketchbookTree) protected readonly tree: SketchbookTree;
@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;
@inject(FrontendApplicationStateService)
protected readonly applicationState: FrontendApplicationStateService;

@inject(ProgressService)
protected readonly progressService: ProgressService;

@postConstruct()
protected init(): void {
super.init();
this.reportBusyProgress();
this.initializeRoot();
}

protected readonly pendingBusyProgress = new Map<string, Deferred<void>>();
protected reportBusyProgress(): void {
this.toDispose.push(
this.onDidChangeBusy((node) => {
const pending = this.pendingBusyProgress.get(node.id);
if (pending) {
if (!node.busy) {
pending.resolve();
this.pendingBusyProgress.delete(node.id);
}
return;
}
if (node.busy) {
const progress = new Deferred<void>();
this.pendingBusyProgress.set(node.id, progress);
this.progressService.withProgress(
'',
'explorer',
() => progress.promise
);
}
})
);
this.toDispose.push(
Disposable.create(() => {
for (const pending of this.pendingBusyProgress.values()) {
pending.resolve();
}
this.pendingBusyProgress.clear();
})
);
const showAllFiles =
this.arduinoPreferences['arduino.sketchbook.showAllFiles'];
this.tree.root = SketchbookTree.RootNode.create(fileStat, showAllFiles);
}

protected async initializeRoot(): Promise<void> {
await Promise.all([
this.applicationState.reachedState('initialized_layout'),
this.workspaceService.roots,
]);
await this.updateRoot();
if (this.toDispose.disposed) {
return;
}
this.toDispose.push(
this.workspaceService.onWorkspaceChanged(() => this.updateRoot())
);
this.toDispose.push(
this.workspaceService.onWorkspaceLocationChanged(() => this.updateRoot())
);
if (this.selectedNodes.length) {
return;
}
const root = this.root;
if (CompositeTreeNode.is(root) && root.children.length === 1) {
const child = root.children[0];
if (
SelectableTreeNode.is(child) &&
!child.selected &&
ExpandableTreeNode.is(child)
) {
this.selectNode(child);
this.expandNode(child);
}
}
}

previewNode(node: TreeNode): void {
if (FileNode.is(node)) {
open(this.openerService, node.uri, {
mode: 'reveal',
preview: true,
});
}
}

*getNodesByUri(uri: URI): IterableIterator<TreeNode> {
const workspace = this.root;
if (WorkspaceNode.is(workspace)) {
for (const root of workspace.children) {
const id = this.tree.createId(root, uri);
const node = this.getNode(id);
if (node) {
yield node;
}
}
}
}

public async updateRoot(): Promise<void> {
this.root = await this.createRoot();
}

protected async createRoot(): Promise<TreeNode | undefined> {
const config = await this.configService.getConfiguration();
const stat = await this.fileService.resolve(new URI(config.sketchDirUri));

if (this.workspaceService.opened) {
const isMulti = stat ? !stat.isDirectory : false;
const workspaceNode = isMulti
? this.createMultipleRootNode()
: WorkspaceNode.createRoot();
workspaceNode.children.push(
await this.tree.createWorkspaceRoot(stat, workspaceNode)
);

return workspaceNode;
}
}

/**
* Create multiple root node used to display
* the multiple root workspace name.
*
* @returns `WorkspaceNode`
*/
protected createMultipleRootNode(): WorkspaceNode {
const workspace = this.workspaceService.workspace;
let name = workspace ? workspace.resource.path.name : 'untitled';
name += ' (Workspace)';
return WorkspaceNode.createRoot(name);
}

/**
* Move the given source file or directory to the given target directory.
*/
async move(source: TreeNode, target: TreeNode): Promise<URI | undefined> {
if (source.parent && WorkspaceRootNode.is(source)) {
// do not support moving a root folder
return undefined;
}
return super.move(source, target);
}

/**
* Reveals node in the navigator by given file uri.
*
* @param uri uri to file which should be revealed in the navigator
* @returns file tree node if the file with given uri was revealed, undefined otherwise
*/
async revealFile(uri: URI): Promise<TreeNode | undefined> {
if (!uri.path.isAbsolute) {
return undefined;
}
let node = this.getNodeClosestToRootByUri(uri);

// success stop condition
// we have to reach workspace root because expanded node could be inside collapsed one
if (WorkspaceRootNode.is(node)) {
if (ExpandableTreeNode.is(node)) {
if (!node.expanded) {
node = await this.expandNode(node);
}
return node;
}
// shouldn't happen, root node is always directory, i.e. expandable
return undefined;
}

// fail stop condition
if (uri.path.isRoot) {
// file system root is reached but workspace root wasn't found, it means that
// given uri is not in workspace root folder or points to not existing file.
return undefined;
}

if (await this.revealFile(uri.parent)) {
if (node === undefined) {
// get node if it wasn't mounted into navigator tree before expansion
node = this.getNodeClosestToRootByUri(uri);
}
if (ExpandableTreeNode.is(node) && !node.expanded) {
node = await this.expandNode(node);
}
return node;
}
return undefined;
}

protected getNodeClosestToRootByUri(uri: URI): TreeNode | undefined {
const nodes = [...this.getNodesByUri(uri)];
return nodes.length > 0
? nodes.reduce(
(
node1,
node2 // return the node closest to the workspace root
) => (node1.id.length >= node2.id.length ? node1 : node2)
)
: undefined;
}

// selectNode gets called when the user single-clicks on an item
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,48 @@
import { inject, injectable } from 'inversify';
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
import { Command } from '@theia/core/lib/common/command';
import { TreeNode, CompositeTreeNode } from '@theia/core/lib/browser/tree';
import {
DirNode,
FileStatNode,
FileTree,
} from '@theia/filesystem/lib/browser/file-tree';
import { TreeNode } from '@theia/core/lib/browser/tree';
import { DirNode } from '@theia/filesystem/lib/browser/file-tree';
import { SketchesService } from '../../../common/protocol';
import { FileStat } from '@theia/filesystem/lib/common/files';
import { SketchbookCommands } from './sketchbook-commands';
import { FileNavigatorTree } from '@theia/navigator/lib/browser/navigator-tree';

@injectable()
export class SketchbookTree extends FileTree {
export class SketchbookTree extends FileNavigatorTree {
@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;

@inject(SketchesService)
protected readonly sketchesService: SketchesService;

async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
if (!FileStatNode.is(parent)) {
return super.resolveChildren(parent);
}
const { root } = this;
if (!root) {
return [];
}
if (!SketchbookTree.RootNode.is(root)) {
return [];
}
const children = (
await Promise.all(
(
await super.resolveChildren(parent)
).map((node) => this.maybeDecorateNode(node, root.showAllFiles))
)
).filter((node) => {
// filter out hidden nodes
if (DirNode.is(node) || FileStatNode.is(node)) {
return node.fileStat.name.indexOf('.') !== 0;
}
return true;
});
if (SketchbookTree.RootNode.is(parent)) {
return children
.filter(DirNode.is)
.filter(
(node) =>
['libraries', 'hardware'].indexOf(
this.labelProvider.getName(node)
) === -1
);
}
if (SketchbookTree.SketchDirNode.is(parent)) {
return children.filter(FileStatNode.is);
}
return children;
}
// async resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {

// if (!FileStatNode.is(parent)) {
// return super.resolveChildren(parent);
// }
// const { root } = this;
// if (!root) {
// return [];
// }
// if (!SketchbookTree.RootNode.is(root)) {
// return [];
// }
// const children = (await Promise.all((await super.resolveChildren(parent)).map(node => this.maybeDecorateNode(node, root.showAllFiles)))).filter(node => {
// // filter out hidden nodes
// if (DirNode.is(node) || FileStatNode.is(node)) {
// return node.fileStat.name.indexOf('.') !== 0
// }
// return true;
// });
// if (SketchbookTree.RootNode.is(parent)) {
// return children.filter(DirNode.is).filter(node => ['libraries', 'hardware'].indexOf(this.labelProvider.getName(node)) === -1);
// }
// if (SketchbookTree.SketchDirNode.is(parent)) {
// return children.filter(FileStatNode.is);
// }
// return children;
// }

protected async maybeDecorateNode(
node: TreeNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import { SketchbookCommands } from './sketchbook-commands';
import { WorkspaceService } from '../../theia/workspace/workspace-service';
import {
ContextMenuRenderer,
Navigatable,
RenderContextMenuOptions,
SelectableTreeNode,
Widget,
} from '@theia/core/lib/browser';
import {
Disposable,
Expand Down Expand Up @@ -76,6 +79,10 @@ export class SketchbookWidgetContribution
}

onStart(): void {
this.shell.currentChanged.connect(() =>
this.onCurrentWidgetChangedHandler()
);

this.arduinoPreferences.onPreferenceChanged(({ preferenceName }) => {
if (preferenceName === 'arduino.sketchbook.showAllFiles') {
this.mainMenuManager.update();
Expand Down Expand Up @@ -187,4 +194,30 @@ export class SketchbookWidgetContribution
order: '0',
});
}

/**
* Reveals and selects node in the file navigator to which given widget is related.
* Does nothing if given widget undefined or doesn't have related resource.
*
* @param widget widget file resource of which should be revealed and selected
*/
async selectWidgetFileNode(widget: Widget | undefined): Promise<void> {
if (Navigatable.is(widget)) {
const resourceUri = widget.getResourceUri();
if (resourceUri) {
const { model } = (await this.widget).getTreeWidget();
const node = await model.revealFile(resourceUri);
if (SelectableTreeNode.is(node)) {
model.selectNode(node);
}
}
}
}

protected onCurrentWidgetChangedHandler(): void {
debugger;
this.selectWidgetFileNode(this.shell.currentWidget);
// if (this.fileNavigatorPreferences['explorer.autoReveal']) {
// }
}
}
Loading

0 comments on commit af726db

Please sign in to comment.