ATL-78: Implemented include library.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2020-09-02 16:18:44 +02:00 committed by Akos Kitta
parent 56ff86629c
commit 7a37aa2e2f
8 changed files with 200 additions and 120 deletions

View File

@ -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 path = require('path');
const shell = require('shelljs'); const shell = require('shelljs');

View File

@ -122,7 +122,6 @@ import { ExamplesServicePath, ExamplesService } from '../common/protocol/example
import { BuiltInExamples, LibraryExamples } from './contributions/examples'; import { BuiltInExamples, LibraryExamples } from './contributions/examples';
import { LibraryServiceProvider } from './library/library-service-provider'; import { LibraryServiceProvider } from './library/library-service-provider';
import { IncludeLibrary } from './contributions/include-library'; import { IncludeLibrary } from './contributions/include-library';
import { IncludeLibraryMenuUpdater } from './library/include-library-menu-updater';
const ElementQueries = require('css-element-queries/src/ElementQueries'); const ElementQueries = require('css-element-queries/src/ElementQueries');
@ -158,7 +157,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Library service // Library service
bind(LibraryServiceProvider).toSelf().inSingletonScope(); bind(LibraryServiceProvider).toSelf().inSingletonScope();
bind(LibraryServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServiceServerPath)).inSingletonScope(); bind(LibraryServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServiceServerPath)).inSingletonScope();
bind(FrontendApplicationContribution).to(IncludeLibraryMenuUpdater).inSingletonScope();
// Library list widget // Library list widget
bind(LibraryListWidget).toSelf(); bind(LibraryListWidget).toSelf();

View File

@ -1,3 +1,4 @@
import * as PQueue from 'p-queue';
import { inject, injectable, postConstruct } from 'inversify'; import { inject, injectable, postConstruct } from 'inversify';
import { MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu'; import { MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';

View File

@ -1,15 +1,49 @@
import { /*inject,*/ injectable } from 'inversify'; import * as PQueue from 'p-queue';
// import { remote } from 'electron'; import { inject, injectable } from 'inversify';
// import { ArduinoMenus } from '../menu/arduino-menus'; 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 { 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() @injectable()
export class IncludeLibrary extends SketchContribution { 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 { registerCommands(registry: CommandRegistry): void {
registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, { registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, {
execute: async arg => { execute: async arg => {
@ -20,9 +54,95 @@ export class IncludeLibrary extends SketchContribution {
}); });
} }
protected async updateMenuActions(): Promise<void> {
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<void> { protected async includeLibrary(library: LibraryPackage): Promise<void> {
// Always include to the main sketch file unless a c, cpp, or h file is the active one. const sketch = await this.sketchServiceClient.currentSketch();
console.log('INCLUDE', library); 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.
} }
} }

View File

@ -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<void> {
this.updateMenuActions();
this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions())
this.libraryServiceProvider.onLibraryPackageInstalled(() => this.updateMenuActions());
this.libraryServiceProvider.onLibraryPackageUninstalled(() => this.updateMenuActions());
}
protected async updateMenuActions(): Promise<void> {
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)),
);
}
}

View File

@ -726,6 +726,11 @@ export class Library extends jspb.Message {
setExamplesList(value: Array<string>): Library; setExamplesList(value: Array<string>): Library;
addExamples(value: string, index?: number): string; addExamples(value: string, index?: number): string;
clearProvidesIncludesList(): void;
getProvidesIncludesList(): Array<string>;
setProvidesIncludesList(value: Array<string>): Library;
addProvidesIncludes(value: string, index?: number): string;
serializeBinary(): Uint8Array; serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): Library.AsObject; toObject(includeInstance?: boolean): Library.AsObject;
@ -764,6 +769,7 @@ export namespace Library {
location: LibraryLocation, location: LibraryLocation,
layout: LibraryLayout, layout: LibraryLayout,
examplesList: Array<string>, examplesList: Array<string>,
providesIncludesList: Array<string>,
} }
} }

View File

