mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-22 10:46:33 +00:00
ATL-1064: Support for nested sketchbook structure
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
ac502053d7
commit
c64ac48fe3
@ -1,15 +1,15 @@
|
||||
import * as PQueue from 'p-queue';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { MenuPath, CompositeMenuNode } from '@theia/core/lib/common/menu';
|
||||
import { MenuPath, CompositeMenuNode, SubMenuOptions } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ExamplesService, ExampleContainer } from '../../common/protocol/examples-service';
|
||||
import { ExamplesService } from '../../common/protocol/examples-service';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { Board } from '../../common/protocol';
|
||||
import { Board, Sketch, SketchContainer } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export abstract class Examples extends SketchContribution {
|
||||
@ -59,18 +59,35 @@ export abstract class Examples extends SketchContribution {
|
||||
}
|
||||
|
||||
registerRecursively(
|
||||
exampleContainerOrPlaceholder: ExampleContainer | string,
|
||||
sketchContainerOrPlaceholder: SketchContainer | (Sketch | SketchContainer)[] | string,
|
||||
menuPath: MenuPath,
|
||||
pushToDispose: DisposableCollection = new DisposableCollection()): void {
|
||||
pushToDispose: DisposableCollection = new DisposableCollection(),
|
||||
subMenuOptions?: SubMenuOptions | undefined): void {
|
||||
|
||||
if (typeof exampleContainerOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, exampleContainerOrPlaceholder);
|
||||
if (typeof sketchContainerOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, sketchContainerOrPlaceholder);
|
||||
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id)));
|
||||
} else {
|
||||
const { label, sketches, children } = exampleContainerOrPlaceholder;
|
||||
const submenuPath = [...menuPath, label];
|
||||
this.menuRegistry.registerSubmenu(submenuPath, label);
|
||||
const sketches: Sketch[] = [];
|
||||
const children: SketchContainer[] = [];
|
||||
let submenuPath = menuPath;
|
||||
|
||||
if (SketchContainer.is(sketchContainerOrPlaceholder)) {
|
||||
const { label } = sketchContainerOrPlaceholder;
|
||||
submenuPath = [...menuPath, label];
|
||||
this.menuRegistry.registerSubmenu(submenuPath, label, subMenuOptions);
|
||||
sketches.push(...sketchContainerOrPlaceholder.sketches);
|
||||
children.push(...sketchContainerOrPlaceholder.children);
|
||||
} else {
|
||||
for (const sketchOrContainer of sketchContainerOrPlaceholder) {
|
||||
if (SketchContainer.is(sketchOrContainer)) {
|
||||
children.push(sketchOrContainer);
|
||||
} else {
|
||||
sketches.push(sketchOrContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
@ -98,22 +115,20 @@ export class BuiltInExamples extends Examples {
|
||||
this.register(); // no `await`
|
||||
}
|
||||
|
||||
protected async register() {
|
||||
let exampleContainers: ExampleContainer[] | undefined;
|
||||
protected async register(): Promise<void> {
|
||||
let sketchContainers: SketchContainer[] | undefined;
|
||||
try {
|
||||
exampleContainers = await this.examplesService.builtIns();
|
||||
sketchContainers = await this.examplesService.builtIns();
|
||||
} catch (e) {
|
||||
console.error('Could not initialize built-in examples.', e);
|
||||
this.messageService.error('Could not initialize built-in examples.');
|
||||
return;
|
||||
}
|
||||
this.toDispose.dispose();
|
||||
for (const container of ['Built-in examples', ...exampleContainers]) {
|
||||
for (const container of ['Built-in examples', ...sketchContainers]) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, this.toDispose);
|
||||
}
|
||||
this.menuManager.update();
|
||||
// TODO: remove
|
||||
console.log(typeof this.menuRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
@ -136,7 +151,7 @@ export class LibraryExamples extends Examples {
|
||||
this.register(board);
|
||||
}
|
||||
|
||||
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard) {
|
||||
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard): Promise<void> {
|
||||
return this.queue.add(async () => {
|
||||
this.toDispose.dispose();
|
||||
if (!board || !board.fqbn) {
|
||||
|
@ -8,6 +8,8 @@ import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { SketchContribution, Sketch, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
import { ExamplesService } from '../../common/protocol/examples-service';
|
||||
import { BuiltInExamples } from './examples';
|
||||
import { Sketchbook } from './sketchbook';
|
||||
import { SketchContainer } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class OpenSketch extends SketchContribution {
|
||||
@ -24,7 +26,10 @@ export class OpenSketch extends SketchContribution {
|
||||
@inject(ExamplesService)
|
||||
protected readonly examplesService: ExamplesService;
|
||||
|
||||
protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection();
|
||||
@inject(Sketchbook)
|
||||
protected readonly sketchbook: Sketchbook;
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, {
|
||||
@ -33,11 +38,11 @@ export class OpenSketch extends SketchContribution {
|
||||
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH__TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: async (_: Widget, target: EventTarget) => {
|
||||
const sketches = await this.sketchService.getSketches();
|
||||
if (!sketches.length) {
|
||||
const container = await this.sketchService.getSketches({ exclude: ['**/hardware/**'] });
|
||||
if (SketchContainer.isEmpty(container)) {
|
||||
this.openSketch();
|
||||
} else {
|
||||
this.toDisposeBeforeCreateNewContextMenu.dispose();
|
||||
this.toDispose.dispose();
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
@ -50,21 +55,12 @@ export class OpenSketch extends SketchContribution {
|
||||
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
label: 'Open...'
|
||||
});
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(OpenSketch.Commands.OPEN_SKETCH)));
|
||||
for (const sketch of sketches) {
|
||||
const command = { id: `arduino-open-sketch--${sketch.uri}` };
|
||||
const handler = { execute: () => this.openSketch(sketch) };
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(registry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP, {
|
||||
commandId: command.id,
|
||||
label: sketch.name
|
||||
});
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
}
|
||||
this.toDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(OpenSketch.Commands.OPEN_SKETCH)));
|
||||
this.sketchbook.registerRecursively([...container.children, ...container.sketches], ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP, this.toDispose);
|
||||
try {
|
||||
const containers = await this.examplesService.builtIns();
|
||||
for (const container of containers) {
|
||||
this.builtInExamples.registerRecursively(container, ArduinoMenus.OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP, this.toDisposeBeforeCreateNewContextMenu);
|
||||
this.builtInExamples.registerRecursively(container, ArduinoMenus.OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP, this.toDispose);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error when collecting built-in examples.', e);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
|
||||
import { CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
import { Examples } from './examples';
|
||||
import { SketchContainer } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class Sketchbook extends SketchContribution {
|
||||
export class Sketchbook extends Examples {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
@ -21,17 +21,16 @@ export class Sketchbook extends SketchContribution {
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
protected toDisposePerSketch = new Map<string, DisposableCollection>();
|
||||
|
||||
onStart(): void {
|
||||
this.sketchService.getSketches().then(sketches => {
|
||||
this.register(sketches);
|
||||
this.sketchService.getSketches({}).then(container => {
|
||||
this.register(container);
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
this.sketchServiceClient.onSketchbookDidChange(({ created, removed }) => {
|
||||
this.unregister(removed);
|
||||
this.register(created);
|
||||
this.mainMenuManager.update();
|
||||
this.sketchServiceClient.onSketchbookDidChange(() => {
|
||||
this.sketchService.getSketches({}).then(container => {
|
||||
this.register(container);
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -39,31 +38,9 @@ export class Sketchbook extends SketchContribution {
|
||||
registry.registerSubmenu(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, 'Sketchbook', { order: '3' });
|
||||
}
|
||||
|
||||
protected register(sketches: Sketch[]): void {
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const toDispose = this.toDisposePerSketch.get(uri);
|
||||
if (toDispose) {
|
||||
toDispose.dispose();
|
||||
}
|
||||
const command = { id: `arduino-sketchbook-open--${uri}` };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, { commandId: command.id, label: sketch.name });
|
||||
this.toDisposePerSketch.set(sketch.uri, new DisposableCollection(
|
||||
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected unregister(sketches: Sketch[]): void {
|
||||
for (const { uri } of sketches) {
|
||||
const toDispose = this.toDisposePerSketch.get(uri);
|
||||
if (toDispose) {
|
||||
toDispose.dispose();
|
||||
}
|
||||
}
|
||||
protected register(container: SketchContainer): void {
|
||||
this.toDispose.dispose();
|
||||
this.registerRecursively([...container.children, ...container.sketches], ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, this.toDispose);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { FrontendApplication } from '@theia/core/lib/browser/frontend-applicatio
|
||||
import { FocusTracker, Widget } from '@theia/core/lib/browser';
|
||||
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
import { SketchesService, Sketch } from '../../../common/protocol/sketches-service';
|
||||
import { SketchesService, Sketch, SketchContainer } from '../../../common/protocol/sketches-service';
|
||||
import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver';
|
||||
|
||||
@injectable()
|
||||
@ -50,7 +50,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
||||
const hash = window.location.hash;
|
||||
const [recentWorkspaces, recentSketches] = await Promise.all([
|
||||
this.server.getRecentWorkspaces(),
|
||||
this.sketchService.getSketches().then(sketches => sketches.map(s => s.uri))
|
||||
this.sketchService.getSketches({}).then(container => SketchContainer.toArray(container).map(s => s.uri))
|
||||
]);
|
||||
const toOpen = await new ArduinoWorkspaceRootResolver({
|
||||
isValid: this.isValid.bind(this)
|
||||
|
@ -1,14 +1,10 @@
|
||||
import { Sketch } from './sketches-service';
|
||||
import { SketchContainer } from './sketches-service';
|
||||
|
||||
export const ExamplesServicePath = '/services/example-service';
|
||||
export const ExamplesService = Symbol('ExamplesService');
|
||||
export interface ExamplesService {
|
||||
builtIns(): Promise<ExampleContainer[]>;
|
||||
installed(options: { fqbn: string }): Promise<{ user: ExampleContainer[], current: ExampleContainer[], any: ExampleContainer[] }>;
|
||||
builtIns(): Promise<SketchContainer[]>;
|
||||
installed(options: { fqbn: string }): Promise<{ user: SketchContainer[], current: SketchContainer[], any: SketchContainer[] }>;
|
||||
}
|
||||
|
||||
export interface ExampleContainer {
|
||||
readonly label: string;
|
||||
readonly children: ExampleContainer[];
|
||||
readonly sketches: Sketch[];
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { ConfigService } from './config-service';
|
||||
import { DisposableCollection, Emitter } from '@theia/core';
|
||||
import { FileChangeType } from '@theia/filesystem/lib/browser';
|
||||
import { SketchContainer } from './sketches-service';
|
||||
|
||||
@injectable()
|
||||
export class SketchesServiceClientImpl implements FrontendApplicationContribution {
|
||||
@ -35,9 +36,9 @@ export class SketchesServiceClientImpl implements FrontendApplicationContributio
|
||||
|
||||
onStart(): void {
|
||||
this.configService.getConfiguration().then(({ sketchDirUri }) => {
|
||||
this.sketchService.getSketches(sketchDirUri).then(sketches => {
|
||||
this.sketchService.getSketches({ uri: sketchDirUri }).then(container => {
|
||||
const sketchbookUri = new URI(sketchDirUri);
|
||||
for (const sketch of sketches) {
|
||||
for (const sketch of SketchContainer.toArray(container)) {
|
||||
this.sketches.set(sketch.uri, sketch);
|
||||
}
|
||||
this.toDispose.push(this.fileService.watch(new URI(sketchDirUri), { recursive: true, excludes: [] }));
|
||||
|
@ -5,10 +5,11 @@ export const SketchesService = Symbol('SketchesService');
|
||||
export interface SketchesService {
|
||||
|
||||
/**
|
||||
* Returns with the direct sketch folders from the location of the `fileStat`.
|
||||
* The sketches returns with inverse-chronological order, the first item is the most recent one.
|
||||
* Resolves to a sketch container representing the hierarchical structure of the sketches.
|
||||
* If `uri` is not given, `directories.user` will be user instead. Specify `exclude` global patterns to filter folders from the sketch container.
|
||||
* If `exclude` is not set `['**\/libraries\/**', '**\/hardware\/**']` will be used instead.
|
||||
*/
|
||||
getSketches(uri?: string): Promise<Sketch[]>;
|
||||
getSketches({ uri, exclude }: { uri?: string, exclude?: string[] }): Promise<SketchContainer>;
|
||||
|
||||
/**
|
||||
* This is the TS implementation of `SketchLoad` from the CLI and should be replaced with a gRPC call eventually.
|
||||
@ -100,3 +101,51 @@ export namespace Sketch {
|
||||
return Extensions.MAIN.some(ext => arg.endsWith(ext));
|
||||
}
|
||||
}
|
||||
|
||||
export interface SketchContainer {
|
||||
readonly label: string;
|
||||
readonly children: SketchContainer[];
|
||||
readonly sketches: Sketch[];
|
||||
}
|
||||
export namespace SketchContainer {
|
||||
|
||||
export function is(arg: any): arg is SketchContainer {
|
||||
return !!arg
|
||||
&& 'label' in arg && typeof arg.label === 'string'
|
||||
&& 'children' in arg && Array.isArray(arg.children)
|
||||
&& 'sketches' in arg && Array.isArray(arg.sketches);
|
||||
}
|
||||
|
||||
/**
|
||||
* `false` if the `container` recursively contains at least one sketch. Otherwise, `true`.
|
||||
*/
|
||||
export function isEmpty(container: SketchContainer): boolean {
|
||||
const hasSketch = (parent: SketchContainer) => {
|
||||
if (parent.sketches.length || parent.children.some(child => hasSketch(child))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return !hasSketch(container);
|
||||
}
|
||||
|
||||
export function prune<T extends SketchContainer>(container: T): T {
|
||||
for (let i = container.children.length - 1; i >= 0; i--) {
|
||||
if (isEmpty(container.children[i])) {
|
||||
container.children.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
export function toArray(container: SketchContainer): Sketch[] {
|
||||
const visit = (parent: SketchContainer, toPushSketch: Sketch[]) => {
|
||||
toPushSketch.push(...parent.sketches);
|
||||
parent.children.map(child => visit(child, toPushSketch));
|
||||
}
|
||||
const sketches: Sketch[] = [];
|
||||
visit(container, sketches);
|
||||
return sketches;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { Sketch } from '../common/protocol/sketches-service';
|
||||
import { Sketch, SketchContainer } from '../common/protocol/sketches-service';
|
||||
import { SketchesServiceImpl } from './sketches-service-impl';
|
||||
import { ExamplesService, ExampleContainer } from '../common/protocol/examples-service';
|
||||
import { ExamplesService } from '../common/protocol/examples-service';
|
||||
import { LibraryLocation, LibraryPackage, LibraryService } from '../common/protocol';
|
||||
import { ConfigServiceImpl } from './config-service-impl';
|
||||
|
||||
@ -22,14 +22,14 @@ export class ExamplesServiceImpl implements ExamplesService {
|
||||
@inject(ConfigServiceImpl)
|
||||
protected readonly configService: ConfigServiceImpl;
|
||||
|
||||
protected _all: ExampleContainer[] | undefined;
|
||||
protected _all: SketchContainer[] | undefined;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.builtIns();
|
||||
}
|
||||
|
||||
async builtIns(): Promise<ExampleContainer[]> {
|
||||
async builtIns(): Promise<SketchContainer[]> {
|
||||
if (this._all) {
|
||||
return this._all;
|
||||
}
|
||||
@ -40,10 +40,10 @@ export class ExamplesServiceImpl implements ExamplesService {
|
||||
}
|
||||
|
||||
// TODO: decide whether it makes sense to cache them. Keys should be: `fqbn` + version of containing core/library.
|
||||
async installed({ fqbn }: { fqbn: string }): Promise<{ user: ExampleContainer[], current: ExampleContainer[], any: ExampleContainer[] }> {
|
||||
const user: ExampleContainer[] = [];
|
||||
const current: ExampleContainer[] = [];
|
||||
const any: ExampleContainer[] = [];
|
||||
async installed({ fqbn }: { fqbn: string }): Promise<{ user: SketchContainer[], current: SketchContainer[], any: SketchContainer[] }> {
|
||||
const user: SketchContainer[] = [];
|
||||
const current: SketchContainer[] = [];
|
||||
const any: SketchContainer[] = [];
|
||||
if (fqbn) {
|
||||
const packages: LibraryPackage[] = await this.libraryService.list({ fqbn });
|
||||
for (const pkg of packages) {
|
||||
@ -66,7 +66,7 @@ export class ExamplesServiceImpl implements ExamplesService {
|
||||
* folder hierarchy. This method tries to workaround it by falling back to the `installDirUri` and manually creating the
|
||||
* location of the examples. Otherwise it creates the example container from the direct examples FS paths.
|
||||
*/
|
||||
protected async tryGroupExamples({ label, exampleUris, installDirUri }: LibraryPackage): Promise<ExampleContainer> {
|
||||
protected async tryGroupExamples({ label, exampleUris, installDirUri }: LibraryPackage): Promise<SketchContainer> {
|
||||
const paths = exampleUris.map(uri => FileUri.fsPath(uri));
|
||||
if (installDirUri) {
|
||||
for (const example of ['example', 'Example', 'EXAMPLE', 'examples', 'Examples', 'EXAMPLES']) {
|
||||
@ -75,7 +75,7 @@ export class ExamplesServiceImpl implements ExamplesService {
|
||||
const isDir = exists && (await promisify(fs.lstat)(examplesPath)).isDirectory();
|
||||
if (isDir) {
|
||||
const fileNames = await promisify(fs.readdir)(examplesPath);
|
||||
const children: ExampleContainer[] = [];
|
||||
const children: SketchContainer[] = [];
|
||||
const sketches: Sketch[] = [];
|
||||
for (const fileName of fileNames) {
|
||||
const subPath = join(examplesPath, fileName);
|
||||
@ -109,7 +109,7 @@ export class ExamplesServiceImpl implements ExamplesService {
|
||||
}
|
||||
|
||||
// Built-ins are included inside the IDE.
|
||||
protected async load(path: string): Promise<ExampleContainer> {
|
||||
protected async load(path: string): Promise<SketchContainer> {
|
||||
if (!await promisify(fs.exists)(path)) {
|
||||
throw new Error('Examples are not available');
|
||||
}
|
||||
@ -119,7 +119,7 @@ export class ExamplesServiceImpl implements ExamplesService {
|
||||
}
|
||||
const names = await promisify(fs.readdir)(path);
|
||||
const sketches: Sketch[] = [];
|
||||
const children: ExampleContainer[] = [];
|
||||
const children: SketchContainer[] = [];
|
||||
for (const p of names.map(name => join(path, name))) {
|
||||
const stat = await promisify(fs.stat)(p);
|
||||
if (stat.isDirectory()) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import * as minimatch from 'minimatch';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as temp from 'temp';
|
||||
@ -10,7 +11,7 @@ import URI from '@theia/core/lib/common/uri';
|
||||
import { FileUri } from '@theia/core/lib/node';
|
||||
import { isWindows } from '@theia/core/lib/common/os';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
import { SketchesService, Sketch } from '../common/protocol/sketches-service';
|
||||
import { SketchesService, Sketch, SketchContainer } from '../common/protocol/sketches-service';
|
||||
import { firstToLowerCase } from '../common/utils';
|
||||
import { NotificationServiceServerImpl } from './notification-service-server';
|
||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
@ -32,8 +33,8 @@ export class SketchesServiceImpl extends CoreClientAware implements SketchesServ
|
||||
|
||||
@inject(EnvVariablesServer)
|
||||
protected readonly envVariableServer: EnvVariablesServer;
|
||||
|
||||
async getSketches(uri?: string): Promise<SketchWithDetails[]> {
|
||||
async getSketches({ uri, exclude }: { uri?: string, exclude?: string[] }): Promise<SketchContainerWithDetails> {
|
||||
const start = Date.now();
|
||||
let sketchbookPath: undefined | string;
|
||||
if (!uri) {
|
||||
const { sketchDirUri } = await this.configService.getConfiguration();
|
||||
@ -44,33 +45,65 @@ export class SketchesServiceImpl extends CoreClientAware implements SketchesServ
|
||||
} else {
|
||||
sketchbookPath = FileUri.fsPath(uri);
|
||||
}
|
||||
const container: SketchContainerWithDetails = {
|
||||
label: uri ? path.basename(sketchbookPath) : 'Sketchbook',
|
||||
sketches: [],
|
||||
children: []
|
||||
};
|
||||
if (!await promisify(fs.exists)(sketchbookPath)) {
|
||||
return [];
|
||||
return container;
|
||||
}
|
||||
const stat = await promisify(fs.stat)(sketchbookPath);
|
||||
if (!stat.isDirectory()) {
|
||||
return [];
|
||||
return container;
|
||||
}
|
||||
|
||||
const sketches: Array<SketchWithDetails> = [];
|
||||
const filenames = await promisify(fs.readdir)(sketchbookPath);
|
||||
for (const fileName of filenames) {
|
||||
const filePath = path.join(sketchbookPath, fileName);
|
||||
const sketch = await this._isSketchFolder(FileUri.create(filePath).toString());
|
||||
if (sketch) {
|
||||
const recursivelyLoad = async (fsPath: string, containerToLoad: SketchContainerWithDetails) => {
|
||||
const filenames = await promisify(fs.readdir)(fsPath);
|
||||
for (const name of filenames) {
|
||||
const childFsPath = path.join(fsPath, name);
|
||||
let skip = false;
|
||||
for (const pattern of exclude || ['**/libraries/**', '**/hardware/**']) {
|
||||
if (!skip && minimatch(childFsPath, pattern)) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const stat = await promisify(fs.stat)(filePath);
|
||||
sketches.push({
|
||||
...sketch,
|
||||
mtimeMs: stat.mtimeMs
|
||||
});
|
||||
const stat = await promisify(fs.stat)(childFsPath);
|
||||
if (stat.isDirectory()) {
|
||||
const sketch = await this._isSketchFolder(FileUri.create(childFsPath).toString());
|
||||
if (sketch) {
|
||||
containerToLoad.sketches.push({
|
||||
...sketch,
|
||||
mtimeMs: stat.mtimeMs
|
||||
});
|
||||
} else {
|
||||
const childContainer: SketchContainerWithDetails = {
|
||||
label: name,
|
||||
children: [],
|
||||
sketches: []
|
||||
};
|
||||
await recursivelyLoad(childFsPath, childContainer);
|
||||
if (!SketchContainer.isEmpty(childContainer)) {
|
||||
containerToLoad.children.push(childContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.warn(`Could not load sketch from ${filePath}.`);
|
||||
console.warn(`Could not load sketch from ${childFsPath}.`);
|
||||
}
|
||||
}
|
||||
containerToLoad.sketches.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
||||
return containerToLoad;
|
||||
}
|
||||
sketches.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
||||
return sketches;
|
||||
|
||||
await recursivelyLoad(sketchbookPath, container);
|
||||
SketchContainer.prune(container);
|
||||
console.debug(`Loading the sketches from ${sketchbookPath} took ${Date.now() - start} ms.`);
|
||||
return container;
|
||||
}
|
||||
|
||||
async loadSketch(uri: string): Promise<SketchWithDetails> {
|
||||
@ -363,3 +396,8 @@ void loop() {
|
||||
interface SketchWithDetails extends Sketch {
|
||||
readonly mtimeMs: number;
|
||||
}
|
||||
interface SketchContainerWithDetails extends SketchContainer {
|
||||
readonly label: string;
|
||||
readonly children: SketchContainerWithDetails[];
|
||||
readonly sketches: SketchWithDetails[];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user