Skip to content

Commit 1c9fcd0

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
ATL-302: Added built-in examples to the app.
Signed-off-by: Akos Kitta <[email protected]>
1 parent b5d7c3b commit 1c9fcd0

27 files changed

+728
-101
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ node_modules/
44
lib/
55
downloads/
66
build/
7+
Examples/
78
!electron/build/
89
src-gen/
910
*webpack.config.js

arduino-ide-extension/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
"description": "An extension for Theia building the Arduino IDE",
55
"license": "MIT",
66
"scripts": {
7-
"prepare": "yarn download-cli && yarn download-ls && yarn run clean && yarn run build",
7+
"prepare": "yarn download-cli && yarn download-ls && yarn clean && yarn download-examples && yarn build",
88
"clean": "rimraf lib",
99
"download-cli": "node ./scripts/download-cli.js",
1010
"download-ls": "node ./scripts/download-ls.js",
11+
"download-examples": "node ./scripts/download-examples.js",
1112
"generate-protocol": "node ./scripts/generate-protocol.js",
1213
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
1314
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
@@ -99,7 +100,8 @@
99100
"lib",
100101
"src",
101102
"build",
102-
"data"
103+
"data",
104+
"examples"
103105
],
104106
"theiaExtensions": [
105107
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @ts-check
2+
3+
(async () => {
4+
5+
const os = require('os');
6+
const path = require('path');
7+
const shell = require('shelljs');
8+
const { v4 } = require('uuid');
9+
10+
const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`);
11+
if (shell.mkdir('-p', repository).code !== 0) {
12+
shell.exit(1);
13+
}
14+
15+
if (shell.exec(`git clone https://github.com/arduino/arduino.git --depth 1 ${repository}`).code !== 0) {
16+
shell.exit(1);
17+
}
18+
19+
const destination = path.join(__dirname, '..', 'Examples');
20+
shell.mkdir('-p', destination);
21+
shell.cp('-fR', path.join(repository, 'build', 'shared', 'examples', '*'), destination);
22+
23+
})();

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ArduinoLanguageClientContribution } from './language/arduino-language-c
1212
import { LibraryListWidget } from './library/library-list-widget';
1313
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
1414
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
15-
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
15+
import { LibraryServiceServer, LibraryServiceServerPath } from '../common/protocol/library-service';
1616
import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service';
1717
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
1818
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
@@ -118,6 +118,11 @@ import { EditorWidgetFactory } from './theia/editor/editor-widget-factory';
118118
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
119119
import { OutputWidget } from './theia/output/output-widget';
120120
import { BurnBootloader } from './contributions/burn-bootloader';
121+
import { ExamplesServicePath, ExamplesService } from '../common/protocol/examples-service';
122+
import { Examples } from './contributions/examples';
123+
import { LibraryServiceProvider } from './library/library-service-provider';
124+
import { IncludeLibrary } from './contributions/include-library';
125+
import { IncludeLibraryMenuUpdater } from './library/include-library-menu-updater';
121126

122127
const ElementQueries = require('css-element-queries/src/ElementQueries');
123128

@@ -151,7 +156,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
151156
bind(ListItemRenderer).toSelf().inSingletonScope();
152157

153158
// Library service
154-
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
159+
bind(LibraryServiceProvider).toSelf().inSingletonScope();
160+
bind(LibraryServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServiceServerPath)).inSingletonScope();
161+
bind(FrontendApplicationContribution).to(IncludeLibraryMenuUpdater).inSingletonScope();
162+
155163
// Library list widget
156164
bind(LibraryListWidget).toSelf();
157165
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
@@ -347,6 +355,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
347355
// File-system extension
348356
bind(FileSystemExt).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, FileSystemExtPath)).inSingletonScope();
349357

358+
// Examples service
359+
bind(ExamplesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ExamplesServicePath)).inSingletonScope();
360+
350361
Contribution.configure(bind, NewSketch);
351362
Contribution.configure(bind, OpenSketch);
352363
Contribution.configure(bind, CloseSketch);
@@ -360,4 +371,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
360371
Contribution.configure(bind, SketchControl);
361372
Contribution.configure(bind, Settings);
362373
Contribution.configure(bind, BurnBootloader);
374+
Contribution.configure(bind, Examples);
375+
Contribution.configure(bind, IncludeLibrary);
363376
});

arduino-ide-extension/src/browser/boards/boards-data-store.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ export class BoardsDataStore implements FrontendApplicationContribution {
2727
protected readonly onChangedEmitter = new Emitter<void>();
2828

2929
onStart(): void {
30-
this.boardsServiceClient.onBoardsPackageInstalled(async ({ pkg }) => {
31-
const { installedVersion: version } = pkg;
30+
this.boardsServiceClient.onBoardsPackageInstalled(async ({ item }) => {
31+
const { installedVersion: version } = item;
3232
if (!version) {
3333
return;
3434
}
3535
let shouldFireChanged = false;
36-
for (const fqbn of pkg.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
36+
for (const fqbn of item.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
3737
const key = this.getStorageKey(fqbn, version);
3838
let data = await this.storageService.getData<ConfigOption[] | undefined>(key);
3939
if (!data || !data.length) {

arduino-ide-extension/src/browser/boards/boards-service-client-impl.ts

+24-16
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
import { injectable, inject, optional } from 'inversify';
1+
import { injectable, inject } from 'inversify';
22
import { Emitter } from '@theia/core/lib/common/event';
33
import { ILogger } from '@theia/core/lib/common/logger';
44
import { MessageService } from '@theia/core/lib/common/message-service';
55
import { StorageService } from '@theia/core/lib/browser/storage-service';
66
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
77
import { RecursiveRequired } from '../../common/types';
8-
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent, BoardsService } from '../../common/protocol';
8+
import {
9+
Port,
10+
Board,
11+
BoardsService,
12+
BoardsPackage,
13+
InstalledEvent,
14+
UninstalledEvent,
15+
BoardsServiceClient,
16+
AttachedBoardsChangeEvent
17+
} from '../../common/protocol';
918
import { BoardsConfig } from './boards-config';
1019
import { naturalCompare } from '../../common/utils';
1120
import { compareAnything } from '../theia/monaco/comparers';
@@ -21,15 +30,14 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
2130
@inject(ILogger)
2231
protected logger: ILogger;
2332

24-
@optional()
2533
@inject(MessageService)
2634
protected messageService: MessageService;
2735

2836
@inject(StorageService)
2937
protected storageService: StorageService;
3038

31-
protected readonly onBoardsPackageInstalledEmitter = new Emitter<BoardInstalledEvent>();
32-
protected readonly onBoardsPackageUninstalledEmitter = new Emitter<BoardUninstalledEvent>();
39+
protected readonly onBoardsPackageInstalledEmitter = new Emitter<InstalledEvent<BoardsPackage>>();
40+
protected readonly onBoardsPackageUninstalledEmitter = new Emitter<UninstalledEvent<BoardsPackage>>();
3341
protected readonly onAttachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
3442
protected readonly onBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
3543
protected readonly onAvailableBoardsChangedEmitter = new Emitter<AvailableBoard[]>();
@@ -119,13 +127,13 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
119127
return false;
120128
}
121129

122-
notifyBoardInstalled(event: BoardInstalledEvent): void {
123-
this.logger.info('Board installed: ', JSON.stringify(event));
130+
notifyInstalled(event: InstalledEvent<BoardsPackage>): void {
131+
this.logger.info('Boards package installed: ', JSON.stringify(event));
124132
this.onBoardsPackageInstalledEmitter.fire(event);
125133
const { selectedBoard } = this.boardsConfig;
126-
const { installedVersion, id } = event.pkg;
134+
const { installedVersion, id } = event.item;
127135
if (selectedBoard) {
128-
const installedBoard = event.pkg.boards.find(({ name }) => name === selectedBoard.name);
136+
const installedBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
129137
if (installedBoard && (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn)) {
130138
this.logger.info(`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`);
131139
this.boardsConfig = {
@@ -136,14 +144,14 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
136144
}
137145
}
138146

139-
notifyBoardUninstalled(event: BoardUninstalledEvent): void {
140-
this.logger.info('Board uninstalled: ', JSON.stringify(event));
147+
notifyUninstalled(event: UninstalledEvent<BoardsPackage>): void {
148+
this.logger.info('Boards package uninstalled: ', JSON.stringify(event));
141149
this.onBoardsPackageUninstalledEmitter.fire(event);
142150
const { selectedBoard } = this.boardsConfig;
143151
if (selectedBoard && selectedBoard.fqbn) {
144-
const uninstalledBoard = event.pkg.boards.find(({ name }) => name === selectedBoard.name);
152+
const uninstalledBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
145153
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
146-
this.logger.info(`Board package ${event.pkg.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
154+
this.logger.info(`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
147155
const selectedBoardWithoutFqbn = {
148156
name: selectedBoard.name
149157
// No FQBN
@@ -219,7 +227,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
219227
}
220228

221229
if (!config.selectedBoard) {
222-
if (!options.silent && this.messageService) {
230+
if (!options.silent) {
223231
this.messageService.warn('No boards selected.', { timeout: 3000 });
224232
}
225233
return false;
@@ -241,14 +249,14 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
241249

242250
const { name } = config.selectedBoard;
243251
if (!config.selectedPort) {
244-
if (!options.silent && this.messageService) {
252+
if (!options.silent) {
245253
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
246254
}
247255
return false;
248256
}
249257

250258
if (!config.selectedBoard.fqbn) {
251-
if (!options.silent && this.messageService) {
259+
if (!options.silent) {
252260
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
253261
}
254262
return false;

arduino-ide-extension/src/browser/contributions/contribution.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { inject, injectable, interfaces } from 'inversify';
22
import URI from '@theia/core/lib/common/uri';
33
import { ILogger } from '@theia/core/lib/common/logger';
44
import { FileSystem } from '@theia/filesystem/lib/common';
5+
import { MaybePromise } from '@theia/core/lib/common/types';
56
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
67
import { MessageService } from '@theia/core/lib/common/message-service';
78
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
@@ -13,11 +14,12 @@ import { Command, CommandRegistry, CommandContribution, CommandService } from '@
1314
import { EditorMode } from '../editor-mode';
1415
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
1516
import { SketchesService, ConfigService, FileSystemExt, Sketch } from '../../common/protocol';
17+
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
1618

1719
export { Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, URI, Sketch, open };
1820

1921
@injectable()
20-
export abstract class Contribution implements CommandContribution, MenuContribution, KeybindingContribution, TabBarToolbarContribution {
22+
export abstract class Contribution implements CommandContribution, MenuContribution, KeybindingContribution, TabBarToolbarContribution, FrontendApplicationContribution {
2123

2224
@inject(ILogger)
2325
protected readonly logger: ILogger;
@@ -37,6 +39,9 @@ export abstract class Contribution implements CommandContribution, MenuContribut
3739
@inject(LabelProvider)
3840
protected readonly labelProvider: LabelProvider;
3941

42+
onStart(app: FrontendApplication): MaybePromise<void> {
43+
}
44+
4045
registerCommands(registry: CommandRegistry): void {
4146
}
4247

@@ -75,11 +80,12 @@ export abstract class SketchContribution extends Contribution {
7580
}
7681

7782
export namespace Contribution {
78-
export function configure<T>(bind: interfaces.Bind, serviceIdentifier: interfaces.ServiceIdentifier<T>): void {
83+
export function configure<T>(bind: interfaces.Bind, serviceIdentifier: typeof Contribution): void {
7984
bind(serviceIdentifier).toSelf().inSingletonScope();
8085
bind(CommandContribution).toService(serviceIdentifier);
8186
bind(MenuContribution).toService(serviceIdentifier);
8287
bind(KeybindingContribution).toService(serviceIdentifier);
8388
bind(TabBarToolbarContribution).toService(serviceIdentifier);
89+
bind(FrontendApplicationContribution).toService(serviceIdentifier);
8490
}
8591
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { inject, injectable } from 'inversify';
2+
import { MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu';
3+
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
4+
import { OpenSketch } from './open-sketch';
5+
import { ArduinoMenus } from '../menu/arduino-menus';
6+
import { MainMenuManager } from '../../common/main-menu-manager';
7+
import { ExamplesService, ExampleContainer } from '../../common/protocol/examples-service';
8+
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
9+
10+
@injectable()
11+
export class Examples extends SketchContribution {
12+
13+
@inject(MainMenuManager)
14+
protected readonly menuManager: MainMenuManager;
15+
16+
@inject(ExamplesService)
17+
protected readonly examplesService: ExamplesService;
18+
19+
@inject(CommandRegistry)
20+
protected readonly commandRegistry: CommandRegistry;
21+
22+
@inject(MenuModelRegistry)
23+
protected readonly menuRegistry: MenuModelRegistry;
24+
25+
protected readonly toDisposeBeforeRegister = new DisposableCollection();
26+
27+
onStart(): void {
28+
this.registerExamples(); // no `await`
29+
}
30+
31+
protected async registerExamples() {
32+
let exampleContainer: ExampleContainer | undefined;
33+
try {
34+
exampleContainer = await this.examplesService.all();
35+
} catch (e) {
36+
console.error('Could not initialize built-in examples.', e);
37+
}
38+
if (!exampleContainer) {
39+
this.messageService.error('Could not initialize built-in examples.');
40+
return;
41+
}
42+
this.toDisposeBeforeRegister.dispose();
43+
this.registerRecursively(exampleContainer, ArduinoMenus.FILE__SKETCH_GROUP, this.toDisposeBeforeRegister, { order: '4' });
44+
this.menuManager.update();
45+
}
46+
47+
registerRecursively(
48+
exampleContainer: ExampleContainer,
49+
menuPath: MenuPath,
50+
pushToDispose: DisposableCollection = new DisposableCollection(),
51+
options?: SubMenuOptions): void {
52+
53+
const { label, sketches, children } = exampleContainer;
54+
const submenuPath = [...menuPath, label];
55+
// TODO: unregister submenu? https://github.com/eclipse-theia/theia/issues/7300
56+
this.menuRegistry.registerSubmenu(submenuPath, label, options);
57+
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
58+
for (const sketch of sketches) {
59+
const { uri } = sketch;
60+
const commandId = `arduino-open-example-${submenuPath.join(':')}--${uri}`;
61+
const command = { id: commandId };
62+
const handler = {
63+
execute: async () => {
64+
const sketch = await this.sketchService.cloneExample(uri);
65+
this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
66+
}
67+
};
68+
pushToDispose.push(this.commandRegistry.registerCommand(command, handler));
69+
this.menuRegistry.registerMenuAction(submenuPath, { commandId, label: sketch.name });
70+
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
71+
}
72+
}
73+
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { /*inject,*/ injectable } from 'inversify';
2+
// import { remote } from 'electron';
3+
// import { ArduinoMenus } from '../menu/arduino-menus';
4+
import { SketchContribution, Command, CommandRegistry } from './contribution';
5+
import { LibraryPackage } from '../../common/protocol';
6+
// import { SaveAsSketch } from './save-as-sketch';
7+
// import { EditorManager } from '@theia/editor/lib/browser';
8+
// import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
9+
10+
@injectable()
11+
export class IncludeLibrary extends SketchContribution {
12+
13+
registerCommands(registry: CommandRegistry): void {
14+
registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, {
15+
execute: async arg => {
16+
if (LibraryPackage.is(arg)) {
17+
this.includeLibrary(arg);
18+
}
19+
}
20+
});
21+
}
22+
23+
protected async includeLibrary(library: LibraryPackage): Promise<void> {
24+
// Always include to the main sketch file unless a c, cpp, or h file is the active one.
25+
console.log('INCLUDE', library);
26+
}
27+
28+
}
29+
30+
export namespace IncludeLibrary {
31+
export namespace Commands {
32+
export const INCLUDE_LIBRARY: Command = {
33+
id: 'arduino-include-library'
34+
};
35+
}
36+
}

0 commit comments

Comments
 (0)