Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c64ac48

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
ATL-1064: Support for nested sketchbook structure
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
1 parent ac50205 commit c64ac48

File tree

9 files changed

+188
-116
lines changed

9 files changed

+188
-116
lines changed

‎arduino-ide-extension/src/browser/contributions/examples.ts‎

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import * as PQueue from 'p-queue';
22
import { inject, injectable, postConstruct } from 'inversify';
3-
import { MenuPath, CompositeMenuNode } from '@theia/core/lib/common/menu';
3+
import { MenuPath, CompositeMenuNode,SubMenuOptions } from '@theia/core/lib/common/menu';
44
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
55
import { OpenSketch } from './open-sketch';
66
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
77
import { MainMenuManager } from '../../common/main-menu-manager';
88
import { BoardsServiceProvider } from '../boards/boards-service-provider';
9-
import { ExamplesService,ExampleContainer } from '../../common/protocol/examples-service';
9+
import { ExamplesService } from '../../common/protocol/examples-service';
1010
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
1111
import { NotificationCenter } from '../notification-center';
12-
import { Board } from '../../common/protocol';
12+
import { Board,Sketch,SketchContainer } from '../../common/protocol';
1313

1414
@injectable()
1515
export abstract class Examples extends SketchContribution {
@@ -59,18 +59,35 @@ export abstract class Examples extends SketchContribution {
5959
}
6060

6161
registerRecursively(
62-
exampleContainerOrPlaceholder: ExampleContainer | string,
62+
sketchContainerOrPlaceholder: SketchContainer|(Sketch|SketchContainer)[] | string,
6363
menuPath: MenuPath,
64-
pushToDispose: DisposableCollection = new DisposableCollection()): void {
64+
pushToDispose: DisposableCollection = new DisposableCollection(),
65+
subMenuOptions?: SubMenuOptions | undefined): void {
6566

66-
if (typeof exampleContainerOrPlaceholder === 'string') {
67-
const placeholder = new PlaceholderMenuNode(menuPath, exampleContainerOrPlaceholder);
67+
if (typeof sketchContainerOrPlaceholder === 'string') {
68+
const placeholder = new PlaceholderMenuNode(menuPath, sketchContainerOrPlaceholder);
6869
this.menuRegistry.registerMenuNode(menuPath, placeholder);
6970
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id)));
7071
} else {
71-
const { label, sketches, children } = exampleContainerOrPlaceholder;
72-
const submenuPath = [...menuPath, label];
73-
this.menuRegistry.registerSubmenu(submenuPath, label);
72+
const sketches: Sketch[] = [];
73+
const children: SketchContainer[] = [];
74+
let submenuPath = menuPath;
75+
76+
if (SketchContainer.is(sketchContainerOrPlaceholder)) {
77+
const { label } = sketchContainerOrPlaceholder;
78+
submenuPath = [...menuPath, label];
79+
this.menuRegistry.registerSubmenu(submenuPath, label, subMenuOptions);
80+
sketches.push(...sketchContainerOrPlaceholder.sketches);
81+
children.push(...sketchContainerOrPlaceholder.children);
82+
} else {
83+
for (const sketchOrContainer of sketchContainerOrPlaceholder) {
84+
if (SketchContainer.is(sketchOrContainer)) {
85+
children.push(sketchOrContainer);
86+
} else {
87+
sketches.push(sketchOrContainer);
88+
}
89+
}
90+
}
7491
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
7592
for (const sketch of sketches) {
7693
const { uri } = sketch;
@@ -98,22 +115,20 @@ export class BuiltInExamples extends Examples {
98115
this.register(); // no `await`
99116
}
100117

101-
protected async register() {
102-
let exampleContainers: ExampleContainer[] | undefined;
118+
protected async register(): Promise<void> {
119+
let sketchContainers: SketchContainer[] | undefined;
103120
try {
104-
exampleContainers = await this.examplesService.builtIns();
121+
sketchContainers = await this.examplesService.builtIns();
105122
} catch (e) {
106123
console.error('Could not initialize built-in examples.', e);
107124
this.messageService.error('Could not initialize built-in examples.');
108125
return;
109126
}
110127
this.toDispose.dispose();
111-
for (const container of ['Built-in examples', ...exampleContainers]) {
128+
for (const container of ['Built-in examples', ...sketchContainers]) {
112129
this.registerRecursively(container, ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, this.toDispose);
113130
}
114131
this.menuManager.update();
115-
// TODO: remove
116-
console.log(typeof this.menuRegistry);
117132
}
118133

119134
}
@@ -136,7 +151,7 @@ export class LibraryExamples extends Examples {
136151
this.register(board);
137152
}
138153

139-
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard) {
154+
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard): Promise<void> {
140155
return this.queue.add(async () => {
141156
this.toDispose.dispose();
142157
if (!board || !board.fqbn) {

‎arduino-ide-extension/src/browser/contributions/open-sketch.ts‎

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
88
import { SketchContribution, Sketch, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
99
import { ExamplesService } from '../../common/protocol/examples-service';
1010
import { BuiltInExamples } from './examples';
11+
import { Sketchbook } from './sketchbook';
12+
import { SketchContainer } from '../../common/protocol';
1113

1214
@injectable()
1315
export class OpenSketch extends SketchContribution {
@@ -24,7 +26,10 @@ export class OpenSketch extends SketchContribution {
2426
@inject(ExamplesService)
2527
protected readonly examplesService: ExamplesService;
2628

27-
protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection();
29+
@inject(Sketchbook)
30+
protected readonly sketchbook: Sketchbook;
31+
32+
protected readonly toDispose = new DisposableCollection();
2833

2934
registerCommands(registry: CommandRegistry): void {
3035
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, {
@@ -33,11 +38,11 @@ export class OpenSketch extends SketchContribution {
3338
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH__TOOLBAR, {
3439
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
3540
execute: async (_: Widget, target: EventTarget) => {
36-
const sketches = await this.sketchService.getSketches();
37-
if (!sketches.length) {
41+
const container = await this.sketchService.getSketches({exclude: ['**/hardware/**']});
42+
if (SketchContainer.isEmpty(container)) {
3843
this.openSketch();
3944
} else {
40-
this.toDisposeBeforeCreateNewContextMenu.dispose();
45+
this.toDispose.dispose();
4146
if (!(target instanceof HTMLElement)) {
4247
return;
4348
}
@@ -50,21 +55,12 @@ export class OpenSketch extends SketchContribution {
5055
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
5156
label: 'Open...'
5257
});
53-
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(OpenSketch.Commands.OPEN_SKETCH)));
54-
for (const sketch of sketches) {
55-
const command = { id: `arduino-open-sketch--${sketch.uri}` };
56-
const handler = { execute: () => this.openSketch(sketch) };
57-
this.toDisposeBeforeCreateNewContextMenu.push(registry.registerCommand(command, handler));
58-
this.menuRegistry.registerMenuAction(ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP, {
59-
commandId: command.id,
60-
label: sketch.name
61-
});
62-
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
63-
}
58+
this.toDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(OpenSketch.Commands.OPEN_SKETCH)));
59+
this.sketchbook.registerRecursively([...container.children, ...container.sketches], ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP, this.toDispose);
6460
try {
6561
const containers = await this.examplesService.builtIns();
6662
for (const container of containers) {
67-
this.builtInExamples.registerRecursively(container, ArduinoMenus.OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP, this.toDisposeBeforeCreateNewContextMenu);
63+
this.builtInExamples.registerRecursively(container, ArduinoMenus.OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP, this.toDispose);
6864
}
6965
} catch (e) {
7066
console.error('Error when collecting built-in examples.', e);
Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { inject, injectable } from 'inversify';
2-
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
3-
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
2+
import { CommandRegistry, MenuModelRegistry } from './contribution';
43
import { ArduinoMenus } from '../menu/arduino-menus';
54
import { MainMenuManager } from '../../common/main-menu-manager';
65
import { NotificationCenter } from '../notification-center';
7-
import { OpenSketch } from './open-sketch';
6+
import { Examples } from './examples';
7+
import { SketchContainer } from '../../common/protocol';
88

99
@injectable()
10-
export class Sketchbook extends SketchContribution {
10+
export class Sketchbook extends Examples {
1111

1212
@inject(CommandRegistry)
1313
protected readonly commandRegistry: CommandRegistry;
@@ -21,49 +21,26 @@ export class Sketchbook extends SketchContribution {
2121
@inject(NotificationCenter)
2222
protected readonly notificationCenter: NotificationCenter;
2323

24-
protected toDisposePerSketch = new Map<string, DisposableCollection>();
25-
2624
onStart(): void {
27-
this.sketchService.getSketches().then(sketches => {
28-
this.register(sketches);
25+
this.sketchService.getSketches({}).then(container => {
26+
this.register(container);
2927
this.mainMenuManager.update();
3028
});
31-
this.sketchServiceClient.onSketchbookDidChange(({ created, removed }) => {
32-
this.unregister(removed);
33-
this.register(created);
34-
this.mainMenuManager.update();
29+
this.sketchServiceClient.onSketchbookDidChange(() => {
30+
this.sketchService.getSketches({}).then(container => {
31+
this.register(container);
32+
this.mainMenuManager.update();
33+
});
3534
});
3635
}
3736

3837
registerMenus(registry: MenuModelRegistry): void {
3938
registry.registerSubmenu(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, 'Sketchbook', { order: '3' });
4039
}
4140

42-
protected register(sketches: Sketch[]): void {
43-
for (const sketch of sketches) {
44-
const { uri } = sketch;
45-
const toDispose = this.toDisposePerSketch.get(uri);
46-
if (toDispose) {
47-
toDispose.dispose();
48-
}
49-
const command = { id: `arduino-sketchbook-open--${uri}` };
50-
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
51-
this.commandRegistry.registerCommand(command, handler);
52-
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, { commandId: command.id, label: sketch.name });
53-
this.toDisposePerSketch.set(sketch.uri, new DisposableCollection(
54-
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
55-
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
56-
));
57-
}
58-
}
59-
60-
protected unregister(sketches: Sketch[]): void {
61-
for (const { uri } of sketches) {
62-
const toDispose = this.toDisposePerSketch.get(uri);
63-
if (toDispose) {
64-
toDispose.dispose();
65-
}
66-
}
41+
protected register(container: SketchContainer): void {
42+
this.toDispose.dispose();
43+
this.registerRecursively([...container.children, ...container.sketches], ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, this.toDispose);
6744
}
6845

6946
}

‎arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { FrontendApplication } from '@theia/core/lib/browser/frontend-applicatio
88
import { FocusTracker, Widget } from '@theia/core/lib/browser';
99
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
1010
import { ConfigService } from '../../../common/protocol/config-service';
11-
import { SketchesService, Sketch } from '../../../common/protocol/sketches-service';
11+
import { SketchesService, Sketch,SketchContainer } from '../../../common/protocol/sketches-service';
1212
import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver';
1313

1414
@injectable()
@@ -50,7 +50,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
5050
const hash = window.location.hash;
5151
const [recentWorkspaces, recentSketches] = await Promise.all([
5252
this.server.getRecentWorkspaces(),
53-
this.sketchService.getSketches().then(sketches => sketches.map(s => s.uri))
53+
this.sketchService.getSketches({}).then(container => SketchContainer.toArray(container).map(s => s.uri))
5454
]);
5555
const toOpen = await new ArduinoWorkspaceRootResolver({
5656
isValid: this.isValid.bind(this)
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
import { Sketch } from './sketches-service';
1+
import { SketchContainer } from './sketches-service';
22

33
export const ExamplesServicePath = '/services/example-service';
44
export const ExamplesService = Symbol('ExamplesService');
55
export interface ExamplesService {
6-
builtIns(): Promise<ExampleContainer[]>;
7-
installed(options: { fqbn: string }): Promise<{ user: ExampleContainer[], current: ExampleContainer[], any: ExampleContainer[] }>;
6+
builtIns(): Promise<SketchContainer[]>;
7+
installed(options: { fqbn: string }): Promise<{ user: SketchContainer[], current: SketchContainer[], any: SketchContainer[] }>;
88
}
99

10-
export interface ExampleContainer {
11-
readonly label: string;
12-
readonly children: ExampleContainer[];
13-
readonly sketches: Sketch[];
14-
}
10+

‎arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser';
99
import { ConfigService } from './config-service';
1010
import { DisposableCollection, Emitter } from '@theia/core';
1111
import { FileChangeType } from '@theia/filesystem/lib/browser';
12+
import { SketchContainer } from './sketches-service';
1213

1314
@injectable()
1415
export class SketchesServiceClientImpl implements FrontendApplicationContribution {
@@ -35,9 +36,9 @@ export class SketchesServiceClientImpl implements FrontendApplicationContributio
3536

3637
onStart(): void {
3738
this.configService.getConfiguration().then(({ sketchDirUri }) => {
38-
this.sketchService.getSketches(sketchDirUri).then(sketches => {
39+
this.sketchService.getSketches({uri: sketchDirUri}).then(container => {
3940
const sketchbookUri = new URI(sketchDirUri);
40-
for (const sketch of sketches) {
41+
for (const sketch of SketchContainer.toArray(container)) {
4142
this.sketches.set(sketch.uri, sketch);
4243
}
4344
this.toDispose.push(this.fileService.watch(new URI(sketchDirUri), { recursive: true, excludes: [] }));

‎arduino-ide-extension/src/common/protocol/sketches-service.ts‎

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ export const SketchesService = Symbol('SketchesService');
55
export interface SketchesService {
66

77
/**
8-
* Returns with the direct sketch folders from the location of the `fileStat`.
9-
* The sketches returns with inverse-chronological order, the first item is the most recent one.
8+
* Resolves to a sketch container representing the hierarchical structure of the sketches.
9+
* If `uri` is not given, `directories.user` will be user instead. Specify `exclude` global patterns to filter folders from the sketch container.
10+
* If `exclude` is not set `['**\/libraries\/**', '**\/hardware\/**']` will be used instead.
1011
*/
11-
getSketches(uri?: string): Promise<Sketch[]>;
12+
getSketches({uri, exclude }: {uri?: string,exclude?: string[]}): Promise<SketchContainer>;
1213

1314
/**
1415
* 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 {
100101
return Extensions.MAIN.some(ext => arg.endsWith(ext));
101102
}
102103
}
104+
105+
export interface SketchContainer {
106+
readonly label: string;
107+
readonly children: SketchContainer[];
108+
readonly sketches: Sketch[];
109+
}
110+
export namespace SketchContainer {
111+
112+
export function is(arg: any): arg is SketchContainer {
113+
return !!arg
114+
&& 'label' in arg && typeof arg.label === 'string'
115+
&& 'children' in arg && Array.isArray(arg.children)
116+
&& 'sketches' in arg && Array.isArray(arg.sketches);
117+
}
118+
119+
/**
120+
* `false` if the `container` recursively contains at least one sketch. Otherwise, `true`.
121+
*/
122+
export function isEmpty(container: SketchContainer): boolean {
123+
const hasSketch = (parent: SketchContainer) => {
124+
if (parent.sketches.length || parent.children.some(child => hasSketch(child))) {
125+
return true;
126+
}
127+
return false;
128+
}
129+
return !hasSketch(container);
130+
}
131+
132+
export function prune<T extends SketchContainer>(container: T): T {
133+
for (let i = container.children.length - 1; i >= 0; i--) {
134+
if (isEmpty(container.children[i])) {
135+
container.children.splice(i, 1);
136+
}
137+
}
138+
return container;
139+
}
140+
141+
export function toArray(container: SketchContainer): Sketch[] {
142+
const visit = (parent: SketchContainer, toPushSketch: Sketch[]) => {
143+
toPushSketch.push(...parent.sketches);
144+
parent.children.map(child => visit(child, toPushSketch));
145+
}
146+
const sketches: Sketch[] = [];
147+
visit(container, sketches);
148+
return sketches;
149+
}
150+
151+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /