From 7a37aa2e2f6d1ee6d7b3055e4e563b14f0928349 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 2 Sep 2020 16:18:44 +0200 Subject: [PATCH] ATL-78: Implemented include library. Signed-off-by: Akos Kitta --- arduino-ide-extension/scripts/download-cli.js | 2 +- .../browser/arduino-ide-frontend-module.ts | 2 - .../src/browser/contributions/examples.ts | 1 + .../browser/contributions/include-library.ts | 138 ++++++++++++++++-- .../library/include-library-menu-updater.ts | 94 ------------ .../node/cli-protocol/commands/lib_pb.d.ts | 6 + .../src/node/cli-protocol/commands/lib_pb.js | 53 ++++++- .../src/node/library-service-server-impl.ts | 24 +-- 8 files changed, 200 insertions(+), 120 deletions(-) delete mode 100644 arduino-ide-extension/src/browser/library/include-library-menu-updater.ts diff --git a/arduino-ide-extension/scripts/download-cli.js b/arduino-ide-extension/scripts/download-cli.js index aa9a1aaa..9912e7c8 100755 --- a/arduino-ide-extension/scripts/download-cli.js +++ b/arduino-ide-extension/scripts/download-cli.js @@ -10,7 +10,7 @@ (() => { - const DEFAULT_VERSION = '0.13.0-rc1'; // require('moment')().format('YYYYMMDD'); + const DEFAULT_VERSION = '0.13.0-rc2'; // require('moment')().format('YYYYMMDD'); const path = require('path'); const shell = require('shelljs'); diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 4dd90d08..119accc7 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -122,7 +122,6 @@ import { ExamplesServicePath, ExamplesService } from '../common/protocol/example import { BuiltInExamples, LibraryExamples } from './contributions/examples'; import { LibraryServiceProvider } from './library/library-service-provider'; import { IncludeLibrary } from './contributions/include-library'; -import { IncludeLibraryMenuUpdater } from './library/include-library-menu-updater'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -158,7 +157,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // Library service bind(LibraryServiceProvider).toSelf().inSingletonScope(); bind(LibraryServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServiceServerPath)).inSingletonScope(); - bind(FrontendApplicationContribution).to(IncludeLibraryMenuUpdater).inSingletonScope(); // Library list widget bind(LibraryListWidget).toSelf(); diff --git a/arduino-ide-extension/src/browser/contributions/examples.ts b/arduino-ide-extension/src/browser/contributions/examples.ts index 8ce9a893..2a02139d 100644 --- a/arduino-ide-extension/src/browser/contributions/examples.ts +++ b/arduino-ide-extension/src/browser/contributions/examples.ts @@ -1,3 +1,4 @@ +import * as PQueue from 'p-queue'; import { inject, injectable, postConstruct } from 'inversify'; import { MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; diff --git a/arduino-ide-extension/src/browser/contributions/include-library.ts b/arduino-ide-extension/src/browser/contributions/include-library.ts index d4bdbc94..78505fe3 100644 --- a/arduino-ide-extension/src/browser/contributions/include-library.ts +++ b/arduino-ide-extension/src/browser/contributions/include-library.ts @@ -1,15 +1,49 @@ -import { /*inject,*/ injectable } from 'inversify'; -// import { remote } from 'electron'; -// import { ArduinoMenus } from '../menu/arduino-menus'; +import * as PQueue from 'p-queue'; +import { inject, injectable } from 'inversify'; +import URI from '@theia/core/lib/common/uri'; +import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; +import { EditorManager } from '@theia/editor/lib/browser'; +import { MenuModelRegistry, MenuPath } from '@theia/core/lib/common/menu'; +import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; +import { ArduinoMenus } from '../menu/arduino-menus'; +import { LibraryPackage, LibraryLocation } from '../../common/protocol'; +import { MainMenuManager } from '../../common/main-menu-manager'; +import { LibraryListWidget } from '../library/library-list-widget'; +import { LibraryServiceProvider } from '../library/library-service-provider'; +import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl'; import { SketchContribution, Command, CommandRegistry } from './contribution'; -import { LibraryPackage } from '../../common/protocol'; -// import { SaveAsSketch } from './save-as-sketch'; -// import { EditorManager } from '@theia/editor/lib/browser'; -// import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; @injectable() export class IncludeLibrary extends SketchContribution { + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; + + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; + + @inject(MainMenuManager) + protected readonly mainMenuManager: MainMenuManager; + + @inject(EditorManager) + protected readonly editorManager: EditorManager; + + @inject(LibraryServiceProvider) + protected readonly libraryServiceProvider: LibraryServiceProvider; + + @inject(BoardsServiceClientImpl) + protected readonly boardsServiceClient: BoardsServiceClientImpl; + + protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); + protected readonly toDispose = new DisposableCollection(); + + onStart(): void { + this.updateMenuActions(); + this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions()) + this.libraryServiceProvider.onLibraryPackageInstalled(() => this.updateMenuActions()); + this.libraryServiceProvider.onLibraryPackageUninstalled(() => this.updateMenuActions()); + } + registerCommands(registry: CommandRegistry): void { registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, { execute: async arg => { @@ -20,9 +54,95 @@ export class IncludeLibrary extends SketchContribution { }); } + protected async updateMenuActions(): Promise { + return this.queue.add(async () => { + this.toDispose.dispose(); + this.mainMenuManager.update(); + const libraries: LibraryPackage[] = [] + const fqbn = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn; + // Do not show board specific examples, when no board is selected. + if (fqbn) { + libraries.push(...await this.libraryServiceProvider.list({ fqbn })); + } + + // `Include Library` submenu + const includeLibMenuPath = [...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include']; + this.menuRegistry.registerSubmenu(includeLibMenuPath, 'Include Library', { order: '1' }); + // `Manage Libraries...` group. + this.menuRegistry.registerMenuAction([...includeLibMenuPath, '0_manage'], { + commandId: `${LibraryListWidget.WIDGET_ID}:toggle`, + label: 'Manage Libraries...' + }); + this.toDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction({ commandId: `${LibraryListWidget.WIDGET_ID}:toggle` }))); + + // `Add .ZIP Library...` + // TODO: implement it + + // `Arduino libraries` + const packageMenuPath = [...includeLibMenuPath, '2_arduino']; + const userMenuPath = [...includeLibMenuPath, '3_contributed']; + for (const library of libraries) { + this.toDispose.push(this.registerLibrary(library, library.location === LibraryLocation.USER ? userMenuPath : packageMenuPath)); + } + + this.mainMenuManager.update(); + }); + } + + protected registerLibrary(library: LibraryPackage, menuPath: MenuPath): Disposable { + const commandId = `arduino-include-library--${library.name}:${library.author}`; + const command = { id: commandId }; + const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, library) }; + const menuAction = { commandId, label: library.name }; + this.menuRegistry.registerMenuAction(menuPath, menuAction); + return new DisposableCollection( + this.commandRegistry.registerCommand(command, handler), + Disposable.create(() => this.menuRegistry.unregisterMenuAction(menuAction)), + ); + } + protected async includeLibrary(library: LibraryPackage): Promise { - // Always include to the main sketch file unless a c, cpp, or h file is the active one. - console.log('INCLUDE', library); + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return; + } + // If the current editor is one of the additional files from the sketch, we use that. + // Otherwise, we pick the editor of the main sketch file. + let codeEditor: monaco.editor.IStandaloneCodeEditor | undefined; + const editor = this.editorManager.currentEditor?.editor; + if (editor instanceof MonacoEditor) { + if (sketch.additionalFileUris.some(uri => uri === editor.uri.toString())) { + codeEditor = editor.getControl(); + } + } + + if (!codeEditor) { + const widget = await this.editorManager.open(new URI(sketch.mainFileUri)); + if (widget.editor instanceof MonacoEditor) { + codeEditor = widget.editor.getControl(); + } + } + + if (!codeEditor) { + return; + } + + const textModel = codeEditor.getModel(); + if (!textModel) { + return; + } + const cursorState = codeEditor.getSelections() || []; + const eol = textModel.getEOL(); + const includes = library.includes.slice(); + includes.push(''); // For the trailing new line. + const text = includes.map(include => include ? `#include <${include}>` : eol).join(eol); + textModel.pushStackElement(); // Start a fresh operation. + textModel.pushEditOperations(cursorState, [{ + range: new monaco.Range(1, 1, 1, 1), + text, + forceMoveMarkers: true + }], () => cursorState); + textModel.pushStackElement(); // Make it undoable. } } diff --git a/arduino-ide-extension/src/browser/library/include-library-menu-updater.ts b/arduino-ide-extension/src/browser/library/include-library-menu-updater.ts deleted file mode 100644 index 2dd1b5b7..00000000 --- a/arduino-ide-extension/src/browser/library/include-library-menu-updater.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as PQueue from 'p-queue'; -import { inject, injectable } from 'inversify'; -import { CommandRegistry } from '@theia/core/lib/common/command'; -import { MenuModelRegistry, MenuPath } from '@theia/core/lib/common/menu'; -import { FrontendApplicationContribution } from '@theia/core/lib/browser'; -import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; -import { ArduinoMenus } from '../menu/arduino-menus'; -import { LibraryPackage } from '../../common/protocol'; -import { IncludeLibrary } from '../contributions/include-library'; -import { MainMenuManager } from '../../common/main-menu-manager'; -import { LibraryListWidget } from './library-list-widget'; -import { LibraryServiceProvider } from './library-service-provider'; -import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl'; - -@injectable() -export class IncludeLibraryMenuUpdater implements FrontendApplicationContribution { - - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; - - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; - - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; - - @inject(LibraryServiceProvider) - protected readonly libraryServiceProvider: LibraryServiceProvider; - - @inject(BoardsServiceClientImpl) - protected readonly boardsServiceClient: BoardsServiceClientImpl; - - protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); - protected readonly toDispose = new DisposableCollection(); - - async onStart(): Promise { - this.updateMenuActions(); - this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions()) - this.libraryServiceProvider.onLibraryPackageInstalled(() => this.updateMenuActions()); - this.libraryServiceProvider.onLibraryPackageUninstalled(() => this.updateMenuActions()); - } - - protected async updateMenuActions(): Promise { - return this.queue.add(async () => { - this.toDispose.dispose(); - this.mainMenuManager.update(); - const libraries: LibraryPackage[] = [] - const fqbn = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn; - // Do not show board specific examples, when no board is selected. - if (fqbn) { - libraries.push(...await this.libraryServiceProvider.list({ fqbn })); - } - - // `Include Library` submenu - const includeLibMenuPath = [...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include']; - this.menuRegistry.registerSubmenu(includeLibMenuPath, 'Include Library', { order: '1' }); - // `Manage Libraries...` group. - this.menuRegistry.registerMenuAction([...includeLibMenuPath, '0_manage'], { - commandId: `${LibraryListWidget.WIDGET_ID}:toggle`, - label: 'Manage Libraries...' - }); - this.toDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction({ commandId: `${LibraryListWidget.WIDGET_ID}:toggle` }))); - - // `Add .ZIP Library...` - // TODO: implement it - - // `Arduino libraries` - const arduinoLibsMenuPath = [...includeLibMenuPath, '2_arduino']; - for (const library of libraries.filter(({ author }) => author.toLowerCase() === 'arduino')) { - this.toDispose.push(this.registerLibrary(library, arduinoLibsMenuPath)); - } - - const contributedLibsMenuPath = [...includeLibMenuPath, '3_contributed']; - for (const library of libraries.filter(({ author }) => author.toLowerCase() !== 'arduino')) { - this.toDispose.push(this.registerLibrary(library, contributedLibsMenuPath)); - } - - this.mainMenuManager.update(); - }); - } - - protected registerLibrary(library: LibraryPackage, menuPath: MenuPath): Disposable { - const commandId = `arduino-include-library--${library.name}:${library.author}`; - const command = { id: commandId }; - const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, library) }; - const menuAction = { commandId, label: library.name }; - this.menuRegistry.registerMenuAction(menuPath, menuAction); - return new DisposableCollection( - this.commandRegistry.registerCommand(command, handler), - Disposable.create(() => this.menuRegistry.unregisterMenuAction(menuAction)), - ); - } - -} diff --git a/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.d.ts index b77c7fe4..f1960aa9 100644 --- a/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.d.ts @@ -726,6 +726,11 @@ export class Library extends jspb.Message { setExamplesList(value: Array): Library; addExamples(value: string, index?: number): string; + clearProvidesIncludesList(): void; + getProvidesIncludesList(): Array; + setProvidesIncludesList(value: Array): Library; + addProvidesIncludes(value: string, index?: number): string; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Library.AsObject; @@ -764,6 +769,7 @@ export namespace Library { location: LibraryLocation, layout: LibraryLayout, examplesList: Array, + providesIncludesList: Array, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.js b/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.js index 522a070d..38689df6 100644 --- a/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.js @@ -4762,7 +4762,7 @@ proto.cc.arduino.cli.commands.InstalledLibrary.prototype.hasRelease = function() * @private {!Array} * @const */ -proto.cc.arduino.cli.commands.Library.repeatedFields_ = [8,9,26]; +proto.cc.arduino.cli.commands.Library.repeatedFields_ = [8,9,26,27]; @@ -4818,7 +4818,8 @@ proto.cc.arduino.cli.commands.Library.toObject = function(includeInstance, msg) propertiesMap: (f = msg.getPropertiesMap()) ? f.toObject(includeInstance, undefined) : [], location: jspb.Message.getFieldWithDefault(msg, 24, 0), layout: jspb.Message.getFieldWithDefault(msg, 25, 0), - examplesList: (f = jspb.Message.getRepeatedField(msg, 26)) == null ? undefined : f + examplesList: (f = jspb.Message.getRepeatedField(msg, 26)) == null ? undefined : f, + providesIncludesList: (f = jspb.Message.getRepeatedField(msg, 27)) == null ? undefined : f }; if (includeInstance) { @@ -4953,6 +4954,10 @@ proto.cc.arduino.cli.commands.Library.deserializeBinaryFromReader = function(msg var value = /** @type {string} */ (reader.readString()); msg.addExamples(value); break; + case 27: + var value = /** @type {string} */ (reader.readString()); + msg.addProvidesIncludes(value); + break; default: reader.skipField(); break; @@ -5147,6 +5152,13 @@ proto.cc.arduino.cli.commands.Library.serializeBinaryToWriter = function(message f ); } + f = message.getProvidesIncludesList(); + if (f.length > 0) { + writer.writeRepeatedString( + 27, + f + ); + } }; @@ -5643,6 +5655,43 @@ proto.cc.arduino.cli.commands.Library.prototype.clearExamplesList = function() { }; +/** + * repeated string provides_includes = 27; + * @return {!Array} + */ +proto.cc.arduino.cli.commands.Library.prototype.getProvidesIncludesList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 27)); +}; + + +/** + * @param {!Array} value + * @return {!proto.cc.arduino.cli.commands.Library} returns this + */ +proto.cc.arduino.cli.commands.Library.prototype.setProvidesIncludesList = function(value) { + return jspb.Message.setField(this, 27, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.cc.arduino.cli.commands.Library} returns this + */ +proto.cc.arduino.cli.commands.Library.prototype.addProvidesIncludes = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 27, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.cc.arduino.cli.commands.Library} returns this + */ +proto.cc.arduino.cli.commands.Library.prototype.clearProvidesIncludesList = function() { + return this.setProvidesIncludesList([]); +}; + + /** * @enum {number} */ diff --git a/arduino-ide-extension/src/node/library-service-server-impl.ts b/arduino-ide-extension/src/node/library-service-server-impl.ts index af77c7d4..8f6cae1b 100644 --- a/arduino-ide-extension/src/node/library-service-server-impl.ts +++ b/arduino-ide-extension/src/node/library-service-server-impl.ts @@ -11,7 +11,8 @@ import { LibraryInstallReq, LibraryInstallResp, LibraryUninstallReq, - LibraryUninstallResp + LibraryUninstallResp, + Library } from './cli-protocol/commands/lib_pb'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; import { Installable } from '../common/protocol/installable'; @@ -87,7 +88,7 @@ export class LibraryServiceServerImpl implements LibraryServiceServer { installable: true, installedVersion, }, item.getLatest()!, availableVersions) - }) + }); return items; } @@ -109,9 +110,8 @@ export class LibraryServiceServerImpl implements LibraryServiceServer { const resp = await new Promise((resolve, reject) => client.libraryList(req, ((error, resp) => !!error ? reject(error) : resolve(resp)))); return resp.getInstalledLibraryList().map(item => { - const release = item.getRelease(); const library = item.getLibrary(); - if (!release || !library) { + if (!library) { return undefined; } const installedVersion = library.getVersion(); @@ -123,11 +123,11 @@ export class LibraryServiceServerImpl implements LibraryServiceServer { description: library.getSentence(), summary: library.getParagraph(), moreInfoLink: library.getWebsite(), - includes: release.getProvidesIncludesList(), + includes: library.getProvidesIncludesList(), location: library.getLocation(), installDirUri: FileUri.create(library.getInstallDir()).toString(), exampleUris: library.getExamplesList().map(fsPath => FileUri.create(fsPath).toString()) - }, release, [library.getVersion()]); + }, library, [library.getVersion()]); }).filter(notEmpty); } @@ -212,7 +212,7 @@ export class LibraryServiceServerImpl implements LibraryServiceServer { } -function toLibrary(pkg: Partial, release: LibraryRelease, availableVersions: string[]): LibraryPackage { +function toLibrary(pkg: Partial, lib: LibraryRelease | Library, availableVersions: string[]): LibraryPackage { return { name: '', label: '', @@ -221,11 +221,11 @@ function toLibrary(pkg: Partial, release: LibraryRelease, availa location: 0, ...pkg, - author: release.getAuthor(), + author: lib.getAuthor(), availableVersions, - includes: release.getProvidesIncludesList(), - description: release.getSentence(), - moreInfoLink: release.getWebsite(), - summary: release.getParagraph() + includes: lib.getProvidesIncludesList(), + description: lib.getSentence(), + moreInfoLink: lib.getWebsite(), + summary: lib.getParagraph() } }