@ -4762,7 +4762,7 @@ proto.cc.arduino.cli.commands.InstalledLibrary.prototype.hasRelease = function()
* @private {!Array<number>} * @private {!Array<number>}
* @const * @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) : [], propertiesMap: (f = msg.getPropertiesMap()) ? f.toObject(includeInstance, undefined) : [],
location: jspb.Message.getFieldWithDefault(msg, 24, 0), location: jspb.Message.getFieldWithDefault(msg, 24, 0),
layout: jspb.Message.getFieldWithDefault(msg, 25, 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) { if (includeInstance) {
@ -4953,6 +4954,10 @@ proto.cc.arduino.cli.commands.Library.deserializeBinaryFromReader = function(msg
var value = /** @type {string} */ (reader.readString()); var value = /** @type {string} */ (reader.readString());
msg.addExamples(value); msg.addExamples(value);
break; break;
case 27:
var value = /** @type {string} */ (reader.readString());
msg.addProvidesIncludes(value);
break;
default: default:
reader.skipField(); reader.skipField();
break; break;
@ -5147,6 +5152,13 @@ proto.cc.arduino.cli.commands.Library.serializeBinaryToWriter = function(message
f 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<string>}
*/
proto.cc.arduino.cli.commands.Library.prototype.getProvidesIncludesList = function() {
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 27));
};
/**
* @param {!Array<string>} 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} * @enum {number}
*/ */

View File

@ -11,7 +11,8 @@ import {
LibraryInstallReq, LibraryInstallReq,
LibraryInstallResp, LibraryInstallResp,
LibraryUninstallReq, LibraryUninstallReq,
LibraryUninstallResp LibraryUninstallResp,
Library
} from './cli-protocol/commands/lib_pb'; } from './cli-protocol/commands/lib_pb';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { Installable } from '../common/protocol/installable'; import { Installable } from '../common/protocol/installable';
@ -87,7 +88,7 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
installable: true, installable: true,
installedVersion, installedVersion,
}, item.getLatest()!, availableVersions) }, item.getLatest()!, availableVersions)
}) });
return items; return items;
} }
@ -109,9 +110,8 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
const resp = await new Promise<LibraryListResp>((resolve, reject) => client.libraryList(req, ((error, resp) => !!error ? reject(error) : resolve(resp)))); const resp = await new Promise<LibraryListResp>((resolve, reject) => client.libraryList(req, ((error, resp) => !!error ? reject(error) : resolve(resp))));
return resp.getInstalledLibraryList().map(item => { return resp.getInstalledLibraryList().map(item => {
const release = item.getRelease();
const library = item.getLibrary(); const library = item.getLibrary();
if (!release || !library) { if (!library) {
return undefined; return undefined;
} }
const installedVersion = library.getVersion(); const installedVersion = library.getVersion();
@ -123,11 +123,11 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
description: library.getSentence(), description: library.getSentence(),
summary: library.getParagraph(), summary: library.getParagraph(),
moreInfoLink: library.getWebsite(), moreInfoLink: library.getWebsite(),
includes: release.getProvidesIncludesList(), includes: library.getProvidesIncludesList(),
location: library.getLocation(), location: library.getLocation(),
installDirUri: FileUri.create(library.getInstallDir()).toString(), installDirUri: FileUri.create(library.getInstallDir()).toString(),
exampleUris: library.getExamplesList().map(fsPath => FileUri.create(fsPath).toString()) exampleUris: library.getExamplesList().map(fsPath => FileUri.create(fsPath).toString())
}, release, [library.getVersion()]); }, library, [library.getVersion()]);
}).filter(notEmpty); }).filter(notEmpty);
} }
@ -212,7 +212,7 @@ export class LibraryServiceServerImpl implements LibraryServiceServer {
} }
function toLibrary(pkg: Partial<LibraryPackage>, release: LibraryRelease, availableVersions: string[]): LibraryPackage { function toLibrary(pkg: Partial<LibraryPackage>, lib: LibraryRelease | Library, availableVersions: string[]): LibraryPackage {
return { return {
name: '', name: '',
label: '', label: '',
@ -221,11 +221,11 @@ function toLibrary(pkg: Partial<LibraryPackage>, release: LibraryRelease, availa
location: 0, location: 0,
...pkg, ...pkg,
author: release.getAuthor(), author: lib.getAuthor(),
availableVersions, availableVersions,
includes: release.getProvidesIncludesList(), includes: lib.getProvidesIncludesList(),
description: release.getSentence(), description: lib.getSentence(),
moreInfoLink: release.getWebsite(), moreInfoLink: lib.getWebsite(),
summary: release.getParagraph() summary: lib.getParagraph()
} }
} }