From e10f0f1683e77af470e5270b50bfc5f2a548ffa4 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Fri, 9 Jul 2021 10:14:42 +0200 Subject: [PATCH 01/11] Make tab width 2 spaces (#445) --- .prettierrc | 11 +- .../src/browser/arduino-commands.ts | 24 +- .../browser/arduino-frontend-contribution.tsx | 885 +++---- .../browser/arduino-ide-frontend-module.ts | 1039 +++++---- .../src/browser/arduino-preferences.ts | 269 ++- .../src/browser/arduino-workspace-resolver.ts | 86 +- .../auth/authentication-client-service.ts | 126 +- .../src/browser/auth/cloud-user-commands.ts | 24 +- .../browser/boards/boards-auto-installer.ts | 182 +- .../boards/boards-config-dialog-widget.tsx | 96 +- .../browser/boards/boards-config-dialog.ts | 208 +- .../src/browser/boards/boards-config.tsx | 736 +++--- .../boards/boards-data-menu-updater.ts | 303 ++- .../src/browser/boards/boards-data-store.ts | 456 ++-- .../src/browser/boards/boards-list-widget.ts | 120 +- .../browser/boards/boards-service-provider.ts | 1087 +++++---- .../browser/boards/boards-toolbar-item.tsx | 387 ++-- .../boards-widget-frontend-contribution.ts | 30 +- .../src/browser/contributions/about.ts | 176 +- .../src/browser/contributions/add-file.ts | 116 +- .../browser/contributions/add-zip-library.ts | 210 +- .../browser/contributions/archive-sketch.ts | 102 +- .../browser/contributions/board-selection.ts | 547 +++-- .../browser/contributions/burn-bootloader.ts | 134 +- .../src/browser/contributions/close.ts | 208 +- .../src/browser/contributions/contribution.ts | 199 +- .../src/browser/contributions/debug.ts | 275 ++- .../contributions/edit-contributions.ts | 569 +++-- .../src/browser/contributions/examples.ts | 436 ++-- .../src/browser/contributions/help.ts | 293 ++- .../browser/contributions/include-library.ts | 379 ++- .../src/browser/contributions/new-sketch.ts | 107 +- .../contributions/open-recent-sketch.ts | 140 +- .../contributions/open-sketch-external.ts | 76 +- .../src/browser/contributions/open-sketch.ts | 370 ++- .../src/browser/contributions/quit-app.ts | 64 +- .../browser/contributions/save-as-sketch.ts | 219 +- .../src/browser/contributions/save-sketch.ts | 98 +- .../src/browser/contributions/settings.ts | 101 +- .../browser/contributions/sketch-control.ts | 474 ++-- .../src/browser/contributions/sketchbook.ts | 92 +- .../browser/contributions/upload-sketch.ts | 369 ++- .../browser/contributions/verify-sketch.ts | 232 +- .../src/browser/create/create-api.ts | 1025 ++++----- .../src/browser/create/create-fs-provider.ts | 354 ++- .../src/browser/create/create-paths.ts | 60 +- .../src/browser/create/create-uri.ts | 52 +- .../dialogs.ts/cloud-share-sketch-dialog.tsx | 266 +-- .../src/browser/dialogs.ts/dialogs.ts | 102 +- .../src/browser/editor-mode.ts | 46 +- .../browser/library/library-list-widget.ts | 328 ++- .../library-widget-frontend-contribution.ts | 50 +- .../local-cache/local-cache-fs-provider.ts | 249 +- .../src/browser/menu/arduino-menus.ts | 354 +-- .../src/browser/monitor/monitor-connection.ts | 619 +++-- .../src/browser/monitor/monitor-model.ts | 262 +-- .../monitor/monitor-service-client-impl.ts | 14 +- .../monitor/monitor-view-contribution.tsx | 248 +- .../src/browser/monitor/monitor-widget.tsx | 702 +++--- .../src/browser/notification-center.ts | 212 +- .../src/browser/response-service-impl.ts | 43 +- .../src/browser/settings.tsx | 2045 ++++++++--------- .../src/browser/storage-wrapper.ts | 46 +- .../browser/theia/core/application-shell.ts | 129 +- .../theia/core/browser-main-menu-factory.ts | 28 +- .../browser/theia/core/browser-menu-module.ts | 16 +- .../browser/theia/core/browser-menu-plugin.ts | 8 +- .../core/common-frontend-contribution.ts | 55 +- .../theia/core/connection-status-service.ts | 118 +- .../theia/core/frontend-application.ts | 44 +- .../src/browser/theia/core/keybindings.ts | 40 +- .../theia/core/shell-layout-restorer.ts | 39 +- .../browser/theia/core/tab-bar-decorator.ts | 49 +- .../browser/theia/core/tab-bar-toolbar.tsx | 102 +- .../src/browser/theia/core/tab-bars.ts | 12 +- .../debug/debug-configuration-manager.ts | 232 +- .../theia/debug/debug-configuration-model.ts | 78 +- .../browser/theia/debug/debug-editor-model.ts | 174 +- ...debug-frontend-application-contribution.ts | 20 +- .../browser/theia/debug/debug-hover-source.ts | 24 +- .../browser/theia/debug/debug-hover-widget.ts | 195 +- .../theia/debug/debug-session-manager.ts | 86 +- .../browser/theia/editor/editor-command.ts | 32 +- .../theia/editor/editor-contribution.ts | 24 +- .../theia/editor/editor-widget-factory.ts | 46 +- .../keymaps/keymaps-frontend-contribution.ts | 18 +- .../theia/markers/problem-contribution.ts | 26 +- .../browser/theia/markers/problem-manager.ts | 50 +- .../notification-center-component.tsx | 82 +- .../theia/messages/notification-component.tsx | 185 +- .../notification-toasts-component.tsx | 38 +- .../theia/messages/notifications-manager.ts | 70 +- .../theia/messages/notifications-renderer.tsx | 24 +- .../theia/monaco/monaco-editor-provider.ts | 154 +- .../monaco/monaco-status-bar-contribution.ts | 4 +- .../theia/monaco/monaco-text-model-service.ts | 110 +- .../theia/navigator/navigator-contribution.ts | 58 +- .../navigator/navigator-tab-bar-decorator.ts | 14 +- .../theia/outline/outline-contribution.ts | 20 +- .../browser/theia/output/output-channel.ts | 75 +- .../output/output-toolbar-contribution.ts | 28 +- .../src/browser/theia/output/output-widget.ts | 8 +- .../output-channel-registry-main.ts | 64 +- .../preferences/preferences-contribution.ts | 24 +- .../src/browser/theia/scm/scm-contribution.ts | 12 +- ...arch-in-workspace-frontend-contribution.ts | 32 +- .../search-in-workspace-result-tree-widget.ts | 62 +- .../search-in-workspace-widget.tsx | 135 +- .../theia/workspace/workspace-commands.ts | 264 ++- .../workspace/workspace-delete-handler.ts | 68 +- .../workspace-frontend-contribution.ts | 66 +- .../theia/workspace/workspace-input-dialog.ts | 52 +- .../theia/workspace/workspace-service.ts | 226 +- .../workspace-variable-contribution.ts | 36 +- .../toolbar/arduino-toolbar-contribution.ts | 88 +- .../src/browser/toolbar/arduino-toolbar.tsx | 301 ++- .../src/browser/widgets/arduino-select.tsx | 112 +- .../cloud-sketchbook-composite-widget.tsx | 85 +- .../cloud-sketchbook-contributions.ts | 632 +++-- .../cloud-sketchbook-tree-container.ts | 26 +- .../cloud-sketchbook-tree-model.ts | 253 +- .../cloud-sketchbook-tree-widget.tsx | 295 ++- .../cloud-sketchbook/cloud-sketchbook-tree.ts | 864 ++++--- .../cloud-sketchbook-widget.ts | 66 +- .../cloud-sketchbook/cloud-user-status.tsx | 204 +- .../component-list/component-list-item.tsx | 103 +- .../widgets/component-list/component-list.tsx | 75 +- .../filterable-list-container.tsx | 292 ++- .../component-list/list-item-renderer.tsx | 206 +- .../list-widget-frontend-contribution.ts | 12 +- .../widgets/component-list/list-widget.tsx | 284 ++- .../widgets/component-list/search-bar.tsx | 70 +- .../widgets/sketchbook/sketchbook-commands.ts | 54 +- .../sketchbook/sketchbook-tree-container.ts | 39 +- .../sketchbook/sketchbook-tree-model.ts | 154 +- .../sketchbook/sketchbook-tree-widget.tsx | 322 +-- .../widgets/sketchbook/sketchbook-tree.ts | 160 +- .../sketchbook-widget-contribution.ts | 298 +-- .../widgets/sketchbook/sketchbook-widget.tsx | 141 +- .../src/common/main-menu-manager.ts | 10 +- .../src/common/protocol/arduino-component.ts | 48 +- .../src/common/protocol/arduino-daemon.ts | 2 +- .../common/protocol/authentication-service.ts | 30 +- .../src/common/protocol/boards-service.ts | 969 ++++---- .../src/common/protocol/config-service.ts | 188 +- .../src/common/protocol/core-service.ts | 78 +- .../src/common/protocol/examples-service.ts | 12 +- .../src/common/protocol/executable-service.ts | 2 +- .../src/common/protocol/filesystem-ext.ts | 2 +- .../src/common/protocol/installable.ts | 216 +- .../src/common/protocol/library-service.ts | 187 +- .../src/common/protocol/monitor-service.ts | 120 +- .../common/protocol/notification-service.ts | 34 +- .../src/common/protocol/response-service.ts | 22 +- .../src/common/protocol/searchable.ts | 14 +- .../protocol/sketches-service-client-impl.ts | 300 ++- .../src/common/protocol/sketches-service.ts | 340 ++- arduino-ide-extension/src/common/types.ts | 4 +- arduino-ide-extension/src/common/utils.ts | 8 +- .../electron-window-service.ts | 61 +- .../theia/core/electron-main-menu-factory.ts | 192 +- .../theia/core/electron-menu-contribution.ts | 50 +- .../theia/core/electron-menu-module.ts | 34 +- .../src/electron-common/splash-service.ts | 2 +- .../arduino-electron-main-module.ts | 30 +- .../src/electron-main/splash/splash-screen.ts | 166 +- .../splash/splash-service-impl.ts | 20 +- .../theia/electron-main-application.ts | 303 ++- .../theia/electron-main-window-service.ts | 45 +- .../src/node/arduino-daemon-impl.ts | 489 ++-- .../src/node/arduino-ide-backend-module.ts | 446 ++-- .../src/node/auth/arduino-auth-provider.ts | 607 +++-- .../src/node/auth/authentication-server.ts | 94 +- .../node/auth/authentication-service-impl.ts | 126 +- .../src/node/auth/keychain.ts | 114 +- arduino-ide-extension/src/node/auth/types.ts | 30 +- arduino-ide-extension/src/node/auth/utils.ts | 146 +- .../src/node/board-discovery.ts | 244 +- .../src/node/boards-service-impl.ts | 809 ++++--- arduino-ide-extension/src/node/cli-config.ts | 232 +- .../src/node/config-service-impl.ts | 483 ++-- .../src/node/core-client-provider.ts | 391 ++-- .../src/node/core-service-impl.ts | 380 ++- arduino-ide-extension/src/node/daemon-log.ts | 262 ++- .../src/node/daemon-watcher.ts | 26 +- .../src/node/examples-service-impl.ts | 317 ++- arduino-ide-extension/src/node/exec-util.ts | 160 +- .../src/node/executable-service-impl.ts | 42 +- .../src/node/grpc-client-provider.ts | 118 +- .../src/node/grpc-installable.ts | 178 +- .../src/node/library-service-server-impl.ts | 680 +++--- .../node/monitor/monitor-client-provider.ts | 32 +- .../src/node/monitor/monitor-service-impl.ts | 390 ++-- .../src/node/node-filesystem-ext.ts | 6 +- .../src/node/notification-service-server.ts | 120 +- .../src/node/sketches-service-impl.ts | 886 ++++--- .../node/theia/core/backend-application.ts | 36 +- .../env-variables/env-variables-server.ts | 13 +- .../src/node/theia/git/git-init.ts | 86 +- .../workspace/default-workspace-server.ts | 26 +- .../src/test/common/boards-service.test.ts | 154 +- .../src/test/common/installable.test.ts | 70 +- .../src/test/node/arduino-daemon-impl.test.ts | 258 +-- .../src/test/node/cli-config.test.ts | 95 +- .../src/test/node/exec-util.test.ts | 44 +- 205 files changed, 19719 insertions(+), 20184 deletions(-) diff --git a/.prettierrc b/.prettierrc index c99b8cca6..b20f01f1c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,13 +1,6 @@ { "singleQuote": true, - "tabWidth": 4, + "tabWidth": 2, "useTabs": false, - "overrides": [ - { - "files": "*.{json,yml}", - "options": { - "tabWidth": 2 - } - } - ] + "printWidth": 80 } diff --git a/arduino-ide-extension/src/browser/arduino-commands.ts b/arduino-ide-extension/src/browser/arduino-commands.ts index eb74e8b61..12673d71b 100644 --- a/arduino-ide-extension/src/browser/arduino-commands.ts +++ b/arduino-ide-extension/src/browser/arduino-commands.ts @@ -4,18 +4,18 @@ import { Command } from '@theia/core/lib/common/command'; * @deprecated all these commands should go under contributions and have their command, menu, keybinding, and toolbar contributions. */ export namespace ArduinoCommands { - export const TOGGLE_COMPILE_FOR_DEBUG: Command = { - id: 'arduino-toggle-compile-for-debug', - }; + export const TOGGLE_COMPILE_FOR_DEBUG: Command = { + id: 'arduino-toggle-compile-for-debug', + }; - /** - * Unlike `OPEN_SKETCH`, it opens all files from a sketch folder. (ino, cpp, etc...) - */ - export const OPEN_SKETCH_FILES: Command = { - id: 'arduino-open-sketch-files', - }; + /** + * Unlike `OPEN_SKETCH`, it opens all files from a sketch folder. (ino, cpp, etc...) + */ + export const OPEN_SKETCH_FILES: Command = { + id: 'arduino-open-sketch-files', + }; - export const OPEN_BOARDS_DIALOG: Command = { - id: 'arduino-open-boards-dialog', - }; + export const OPEN_BOARDS_DIALOG: Command = { + id: 'arduino-open-boards-dialog', + }; } diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 25871b14b..ecb23c9ea 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -1,18 +1,38 @@ import { Mutex } from 'async-mutex'; -import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, SelectionService, ILogger, DisposableCollection } from '@theia/core'; import { - ContextMenuRenderer, - FrontendApplication, FrontendApplicationContribution, - OpenerService, StatusBar, StatusBarAlignment + MAIN_MENU_BAR, + MenuContribution, + MenuModelRegistry, + SelectionService, + ILogger, + DisposableCollection, +} from '@theia/core'; +import { + ContextMenuRenderer, + FrontendApplication, + FrontendApplicationContribution, + OpenerService, + StatusBar, + StatusBarAlignment, } from '@theia/core/lib/browser'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; import { ColorRegistry } from '@theia/core/lib/browser/color-registry'; import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution'; -import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; -import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command'; +import { + TabBarToolbarContribution, + TabBarToolbarRegistry, +} from '@theia/core/lib/browser/shell/tab-bar-toolbar'; +import { + CommandContribution, + CommandRegistry, +} from '@theia/core/lib/common/command'; import { MessageService } from '@theia/core/lib/common/message-service'; import URI from '@theia/core/lib/common/uri'; -import { EditorMainMenu, EditorManager, EditorOpenerOptions } from '@theia/editor/lib/browser'; +import { + EditorMainMenu, + EditorManager, + EditorOpenerOptions, +} from '@theia/editor/lib/browser'; import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog'; import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution'; import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu'; @@ -26,7 +46,14 @@ import { inject, injectable, postConstruct } from 'inversify'; import * as React from 'react'; import { remote } from 'electron'; import { MainMenuManager } from '../common/main-menu-manager'; -import { BoardsService, CoreService, Port, SketchesService, ExecutableService, Sketch } from '../common/protocol'; +import { + BoardsService, + CoreService, + Port, + SketchesService, + ExecutableService, + Sketch, +} from '../common/protocol'; import { ArduinoDaemon } from '../common/protocol/arduino-daemon'; import { ConfigService } from '../common/protocol/config-service'; import { FileSystemExt } from '../common/protocol/filesystem-ext'; @@ -53,434 +80,514 @@ import { FrontendApplicationStateService } from '@theia/core/lib/browser/fronten import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution'; @injectable() -export class ArduinoFrontendContribution implements FrontendApplicationContribution, - TabBarToolbarContribution, CommandContribution, MenuContribution, ColorContribution { +export class ArduinoFrontendContribution + implements + FrontendApplicationContribution, + TabBarToolbarContribution, + CommandContribution, + MenuContribution, + ColorContribution +{ + @inject(ILogger) + protected logger: ILogger; - @inject(ILogger) - protected logger: ILogger; + @inject(MessageService) + protected readonly messageService: MessageService; - @inject(MessageService) - protected readonly messageService: MessageService; + @inject(BoardsService) + protected readonly boardsService: BoardsService; - @inject(BoardsService) - protected readonly boardsService: BoardsService; + @inject(CoreService) + protected readonly coreService: CoreService; - @inject(CoreService) - protected readonly coreService: CoreService; + @inject(BoardsServiceProvider) + protected readonly boardsServiceClientImpl: BoardsServiceProvider; - @inject(BoardsServiceProvider) - protected readonly boardsServiceClientImpl: BoardsServiceProvider; + @inject(SelectionService) + protected readonly selectionService: SelectionService; - @inject(SelectionService) - protected readonly selectionService: SelectionService; + @inject(EditorManager) + protected readonly editorManager: EditorManager; - @inject(EditorManager) - protected readonly editorManager: EditorManager; + @inject(ContextMenuRenderer) + protected readonly contextMenuRenderer: ContextMenuRenderer; - @inject(ContextMenuRenderer) - protected readonly contextMenuRenderer: ContextMenuRenderer; + @inject(FileDialogService) + protected readonly fileDialogService: FileDialogService; - @inject(FileDialogService) - protected readonly fileDialogService: FileDialogService; + @inject(FileService) + protected readonly fileService: FileService; - @inject(FileService) - protected readonly fileService: FileService; + @inject(SketchesService) + protected readonly sketchService: SketchesService; - @inject(SketchesService) - protected readonly sketchService: SketchesService; + @inject(BoardsConfigDialog) + protected readonly boardsConfigDialog: BoardsConfigDialog; - @inject(BoardsConfigDialog) - protected readonly boardsConfigDialog: BoardsConfigDialog; + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + @inject(StatusBar) + protected readonly statusBar: StatusBar; - @inject(StatusBar) - protected readonly statusBar: StatusBar; + @inject(WorkspaceService) + protected readonly workspaceService: WorkspaceService; - @inject(WorkspaceService) - protected readonly workspaceService: WorkspaceService; + @inject(MonitorConnection) + protected readonly monitorConnection: MonitorConnection; - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; + @inject(FileNavigatorContribution) + protected readonly fileNavigatorContributions: FileNavigatorContribution; - @inject(FileNavigatorContribution) - protected readonly fileNavigatorContributions: FileNavigatorContribution; + @inject(OutputContribution) + protected readonly outputContribution: OutputContribution; - @inject(OutputContribution) - protected readonly outputContribution: OutputContribution; + @inject(OutlineViewContribution) + protected readonly outlineContribution: OutlineViewContribution; - @inject(OutlineViewContribution) - protected readonly outlineContribution: OutlineViewContribution; + @inject(ProblemContribution) + protected readonly problemContribution: ProblemContribution; - @inject(ProblemContribution) - protected readonly problemContribution: ProblemContribution; + @inject(ScmContribution) + protected readonly scmContribution: ScmContribution; - @inject(ScmContribution) - protected readonly scmContribution: ScmContribution; + @inject(SearchInWorkspaceFrontendContribution) + protected readonly siwContribution: SearchInWorkspaceFrontendContribution; - @inject(SearchInWorkspaceFrontendContribution) - protected readonly siwContribution: SearchInWorkspaceFrontendContribution; + @inject(SketchbookWidgetContribution) + protected readonly sketchbookWidgetContribution: SketchbookWidgetContribution; - @inject(SketchbookWidgetContribution) - protected readonly sketchbookWidgetContribution: SketchbookWidgetContribution; + @inject(EditorMode) + protected readonly editorMode: EditorMode; - @inject(EditorMode) - protected readonly editorMode: EditorMode; + @inject(ArduinoDaemon) + protected readonly daemon: ArduinoDaemon; - @inject(ArduinoDaemon) - protected readonly daemon: ArduinoDaemon; + @inject(OpenerService) + protected readonly openerService: OpenerService; - @inject(OpenerService) - protected readonly openerService: OpenerService; + @inject(ConfigService) + protected readonly configService: ConfigService; - @inject(ConfigService) - protected readonly configService: ConfigService; + @inject(BoardsDataStore) + protected readonly boardsDataStore: BoardsDataStore; - @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; + @inject(MainMenuManager) + protected readonly mainMenuManager: MainMenuManager; - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; + @inject(FileSystemExt) + protected readonly fileSystemExt: FileSystemExt; - @inject(FileSystemExt) - protected readonly fileSystemExt: FileSystemExt; + @inject(HostedPluginSupport) + protected hostedPluginSupport: HostedPluginSupport; - @inject(HostedPluginSupport) - protected hostedPluginSupport: HostedPluginSupport; + @inject(ExecutableService) + protected executableService: ExecutableService; - @inject(ExecutableService) - protected executableService: ExecutableService; + @inject(ResponseService) + protected readonly responseService: ResponseService; - @inject(ResponseService) - protected readonly responseService: ResponseService; + @inject(ArduinoPreferences) + protected readonly arduinoPreferences: ArduinoPreferences; - @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + @inject(SketchesServiceClientImpl) + protected readonly sketchServiceClient: SketchesServiceClientImpl; - @inject(SketchesServiceClientImpl) - protected readonly sketchServiceClient: SketchesServiceClientImpl; + @inject(FrontendApplicationStateService) + protected readonly appStateService: FrontendApplicationStateService; - @inject(FrontendApplicationStateService) - protected readonly appStateService: FrontendApplicationStateService; - - protected invalidConfigPopup: Promise | undefined; - protected toDisposeOnStop = new DisposableCollection(); - - @postConstruct() - protected async init(): Promise { - if (!window.navigator.onLine) { - // tslint:disable-next-line:max-line-length - this.messageService.warn('You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.'); - } - const updateStatusBar = ({ selectedBoard, selectedPort }: BoardsConfig.Config) => { - this.statusBar.setElement('arduino-selected-board', { - alignment: StatusBarAlignment.RIGHT, - text: selectedBoard ? `$(microchip) ${selectedBoard.name}` : '$(close) no board selected', - className: 'arduino-selected-board' - }); - if (selectedBoard) { - this.statusBar.setElement('arduino-selected-port', { - alignment: StatusBarAlignment.RIGHT, - text: selectedPort ? `on ${Port.toString(selectedPort)}` : '[not connected]', - className: 'arduino-selected-port' - }); - } - } - this.boardsServiceClientImpl.onBoardsConfigChanged(updateStatusBar); - updateStatusBar(this.boardsServiceClientImpl.boardsConfig); - this.appStateService.reachedState('ready').then(async () => { - const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch && (!await this.sketchService.isTemp(sketch))) { - this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri))); - this.toDisposeOnStop.push(this.fileService.onDidFilesChange(async event => { - for (const { type, resource } of event.changes) { - if (type === FileChangeType.ADDED && resource.parent.toString() === sketch.uri) { - const reloadedSketch = await this.sketchService.loadSketch(sketch.uri) - if (Sketch.isInSketch(resource, reloadedSketch)) { - this.ensureOpened(resource.toString(), true, { mode: 'open' }); - } - } - } - })); - } - }); + protected invalidConfigPopup: + | Promise + | undefined; + protected toDisposeOnStop = new DisposableCollection(); + @postConstruct() + protected async init(): Promise { + if (!window.navigator.onLine) { + // tslint:disable-next-line:max-line-length + this.messageService.warn( + 'You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.' + ); } - - onStart(app: FrontendApplication): void { - // Initialize all `pro-mode` widgets. This is a NOOP if in normal mode. - for (const viewContribution of [ - this.fileNavigatorContributions, - this.outputContribution, - this.outlineContribution, - this.problemContribution, - this.scmContribution, - this.siwContribution, - this.sketchbookWidgetContribution] as Array) { - if (viewContribution.initializeLayout) { - viewContribution.initializeLayout(app); - } - } - const start = async ({ selectedBoard }: BoardsConfig.Config) => { - if (selectedBoard) { - const { name, fqbn } = selectedBoard; - if (fqbn) { - this.startLanguageServer(fqbn, name); - } - } - }; - this.boardsServiceClientImpl.onBoardsConfigChanged(start); - this.arduinoPreferences.onPreferenceChanged(event => { - if (event.preferenceName === 'arduino.language.log' && event.newValue !== event.oldValue) { - start(this.boardsServiceClientImpl.boardsConfig); - } - }); - this.arduinoPreferences.ready.then(() => { - const webContents = remote.getCurrentWebContents(); - const zoomLevel = this.arduinoPreferences.get('arduino.window.zoomLevel'); - webContents.setZoomLevel(zoomLevel); + const updateStatusBar = ({ + selectedBoard, + selectedPort, + }: BoardsConfig.Config) => { + this.statusBar.setElement('arduino-selected-board', { + alignment: StatusBarAlignment.RIGHT, + text: selectedBoard + ? `$(microchip) ${selectedBoard.name}` + : '$(close) no board selected', + className: 'arduino-selected-board', + }); + if (selectedBoard) { + this.statusBar.setElement('arduino-selected-port', { + alignment: StatusBarAlignment.RIGHT, + text: selectedPort + ? `on ${Port.toString(selectedPort)}` + : '[not connected]', + className: 'arduino-selected-port', }); - this.arduinoPreferences.onPreferenceChanged(event => { - if (event.preferenceName === 'arduino.window.zoomLevel' && typeof event.newValue === 'number' && event.newValue !== event.oldValue) { - const webContents = remote.getCurrentWebContents(); - webContents.setZoomLevel(event.newValue || 0); - } - }); - app.shell.leftPanelHandler.removeMenu('settings-menu'); - } - - onStop(): void { - this.toDisposeOnStop.dispose(); - } - - protected languageServerFqbn?: string; - protected languageServerStartMutex = new Mutex(); - protected async startLanguageServer(fqbn: string, name: string | undefined): Promise { - const release = await this.languageServerStartMutex.acquire(); - try { - await this.hostedPluginSupport.didStart; - const details = await this.boardsService.getBoardDetails({ fqbn }); - if (!details) { - // Core is not installed for the selected board. - console.info(`Could not start language server for ${fqbn}. The core is not installed for the board.`); - if (this.languageServerFqbn) { - try { - await this.commandRegistry.executeCommand('arduino.languageserver.stop'); - console.info(`Stopped language server process for ${this.languageServerFqbn}.`); - this.languageServerFqbn = undefined; - } catch (e) { - console.error(`Failed to start language server process for ${this.languageServerFqbn}`, e); - throw e; - } + } + }; + this.boardsServiceClientImpl.onBoardsConfigChanged(updateStatusBar); + updateStatusBar(this.boardsServiceClientImpl.boardsConfig); + this.appStateService.reachedState('ready').then(async () => { + const sketch = await this.sketchServiceClient.currentSketch(); + if (sketch && !(await this.sketchService.isTemp(sketch))) { + this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri))); + this.toDisposeOnStop.push( + this.fileService.onDidFilesChange(async (event) => { + for (const { type, resource } of event.changes) { + if ( + type === FileChangeType.ADDED && + resource.parent.toString() === sketch.uri + ) { + const reloadedSketch = await this.sketchService.loadSketch( + sketch.uri + ); + if (Sketch.isInSketch(resource, reloadedSketch)) { + this.ensureOpened(resource.toString(), true, { + mode: 'open', + }); } - return; - } - if (fqbn === this.languageServerFqbn) { - // NOOP - return; + } } - this.logger.info(`Starting language server: ${fqbn}`); - const log = this.arduinoPreferences.get('arduino.language.log'); - let currentSketchPath: string | undefined = undefined; - if (log) { - const currentSketch = await this.sketchServiceClient.currentSketch(); - if (currentSketch) { - currentSketchPath = await this.fileService.fsPath(new URI(currentSketch.uri)); - } - } - const { clangdUri, cliUri, lsUri } = await this.executableService.list(); - const [clangdPath, cliPath, lsPath, cliConfigPath] = await Promise.all([ - this.fileService.fsPath(new URI(clangdUri)), - this.fileService.fsPath(new URI(cliUri)), - this.fileService.fsPath(new URI(lsUri)), - this.fileService.fsPath(new URI(await this.configService.getCliConfigFileUri())) - ]); - this.languageServerFqbn = await Promise.race([ - new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${20_000} ms.`)), 20_000)), - this.commandRegistry.executeCommand('arduino.languageserver.start', { - lsPath, - cliPath, - clangdPath, - log: currentSketchPath ? currentSketchPath : log, - cliConfigPath, - board: { - fqbn, - name: name ? `"${name}"` : undefined - } - }) - ]); - } catch (e) { - console.log(`Failed to start language server for ${fqbn}`, e); - this.languageServerFqbn = undefined; - } finally { - release(); - } - } - - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem({ - id: BoardsToolBarItem.TOOLBAR_ID, - render: () => , - isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', - priority: 7 - }); - registry.registerItem({ - id: 'toggle-serial-monitor', - command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR, - tooltip: 'Serial Monitor' - }); - } - - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, { - execute: () => this.editorMode.toggleCompileForDebug(), - isToggled: () => this.editorMode.compileForDebug - }); - registry.registerCommand(ArduinoCommands.OPEN_SKETCH_FILES, { - execute: async (uri: URI) => { - this.openSketchFiles(uri); - } - }); - registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, { - execute: async (query?: string | undefined) => { - const boardsConfig = await this.boardsConfigDialog.open(query); - if (boardsConfig) { - this.boardsServiceClientImpl.boardsConfig = boardsConfig; - } - } - }); + }) + ); + } + }); + } + + onStart(app: FrontendApplication): void { + // Initialize all `pro-mode` widgets. This is a NOOP if in normal mode. + for (const viewContribution of [ + this.fileNavigatorContributions, + this.outputContribution, + this.outlineContribution, + this.problemContribution, + this.scmContribution, + this.siwContribution, + this.sketchbookWidgetContribution, + ] as Array) { + if (viewContribution.initializeLayout) { + viewContribution.initializeLayout(app); + } } - - registerMenus(registry: MenuModelRegistry) { - const menuId = (menuPath: string[]): string => { - const index = menuPath.length - 1; - const menuId = menuPath[index]; - return menuId; + const start = async ({ selectedBoard }: BoardsConfig.Config) => { + if (selectedBoard) { + const { name, fqbn } = selectedBoard; + if (fqbn) { + this.startLanguageServer(fqbn, name); } - registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(MonacoMenus.SELECTION)); - registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(EditorMainMenu.GO)); - registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL)); - registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW)); - - registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch'); - registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools'); - registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { - commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, - label: 'Optimize for Debugging', - order: '4' - }); - } - - protected async openSketchFiles(uri: URI): Promise { - try { - const sketch = await this.sketchService.loadSketch(uri.toString()); - const { mainFileUri, rootFolderFileUris } = sketch; - for (const uri of [mainFileUri, ...rootFolderFileUris]) { - await this.ensureOpened(uri); - } - await this.ensureOpened(mainFileUri, true); - if (mainFileUri.endsWith('.pde')) { - const message = `The '${sketch.name}' still uses the old \`.pde\` format. Do you want to switch to the new \`.ino\` extension?`; - this.messageService.info(message, 'Later', 'Yes').then(async answer => { - if (answer === 'Yes') { - this.commandRegistry.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { execOnlyIfTemp: false, openAfterMove: true, wipeOriginal: false }); - } - }); - } - } catch (e) { - console.error(e); - const message = e instanceof Error ? e.message : JSON.stringify(e); - this.messageService.error(message); + } + }; + this.boardsServiceClientImpl.onBoardsConfigChanged(start); + this.arduinoPreferences.onPreferenceChanged((event) => { + if ( + event.preferenceName === 'arduino.language.log' && + event.newValue !== event.oldValue + ) { + start(this.boardsServiceClientImpl.boardsConfig); + } + }); + this.arduinoPreferences.ready.then(() => { + const webContents = remote.getCurrentWebContents(); + const zoomLevel = this.arduinoPreferences.get('arduino.window.zoomLevel'); + webContents.setZoomLevel(zoomLevel); + }); + this.arduinoPreferences.onPreferenceChanged((event) => { + if ( + event.preferenceName === 'arduino.window.zoomLevel' && + typeof event.newValue === 'number' && + event.newValue !== event.oldValue + ) { + const webContents = remote.getCurrentWebContents(); + webContents.setZoomLevel(event.newValue || 0); + } + }); + app.shell.leftPanelHandler.removeMenu('settings-menu'); + } + + onStop(): void { + this.toDisposeOnStop.dispose(); + } + + protected languageServerFqbn?: string; + protected languageServerStartMutex = new Mutex(); + protected async startLanguageServer( + fqbn: string, + name: string | undefined + ): Promise { + const release = await this.languageServerStartMutex.acquire(); + try { + await this.hostedPluginSupport.didStart; + const details = await this.boardsService.getBoardDetails({ fqbn }); + if (!details) { + // Core is not installed for the selected board. + console.info( + `Could not start language server for ${fqbn}. The core is not installed for the board.` + ); + if (this.languageServerFqbn) { + try { + await this.commandRegistry.executeCommand( + 'arduino.languageserver.stop' + ); + console.info( + `Stopped language server process for ${this.languageServerFqbn}.` + ); + this.languageServerFqbn = undefined; + } catch (e) { + console.error( + `Failed to start language server process for ${this.languageServerFqbn}`, + e + ); + throw e; + } } - } - - protected async ensureOpened(uri: string, forceOpen = false, options?: EditorOpenerOptions | undefined): Promise { - const widget = this.editorManager.all.find(widget => widget.editor.uri.toString() === uri); - if (!widget || forceOpen) { - return this.editorManager.open(new URI(uri), options); + return; + } + if (fqbn === this.languageServerFqbn) { + // NOOP + return; + } + this.logger.info(`Starting language server: ${fqbn}`); + const log = this.arduinoPreferences.get('arduino.language.log'); + let currentSketchPath: string | undefined = undefined; + if (log) { + const currentSketch = await this.sketchServiceClient.currentSketch(); + if (currentSketch) { + currentSketchPath = await this.fileService.fsPath( + new URI(currentSketch.uri) + ); } - } - - registerColors(colors: ColorRegistry): void { - colors.register( - { - id: 'arduino.branding.primary', - defaults: { - dark: 'statusBar.background', - light: 'statusBar.background' - }, - description: 'The primary branding color, such as dialog titles, library, and board manager list labels.' - }, - { - id: 'arduino.branding.secondary', - defaults: { - dark: 'statusBar.background', - light: 'statusBar.background' - }, - description: 'Secondary branding color for list selections, dropdowns, and widget borders.' - }, - { - id: 'arduino.foreground', - defaults: { - dark: 'editorWidget.background', - light: 'editorWidget.background', - hc: 'editorWidget.background' - }, - description: 'Color of the Arduino IDE foreground which is used for dialogs, such as the Select Board dialog.' - }, - { - id: 'arduino.toolbar.background', - defaults: { - dark: 'button.background', - light: 'button.background', - hc: 'activityBar.inactiveForeground' - }, - description: 'Background color of the toolbar items. Such as Upload, Verify, etc.' - }, - { - id: 'arduino.toolbar.hoverBackground', - defaults: { - dark: 'button.hoverBackground', - light: 'button.foreground', - hc: 'textLink.foreground' - }, - description: 'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.' + } + const { clangdUri, cliUri, lsUri } = await this.executableService.list(); + const [clangdPath, cliPath, lsPath, cliConfigPath] = await Promise.all([ + this.fileService.fsPath(new URI(clangdUri)), + this.fileService.fsPath(new URI(cliUri)), + this.fileService.fsPath(new URI(lsUri)), + this.fileService.fsPath( + new URI(await this.configService.getCliConfigFileUri()) + ), + ]); + this.languageServerFqbn = await Promise.race([ + new Promise((_, reject) => + setTimeout( + () => reject(new Error(`Timeout after ${20_000} ms.`)), + 20_000 + ) + ), + this.commandRegistry.executeCommand( + 'arduino.languageserver.start', + { + lsPath, + cliPath, + clangdPath, + log: currentSketchPath ? currentSketchPath : log, + cliConfigPath, + board: { + fqbn, + name: name ? `"${name}"` : undefined, }, - { - id: 'arduino.toolbar.toggleBackground', - defaults: { - dark: 'editor.selectionBackground', - light: 'editor.selectionBackground', - hc: 'textPreformat.foreground' - }, - description: 'Toggle color of the toolbar items when they are currently toggled (the command is in progress)' - }, - { - id: 'arduino.output.foreground', - defaults: { - dark: 'editor.foreground', - light: 'editor.foreground', - hc: 'editor.foreground' - }, - description: 'Color of the text in the Output view.' - }, - { - id: 'arduino.output.background', - defaults: { - dark: 'editor.background', - light: 'editor.background', - hc: 'editor.background' - }, - description: 'Background color of the Output view.' + } + ), + ]); + } catch (e) { + console.log(`Failed to start language server for ${fqbn}`, e); + this.languageServerFqbn = undefined; + } finally { + release(); + } + } + + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem({ + id: BoardsToolBarItem.TOOLBAR_ID, + render: () => ( + + ), + isVisible: (widget) => + ArduinoToolbar.is(widget) && widget.side === 'left', + priority: 7, + }); + registry.registerItem({ + id: 'toggle-serial-monitor', + command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR, + tooltip: 'Serial Monitor', + }); + } + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, { + execute: () => this.editorMode.toggleCompileForDebug(), + isToggled: () => this.editorMode.compileForDebug, + }); + registry.registerCommand(ArduinoCommands.OPEN_SKETCH_FILES, { + execute: async (uri: URI) => { + this.openSketchFiles(uri); + }, + }); + registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, { + execute: async (query?: string | undefined) => { + const boardsConfig = await this.boardsConfigDialog.open(query); + if (boardsConfig) { + this.boardsServiceClientImpl.boardsConfig = boardsConfig; + } + }, + }); + } + + registerMenus(registry: MenuModelRegistry) { + const menuId = (menuPath: string[]): string => { + const index = menuPath.length - 1; + const menuId = menuPath[index]; + return menuId; + }; + registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(MonacoMenus.SELECTION)); + registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(EditorMainMenu.GO)); + registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL)); + registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW)); + + registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch'); + registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools'); + registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { + commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, + label: 'Optimize for Debugging', + order: '4', + }); + } + + protected async openSketchFiles(uri: URI): Promise { + try { + const sketch = await this.sketchService.loadSketch(uri.toString()); + const { mainFileUri, rootFolderFileUris } = sketch; + for (const uri of [mainFileUri, ...rootFolderFileUris]) { + await this.ensureOpened(uri); + } + await this.ensureOpened(mainFileUri, true); + if (mainFileUri.endsWith('.pde')) { + const message = `The '${sketch.name}' still uses the old \`.pde\` format. Do you want to switch to the new \`.ino\` extension?`; + this.messageService + .info(message, 'Later', 'Yes') + .then(async (answer) => { + if (answer === 'Yes') { + this.commandRegistry.executeCommand( + SaveAsSketch.Commands.SAVE_AS_SKETCH.id, + { + execOnlyIfTemp: false, + openAfterMove: true, + wipeOriginal: false, + } + ); } - ); + }); + } + } catch (e) { + console.error(e); + const message = e instanceof Error ? e.message : JSON.stringify(e); + this.messageService.error(message); } - + } + + protected async ensureOpened( + uri: string, + forceOpen = false, + options?: EditorOpenerOptions | undefined + ): Promise { + const widget = this.editorManager.all.find( + (widget) => widget.editor.uri.toString() === uri + ); + if (!widget || forceOpen) { + return this.editorManager.open(new URI(uri), options); + } + } + + registerColors(colors: ColorRegistry): void { + colors.register( + { + id: 'arduino.branding.primary', + defaults: { + dark: 'statusBar.background', + light: 'statusBar.background', + }, + description: + 'The primary branding color, such as dialog titles, library, and board manager list labels.', + }, + { + id: 'arduino.branding.secondary', + defaults: { + dark: 'statusBar.background', + light: 'statusBar.background', + }, + description: + 'Secondary branding color for list selections, dropdowns, and widget borders.', + }, + { + id: 'arduino.foreground', + defaults: { + dark: 'editorWidget.background', + light: 'editorWidget.background', + hc: 'editorWidget.background', + }, + description: + 'Color of the Arduino IDE foreground which is used for dialogs, such as the Select Board dialog.', + }, + { + id: 'arduino.toolbar.background', + defaults: { + dark: 'button.background', + light: 'button.background', + hc: 'activityBar.inactiveForeground', + }, + description: + 'Background color of the toolbar items. Such as Upload, Verify, etc.', + }, + { + id: 'arduino.toolbar.hoverBackground', + defaults: { + dark: 'button.hoverBackground', + light: 'button.foreground', + hc: 'textLink.foreground', + }, + description: + 'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.', + }, + { + id: 'arduino.toolbar.toggleBackground', + defaults: { + dark: 'editor.selectionBackground', + light: 'editor.selectionBackground', + hc: 'textPreformat.foreground', + }, + description: + 'Toggle color of the toolbar items when they are currently toggled (the command is in progress)', + }, + { + id: 'arduino.output.foreground', + defaults: { + dark: 'editor.foreground', + light: 'editor.foreground', + hc: 'editor.foreground', + }, + description: 'Color of the text in the Output view.', + }, + { + id: 'arduino.output.background', + defaults: { + dark: 'editor.background', + light: 'editor.background', + hc: 'editor.background', + }, + description: 'Background color of the Output view.', + } + ); + } } 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 efb0205a5..1cb6f0294 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -4,27 +4,27 @@ import { WidgetFactory } from '@theia/core/lib/browser/widget-manager'; import { CommandContribution } from '@theia/core/lib/common/command'; import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; import { - TabBarToolbarContribution, - TabBarToolbarFactory, + TabBarToolbarContribution, + TabBarToolbarFactory, } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider'; import { - FrontendApplicationContribution, - FrontendApplication as TheiaFrontendApplication, + FrontendApplicationContribution, + FrontendApplication as TheiaFrontendApplication, } from '@theia/core/lib/browser/frontend-application'; import { LibraryListWidget } from './library/library-list-widget'; import { ArduinoFrontendContribution } from './arduino-frontend-contribution'; import { - LibraryService, - LibraryServicePath, + LibraryService, + LibraryServicePath, } from '../common/protocol/library-service'; import { - BoardsService, - BoardsServicePath, + BoardsService, + BoardsServicePath, } from '../common/protocol/boards-service'; import { - SketchesService, - SketchesServicePath, + SketchesService, + SketchesServicePath, } from '../common/protocol/sketches-service'; import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl'; import { CoreService, CoreServicePath } from '../common/protocol/core-service'; @@ -47,21 +47,21 @@ import { EditorContribution } from './theia/editor/editor-contribution'; import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution'; import { MonacoStatusBarContribution } from './theia/monaco/monaco-status-bar-contribution'; import { - ApplicationShell as TheiaApplicationShell, - ShellLayoutRestorer as TheiaShellLayoutRestorer, - CommonFrontendContribution as TheiaCommonFrontendContribution, - KeybindingRegistry as TheiaKeybindingRegistry, - TabBarRendererFactory, - ContextMenuRenderer, - createTreeContainer, - TreeWidget, + ApplicationShell as TheiaApplicationShell, + ShellLayoutRestorer as TheiaShellLayoutRestorer, + CommonFrontendContribution as TheiaCommonFrontendContribution, + KeybindingRegistry as TheiaKeybindingRegistry, + TabBarRendererFactory, + ContextMenuRenderer, + createTreeContainer, + TreeWidget, } from '@theia/core/lib/browser'; import { MenuContribution } from '@theia/core/lib/common/menu'; import { ApplicationShell } from './theia/core/application-shell'; import { FrontendApplication } from './theia/core/frontend-application'; import { - BoardsConfigDialog, - BoardsConfigDialogProps, + BoardsConfigDialog, + BoardsConfigDialogProps, } from './boards/boards-config-dialog'; import { BoardsConfigDialogWidget } from './boards/boards-config-dialog-widget'; import { ScmContribution as TheiaScmContribution } from '@theia/scm/lib/browser/scm-contribution'; @@ -71,13 +71,13 @@ import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspa import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution'; import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl'; import { - MonitorServicePath, - MonitorService, - MonitorServiceClient, + MonitorServicePath, + MonitorService, + MonitorServiceClient, } from '../common/protocol/monitor-service'; import { - ConfigService, - ConfigServicePath, + ConfigService, + ConfigServicePath, } from '../common/protocol/config-service'; import { MonitorWidget } from './monitor/monitor-widget'; import { MonitorViewContribution } from './monitor/monitor-view-contribution'; @@ -94,33 +94,33 @@ import { ListItemRenderer } from './widgets/component-list/list-item-renderer'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service'; import { - ArduinoDaemonPath, - ArduinoDaemon, + ArduinoDaemonPath, + ArduinoDaemon, } from '../common/protocol/arduino-daemon'; import { EditorCommandContribution as TheiaEditorCommandContribution } from '@theia/editor/lib/browser'; import { - FrontendConnectionStatusService, - ApplicationConnectionStatusContribution, + FrontendConnectionStatusService, + ApplicationConnectionStatusContribution, } from './theia/core/connection-status-service'; import { - FrontendConnectionStatusService as TheiaFrontendConnectionStatusService, - ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution, + FrontendConnectionStatusService as TheiaFrontendConnectionStatusService, + ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution, } from '@theia/core/lib/browser/connection-status-service'; import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater'; import { BoardsDataStore } from './boards/boards-data-store'; import { ILogger } from '@theia/core'; import { - FileSystemExt, - FileSystemExtPath, + FileSystemExt, + FileSystemExtPath, } from '../common/protocol/filesystem-ext'; import { - WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution, - FileMenuContribution as TheiaFileMenuContribution, - WorkspaceCommandContribution as TheiaWorkspaceCommandContribution, + WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution, + FileMenuContribution as TheiaFileMenuContribution, + WorkspaceCommandContribution as TheiaWorkspaceCommandContribution, } from '@theia/workspace/lib/browser'; import { - WorkspaceFrontendContribution, - ArduinoFileMenuContribution, + WorkspaceFrontendContribution, + ArduinoFileMenuContribution, } from './theia/workspace/workspace-frontend-contribution'; import { Contribution } from './contributions/contribution'; import { NewSketch } from './contributions/new-sketch'; @@ -149,29 +149,29 @@ import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/out import { OutputWidget } from './theia/output/output-widget'; import { BurnBootloader } from './contributions/burn-bootloader'; import { - ExamplesServicePath, - ExamplesService, + ExamplesServicePath, + ExamplesService, } from '../common/protocol/examples-service'; import { BuiltInExamples, LibraryExamples } from './contributions/examples'; import { IncludeLibrary } from './contributions/include-library'; import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/common/output-channel'; import { OutputChannelManager } from './theia/output/output-channel'; import { - OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, - OutputChannelRegistryMainImpl, + OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, + OutputChannelRegistryMainImpl, } from './theia/plugin-ext/output-channel-registry-main'; import { ExecutableService, ExecutableServicePath } from '../common/protocol'; import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service'; import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service'; import { ResponseServiceImpl } from './response-service-impl'; import { - ResponseServicePath, - ResponseService, + ResponseServicePath, + ResponseService, } from '../common/protocol/response-service'; import { NotificationCenter } from './notification-center'; import { - NotificationServicePath, - NotificationServiceServer, + NotificationServicePath, + NotificationServiceServer, } from '../common/protocol'; import { About } from './contributions/about'; import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service'; @@ -190,10 +190,10 @@ import { OpenRecentSketch } from './contributions/open-recent-sketch'; import { Help } from './contributions/help'; import { bindArduinoPreferences } from './arduino-preferences'; import { - SettingsService, - SettingsDialog, - SettingsWidget, - SettingsDialogProps, + SettingsService, + SettingsDialog, + SettingsWidget, + SettingsDialogProps, } from './settings'; import { AddFile } from './contributions/add-file'; import { ArchiveSketch } from './contributions/archive-sketch'; @@ -226,8 +226,8 @@ import { CreateApi } from './create/create-api'; import { ShareSketchDialog } from './dialogs.ts/cloud-share-sketch-dialog'; import { AuthenticationClientService } from './auth/authentication-client-service'; import { - AuthenticationService, - AuthenticationServicePath, + AuthenticationService, + AuthenticationServicePath, } from '../common/protocol/authentication-service'; import { CreateFsProvider } from './create/create-fs-provider'; import { FileServiceContribution } from '@theia/filesystem/lib/browser/file-service'; @@ -240,483 +240,474 @@ import { createSketchbookTreeWidget } from './widgets/sketchbook/sketchbook-tree const ElementQueries = require('css-element-queries/src/ElementQueries'); MonacoThemingService.register({ - id: 'arduino-theme', - label: 'Light (Arduino)', - uiTheme: 'vs', - json: require('../../src/browser/data/arduino.color-theme.json'), + id: 'arduino-theme', + label: 'Light (Arduino)', + uiTheme: 'vs', + json: require('../../src/browser/data/arduino.color-theme.json'), }); export default new ContainerModule((bind, unbind, isBound, rebind) => { - ElementQueries.listen(); - ElementQueries.init(); - - // Commands and toolbar items - bind(ArduinoFrontendContribution).toSelf().inSingletonScope(); - bind(CommandContribution).toService(ArduinoFrontendContribution); - bind(MenuContribution).toService(ArduinoFrontendContribution); - bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution); - bind(FrontendApplicationContribution).toService( - ArduinoFrontendContribution - ); - bind(ColorContribution).toService(ArduinoFrontendContribution); - - bind(ArduinoToolbarContribution).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution); - - // Renderer for both the library and the core widgets. - bind(ListItemRenderer).toSelf().inSingletonScope(); - - // Library service - bind(LibraryService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - LibraryServicePath - ) - ) - .inSingletonScope(); - - // Library list widget - bind(LibraryListWidget).toSelf(); - bindViewContribution(bind, LibraryListWidgetFrontendContribution); - bind(WidgetFactory).toDynamicValue((context) => ({ - id: LibraryListWidget.WIDGET_ID, - createWidget: () => context.container.get(LibraryListWidget), - })); - bind(FrontendApplicationContribution).toService( - LibraryListWidgetFrontendContribution - ); - - // Sketch list service - bind(SketchesService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - SketchesServicePath - ) - ) - .inSingletonScope(); - bind(SketchesServiceClientImpl).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(SketchesServiceClientImpl); - - // Config service - bind(ConfigService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - ConfigServicePath - ) - ) - .inSingletonScope(); - - // Boards service - bind(BoardsService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - BoardsServicePath - ) - ) - .inSingletonScope(); - // Boards service client to receive and delegate notifications from the backend. - bind(BoardsServiceProvider).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(BoardsServiceProvider); - - // To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board. - bind(FrontendApplicationContribution) - .to(BoardsDataMenuUpdater) - .inSingletonScope(); - bind(BoardsDataStore).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(BoardsDataStore); - // Logger for the Arduino daemon - bind(ILogger) - .toDynamicValue((ctx) => { - const parentLogger = ctx.container.get(ILogger); - return parentLogger.child('store'); - }) - .inSingletonScope() - .whenTargetNamed('store'); - - // Boards auto-installer - bind(BoardsAutoInstaller).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(BoardsAutoInstaller); - - // Boards list widget - bind(BoardsListWidget).toSelf(); - bindViewContribution(bind, BoardsListWidgetFrontendContribution); - bind(WidgetFactory).toDynamicValue((context) => ({ - id: BoardsListWidget.WIDGET_ID, - createWidget: () => context.container.get(BoardsListWidget), - })); - bind(FrontendApplicationContribution).toService( - BoardsListWidgetFrontendContribution - ); - - // Board select dialog - bind(BoardsConfigDialogWidget).toSelf().inSingletonScope(); - bind(BoardsConfigDialog).toSelf().inSingletonScope(); - bind(BoardsConfigDialogProps).toConstantValue({ - title: 'Select Board', - }); - - // Core service - bind(CoreService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - CoreServicePath - ) - ) - .inSingletonScope(); - - // Serial monitor - bind(MonitorModel).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(MonitorModel); - bind(MonitorWidget).toSelf(); - bindViewContribution(bind, MonitorViewContribution); - bind(TabBarToolbarContribution).toService(MonitorViewContribution); - bind(WidgetFactory).toDynamicValue((context) => ({ - id: MonitorWidget.ID, - createWidget: () => context.container.get(MonitorWidget), - })); - // Frontend binding for the serial monitor service - bind(MonitorService) - .toDynamicValue((context) => { - const connection = context.container.get( - WebSocketConnectionProvider - ); - const client = context.container.get(MonitorServiceClientImpl); - return connection.createProxy(MonitorServicePath, client); - }) - .inSingletonScope(); - bind(MonitorConnection).toSelf().inSingletonScope(); - // Serial monitor service client to receive and delegate notifications from the backend. - bind(MonitorServiceClientImpl).toSelf().inSingletonScope(); - bind(MonitorServiceClient) - .toDynamicValue((context) => { - const client = context.container.get(MonitorServiceClientImpl); - WebSocketConnectionProvider.createProxy( - context.container, - MonitorServicePath, - client - ); - return client; - }) - .inSingletonScope(); - - bind(WorkspaceService).toSelf().inSingletonScope(); - rebind(TheiaWorkspaceService).toService(WorkspaceService); - bind(WorkspaceVariableContribution).toSelf().inSingletonScope(); - rebind(TheiaWorkspaceVariableContribution).toService( - WorkspaceVariableContribution - ); - - // Customizing default Theia layout based on the editor mode: `pro-mode` or `classic`. - bind(EditorMode).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(EditorMode); - - // Layout and shell customizations. - rebind(TheiaOutlineViewContribution) - .to(OutlineViewContribution) - .inSingletonScope(); - rebind(TheiaProblemContribution).to(ProblemContribution).inSingletonScope(); - rebind(TheiaFileNavigatorContribution) - .to(FileNavigatorContribution) - .inSingletonScope(); - rebind(TheiaKeymapsFrontendContribution) - .to(KeymapsFrontendContribution) - .inSingletonScope(); - rebind(TheiaEditorContribution).to(EditorContribution).inSingletonScope(); - rebind(TheiaMonacoStatusBarContribution) - .to(MonacoStatusBarContribution) - .inSingletonScope(); - rebind(TheiaApplicationShell).to(ApplicationShell).inSingletonScope(); - rebind(TheiaScmContribution).to(ScmContribution).inSingletonScope(); - rebind(TheiaSearchInWorkspaceFrontendContribution) - .to(SearchInWorkspaceFrontendContribution) - .inSingletonScope(); - rebind(TheiaFrontendApplication).to(FrontendApplication).inSingletonScope(); - rebind(TheiaWorkspaceFrontendContribution) - .to(WorkspaceFrontendContribution) - .inSingletonScope(); - rebind(TheiaFileMenuContribution) - .to(ArduinoFileMenuContribution) - .inSingletonScope(); - rebind(TheiaCommonFrontendContribution) - .to(CommonFrontendContribution) - .inSingletonScope(); - rebind(TheiaPreferencesContribution) - .to(PreferencesContribution) - .inSingletonScope(); - rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope(); - rebind(TheiaWorkspaceCommandContribution) - .to(WorkspaceCommandContribution) - .inSingletonScope(); - rebind(TheiaWorkspaceDeleteHandler) - .to(WorkspaceDeleteHandler) - .inSingletonScope(); - rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope(); - rebind(TabBarToolbarFactory).toFactory( - ({ container: parentContainer }) => - () => { - const container = parentContainer.createChild(); - container.bind(TabBarToolbar).toSelf().inSingletonScope(); - return container.get(TabBarToolbar); - } - ); - bind(OutputWidget).toSelf().inSingletonScope(); - rebind(TheiaOutputWidget).toService(OutputWidget); - bind(OutputChannelManager).toSelf().inSingletonScope(); - rebind(TheiaOutputChannelManager).toService(OutputChannelManager); - bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope(); - rebind(TheiaOutputChannelRegistryMainImpl).toService( - OutputChannelRegistryMainImpl - ); - bind(MonacoTextModelService).toSelf().inSingletonScope(); - rebind(TheiaMonacoTextModelService).toService(MonacoTextModelService); - bind(MonacoEditorProvider).toSelf().inSingletonScope(); - rebind(TheiaMonacoEditorProvider).toService(MonacoEditorProvider); - - bind(SearchInWorkspaceWidget).toSelf(); - rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget); - rebind(TheiaSearchInWorkspaceResultTreeWidget).toDynamicValue( - ({ container }) => { - const childContainer = createTreeContainer(container); - childContainer.bind(SearchInWorkspaceResultTreeWidget).toSelf(); - childContainer - .rebind(TreeWidget) - .toService(SearchInWorkspaceResultTreeWidget); - return childContainer.get(SearchInWorkspaceResultTreeWidget); - } - ); - - // Show a disconnected status bar, when the daemon is not available - bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope(); - rebind(TheiaApplicationConnectionStatusContribution).toService( - ApplicationConnectionStatusContribution - ); - bind(FrontendConnectionStatusService).toSelf().inSingletonScope(); - rebind(TheiaFrontendConnectionStatusService).toService( - FrontendConnectionStatusService - ); - - // Decorator customizations - bind(TabBarDecoratorService).toSelf().inSingletonScope(); - rebind(TheiaTabBarDecoratorService).toService(TabBarDecoratorService); - - // Problem markers - bind(ProblemManager).toSelf().inSingletonScope(); - rebind(TheiaProblemManager).toService(ProblemManager); - - // Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579 - bind(ShellLayoutRestorer).toSelf().inSingletonScope(); - rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer); - - // No dropdown for the _Output_ view. - bind(OutputToolbarContribution).toSelf().inSingletonScope(); - rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution); - - bind(ArduinoDaemon) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - ArduinoDaemonPath - ) - ) - .inSingletonScope(); - - // File-system extension - bind(FileSystemExt) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - FileSystemExtPath - ) - ) - .inSingletonScope(); - - // Examples service@ - bind(ExamplesService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - ExamplesServicePath - ) - ) - .inSingletonScope(); - - // Executable URIs known by the backend - bind(ExecutableService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - ExecutableServicePath - ) - ) - .inSingletonScope(); - - Contribution.configure(bind, NewSketch); - Contribution.configure(bind, OpenSketch); - Contribution.configure(bind, Close); - Contribution.configure(bind, SaveSketch); - Contribution.configure(bind, SaveAsSketch); - Contribution.configure(bind, VerifySketch); - Contribution.configure(bind, UploadSketch); - Contribution.configure(bind, OpenSketchExternal); - Contribution.configure(bind, EditContributions); - Contribution.configure(bind, QuitApp); - Contribution.configure(bind, SketchControl); - Contribution.configure(bind, Settings); - Contribution.configure(bind, BurnBootloader); - Contribution.configure(bind, BuiltInExamples); - Contribution.configure(bind, LibraryExamples); - Contribution.configure(bind, IncludeLibrary); - Contribution.configure(bind, About); - Contribution.configure(bind, Debug); - Contribution.configure(bind, Sketchbook); - Contribution.configure(bind, BoardSelection); - Contribution.configure(bind, OpenRecentSketch); - Contribution.configure(bind, Help); - Contribution.configure(bind, AddFile); - Contribution.configure(bind, ArchiveSketch); - Contribution.configure(bind, AddZipLibrary); - - bind(ResponseServiceImpl) - .toSelf() - .inSingletonScope() - .onActivation(({ container }, responseService) => { - WebSocketConnectionProvider.createProxy( - container, - ResponseServicePath, - responseService - ); - return responseService; - }); - bind(ResponseService).toService(ResponseServiceImpl); - - bind(NotificationCenter).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(NotificationCenter); - bind(NotificationServiceServer) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - NotificationServicePath - ) - ) - .inSingletonScope(); - - // Enable the dirty indicator on uncloseable widgets. - rebind(TabBarRendererFactory).toFactory((context) => () => { - const contextMenuRenderer = - context.container.get(ContextMenuRenderer); - const decoratorService = context.container.get( - TabBarDecoratorService - ); - const iconThemeService = - context.container.get(IconThemeService); - return new TabBarRenderer( - contextMenuRenderer, - decoratorService, - iconThemeService - ); - }); - - // Workaround for https://github.com/eclipse-theia/theia/issues/8722 - // Do not trigger a save on IDE startup if `"editor.autoSave": "on"` was set as a preference. - bind(EditorCommandContribution).toSelf().inSingletonScope(); - rebind(TheiaEditorCommandContribution).toService(EditorCommandContribution); - - // Silent the badge decoration in the Explorer view. - bind(NavigatorTabBarDecorator).toSelf().inSingletonScope(); - rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator); - - // To avoid running `Save All` when there are no dirty editors before starting the debug session. - bind(DebugSessionManager).toSelf().inSingletonScope(); - rebind(TheiaDebugSessionManager).toService(DebugSessionManager); - // To remove the `Run` menu item from the application menu. - bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope(); - rebind(TheiaDebugFrontendApplicationContribution).toService( - DebugFrontendApplicationContribution - ); - // To be able to use a `launch.json` from outside of the workspace. - bind(DebugConfigurationManager).toSelf().inSingletonScope(); - rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager); - - // Patch for the debug hover: https://github.com/eclipse-theia/theia/pull/9256/ - rebind(DebugEditorModelFactory) - .toDynamicValue( - ({ container }) => - ( - ((editor) => - DebugEditorModel.createModel(container, editor)) - ) - ) - .inSingletonScope(); - - // Preferences - bindArduinoPreferences(bind); - - // Settings wrapper for the preferences and the CLI config. - bind(SettingsService).toSelf().inSingletonScope(); - // Settings dialog and widget - bind(SettingsWidget).toSelf().inSingletonScope(); - bind(SettingsDialog).toSelf().inSingletonScope(); - bind(SettingsDialogProps).toConstantValue({ - title: 'Preferences', + ElementQueries.listen(); + ElementQueries.init(); + + // Commands and toolbar items + bind(ArduinoFrontendContribution).toSelf().inSingletonScope(); + bind(CommandContribution).toService(ArduinoFrontendContribution); + bind(MenuContribution).toService(ArduinoFrontendContribution); + bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution); + bind(FrontendApplicationContribution).toService(ArduinoFrontendContribution); + bind(ColorContribution).toService(ArduinoFrontendContribution); + + bind(ArduinoToolbarContribution).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution); + + // Renderer for both the library and the core widgets. + bind(ListItemRenderer).toSelf().inSingletonScope(); + + // Library service + bind(LibraryService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + LibraryServicePath + ) + ) + .inSingletonScope(); + + // Library list widget + bind(LibraryListWidget).toSelf(); + bindViewContribution(bind, LibraryListWidgetFrontendContribution); + bind(WidgetFactory).toDynamicValue((context) => ({ + id: LibraryListWidget.WIDGET_ID, + createWidget: () => context.container.get(LibraryListWidget), + })); + bind(FrontendApplicationContribution).toService( + LibraryListWidgetFrontendContribution + ); + + // Sketch list service + bind(SketchesService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + SketchesServicePath + ) + ) + .inSingletonScope(); + bind(SketchesServiceClientImpl).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(SketchesServiceClientImpl); + + // Config service + bind(ConfigService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + ConfigServicePath + ) + ) + .inSingletonScope(); + + // Boards service + bind(BoardsService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + BoardsServicePath + ) + ) + .inSingletonScope(); + // Boards service client to receive and delegate notifications from the backend. + bind(BoardsServiceProvider).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(BoardsServiceProvider); + + // To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board. + bind(FrontendApplicationContribution) + .to(BoardsDataMenuUpdater) + .inSingletonScope(); + bind(BoardsDataStore).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(BoardsDataStore); + // Logger for the Arduino daemon + bind(ILogger) + .toDynamicValue((ctx) => { + const parentLogger = ctx.container.get(ILogger); + return parentLogger.child('store'); + }) + .inSingletonScope() + .whenTargetNamed('store'); + + // Boards auto-installer + bind(BoardsAutoInstaller).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(BoardsAutoInstaller); + + // Boards list widget + bind(BoardsListWidget).toSelf(); + bindViewContribution(bind, BoardsListWidgetFrontendContribution); + bind(WidgetFactory).toDynamicValue((context) => ({ + id: BoardsListWidget.WIDGET_ID, + createWidget: () => context.container.get(BoardsListWidget), + })); + bind(FrontendApplicationContribution).toService( + BoardsListWidgetFrontendContribution + ); + + // Board select dialog + bind(BoardsConfigDialogWidget).toSelf().inSingletonScope(); + bind(BoardsConfigDialog).toSelf().inSingletonScope(); + bind(BoardsConfigDialogProps).toConstantValue({ + title: 'Select Board', + }); + + // Core service + bind(CoreService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + CoreServicePath + ) + ) + .inSingletonScope(); + + // Serial monitor + bind(MonitorModel).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(MonitorModel); + bind(MonitorWidget).toSelf(); + bindViewContribution(bind, MonitorViewContribution); + bind(TabBarToolbarContribution).toService(MonitorViewContribution); + bind(WidgetFactory).toDynamicValue((context) => ({ + id: MonitorWidget.ID, + createWidget: () => context.container.get(MonitorWidget), + })); + // Frontend binding for the serial monitor service + bind(MonitorService) + .toDynamicValue((context) => { + const connection = context.container.get(WebSocketConnectionProvider); + const client = context.container.get(MonitorServiceClientImpl); + return connection.createProxy(MonitorServicePath, client); + }) + .inSingletonScope(); + bind(MonitorConnection).toSelf().inSingletonScope(); + // Serial monitor service client to receive and delegate notifications from the backend. + bind(MonitorServiceClientImpl).toSelf().inSingletonScope(); + bind(MonitorServiceClient) + .toDynamicValue((context) => { + const client = context.container.get(MonitorServiceClientImpl); + WebSocketConnectionProvider.createProxy( + context.container, + MonitorServicePath, + client + ); + return client; + }) + .inSingletonScope(); + + bind(WorkspaceService).toSelf().inSingletonScope(); + rebind(TheiaWorkspaceService).toService(WorkspaceService); + bind(WorkspaceVariableContribution).toSelf().inSingletonScope(); + rebind(TheiaWorkspaceVariableContribution).toService( + WorkspaceVariableContribution + ); + + // Customizing default Theia layout based on the editor mode: `pro-mode` or `classic`. + bind(EditorMode).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(EditorMode); + + // Layout and shell customizations. + rebind(TheiaOutlineViewContribution) + .to(OutlineViewContribution) + .inSingletonScope(); + rebind(TheiaProblemContribution).to(ProblemContribution).inSingletonScope(); + rebind(TheiaFileNavigatorContribution) + .to(FileNavigatorContribution) + .inSingletonScope(); + rebind(TheiaKeymapsFrontendContribution) + .to(KeymapsFrontendContribution) + .inSingletonScope(); + rebind(TheiaEditorContribution).to(EditorContribution).inSingletonScope(); + rebind(TheiaMonacoStatusBarContribution) + .to(MonacoStatusBarContribution) + .inSingletonScope(); + rebind(TheiaApplicationShell).to(ApplicationShell).inSingletonScope(); + rebind(TheiaScmContribution).to(ScmContribution).inSingletonScope(); + rebind(TheiaSearchInWorkspaceFrontendContribution) + .to(SearchInWorkspaceFrontendContribution) + .inSingletonScope(); + rebind(TheiaFrontendApplication).to(FrontendApplication).inSingletonScope(); + rebind(TheiaWorkspaceFrontendContribution) + .to(WorkspaceFrontendContribution) + .inSingletonScope(); + rebind(TheiaFileMenuContribution) + .to(ArduinoFileMenuContribution) + .inSingletonScope(); + rebind(TheiaCommonFrontendContribution) + .to(CommonFrontendContribution) + .inSingletonScope(); + rebind(TheiaPreferencesContribution) + .to(PreferencesContribution) + .inSingletonScope(); + rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope(); + rebind(TheiaWorkspaceCommandContribution) + .to(WorkspaceCommandContribution) + .inSingletonScope(); + rebind(TheiaWorkspaceDeleteHandler) + .to(WorkspaceDeleteHandler) + .inSingletonScope(); + rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope(); + rebind(TabBarToolbarFactory).toFactory( + ({ container: parentContainer }) => + () => { + const container = parentContainer.createChild(); + container.bind(TabBarToolbar).toSelf().inSingletonScope(); + return container.get(TabBarToolbar); + } + ); + bind(OutputWidget).toSelf().inSingletonScope(); + rebind(TheiaOutputWidget).toService(OutputWidget); + bind(OutputChannelManager).toSelf().inSingletonScope(); + rebind(TheiaOutputChannelManager).toService(OutputChannelManager); + bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope(); + rebind(TheiaOutputChannelRegistryMainImpl).toService( + OutputChannelRegistryMainImpl + ); + bind(MonacoTextModelService).toSelf().inSingletonScope(); + rebind(TheiaMonacoTextModelService).toService(MonacoTextModelService); + bind(MonacoEditorProvider).toSelf().inSingletonScope(); + rebind(TheiaMonacoEditorProvider).toService(MonacoEditorProvider); + + bind(SearchInWorkspaceWidget).toSelf(); + rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget); + rebind(TheiaSearchInWorkspaceResultTreeWidget).toDynamicValue( + ({ container }) => { + const childContainer = createTreeContainer(container); + childContainer.bind(SearchInWorkspaceResultTreeWidget).toSelf(); + childContainer + .rebind(TreeWidget) + .toService(SearchInWorkspaceResultTreeWidget); + return childContainer.get(SearchInWorkspaceResultTreeWidget); + } + ); + + // Show a disconnected status bar, when the daemon is not available + bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope(); + rebind(TheiaApplicationConnectionStatusContribution).toService( + ApplicationConnectionStatusContribution + ); + bind(FrontendConnectionStatusService).toSelf().inSingletonScope(); + rebind(TheiaFrontendConnectionStatusService).toService( + FrontendConnectionStatusService + ); + + // Decorator customizations + bind(TabBarDecoratorService).toSelf().inSingletonScope(); + rebind(TheiaTabBarDecoratorService).toService(TabBarDecoratorService); + + // Problem markers + bind(ProblemManager).toSelf().inSingletonScope(); + rebind(TheiaProblemManager).toService(ProblemManager); + + // Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579 + bind(ShellLayoutRestorer).toSelf().inSingletonScope(); + rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer); + + // No dropdown for the _Output_ view. + bind(OutputToolbarContribution).toSelf().inSingletonScope(); + rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution); + + bind(ArduinoDaemon) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + ArduinoDaemonPath + ) + ) + .inSingletonScope(); + + // File-system extension + bind(FileSystemExt) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + FileSystemExtPath + ) + ) + .inSingletonScope(); + + // Examples service@ + bind(ExamplesService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + ExamplesServicePath + ) + ) + .inSingletonScope(); + + // Executable URIs known by the backend + bind(ExecutableService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + ExecutableServicePath + ) + ) + .inSingletonScope(); + + Contribution.configure(bind, NewSketch); + Contribution.configure(bind, OpenSketch); + Contribution.configure(bind, Close); + Contribution.configure(bind, SaveSketch); + Contribution.configure(bind, SaveAsSketch); + Contribution.configure(bind, VerifySketch); + Contribution.configure(bind, UploadSketch); + Contribution.configure(bind, OpenSketchExternal); + Contribution.configure(bind, EditContributions); + Contribution.configure(bind, QuitApp); + Contribution.configure(bind, SketchControl); + Contribution.configure(bind, Settings); + Contribution.configure(bind, BurnBootloader); + Contribution.configure(bind, BuiltInExamples); + Contribution.configure(bind, LibraryExamples); + Contribution.configure(bind, IncludeLibrary); + Contribution.configure(bind, About); + Contribution.configure(bind, Debug); + Contribution.configure(bind, Sketchbook); + Contribution.configure(bind, BoardSelection); + Contribution.configure(bind, OpenRecentSketch); + Contribution.configure(bind, Help); + Contribution.configure(bind, AddFile); + Contribution.configure(bind, ArchiveSketch); + Contribution.configure(bind, AddZipLibrary); + + bind(ResponseServiceImpl) + .toSelf() + .inSingletonScope() + .onActivation(({ container }, responseService) => { + WebSocketConnectionProvider.createProxy( + container, + ResponseServicePath, + responseService + ); + return responseService; }); - - bind(StorageWrapper).toSelf().inSingletonScope(); - bind(CommandContribution).toService(StorageWrapper); - - bind(NotificationManager).toSelf().inSingletonScope(); - rebind(TheiaNotificationManager).toService(NotificationManager); - bind(NotificationsRenderer).toSelf().inSingletonScope(); - rebind(TheiaNotificationsRenderer).toService(NotificationsRenderer); - - // UI for the Sketchbook - bind(SketchbookWidget).toSelf(); - bind(SketchbookTreeWidget).toDynamicValue(({ container }) => - createSketchbookTreeWidget(container) - ); - bindViewContribution(bind, SketchbookWidgetContribution); - bind(FrontendApplicationContribution).toService( - SketchbookWidgetContribution - ); - bind(WidgetFactory).toDynamicValue(({ container }) => ({ - id: 'arduino-sketchbook-widget', - createWidget: () => container.get(SketchbookWidget), - })); - - bind(CloudSketchbookWidget).toSelf(); - rebind(SketchbookWidget).toService(CloudSketchbookWidget); - bind(CloudSketchbookTreeWidget).toDynamicValue(({ container }) => - createCloudSketchbookTreeWidget(container) + bind(ResponseService).toService(ResponseServiceImpl); + + bind(NotificationCenter).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(NotificationCenter); + bind(NotificationServiceServer) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + NotificationServicePath + ) + ) + .inSingletonScope(); + + // Enable the dirty indicator on uncloseable widgets. + rebind(TabBarRendererFactory).toFactory((context) => () => { + const contextMenuRenderer = + context.container.get(ContextMenuRenderer); + const decoratorService = context.container.get( + TabBarDecoratorService ); - bind(CreateApi).toSelf().inSingletonScope(); - bind(ShareSketchDialog).toSelf().inSingletonScope(); - bind(AuthenticationClientService).toSelf().inSingletonScope(); - bind(CommandContribution).toService(AuthenticationClientService); - bind(FrontendApplicationContribution).toService( - AuthenticationClientService + const iconThemeService = + context.container.get(IconThemeService); + return new TabBarRenderer( + contextMenuRenderer, + decoratorService, + iconThemeService ); - bind(AuthenticationService) - .toDynamicValue((context) => - WebSocketConnectionProvider.createProxy( - context.container, - AuthenticationServicePath - ) + }); + + // Workaround for https://github.com/eclipse-theia/theia/issues/8722 + // Do not trigger a save on IDE startup if `"editor.autoSave": "on"` was set as a preference. + bind(EditorCommandContribution).toSelf().inSingletonScope(); + rebind(TheiaEditorCommandContribution).toService(EditorCommandContribution); + + // Silent the badge decoration in the Explorer view. + bind(NavigatorTabBarDecorator).toSelf().inSingletonScope(); + rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator); + + // To avoid running `Save All` when there are no dirty editors before starting the debug session. + bind(DebugSessionManager).toSelf().inSingletonScope(); + rebind(TheiaDebugSessionManager).toService(DebugSessionManager); + // To remove the `Run` menu item from the application menu. + bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope(); + rebind(TheiaDebugFrontendApplicationContribution).toService( + DebugFrontendApplicationContribution + ); + // To be able to use a `launch.json` from outside of the workspace. + bind(DebugConfigurationManager).toSelf().inSingletonScope(); + rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager); + + // Patch for the debug hover: https://github.com/eclipse-theia/theia/pull/9256/ + rebind(DebugEditorModelFactory) + .toDynamicValue( + ({ container }) => + ( + ((editor) => DebugEditorModel.createModel(container, editor)) ) - .inSingletonScope(); - bind(CreateFsProvider).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(CreateFsProvider); - bind(FileServiceContribution).toService(CreateFsProvider); - bind(CloudSketchbookContribution).toSelf().inSingletonScope(); - bind(CommandContribution).toService(CloudSketchbookContribution); - bind(LocalCacheFsProvider).toSelf().inSingletonScope(); - bind(FileServiceContribution).toService(LocalCacheFsProvider); - bind(CloudSketchbookCompositeWidget).toSelf(); - bind(WidgetFactory).toDynamicValue((ctx) => ({ - id: 'cloud-sketchbook-composite-widget', - createWidget: () => ctx.container.get(CloudSketchbookCompositeWidget), - })); + ) + .inSingletonScope(); + + // Preferences + bindArduinoPreferences(bind); + + // Settings wrapper for the preferences and the CLI config. + bind(SettingsService).toSelf().inSingletonScope(); + // Settings dialog and widget + bind(SettingsWidget).toSelf().inSingletonScope(); + bind(SettingsDialog).toSelf().inSingletonScope(); + bind(SettingsDialogProps).toConstantValue({ + title: 'Preferences', + }); + + bind(StorageWrapper).toSelf().inSingletonScope(); + bind(CommandContribution).toService(StorageWrapper); + + bind(NotificationManager).toSelf().inSingletonScope(); + rebind(TheiaNotificationManager).toService(NotificationManager); + bind(NotificationsRenderer).toSelf().inSingletonScope(); + rebind(TheiaNotificationsRenderer).toService(NotificationsRenderer); + + // UI for the Sketchbook + bind(SketchbookWidget).toSelf(); + bind(SketchbookTreeWidget).toDynamicValue(({ container }) => + createSketchbookTreeWidget(container) + ); + bindViewContribution(bind, SketchbookWidgetContribution); + bind(FrontendApplicationContribution).toService(SketchbookWidgetContribution); + bind(WidgetFactory).toDynamicValue(({ container }) => ({ + id: 'arduino-sketchbook-widget', + createWidget: () => container.get(SketchbookWidget), + })); + + bind(CloudSketchbookWidget).toSelf(); + rebind(SketchbookWidget).toService(CloudSketchbookWidget); + bind(CloudSketchbookTreeWidget).toDynamicValue(({ container }) => + createCloudSketchbookTreeWidget(container) + ); + bind(CreateApi).toSelf().inSingletonScope(); + bind(ShareSketchDialog).toSelf().inSingletonScope(); + bind(AuthenticationClientService).toSelf().inSingletonScope(); + bind(CommandContribution).toService(AuthenticationClientService); + bind(FrontendApplicationContribution).toService(AuthenticationClientService); + bind(AuthenticationService) + .toDynamicValue((context) => + WebSocketConnectionProvider.createProxy( + context.container, + AuthenticationServicePath + ) + ) + .inSingletonScope(); + bind(CreateFsProvider).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(CreateFsProvider); + bind(FileServiceContribution).toService(CreateFsProvider); + bind(CloudSketchbookContribution).toSelf().inSingletonScope(); + bind(CommandContribution).toService(CloudSketchbookContribution); + bind(LocalCacheFsProvider).toSelf().inSingletonScope(); + bind(FileServiceContribution).toService(LocalCacheFsProvider); + bind(CloudSketchbookCompositeWidget).toSelf(); + bind(WidgetFactory).toDynamicValue((ctx) => ({ + id: 'cloud-sketchbook-composite-widget', + createWidget: () => ctx.container.get(CloudSketchbookCompositeWidget), + })); }); diff --git a/arduino-ide-extension/src/browser/arduino-preferences.ts b/arduino-ide-extension/src/browser/arduino-preferences.ts index 9cef1b134..942c03f54 100644 --- a/arduino-ide-extension/src/browser/arduino-preferences.ts +++ b/arduino-ide-extension/src/browser/arduino-preferences.ts @@ -1,156 +1,155 @@ import { interfaces } from 'inversify'; import { - createPreferenceProxy, - PreferenceProxy, - PreferenceService, - PreferenceContribution, - PreferenceSchema, + createPreferenceProxy, + PreferenceProxy, + PreferenceService, + PreferenceContribution, + PreferenceSchema, } from '@theia/core/lib/browser/preferences'; import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol'; export const ArduinoConfigSchema: PreferenceSchema = { - type: 'object', - properties: { - 'arduino.language.log': { - type: 'boolean', - description: - "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.", - default: false, - }, - 'arduino.compile.verbose': { - type: 'boolean', - description: 'True for verbose compile output. False by default', - default: false, - }, - 'arduino.compile.warnings': { - enum: [...CompilerWarningLiterals], - description: - "Tells gcc which warning level to use. It's 'None' by default", - default: 'None', - }, - 'arduino.upload.verbose': { - type: 'boolean', - description: 'True for verbose upload output. False by default.', - default: false, - }, - 'arduino.upload.verify': { - type: 'boolean', - default: false, - }, - 'arduino.window.autoScale': { - type: 'boolean', - description: - 'True if the user interface automatically scales with the font size.', - default: true, - }, - 'arduino.window.zoomLevel': { - type: 'number', - description: - 'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.', - default: 0, - }, - 'arduino.ide.autoUpdate': { - type: 'boolean', - description: - 'True to enable automatic update checks. The IDE will check for updates automatically and periodically.', - default: true, - }, - 'arduino.sketchbook.showAllFiles': { - type: 'boolean', - description: - 'True to show all sketch files inside the sketch. It is false by default.', - default: false, - }, - 'arduino.cloud.enabled': { - type: 'boolean', - description: - 'True if the sketch sync functions are enabled. Defaults to true.', - default: true, - }, - 'arduino.cloud.pull.warn': { - type: 'boolean', - description: - 'True if users should be warned before pulling a cloud sketch. Defaults to true.', - default: true, - }, - 'arduino.cloud.push.warn': { - type: 'boolean', - description: - 'True if users should be warned before pushing a cloud sketch. Defaults to true.', - default: true, - }, - 'arduino.cloud.pushpublic.warn': { - type: 'boolean', - description: - 'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.', - default: true, - }, - 'arduino.cloud.sketchSyncEnpoint': { - type: 'string', - description: - 'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.', - default: 'https://api2.arduino.cc/create', - }, - 'arduino.auth.clientID': { - type: 'string', - description: 'The OAuth2 client ID.', - default: 'C34Ya6ex77jTNxyKWj01lCe1vAHIaPIo', - }, - 'arduino.auth.domain': { - type: 'string', - description: 'The OAuth2 domain.', - default: 'login.arduino.cc', - }, - 'arduino.auth.audience': { - type: 'string', - description: 'The 0Auth2 audience.', - default: 'https://api.arduino.cc', - }, - 'arduino.auth.registerUri': { - type: 'string', - description: 'The URI used to register a new user.', - default: 'https://auth.arduino.cc/login#/register', - }, + type: 'object', + properties: { + 'arduino.language.log': { + type: 'boolean', + description: + "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.", + default: false, }, + 'arduino.compile.verbose': { + type: 'boolean', + description: 'True for verbose compile output. False by default', + default: false, + }, + 'arduino.compile.warnings': { + enum: [...CompilerWarningLiterals], + description: + "Tells gcc which warning level to use. It's 'None' by default", + default: 'None', + }, + 'arduino.upload.verbose': { + type: 'boolean', + description: 'True for verbose upload output. False by default.', + default: false, + }, + 'arduino.upload.verify': { + type: 'boolean', + default: false, + }, + 'arduino.window.autoScale': { + type: 'boolean', + description: + 'True if the user interface automatically scales with the font size.', + default: true, + }, + 'arduino.window.zoomLevel': { + type: 'number', + description: + 'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.', + default: 0, + }, + 'arduino.ide.autoUpdate': { + type: 'boolean', + description: + 'True to enable automatic update checks. The IDE will check for updates automatically and periodically.', + default: true, + }, + 'arduino.sketchbook.showAllFiles': { + type: 'boolean', + description: + 'True to show all sketch files inside the sketch. It is false by default.', + default: false, + }, + 'arduino.cloud.enabled': { + type: 'boolean', + description: + 'True if the sketch sync functions are enabled. Defaults to true.', + default: true, + }, + 'arduino.cloud.pull.warn': { + type: 'boolean', + description: + 'True if users should be warned before pulling a cloud sketch. Defaults to true.', + default: true, + }, + 'arduino.cloud.push.warn': { + type: 'boolean', + description: + 'True if users should be warned before pushing a cloud sketch. Defaults to true.', + default: true, + }, + 'arduino.cloud.pushpublic.warn': { + type: 'boolean', + description: + 'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.', + default: true, + }, + 'arduino.cloud.sketchSyncEnpoint': { + type: 'string', + description: + 'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.', + default: 'https://api2.arduino.cc/create', + }, + 'arduino.auth.clientID': { + type: 'string', + description: 'The OAuth2 client ID.', + default: 'C34Ya6ex77jTNxyKWj01lCe1vAHIaPIo', + }, + 'arduino.auth.domain': { + type: 'string', + description: 'The OAuth2 domain.', + default: 'login.arduino.cc', + }, + 'arduino.auth.audience': { + type: 'string', + description: 'The 0Auth2 audience.', + default: 'https://api.arduino.cc', + }, + 'arduino.auth.registerUri': { + type: 'string', + description: 'The URI used to register a new user.', + default: 'https://auth.arduino.cc/login#/register', + }, + }, }; export interface ArduinoConfiguration { - 'arduino.language.log': boolean; - 'arduino.compile.verbose': boolean; - 'arduino.compile.warnings': CompilerWarnings; - 'arduino.upload.verbose': boolean; - 'arduino.upload.verify': boolean; - 'arduino.window.autoScale': boolean; - 'arduino.window.zoomLevel': number; - 'arduino.ide.autoUpdate': boolean; - 'arduino.sketchbook.showAllFiles': boolean; - 'arduino.cloud.enabled': boolean; - 'arduino.cloud.pull.warn': boolean; - 'arduino.cloud.push.warn': boolean; - 'arduino.cloud.pushpublic.warn': boolean; - 'arduino.cloud.sketchSyncEnpoint': string; - 'arduino.auth.clientID': string; - 'arduino.auth.domain': string; - 'arduino.auth.audience': string; - 'arduino.auth.registerUri': string; + 'arduino.language.log': boolean; + 'arduino.compile.verbose': boolean; + 'arduino.compile.warnings': CompilerWarnings; + 'arduino.upload.verbose': boolean; + 'arduino.upload.verify': boolean; + 'arduino.window.autoScale': boolean; + 'arduino.window.zoomLevel': number; + 'arduino.ide.autoUpdate': boolean; + 'arduino.sketchbook.showAllFiles': boolean; + 'arduino.cloud.enabled': boolean; + 'arduino.cloud.pull.warn': boolean; + 'arduino.cloud.push.warn': boolean; + 'arduino.cloud.pushpublic.warn': boolean; + 'arduino.cloud.sketchSyncEnpoint': string; + 'arduino.auth.clientID': string; + 'arduino.auth.domain': string; + 'arduino.auth.audience': string; + 'arduino.auth.registerUri': string; } export const ArduinoPreferences = Symbol('ArduinoPreferences'); export type ArduinoPreferences = PreferenceProxy; export function createArduinoPreferences( - preferences: PreferenceService + preferences: PreferenceService ): ArduinoPreferences { - return createPreferenceProxy(preferences, ArduinoConfigSchema); + return createPreferenceProxy(preferences, ArduinoConfigSchema); } export function bindArduinoPreferences(bind: interfaces.Bind): void { - bind(ArduinoPreferences).toDynamicValue((ctx) => { - const preferences = - ctx.container.get(PreferenceService); - return createArduinoPreferences(preferences); - }); - bind(PreferenceContribution).toConstantValue({ - schema: ArduinoConfigSchema, - }); + bind(ArduinoPreferences).toDynamicValue((ctx) => { + const preferences = ctx.container.get(PreferenceService); + return createArduinoPreferences(preferences); + }); + bind(PreferenceContribution).toConstantValue({ + schema: ArduinoConfigSchema, + }); } diff --git a/arduino-ide-extension/src/browser/arduino-workspace-resolver.ts b/arduino-ide-extension/src/browser/arduino-workspace-resolver.ts index 0db1aeafd..8158808b4 100644 --- a/arduino-ide-extension/src/browser/arduino-workspace-resolver.ts +++ b/arduino-ide-extension/src/browser/arduino-workspace-resolver.ts @@ -20,54 +20,54 @@ import { MaybePromise } from '@theia/core/lib/common/types'; * - `try open recent workspace roots`, then `try open last modified sketches`, finally `create new sketch`. */ namespace ArduinoWorkspaceRootResolver { - export interface InitOptions { - readonly isValid: (uri: string) => MaybePromise; - } - export interface ResolveOptions { - readonly hash?: string; - readonly recentWorkspaces: string[]; - // Gathered from the default sketch folder. The default sketch folder is defined by the CLI. - readonly recentSketches: string[]; - } + export interface InitOptions { + readonly isValid: (uri: string) => MaybePromise; + } + export interface ResolveOptions { + readonly hash?: string; + readonly recentWorkspaces: string[]; + // Gathered from the default sketch folder. The default sketch folder is defined by the CLI. + readonly recentSketches: string[]; + } } export class ArduinoWorkspaceRootResolver { - constructor(protected options: ArduinoWorkspaceRootResolver.InitOptions) {} + constructor(protected options: ArduinoWorkspaceRootResolver.InitOptions) {} - async resolve( - options: ArduinoWorkspaceRootResolver.ResolveOptions - ): Promise<{ uri: string } | undefined> { - const { hash, recentWorkspaces, recentSketches } = options; - for (const uri of [ - this.hashToUri(hash), - ...recentWorkspaces, - ...recentSketches, - ].filter(notEmpty)) { - const valid = await this.isValid(uri); - if (valid) { - return { uri }; - } - } - return undefined; + async resolve( + options: ArduinoWorkspaceRootResolver.ResolveOptions + ): Promise<{ uri: string } | undefined> { + const { hash, recentWorkspaces, recentSketches } = options; + for (const uri of [ + this.hashToUri(hash), + ...recentWorkspaces, + ...recentSketches, + ].filter(notEmpty)) { + const valid = await this.isValid(uri); + if (valid) { + return { uri }; + } } + return undefined; + } - protected isValid(uri: string): MaybePromise { - return this.options.isValid(uri); - } + protected isValid(uri: string): MaybePromise { + return this.options.isValid(uri); + } - // Note: here, the `hash` was defined as new `URI(yourValidFsPath).path` so we have to map it to a valid FS path first. - // This is important for Windows only and a NOOP on POSIX. - // Note: we set the `new URI(myValidUri).path.toString()` as the `hash`. See: - // - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L143 and - // - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L423 - protected hashToUri(hash: string | undefined): string | undefined { - if (hash && hash.length > 1 && hash.startsWith('#')) { - const path = hash.slice(1); // Trim the leading `#`. - return new URI( - toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0)) - ) - .withScheme('file') - .toString(); - } - return undefined; + // Note: here, the `hash` was defined as new `URI(yourValidFsPath).path` so we have to map it to a valid FS path first. + // This is important for Windows only and a NOOP on POSIX. + // Note: we set the `new URI(myValidUri).path.toString()` as the `hash`. See: + // - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L143 and + // - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L423 + protected hashToUri(hash: string | undefined): string | undefined { + if (hash && hash.length > 1 && hash.startsWith('#')) { + const path = hash.slice(1); // Trim the leading `#`. + return new URI( + toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0)) + ) + .withScheme('file') + .toString(); } + return undefined; + } } diff --git a/arduino-ide-extension/src/browser/auth/authentication-client-service.ts b/arduino-ide-extension/src/browser/auth/authentication-client-service.ts index d145680ec..2b0fb9cdf 100644 --- a/arduino-ide-extension/src/browser/auth/authentication-client-service.ts +++ b/arduino-ide-extension/src/browser/auth/authentication-client-service.ts @@ -5,13 +5,13 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { - CommandRegistry, - CommandContribution, + CommandRegistry, + CommandContribution, } from '@theia/core/lib/common/command'; import { - AuthenticationService, - AuthenticationServiceClient, - AuthenticationSession, + AuthenticationService, + AuthenticationServiceClient, + AuthenticationSession, } from '../../common/protocol/authentication-service'; import { CloudUserCommands } from './cloud-user-commands'; import { serverPort } from '../../node/auth/authentication-server'; @@ -20,74 +20,74 @@ import { ArduinoPreferences } from '../arduino-preferences'; @injectable() export class AuthenticationClientService - implements - FrontendApplicationContribution, - CommandContribution, - AuthenticationServiceClient + implements + FrontendApplicationContribution, + CommandContribution, + AuthenticationServiceClient { - @inject(AuthenticationService) - protected readonly service: JsonRpcProxy; + @inject(AuthenticationService) + protected readonly service: JsonRpcProxy; - @inject(WindowService) - protected readonly windowService: WindowService; + @inject(WindowService) + protected readonly windowService: WindowService; - @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + @inject(ArduinoPreferences) + protected readonly arduinoPreferences: ArduinoPreferences; - protected authOptions: AuthOptions; - protected _session: AuthenticationSession | undefined; - protected readonly toDispose = new DisposableCollection(); - protected readonly onSessionDidChangeEmitter = new Emitter< - AuthenticationSession | undefined - >(); + protected authOptions: AuthOptions; + protected _session: AuthenticationSession | undefined; + protected readonly toDispose = new DisposableCollection(); + protected readonly onSessionDidChangeEmitter = new Emitter< + AuthenticationSession | undefined + >(); - readonly onSessionDidChange = this.onSessionDidChangeEmitter.event; + readonly onSessionDidChange = this.onSessionDidChangeEmitter.event; - onStart(): void { - this.toDispose.push(this.onSessionDidChangeEmitter); - this.service.setClient(this); - this.service - .session() - .then((session) => this.notifySessionDidChange(session)); + onStart(): void { + this.toDispose.push(this.onSessionDidChangeEmitter); + this.service.setClient(this); + this.service + .session() + .then((session) => this.notifySessionDidChange(session)); + this.setOptions(); + this.arduinoPreferences.onPreferenceChanged((event) => { + if (event.preferenceName.startsWith('arduino.auth.')) { this.setOptions(); - this.arduinoPreferences.onPreferenceChanged((event) => { - if (event.preferenceName.startsWith('arduino.auth.')) { - this.setOptions(); - } - }); - } + } + }); + } - setOptions(): void { - this.service.setOptions({ - redirectUri: `http://localhost:${serverPort}/callback`, - responseType: 'code', - clientID: this.arduinoPreferences['arduino.auth.clientID'], - domain: this.arduinoPreferences['arduino.auth.domain'], - audience: this.arduinoPreferences['arduino.auth.audience'], - registerUri: this.arduinoPreferences['arduino.auth.registerUri'], - scopes: ['openid', 'profile', 'email', 'offline_access'], - }); - } + setOptions(): void { + this.service.setOptions({ + redirectUri: `http://localhost:${serverPort}/callback`, + responseType: 'code', + clientID: this.arduinoPreferences['arduino.auth.clientID'], + domain: this.arduinoPreferences['arduino.auth.domain'], + audience: this.arduinoPreferences['arduino.auth.audience'], + registerUri: this.arduinoPreferences['arduino.auth.registerUri'], + scopes: ['openid', 'profile', 'email', 'offline_access'], + }); + } - protected updateSession(session?: AuthenticationSession | undefined) { - this._session = session; - this.onSessionDidChangeEmitter.fire(this._session); - } + protected updateSession(session?: AuthenticationSession | undefined) { + this._session = session; + this.onSessionDidChangeEmitter.fire(this._session); + } - get session(): AuthenticationSession | undefined { - return this._session; - } + get session(): AuthenticationSession | undefined { + return this._session; + } - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(CloudUserCommands.LOGIN, { - execute: () => this.service.login(), - }); - registry.registerCommand(CloudUserCommands.LOGOUT, { - execute: () => this.service.logout(), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(CloudUserCommands.LOGIN, { + execute: () => this.service.login(), + }); + registry.registerCommand(CloudUserCommands.LOGOUT, { + execute: () => this.service.logout(), + }); + } - notifySessionDidChange(session: AuthenticationSession | undefined): void { - this.updateSession(session); - } + notifySessionDidChange(session: AuthenticationSession | undefined): void { + this.updateSession(session); + } } diff --git a/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts b/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts index 8947f0016..7a4d6c89e 100644 --- a/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts +++ b/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts @@ -1,18 +1,18 @@ import { Command } from '@theia/core/lib/common/command'; export namespace CloudUserCommands { - export const LOGIN: Command = { - id: 'arduino-cloud--login', - label: 'Sign in', - }; + export const LOGIN: Command = { + id: 'arduino-cloud--login', + label: 'Sign in', + }; - export const LOGOUT: Command = { - id: 'arduino-cloud--logout', - label: 'Sign Out', - }; + export const LOGOUT: Command = { + id: 'arduino-cloud--logout', + label: 'Sign Out', + }; - export const OPEN_PROFILE_CONTEXT_MENU: Command = { - id: 'arduino-cloud-sketchbook--open-profile-menu', - label: 'Contextual menu', - }; + export const OPEN_PROFILE_CONTEXT_MENU: Command = { + id: 'arduino-cloud-sketchbook--open-profile-menu', + label: 'Contextual menu', + }; } diff --git a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts index 68d1c65b3..60cb54a00 100644 --- a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts +++ b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts @@ -2,9 +2,9 @@ import { injectable, inject } from 'inversify'; import { MessageService } from '@theia/core/lib/common/message-service'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { - BoardsService, - BoardsPackage, - Board, + BoardsService, + BoardsPackage, + Board, } from '../../common/protocol/boards-service'; import { BoardsServiceProvider } from './boards-service-provider'; import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution'; @@ -18,111 +18,103 @@ import { ResponseServiceImpl } from '../response-service-impl'; */ @injectable() export class BoardsAutoInstaller implements FrontendApplicationContribution { - @inject(MessageService) - protected readonly messageService: MessageService; + @inject(MessageService) + protected readonly messageService: MessageService; - @inject(BoardsService) - protected readonly boardsService: BoardsService; + @inject(BoardsService) + protected readonly boardsService: BoardsService; - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceClient: BoardsServiceProvider; - @inject(ResponseServiceImpl) - protected readonly responseService: ResponseServiceImpl; + @inject(ResponseServiceImpl) + protected readonly responseService: ResponseServiceImpl; - @inject(BoardsListWidgetFrontendContribution) - protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution; + @inject(BoardsListWidgetFrontendContribution) + protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution; - // Workaround for https://github.com/eclipse-theia/theia/issues/9349 - protected notifications: Board[] = []; + // Workaround for https://github.com/eclipse-theia/theia/issues/9349 + protected notifications: Board[] = []; - onStart(): void { - this.boardsServiceClient.onBoardsConfigChanged( - this.ensureCoreExists.bind(this) + onStart(): void { + this.boardsServiceClient.onBoardsConfigChanged( + this.ensureCoreExists.bind(this) + ); + this.ensureCoreExists(this.boardsServiceClient.boardsConfig); + } + + protected ensureCoreExists(config: BoardsConfig.Config): void { + const { selectedBoard } = config; + if ( + selectedBoard && + !this.notifications.find((board) => Board.sameAs(board, selectedBoard)) + ) { + this.notifications.push(selectedBoard); + this.boardsService.search({}).then((packages) => { + // filter packagesForBoard selecting matches from the cli (installed packages) + // and matches based on the board name + // NOTE: this ensures the Deprecated & new packages are all in the array + // so that we can check if any of the valid packages is already installed + const packagesForBoard = packages.filter( + (pkg) => + BoardsPackage.contains(selectedBoard, pkg) || + pkg.boards.some((board) => board.name === selectedBoard.name) ); - this.ensureCoreExists(this.boardsServiceClient.boardsConfig); - } - protected ensureCoreExists(config: BoardsConfig.Config): void { - const { selectedBoard } = config; + // check if one of the packages for the board is already installed. if so, no hint if ( - selectedBoard && - !this.notifications.find((board) => - Board.sameAs(board, selectedBoard) - ) + packagesForBoard.some(({ installedVersion }) => !!installedVersion) ) { - this.notifications.push(selectedBoard); - this.boardsService.search({}).then((packages) => { - // filter packagesForBoard selecting matches from the cli (installed packages) - // and matches based on the board name - // NOTE: this ensures the Deprecated & new packages are all in the array - // so that we can check if any of the valid packages is already installed - const packagesForBoard = packages.filter( - (pkg) => - BoardsPackage.contains(selectedBoard, pkg) || - pkg.boards.some( - (board) => board.name === selectedBoard.name - ) - ); - - // check if one of the packages for the board is already installed. if so, no hint - if ( - packagesForBoard.some( - ({ installedVersion }) => !!installedVersion - ) - ) { - return; - } + return; + } - // filter the installable (not installed) packages, - // CLI returns the packages already sorted with the deprecated ones at the end of the list - // in order to ensure the new ones are preferred - const candidates = packagesForBoard.filter( - ({ installable, installedVersion }) => - installable && !installedVersion - ); + // filter the installable (not installed) packages, + // CLI returns the packages already sorted with the deprecated ones at the end of the list + // in order to ensure the new ones are preferred + const candidates = packagesForBoard.filter( + ({ installable, installedVersion }) => + installable && !installedVersion + ); - const candidate = candidates[0]; - if (candidate) { - const version = candidate.availableVersions[0] - ? `[v ${candidate.availableVersions[0]}]` - : ''; - // tslint:disable-next-line:max-line-length - this.messageService - .info( - `The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, - 'Install Manually', - 'Yes' - ) - .then(async (answer) => { - const index = this.notifications.findIndex( - (board) => Board.sameAs(board, selectedBoard) - ); - if (index !== -1) { - this.notifications.splice(index, 1); - } - if (answer === 'Yes') { - await Installable.installWithProgress({ - installable: this.boardsService, - item: candidate, - messageService: this.messageService, - responseService: this.responseService, - version: candidate.availableVersions[0], - }); - return; - } - if (answer) { - this.boardsManagerFrontendContribution - .openView({ reveal: true }) - .then((widget) => - widget.refresh( - candidate.name.toLocaleLowerCase() - ) - ); - } - }); - } + const candidate = candidates[0]; + if (candidate) { + const version = candidate.availableVersions[0] + ? `[v ${candidate.availableVersions[0]}]` + : ''; + // tslint:disable-next-line:max-line-length + this.messageService + .info( + `The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, + 'Install Manually', + 'Yes' + ) + .then(async (answer) => { + const index = this.notifications.findIndex((board) => + Board.sameAs(board, selectedBoard) + ); + if (index !== -1) { + this.notifications.splice(index, 1); + } + if (answer === 'Yes') { + await Installable.installWithProgress({ + installable: this.boardsService, + item: candidate, + messageService: this.messageService, + responseService: this.responseService, + version: candidate.availableVersions[0], + }); + return; + } + if (answer) { + this.boardsManagerFrontendContribution + .openView({ reveal: true }) + .then((widget) => + widget.refresh(candidate.name.toLocaleLowerCase()) + ); + } }); } + }); } + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx b/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx index ecb01b3c4..a4e962546 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog-widget.tsx @@ -9,64 +9,62 @@ import { NotificationCenter } from '../notification-center'; @injectable() export class BoardsConfigDialogWidget extends ReactWidget { - @inject(BoardsService) - protected readonly boardsService: BoardsService; + @inject(BoardsService) + protected readonly boardsService: BoardsService; - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceClient: BoardsServiceProvider; - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; - protected readonly onFilterTextDidChangeEmitter = new Emitter(); - protected readonly onBoardConfigChangedEmitter = - new Emitter(); - readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event; + protected readonly onFilterTextDidChangeEmitter = new Emitter(); + protected readonly onBoardConfigChangedEmitter = + new Emitter(); + readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event; - protected focusNode: HTMLElement | undefined; + protected focusNode: HTMLElement | undefined; - constructor() { - super(); - this.id = 'select-board-dialog'; - this.toDispose.pushAll([ - this.onBoardConfigChangedEmitter, - this.onFilterTextDidChangeEmitter, - ]); - } + constructor() { + super(); + this.id = 'select-board-dialog'; + this.toDispose.pushAll([ + this.onBoardConfigChangedEmitter, + this.onFilterTextDidChangeEmitter, + ]); + } - search(query: string): void { - this.onFilterTextDidChangeEmitter.fire(query); - } + search(query: string): void { + this.onFilterTextDidChangeEmitter.fire(query); + } - protected fireConfigChanged = (config: BoardsConfig.Config) => { - this.onBoardConfigChangedEmitter.fire(config); - }; + protected fireConfigChanged = (config: BoardsConfig.Config) => { + this.onBoardConfigChangedEmitter.fire(config); + }; - protected setFocusNode = (element: HTMLElement | undefined) => { - this.focusNode = element; - }; + protected setFocusNode = (element: HTMLElement | undefined) => { + this.focusNode = element; + }; - protected render(): React.ReactNode { - return ( -
- -
- ); - } + protected render(): React.ReactNode { + return ( +
+ +
+ ); + } - protected onActivateRequest(msg: Message): void { - super.onActivateRequest(msg); - if (this.focusNode instanceof HTMLInputElement) { - this.focusNode.select(); - } - (this.focusNode || this.node).focus(); + protected onActivateRequest(msg: Message): void { + super.onActivateRequest(msg); + if (this.focusNode instanceof HTMLInputElement) { + this.focusNode.select(); } + (this.focusNode || this.node).focus(); + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts index 830db7d89..6598156ae 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts @@ -1,10 +1,10 @@ import { injectable, inject, postConstruct } from 'inversify'; import { Message } from '@phosphor/messaging'; import { - AbstractDialog, - DialogProps, - Widget, - DialogError, + AbstractDialog, + DialogProps, + Widget, + DialogError, } from '@theia/core/lib/browser'; import { BoardsConfig } from './boards-config'; import { BoardsService } from '../../common/protocol/boards-service'; @@ -16,119 +16,119 @@ export class BoardsConfigDialogProps extends DialogProps {} @injectable() export class BoardsConfigDialog extends AbstractDialog { - @inject(BoardsConfigDialogWidget) - protected readonly widget: BoardsConfigDialogWidget; + @inject(BoardsConfigDialogWidget) + protected readonly widget: BoardsConfigDialogWidget; - @inject(BoardsService) - protected readonly boardService: BoardsService; + @inject(BoardsService) + protected readonly boardService: BoardsService; - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceClient: BoardsServiceProvider; - protected config: BoardsConfig.Config = {}; + protected config: BoardsConfig.Config = {}; - constructor( - @inject(BoardsConfigDialogProps) - protected readonly props: BoardsConfigDialogProps - ) { - super(props); + constructor( + @inject(BoardsConfigDialogProps) + protected readonly props: BoardsConfigDialogProps + ) { + super(props); - this.contentNode.classList.add('select-board-dialog'); - this.contentNode.appendChild(this.createDescription()); + this.contentNode.classList.add('select-board-dialog'); + this.contentNode.appendChild(this.createDescription()); - this.appendCloseButton('CANCEL'); - this.appendAcceptButton('OK'); - } - - @postConstruct() - protected init(): void { - this.toDispose.push( - this.boardsServiceClient.onBoardsConfigChanged((config) => { - this.config = config; - this.update(); - }) - ); - } - - /** - * Pass in an empty string if you want to reset the search term. Using `undefined` has no effect. - */ - async open( - query: string | undefined = undefined - ): Promise { - if (typeof query === 'string') { - this.widget.search(query); - } - return super.open(); - } - - protected createDescription(): HTMLElement { - const head = document.createElement('div'); - head.classList.add('head'); - - const title = document.createElement('div'); - title.textContent = 'Select Other Board & Port'; - title.classList.add('title'); - head.appendChild(title); - - const text = document.createElement('div'); - text.classList.add('text'); - head.appendChild(text); - - for (const paragraph of [ - 'Select both a Board and a Port if you want to upload a sketch.', - 'If you only select a Board you will be able just to compile, but not to upload your sketch.', - ]) { - const p = document.createElement('div'); - p.textContent = paragraph; - text.appendChild(p); - } - - return head; - } + this.appendCloseButton('CANCEL'); + this.appendAcceptButton('OK'); + } - protected onAfterAttach(msg: Message): void { - if (this.widget.isAttached) { - Widget.detach(this.widget); - } - Widget.attach(this.widget, this.contentNode); - this.toDisposeOnDetach.push( - this.widget.onBoardConfigChanged((config) => { - this.config = config; - this.update(); - }) - ); - super.onAfterAttach(msg); + @postConstruct() + protected init(): void { + this.toDispose.push( + this.boardsServiceClient.onBoardsConfigChanged((config) => { + this.config = config; this.update(); + }) + ); + } + + /** + * Pass in an empty string if you want to reset the search term. Using `undefined` has no effect. + */ + async open( + query: string | undefined = undefined + ): Promise { + if (typeof query === 'string') { + this.widget.search(query); } - - protected onUpdateRequest(msg: Message) { - super.onUpdateRequest(msg); - this.widget.update(); + return super.open(); + } + + protected createDescription(): HTMLElement { + const head = document.createElement('div'); + head.classList.add('head'); + + const title = document.createElement('div'); + title.textContent = 'Select Other Board & Port'; + title.classList.add('title'); + head.appendChild(title); + + const text = document.createElement('div'); + text.classList.add('text'); + head.appendChild(text); + + for (const paragraph of [ + 'Select both a Board and a Port if you want to upload a sketch.', + 'If you only select a Board you will be able just to compile, but not to upload your sketch.', + ]) { + const p = document.createElement('div'); + p.textContent = paragraph; + text.appendChild(p); } - protected onActivateRequest(msg: Message): void { - super.onActivateRequest(msg); - this.widget.activate(); - } + return head; + } - protected handleEnter(event: KeyboardEvent): boolean | void { - if (event.target instanceof HTMLTextAreaElement) { - return false; - } + protected onAfterAttach(msg: Message): void { + if (this.widget.isAttached) { + Widget.detach(this.widget); } - - protected isValid(value: BoardsConfig.Config): DialogError { - if (!value.selectedBoard) { - if (value.selectedPort) { - return 'Please pick a board connected to the port you have selected.'; - } - return false; - } - return ''; + Widget.attach(this.widget, this.contentNode); + this.toDisposeOnDetach.push( + this.widget.onBoardConfigChanged((config) => { + this.config = config; + this.update(); + }) + ); + super.onAfterAttach(msg); + this.update(); + } + + protected onUpdateRequest(msg: Message) { + super.onUpdateRequest(msg); + this.widget.update(); + } + + protected onActivateRequest(msg: Message): void { + super.onActivateRequest(msg); + this.widget.activate(); + } + + protected handleEnter(event: KeyboardEvent): boolean | void { + if (event.target instanceof HTMLTextAreaElement) { + return false; } - - get value(): BoardsConfig.Config { - return this.config; + } + + protected isValid(value: BoardsConfig.Config): DialogError { + if (!value.selectedBoard) { + if (value.selectedPort) { + return 'Please pick a board connected to the port you have selected.'; + } + return false; } + return ''; + } + + get value(): BoardsConfig.Config { + return this.config; + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx index dc5b7c965..d4772a2e6 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx @@ -4,408 +4,396 @@ import { notEmpty } from '@theia/core/lib/common/objects'; import { MaybePromise } from '@theia/core/lib/common/types'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { - Board, - Port, - AttachedBoardsChangeEvent, - BoardWithPackage, + Board, + Port, + AttachedBoardsChangeEvent, + BoardWithPackage, } from '../../common/protocol/boards-service'; import { NotificationCenter } from '../notification-center'; import { BoardsServiceProvider } from './boards-service-provider'; export namespace BoardsConfig { - export interface Config { - selectedBoard?: Board; - selectedPort?: Port; - } - - export interface Props { - readonly boardsServiceProvider: BoardsServiceProvider; - readonly notificationCenter: NotificationCenter; - readonly onConfigChange: (config: Config) => void; - readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; - readonly onFilteredTextDidChangeEvent: Event; - } - - export interface State extends Config { - searchResults: Array; - knownPorts: Port[]; - showAllPorts: boolean; - query: string; - } + export interface Config { + selectedBoard?: Board; + selectedPort?: Port; + } + + export interface Props { + readonly boardsServiceProvider: BoardsServiceProvider; + readonly notificationCenter: NotificationCenter; + readonly onConfigChange: (config: Config) => void; + readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; + readonly onFilteredTextDidChangeEvent: Event; + } + + export interface State extends Config { + searchResults: Array; + knownPorts: Port[]; + showAllPorts: boolean; + query: string; + } } export abstract class Item extends React.Component<{ - item: T; - label: string; - selected: boolean; - onClick: (item: T) => void; - missing?: boolean; - details?: string; + item: T; + label: string; + selected: boolean; + onClick: (item: T) => void; + missing?: boolean; + details?: string; }> { - render(): React.ReactNode { - const { selected, label, missing, details } = this.props; - const classNames = ['item']; - if (selected) { - classNames.push('selected'); - } - if (missing === true) { - classNames.push('missing'); - } - return ( -
-
{label}
- {!details ? '' :
{details}
} - {!selected ? ( - '' - ) : ( -
- -
- )} -
- ); + render(): React.ReactNode { + const { selected, label, missing, details } = this.props; + const classNames = ['item']; + if (selected) { + classNames.push('selected'); } - - protected onClick = () => { - this.props.onClick(this.props.item); - }; + if (missing === true) { + classNames.push('missing'); + } + return ( +
+
{label}
+ {!details ? '' :
{details}
} + {!selected ? ( + '' + ) : ( +
+ +
+ )} +
+ ); + } + + protected onClick = () => { + this.props.onClick(this.props.item); + }; } export class BoardsConfig extends React.Component< - BoardsConfig.Props, - BoardsConfig.State + BoardsConfig.Props, + BoardsConfig.State > { - protected toDispose = new DisposableCollection(); - - constructor(props: BoardsConfig.Props) { - super(props); - - const { boardsConfig } = props.boardsServiceProvider; - this.state = { - searchResults: [], - knownPorts: [], - showAllPorts: false, - query: '', - ...boardsConfig, - }; - } - - componentDidMount() { - this.updateBoards(); - this.updatePorts( - this.props.boardsServiceProvider.availableBoards - .map(({ port }) => port) - .filter(notEmpty) - ); - this.toDispose.pushAll([ - this.props.notificationCenter.onAttachedBoardsChanged((event) => - this.updatePorts( - event.newState.ports, - AttachedBoardsChangeEvent.diff(event).detached.ports - ) - ), - this.props.boardsServiceProvider.onBoardsConfigChanged( - ({ selectedBoard, selectedPort }) => { - this.setState({ selectedBoard, selectedPort }, () => - this.fireConfigChanged() - ); - } - ), - this.props.notificationCenter.onPlatformInstalled(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onPlatformUninstalled(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onIndexUpdated(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onDaemonStarted(() => - this.updateBoards(this.state.query) - ), - this.props.notificationCenter.onDaemonStopped(() => - this.setState({ searchResults: [] }) - ), - this.props.onFilteredTextDidChangeEvent((query) => - this.setState({ query }, () => - this.updateBoards(this.state.query) - ) - ), - ]); - } - - componentWillUnmount(): void { - this.toDispose.dispose(); - } - - protected fireConfigChanged() { - const { selectedBoard, selectedPort } = this.state; - this.props.onConfigChange({ selectedBoard, selectedPort }); - } - - protected updateBoards = ( - eventOrQuery: React.ChangeEvent | string = '' - ) => { - const query = - typeof eventOrQuery === 'string' - ? eventOrQuery - : eventOrQuery.target.value.toLowerCase(); - this.setState({ query }); - this.queryBoards({ query }).then((searchResults) => - this.setState({ searchResults }) - ); - }; - - protected updatePorts = (ports: Port[] = [], removedPorts: Port[] = []) => { - this.queryPorts(Promise.resolve(ports)).then(({ knownPorts }) => { - let { selectedPort } = this.state; - // If the currently selected port is not available anymore, unset the selected port. - if (removedPorts.some((port) => Port.equals(port, selectedPort))) { - selectedPort = undefined; - } - this.setState({ knownPorts, selectedPort }, () => - this.fireConfigChanged() - ); - }); - }; - - protected queryBoards = ( - options: { query?: string } = {} - ): Promise> => { - return this.props.boardsServiceProvider.searchBoards(options); + protected toDispose = new DisposableCollection(); + + constructor(props: BoardsConfig.Props) { + super(props); + + const { boardsConfig } = props.boardsServiceProvider; + this.state = { + searchResults: [], + knownPorts: [], + showAllPorts: false, + query: '', + ...boardsConfig, }; - - protected get availablePorts(): MaybePromise { - return this.props.boardsServiceProvider.availableBoards - .map(({ port }) => port) - .filter(notEmpty); + } + + componentDidMount() { + this.updateBoards(); + this.updatePorts( + this.props.boardsServiceProvider.availableBoards + .map(({ port }) => port) + .filter(notEmpty) + ); + this.toDispose.pushAll([ + this.props.notificationCenter.onAttachedBoardsChanged((event) => + this.updatePorts( + event.newState.ports, + AttachedBoardsChangeEvent.diff(event).detached.ports + ) + ), + this.props.boardsServiceProvider.onBoardsConfigChanged( + ({ selectedBoard, selectedPort }) => { + this.setState({ selectedBoard, selectedPort }, () => + this.fireConfigChanged() + ); + } + ), + this.props.notificationCenter.onPlatformInstalled(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onPlatformUninstalled(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onIndexUpdated(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onDaemonStarted(() => + this.updateBoards(this.state.query) + ), + this.props.notificationCenter.onDaemonStopped(() => + this.setState({ searchResults: [] }) + ), + this.props.onFilteredTextDidChangeEvent((query) => + this.setState({ query }, () => this.updateBoards(this.state.query)) + ), + ]); + } + + componentWillUnmount(): void { + this.toDispose.dispose(); + } + + protected fireConfigChanged() { + const { selectedBoard, selectedPort } = this.state; + this.props.onConfigChange({ selectedBoard, selectedPort }); + } + + protected updateBoards = ( + eventOrQuery: React.ChangeEvent | string = '' + ) => { + const query = + typeof eventOrQuery === 'string' + ? eventOrQuery + : eventOrQuery.target.value.toLowerCase(); + this.setState({ query }); + this.queryBoards({ query }).then((searchResults) => + this.setState({ searchResults }) + ); + }; + + protected updatePorts = (ports: Port[] = [], removedPorts: Port[] = []) => { + this.queryPorts(Promise.resolve(ports)).then(({ knownPorts }) => { + let { selectedPort } = this.state; + // If the currently selected port is not available anymore, unset the selected port. + if (removedPorts.some((port) => Port.equals(port, selectedPort))) { + selectedPort = undefined; + } + this.setState({ knownPorts, selectedPort }, () => + this.fireConfigChanged() + ); + }); + }; + + protected queryBoards = ( + options: { query?: string } = {} + ): Promise> => { + return this.props.boardsServiceProvider.searchBoards(options); + }; + + protected get availablePorts(): MaybePromise { + return this.props.boardsServiceProvider.availableBoards + .map(({ port }) => port) + .filter(notEmpty); + } + + protected queryPorts = async ( + availablePorts: MaybePromise = this.availablePorts + ) => { + const ports = await availablePorts; + return { knownPorts: ports.sort(Port.compare) }; + }; + + protected toggleFilterPorts = () => { + this.setState({ showAllPorts: !this.state.showAllPorts }); + }; + + protected selectPort = (selectedPort: Port | undefined) => { + this.setState({ selectedPort }, () => this.fireConfigChanged()); + }; + + protected selectBoard = (selectedBoard: BoardWithPackage | undefined) => { + this.setState({ selectedBoard }, () => this.fireConfigChanged()); + }; + + protected focusNodeSet = (element: HTMLElement | null) => { + this.props.onFocusNodeSet(element || undefined); + }; + + render(): React.ReactNode { + return ( +
+ {this.renderContainer('boards', this.renderBoards.bind(this))} + {this.renderContainer( + 'ports', + this.renderPorts.bind(this), + this.renderPortsFooter.bind(this) + )} +
+ ); + } + + protected renderContainer( + title: string, + contentRenderer: () => React.ReactNode, + footerRenderer?: () => React.ReactNode + ): React.ReactNode { + return ( +
+
+
{title}
+ {contentRenderer()} +
{footerRenderer ? footerRenderer() : ''}
+
+
+ ); + } + + protected renderBoards(): React.ReactNode { + const { selectedBoard, searchResults, query } = this.state; + // Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560 + // It is tricky when the core is not yet installed, no FQBNs are available. + const distinctBoards = new Map(); + const toKey = ({ name, packageName, fqbn }: Board.Detailed) => + !!fqbn ? `${name}-${packageName}-${fqbn}` : `${name}-${packageName}`; + for (const board of Board.decorateBoards(selectedBoard, searchResults)) { + const key = toKey(board); + if (!distinctBoards.has(key)) { + distinctBoards.set(key, board); + } } - protected queryPorts = async ( - availablePorts: MaybePromise = this.availablePorts - ) => { - const ports = await availablePorts; - return { knownPorts: ports.sort(Port.compare) }; - }; - - protected toggleFilterPorts = () => { - this.setState({ showAllPorts: !this.state.showAllPorts }); - }; - - protected selectPort = (selectedPort: Port | undefined) => { - this.setState({ selectedPort }, () => this.fireConfigChanged()); - }; - - protected selectBoard = (selectedBoard: BoardWithPackage | undefined) => { - this.setState({ selectedBoard }, () => this.fireConfigChanged()); - }; - - protected focusNodeSet = (element: HTMLElement | null) => { - this.props.onFocusNodeSet(element || undefined); - }; - - render(): React.ReactNode { - return ( -
- {this.renderContainer('boards', this.renderBoards.bind(this))} - {this.renderContainer( - 'ports', - this.renderPorts.bind(this), - this.renderPortsFooter.bind(this) - )} -
- ); - } + return ( + +
+ + +
+
+ {Array.from(distinctBoards.values()).map((board) => ( + + key={`${board.name}-${board.packageName}`} + item={board} + label={board.name} + details={board.details} + selected={board.selected} + onClick={this.selectBoard} + missing={board.missing} + /> + ))} +
+
+ ); + } + + protected renderPorts(): React.ReactNode { + const filter = this.state.showAllPorts ? () => true : Port.isBoardPort; + const ports = this.state.knownPorts.filter(filter); + return !ports.length ? ( +
No ports discovered
+ ) : ( +
+ {ports.map((port) => ( + + key={Port.toString(port)} + item={port} + label={Port.toString(port)} + selected={Port.equals(this.state.selectedPort, port)} + onClick={this.selectPort} + /> + ))} +
+ ); + } + + protected renderPortsFooter(): React.ReactNode { + return ( +
+ +
+ ); + } +} - protected renderContainer( - title: string, - contentRenderer: () => React.ReactNode, - footerRenderer?: () => React.ReactNode - ): React.ReactNode { +export namespace BoardsConfig { + export namespace Config { + export function sameAs(config: Config, other: Config | Board): boolean { + const { selectedBoard, selectedPort } = config; + if (Board.is(other)) { return ( -
-
-
{title}
- {contentRenderer()} -
- {footerRenderer ? footerRenderer() : ''} -
-
-
+ !!selectedBoard && + Board.equals(other, selectedBoard) && + Port.sameAs(selectedPort, other.port) ); + } + return sameAs(config, other); } - protected renderBoards(): React.ReactNode { - const { selectedBoard, searchResults, query } = this.state; - // Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560 - // It is tricky when the core is not yet installed, no FQBNs are available. - const distinctBoards = new Map(); - const toKey = ({ name, packageName, fqbn }: Board.Detailed) => - !!fqbn - ? `${name}-${packageName}-${fqbn}` - : `${name}-${packageName}`; - for (const board of Board.decorateBoards( - selectedBoard, - searchResults - )) { - const key = toKey(board); - if (!distinctBoards.has(key)) { - distinctBoards.set(key, board); - } - } - - return ( - -
- - -
-
- {Array.from(distinctBoards.values()).map((board) => ( - - key={`${board.name}-${board.packageName}`} - item={board} - label={board.name} - details={board.details} - selected={board.selected} - onClick={this.selectBoard} - missing={board.missing} - /> - ))} -
-
- ); + export function equals(left: Config, right: Config): boolean { + return ( + left.selectedBoard === right.selectedBoard && + left.selectedPort === right.selectedPort + ); } - protected renderPorts(): React.ReactNode { - const filter = this.state.showAllPorts ? () => true : Port.isBoardPort; - const ports = this.state.knownPorts.filter(filter); - return !ports.length ? ( -
No ports discovered
- ) : ( -
- {ports.map((port) => ( - - key={Port.toString(port)} - item={port} - label={Port.toString(port)} - selected={Port.equals(this.state.selectedPort, port)} - onClick={this.selectPort} - /> - ))} -
- ); + export function toString( + config: Config, + options: { default: string } = { default: '' } + ): string { + const { selectedBoard, selectedPort: port } = config; + if (!selectedBoard) { + return options.default; + } + const { name } = selectedBoard; + return `${name}${port ? ' at ' + Port.toString(port) : ''}`; } - protected renderPortsFooter(): React.ReactNode { - return ( -
- -
- ); + export function setConfig( + config: Config | undefined, + urlToAttachTo: URL + ): URL { + const copy = new URL(urlToAttachTo.toString()); + if (!config) { + copy.searchParams.delete('boards-config'); + return copy; + } + + const selectedBoard = config.selectedBoard + ? { + name: config.selectedBoard.name, + fqbn: config.selectedBoard.fqbn, + } + : undefined; + const selectedPort = config.selectedPort + ? { + protocol: config.selectedPort.protocol, + address: config.selectedPort.address, + } + : undefined; + const jsonConfig = JSON.stringify({ selectedBoard, selectedPort }); + copy.searchParams.set('boards-config', encodeURIComponent(jsonConfig)); + return copy; } -} - -export namespace BoardsConfig { - export namespace Config { - export function sameAs(config: Config, other: Config | Board): boolean { - const { selectedBoard, selectedPort } = config; - if (Board.is(other)) { - return ( - !!selectedBoard && - Board.equals(other, selectedBoard) && - Port.sameAs(selectedPort, other.port) - ); - } - return sameAs(config, other); - } - - export function equals(left: Config, right: Config): boolean { - return ( - left.selectedBoard === right.selectedBoard && - left.selectedPort === right.selectedPort - ); - } - - export function toString( - config: Config, - options: { default: string } = { default: '' } - ): string { - const { selectedBoard, selectedPort: port } = config; - if (!selectedBoard) { - return options.default; - } - const { name } = selectedBoard; - return `${name}${port ? ' at ' + Port.toString(port) : ''}`; - } - - export function setConfig( - config: Config | undefined, - urlToAttachTo: URL - ): URL { - const copy = new URL(urlToAttachTo.toString()); - if (!config) { - copy.searchParams.delete('boards-config'); - return copy; - } - - const selectedBoard = config.selectedBoard - ? { - name: config.selectedBoard.name, - fqbn: config.selectedBoard.fqbn, - } - : undefined; - const selectedPort = config.selectedPort - ? { - protocol: config.selectedPort.protocol, - address: config.selectedPort.address, - } - : undefined; - const jsonConfig = JSON.stringify({ selectedBoard, selectedPort }); - copy.searchParams.set( - 'boards-config', - encodeURIComponent(jsonConfig) - ); - return copy; - } - export function getConfig(url: URL): Config | undefined { - const encoded = url.searchParams.get('boards-config'); - if (!encoded) { - return undefined; - } - try { - const raw = decodeURIComponent(encoded); - const candidate = JSON.parse(raw); - if (typeof candidate === 'object') { - return candidate; - } - console.warn( - `Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}` - ); - return undefined; - } catch (e) { - console.log(`Could not get board config from URL: ${url}.`, e); - return undefined; - } + export function getConfig(url: URL): Config | undefined { + const encoded = url.searchParams.get('boards-config'); + if (!encoded) { + return undefined; + } + try { + const raw = decodeURIComponent(encoded); + const candidate = JSON.parse(raw); + if (typeof candidate === 'object') { + return candidate; } + console.warn( + `Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}` + ); + return undefined; + } catch (e) { + console.log(`Could not get board config from URL: ${url}.`, e); + return undefined; + } } + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts index a7beed9bb..4b2712803 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts +++ b/arduino-ide-extension/src/browser/boards/boards-data-menu-updater.ts @@ -3,8 +3,8 @@ import { inject, injectable } from 'inversify'; import { CommandRegistry } from '@theia/core/lib/common/command'; import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { - Disposable, - DisposableCollection, + Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; import { BoardsServiceProvider } from './boards-service-provider'; import { Board, ConfigOption, Programmer } from '../../common/protocol'; @@ -15,178 +15,145 @@ import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus'; @injectable() export class BoardsDataMenuUpdater implements FrontendApplicationContribution { - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; + @inject(MainMenuManager) + protected readonly mainMenuManager: MainMenuManager; - @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; + @inject(BoardsDataStore) + protected readonly boardsDataStore: BoardsDataStore; - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceClient: BoardsServiceProvider; - protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); - protected readonly toDisposeOnBoardChange = new DisposableCollection(); + protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); + protected readonly toDisposeOnBoardChange = new DisposableCollection(); - async onStart(): Promise { - this.updateMenuActions( - this.boardsServiceClient.boardsConfig.selectedBoard - ); - this.boardsDataStore.onChanged(() => - this.updateMenuActions( - this.boardsServiceClient.boardsConfig.selectedBoard - ) - ); - this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => - this.updateMenuActions(selectedBoard) - ); - } + async onStart(): Promise { + this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard); + this.boardsDataStore.onChanged(() => + this.updateMenuActions( + this.boardsServiceClient.boardsConfig.selectedBoard + ) + ); + this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => + this.updateMenuActions(selectedBoard) + ); + } - protected async updateMenuActions( - selectedBoard: Board | undefined - ): Promise { - return this.queue.add(async () => { - this.toDisposeOnBoardChange.dispose(); - this.mainMenuManager.update(); - if (selectedBoard) { - const { fqbn } = selectedBoard; - if (fqbn) { - const { configOptions, programmers, selectedProgrammer } = - await this.boardsDataStore.getData(fqbn); - if (configOptions.length) { - const boardsConfigMenuPath = [ - ...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, - 'z01_boardsConfig', - ]; // `z_` is for ordering. - for (const { - label, - option, - values, - } of configOptions.sort( - ConfigOption.LABEL_COMPARATOR - )) { - const menuPath = [ - ...boardsConfigMenuPath, - `${option}`, - ]; - const commands = new Map< - string, - Disposable & { label: string } - >(); - for (const value of values) { - const id = `${fqbn}-${option}--${value.value}`; - const command = { id }; - const selectedValue = value.value; - const handler = { - execute: () => - this.boardsDataStore.selectConfigOption( - { fqbn, option, selectedValue } - ), - isToggled: () => value.selected, - }; - commands.set( - id, - Object.assign( - this.commandRegistry.registerCommand( - command, - handler - ), - { label: value.label } - ) - ); - } - this.menuRegistry.registerSubmenu(menuPath, label); - this.toDisposeOnBoardChange.pushAll([ - ...commands.values(), - Disposable.create(() => - unregisterSubmenu( - menuPath, - this.menuRegistry - ) - ), - ...Array.from(commands.keys()).map( - (commandId, i) => { - const { label } = - commands.get(commandId)!; - this.menuRegistry.registerMenuAction( - menuPath, - { commandId, order: `${i}`, label } - ); - return Disposable.create(() => - this.menuRegistry.unregisterMenuAction( - commandId - ) - ); - } - ), - ]); - } - } - if (programmers.length) { - const programmersMenuPath = [ - ...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, - 'z02_programmers', - ]; - const label = selectedProgrammer - ? `Programmer: "${selectedProgrammer.name}"` - : 'Programmer'; - this.menuRegistry.registerSubmenu( - programmersMenuPath, - label - ); - this.toDisposeOnBoardChange.push( - Disposable.create(() => - unregisterSubmenu( - programmersMenuPath, - this.menuRegistry - ) - ) - ); - for (const programmer of programmers) { - const { id, name } = programmer; - const command = { id: `${fqbn}-programmer--${id}` }; - const handler = { - execute: () => - this.boardsDataStore.selectProgrammer({ - fqbn, - selectedProgrammer: programmer, - }), - isToggled: () => - Programmer.equals( - programmer, - selectedProgrammer - ), - }; - this.menuRegistry.registerMenuAction( - programmersMenuPath, - { commandId: command.id, label: name } - ); - this.commandRegistry.registerCommand( - command, - handler - ); - this.toDisposeOnBoardChange.pushAll([ - Disposable.create(() => - this.commandRegistry.unregisterCommand( - command - ) - ), - Disposable.create(() => - this.menuRegistry.unregisterMenuAction( - command.id - ) - ), - ]); - } - } - this.mainMenuManager.update(); - } + protected async updateMenuActions( + selectedBoard: Board | undefined + ): Promise { + return this.queue.add(async () => { + this.toDisposeOnBoardChange.dispose(); + this.mainMenuManager.update(); + if (selectedBoard) { + const { fqbn } = selectedBoard; + if (fqbn) { + const { configOptions, programmers, selectedProgrammer } = + await this.boardsDataStore.getData(fqbn); + if (configOptions.length) { + const boardsConfigMenuPath = [ + ...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, + 'z01_boardsConfig', + ]; // `z_` is for ordering. + for (const { label, option, values } of configOptions.sort( + ConfigOption.LABEL_COMPARATOR + )) { + const menuPath = [...boardsConfigMenuPath, `${option}`]; + const commands = new Map< + string, + Disposable & { label: string } + >(); + for (const value of values) { + const id = `${fqbn}-${option}--${value.value}`; + const command = { id }; + const selectedValue = value.value; + const handler = { + execute: () => + this.boardsDataStore.selectConfigOption({ + fqbn, + option, + selectedValue, + }), + isToggled: () => value.selected, + }; + commands.set( + id, + Object.assign( + this.commandRegistry.registerCommand(command, handler), + { label: value.label } + ) + ); + } + this.menuRegistry.registerSubmenu(menuPath, label); + this.toDisposeOnBoardChange.pushAll([ + ...commands.values(), + Disposable.create(() => + unregisterSubmenu(menuPath, this.menuRegistry) + ), + ...Array.from(commands.keys()).map((commandId, i) => { + const { label } = commands.get(commandId)!; + this.menuRegistry.registerMenuAction(menuPath, { + commandId, + order: `${i}`, + label, + }); + return Disposable.create(() => + this.menuRegistry.unregisterMenuAction(commandId) + ); + }), + ]); } - }); - } + } + if (programmers.length) { + const programmersMenuPath = [ + ...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, + 'z02_programmers', + ]; + const label = selectedProgrammer + ? `Programmer: "${selectedProgrammer.name}"` + : 'Programmer'; + this.menuRegistry.registerSubmenu(programmersMenuPath, label); + this.toDisposeOnBoardChange.push( + Disposable.create(() => + unregisterSubmenu(programmersMenuPath, this.menuRegistry) + ) + ); + for (const programmer of programmers) { + const { id, name } = programmer; + const command = { id: `${fqbn}-programmer--${id}` }; + const handler = { + execute: () => + this.boardsDataStore.selectProgrammer({ + fqbn, + selectedProgrammer: programmer, + }), + isToggled: () => + Programmer.equals(programmer, selectedProgrammer), + }; + this.menuRegistry.registerMenuAction(programmersMenuPath, { + commandId: command.id, + label: name, + }); + this.commandRegistry.registerCommand(command, handler); + this.toDisposeOnBoardChange.pushAll([ + Disposable.create(() => + this.commandRegistry.unregisterCommand(command) + ), + Disposable.create(() => + this.menuRegistry.unregisterMenuAction(command.id) + ), + ]); + } + } + this.mainMenuManager.update(); + } + } + }); + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-data-store.ts b/arduino-ide-extension/src/browser/boards/boards-data-store.ts index 4771e9522..fea413eb0 100644 --- a/arduino-ide-extension/src/browser/boards/boards-data-store.ts +++ b/arduino-ide-extension/src/browser/boards/boards-data-store.ts @@ -4,277 +4,269 @@ import { deepClone } from '@theia/core/lib/common/objects'; import { MaybePromise } from '@theia/core/lib/common/types'; import { Event, Emitter } from '@theia/core/lib/common/event'; import { - FrontendApplicationContribution, - LocalStorageService, + FrontendApplicationContribution, + LocalStorageService, } from '@theia/core/lib/browser'; import { notEmpty } from '../../common/utils'; import { - BoardsService, - ConfigOption, - Installable, - BoardDetails, - Programmer, + BoardsService, + ConfigOption, + Installable, + BoardDetails, + Programmer, } from '../../common/protocol'; import { NotificationCenter } from '../notification-center'; @injectable() export class BoardsDataStore implements FrontendApplicationContribution { - @inject(ILogger) - @named('store') - protected readonly logger: ILogger; + @inject(ILogger) + @named('store') + protected readonly logger: ILogger; - @inject(BoardsService) - protected readonly boardsService: BoardsService; + @inject(BoardsService) + protected readonly boardsService: BoardsService; - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; - @inject(LocalStorageService) - protected readonly storageService: LocalStorageService; + @inject(LocalStorageService) + protected readonly storageService: LocalStorageService; - protected readonly onChangedEmitter = new Emitter(); + protected readonly onChangedEmitter = new Emitter(); - onStart(): void { - this.notificationCenter.onPlatformInstalled(async ({ item }) => { - const { installedVersion: version } = item; - if (!version) { - return; - } - let shouldFireChanged = false; - for (const fqbn of item.boards - .map(({ fqbn }) => fqbn) - .filter(notEmpty) - .filter((fqbn) => !!fqbn)) { - const key = this.getStorageKey(fqbn, version); - let data = await this.storageService.getData< - ConfigOption[] | undefined - >(key); - if (!data || !data.length) { - const details = await this.getBoardDetailsSafe(fqbn); - if (details) { - data = details.configOptions; - if (data.length) { - await this.storageService.setData(key, data); - shouldFireChanged = true; - } - } - } - } - if (shouldFireChanged) { - this.fireChanged(); + onStart(): void { + this.notificationCenter.onPlatformInstalled(async ({ item }) => { + const { installedVersion: version } = item; + if (!version) { + return; + } + let shouldFireChanged = false; + for (const fqbn of item.boards + .map(({ fqbn }) => fqbn) + .filter(notEmpty) + .filter((fqbn) => !!fqbn)) { + const key = this.getStorageKey(fqbn, version); + let data = await this.storageService.getData< + ConfigOption[] | undefined + >(key); + if (!data || !data.length) { + const details = await this.getBoardDetailsSafe(fqbn); + if (details) { + data = details.configOptions; + if (data.length) { + await this.storageService.setData(key, data); + shouldFireChanged = true; } - }); - } + } + } + } + if (shouldFireChanged) { + this.fireChanged(); + } + }); + } + + get onChanged(): Event { + return this.onChangedEmitter.event; + } - get onChanged(): Event { - return this.onChangedEmitter.event; + async appendConfigToFqbn( + fqbn: string | undefined, + boardsPackageVersion: MaybePromise< + Installable.Version | undefined + > = this.getBoardsPackageVersion(fqbn) + ): Promise { + if (!fqbn) { + return undefined; } - async appendConfigToFqbn( - fqbn: string | undefined, - boardsPackageVersion: MaybePromise< - Installable.Version | undefined - > = this.getBoardsPackageVersion(fqbn) - ): Promise { - if (!fqbn) { - return undefined; - } + const { configOptions } = await this.getData(fqbn, boardsPackageVersion); + return ConfigOption.decorate(fqbn, configOptions); + } - const { configOptions } = await this.getData( - fqbn, - boardsPackageVersion - ); - return ConfigOption.decorate(fqbn, configOptions); + async getData( + fqbn: string | undefined, + boardsPackageVersion: MaybePromise< + Installable.Version | undefined + > = this.getBoardsPackageVersion(fqbn) + ): Promise { + if (!fqbn) { + return BoardsDataStore.Data.EMPTY; } - async getData( - fqbn: string | undefined, - boardsPackageVersion: MaybePromise< - Installable.Version | undefined - > = this.getBoardsPackageVersion(fqbn) - ): Promise { - if (!fqbn) { - return BoardsDataStore.Data.EMPTY; - } + const version = await boardsPackageVersion; + if (!version) { + return BoardsDataStore.Data.EMPTY; + } + const key = this.getStorageKey(fqbn, version); + let data = await this.storageService.getData< + BoardsDataStore.Data | undefined + >(key, undefined); + if (BoardsDataStore.Data.is(data)) { + return data; + } - const version = await boardsPackageVersion; - if (!version) { - return BoardsDataStore.Data.EMPTY; - } - const key = this.getStorageKey(fqbn, version); - let data = await this.storageService.getData< - BoardsDataStore.Data | undefined - >(key, undefined); - if (BoardsDataStore.Data.is(data)) { - return data; - } + const boardDetails = await this.getBoardDetailsSafe(fqbn); + if (!boardDetails) { + return BoardsDataStore.Data.EMPTY; + } - const boardDetails = await this.getBoardDetailsSafe(fqbn); - if (!boardDetails) { - return BoardsDataStore.Data.EMPTY; - } + data = { + configOptions: boardDetails.configOptions, + programmers: boardDetails.programmers, + }; + await this.storageService.setData(key, data); + return data; + } - data = { - configOptions: boardDetails.configOptions, - programmers: boardDetails.programmers, - }; - await this.storageService.setData(key, data); - return data; + async selectProgrammer( + { + fqbn, + selectedProgrammer, + }: { fqbn: string; selectedProgrammer: Programmer }, + boardsPackageVersion: MaybePromise< + Installable.Version | undefined + > = this.getBoardsPackageVersion(fqbn) + ): Promise { + const data = deepClone(await this.getData(fqbn, boardsPackageVersion)); + const { programmers } = data; + if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) { + return false; } - async selectProgrammer( - { - fqbn, - selectedProgrammer, - }: { fqbn: string; selectedProgrammer: Programmer }, - boardsPackageVersion: MaybePromise< - Installable.Version | undefined - > = this.getBoardsPackageVersion(fqbn) - ): Promise { - const data = deepClone(await this.getData(fqbn, boardsPackageVersion)); - const { programmers } = data; - if ( - !programmers.find((p) => Programmer.equals(selectedProgrammer, p)) - ) { - return false; - } + const version = await boardsPackageVersion; + if (!version) { + return false; + } - const version = await boardsPackageVersion; - if (!version) { - return false; - } + await this.setData({ + fqbn, + data: { ...data, selectedProgrammer }, + version, + }); + this.fireChanged(); + return true; + } - await this.setData({ - fqbn, - data: { ...data, selectedProgrammer }, - version, - }); - this.fireChanged(); - return true; + async selectConfigOption( + { + fqbn, + option, + selectedValue, + }: { fqbn: string; option: string; selectedValue: string }, + boardsPackageVersion: MaybePromise< + Installable.Version | undefined + > = this.getBoardsPackageVersion(fqbn) + ): Promise { + const data = deepClone(await this.getData(fqbn, boardsPackageVersion)); + const { configOptions } = data; + const configOption = configOptions.find((c) => c.option === option); + if (!configOption) { + return false; + } + let updated = false; + for (const value of configOption.values) { + if (value.value === selectedValue) { + (value as any).selected = true; + updated = true; + } else { + (value as any).selected = false; + } + } + if (!updated) { + return false; + } + const version = await boardsPackageVersion; + if (!version) { + return false; } - async selectConfigOption( - { - fqbn, - option, - selectedValue, - }: { fqbn: string; option: string; selectedValue: string }, - boardsPackageVersion: MaybePromise< - Installable.Version | undefined - > = this.getBoardsPackageVersion(fqbn) - ): Promise { - const data = deepClone(await this.getData(fqbn, boardsPackageVersion)); - const { configOptions } = data; - const configOption = configOptions.find((c) => c.option === option); - if (!configOption) { - return false; - } - let updated = false; - for (const value of configOption.values) { - if (value.value === selectedValue) { - (value as any).selected = true; - updated = true; - } else { - (value as any).selected = false; - } - } - if (!updated) { - return false; - } - const version = await boardsPackageVersion; - if (!version) { - return false; - } + await this.setData({ fqbn, data, version }); + this.fireChanged(); + return true; + } - await this.setData({ fqbn, data, version }); - this.fireChanged(); - return true; - } + protected async setData({ + fqbn, + data, + version, + }: { + fqbn: string; + data: BoardsDataStore.Data; + version: Installable.Version; + }): Promise { + const key = this.getStorageKey(fqbn, version); + return this.storageService.setData(key, data); + } - protected async setData({ - fqbn, - data, - version, - }: { - fqbn: string; - data: BoardsDataStore.Data; - version: Installable.Version; - }): Promise { - const key = this.getStorageKey(fqbn, version); - return this.storageService.setData(key, data); - } + protected getStorageKey(fqbn: string, version: Installable.Version): string { + return `.arduinoIDE-configOptions-${version}-${fqbn}`; + } - protected getStorageKey( - fqbn: string, - version: Installable.Version - ): string { - return `.arduinoIDE-configOptions-${version}-${fqbn}`; + protected async getBoardDetailsSafe( + fqbn: string + ): Promise { + try { + const details = this.boardsService.getBoardDetails({ fqbn }); + return details; + } catch (err) { + if ( + err instanceof Error && + err.message.includes('loading board data') && + err.message.includes('is not installed') + ) { + this.logger.warn( + `The boards package is not installed for board with FQBN: ${fqbn}` + ); + } else { + this.logger.error( + `An unexpected error occurred while retrieving the board details for ${fqbn}.`, + err + ); + } + return undefined; } + } - protected async getBoardDetailsSafe( - fqbn: string - ): Promise { - try { - const details = this.boardsService.getBoardDetails({ fqbn }); - return details; - } catch (err) { - if ( - err instanceof Error && - err.message.includes('loading board data') && - err.message.includes('is not installed') - ) { - this.logger.warn( - `The boards package is not installed for board with FQBN: ${fqbn}` - ); - } else { - this.logger.error( - `An unexpected error occurred while retrieving the board details for ${fqbn}.`, - err - ); - } - return undefined; - } - } + protected fireChanged(): void { + this.onChangedEmitter.fire(); + } - protected fireChanged(): void { - this.onChangedEmitter.fire(); + protected async getBoardsPackageVersion( + fqbn: string | undefined + ): Promise { + if (!fqbn) { + return undefined; } - - protected async getBoardsPackageVersion( - fqbn: string | undefined - ): Promise { - if (!fqbn) { - return undefined; - } - const boardsPackage = await this.boardsService.getContainerBoardPackage( - { fqbn } - ); - if (!boardsPackage) { - return undefined; - } - return boardsPackage.installedVersion; + const boardsPackage = await this.boardsService.getContainerBoardPackage({ + fqbn, + }); + if (!boardsPackage) { + return undefined; } + return boardsPackage.installedVersion; + } } export namespace BoardsDataStore { - export interface Data { - readonly configOptions: ConfigOption[]; - readonly programmers: Programmer[]; - readonly selectedProgrammer?: Programmer; - } - export namespace Data { - export const EMPTY: Data = { - configOptions: [], - programmers: [], - }; - export function is(arg: any): arg is Data { - return ( - !!arg && - 'configOptions' in arg && - Array.isArray(arg['configOptions']) && - 'programmers' in arg && - Array.isArray(arg['programmers']) - ); - } + export interface Data { + readonly configOptions: ConfigOption[]; + readonly programmers: Programmer[]; + readonly selectedProgrammer?: Programmer; + } + export namespace Data { + export const EMPTY: Data = { + configOptions: [], + programmers: [], + }; + export function is(arg: any): arg is Data { + return ( + !!arg && + 'configOptions' in arg && + Array.isArray(arg['configOptions']) && + 'programmers' in arg && + Array.isArray(arg['programmers']) + ); } + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts index fdb8408ff..a4aee6aa4 100644 --- a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts +++ b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts @@ -1,73 +1,73 @@ import { inject, injectable, postConstruct } from 'inversify'; import { - BoardsPackage, - BoardsService, + BoardsPackage, + BoardsService, } from '../../common/protocol/boards-service'; import { ListWidget } from '../widgets/component-list/list-widget'; import { ListItemRenderer } from '../widgets/component-list/list-item-renderer'; @injectable() export class BoardsListWidget extends ListWidget { - static WIDGET_ID = 'boards-list-widget'; - static WIDGET_LABEL = 'Boards Manager'; + static WIDGET_ID = 'boards-list-widget'; + static WIDGET_LABEL = 'Boards Manager'; - constructor( - @inject(BoardsService) protected service: BoardsService, - @inject(ListItemRenderer) - protected itemRenderer: ListItemRenderer - ) { - super({ - id: BoardsListWidget.WIDGET_ID, - label: BoardsListWidget.WIDGET_LABEL, - iconClass: 'fa fa-microchip', - searchable: service, - installable: service, - itemLabel: (item: BoardsPackage) => item.name, - itemDeprecated: (item: BoardsPackage) => item.deprecated, - itemRenderer, - }); - } + constructor( + @inject(BoardsService) protected service: BoardsService, + @inject(ListItemRenderer) + protected itemRenderer: ListItemRenderer + ) { + super({ + id: BoardsListWidget.WIDGET_ID, + label: BoardsListWidget.WIDGET_LABEL, + iconClass: 'fa fa-microchip', + searchable: service, + installable: service, + itemLabel: (item: BoardsPackage) => item.name, + itemDeprecated: (item: BoardsPackage) => item.deprecated, + itemRenderer, + }); + } - @postConstruct() - protected init(): void { - super.init(); - this.toDispose.pushAll([ - this.notificationCenter.onPlatformInstalled(() => - this.refresh(undefined) - ), - this.notificationCenter.onPlatformUninstalled(() => - this.refresh(undefined) - ), - ]); - } + @postConstruct() + protected init(): void { + super.init(); + this.toDispose.pushAll([ + this.notificationCenter.onPlatformInstalled(() => + this.refresh(undefined) + ), + this.notificationCenter.onPlatformUninstalled(() => + this.refresh(undefined) + ), + ]); + } - protected async install({ - item, - progressId, - version, - }: { - item: BoardsPackage; - progressId: string; - version: string; - }): Promise { - await super.install({ item, progressId, version }); - this.messageService.info( - `Successfully installed platform ${item.name}:${version}`, - { timeout: 3000 } - ); - } + protected async install({ + item, + progressId, + version, + }: { + item: BoardsPackage; + progressId: string; + version: string; + }): Promise { + await super.install({ item, progressId, version }); + this.messageService.info( + `Successfully installed platform ${item.name}:${version}`, + { timeout: 3000 } + ); + } - protected async uninstall({ - item, - progressId, - }: { - item: BoardsPackage; - progressId: string; - }): Promise { - await super.uninstall({ item, progressId }); - this.messageService.info( - `Successfully uninstalled platform ${item.name}:${item.installedVersion}`, - { timeout: 3000 } - ); - } + protected async uninstall({ + item, + progressId, + }: { + item: BoardsPackage; + progressId: string; + }): Promise { + await super.uninstall({ item, progressId }); + this.messageService.info( + `Successfully uninstalled platform ${item.name}:${item.installedVersion}`, + { timeout: 3000 } + ); + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts index d5a216093..3b7fef9fa 100644 --- a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts +++ b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts @@ -6,12 +6,12 @@ import { MessageService } from '@theia/core/lib/common/message-service'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { RecursiveRequired } from '../../common/types'; import { - Port, - Board, - BoardsService, - BoardsPackage, - AttachedBoardsChangeEvent, - BoardWithPackage, + Port, + Board, + BoardsService, + BoardsPackage, + AttachedBoardsChangeEvent, + BoardWithPackage, } from '../../common/protocol'; import { BoardsConfig } from './boards-config'; import { naturalCompare } from '../../common/utils'; @@ -21,542 +21,509 @@ import { StorageWrapper } from '../storage-wrapper'; @injectable() export class BoardsServiceProvider implements FrontendApplicationContribution { - @inject(ILogger) - protected logger: ILogger; - - @inject(MessageService) - protected messageService: MessageService; - - @inject(BoardsService) - protected boardsService: BoardsService; - - @inject(CommandService) - protected commandService: CommandService; - - @inject(NotificationCenter) - protected notificationCenter: NotificationCenter; - - protected readonly onBoardsConfigChangedEmitter = - new Emitter(); - protected readonly onAvailableBoardsChangedEmitter = new Emitter< - AvailableBoard[] - >(); - - /** - * Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it. - * It happens with certain boards on Windows. For example, the `MKR1000` boards is selected on post `COM5` on Windows, - * perform an upload, the board automatically disconnects and reconnects, but on another port, `COM10`. - * We have to listen on such changes and auto-reconnect the same board on another port. - * See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ - */ - protected latestValidBoardsConfig: - | RecursiveRequired - | undefined = undefined; - protected latestBoardsConfig: BoardsConfig.Config | undefined = undefined; - protected _boardsConfig: BoardsConfig.Config = {}; - protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only. - protected _availablePorts: Port[] = []; - protected _availableBoards: AvailableBoard[] = []; - - /** - * Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\ - * This even also fires, when the boards package was not available for the currently selected board, - * and the user installs the board package. Note: installing a board package will set the `fqbn` of the - * currently selected board.\ - * This even also emitted when the board package for the currently selected board was uninstalled. - */ - readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event; - readonly onAvailableBoardsChanged = - this.onAvailableBoardsChangedEmitter.event; - - onStart(): void { - this.notificationCenter.onAttachedBoardsChanged( - this.notifyAttachedBoardsChanged.bind(this) - ); - this.notificationCenter.onPlatformInstalled( - this.notifyPlatformInstalled.bind(this) - ); - this.notificationCenter.onPlatformUninstalled( - this.notifyPlatformUninstalled.bind(this) - ); - - Promise.all([ - this.boardsService.getAttachedBoards(), - this.boardsService.getAvailablePorts(), - this.loadState(), - ]).then(([attachedBoards, availablePorts]) => { - this._attachedBoards = attachedBoards; - this._availablePorts = availablePorts; - this.reconcileAvailableBoards().then(() => this.tryReconnect()); - }); + @inject(ILogger) + protected logger: ILogger; + + @inject(MessageService) + protected messageService: MessageService; + + @inject(BoardsService) + protected boardsService: BoardsService; + + @inject(CommandService) + protected commandService: CommandService; + + @inject(NotificationCenter) + protected notificationCenter: NotificationCenter; + + protected readonly onBoardsConfigChangedEmitter = + new Emitter(); + protected readonly onAvailableBoardsChangedEmitter = new Emitter< + AvailableBoard[] + >(); + + /** + * Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it. + * It happens with certain boards on Windows. For example, the `MKR1000` boards is selected on post `COM5` on Windows, + * perform an upload, the board automatically disconnects and reconnects, but on another port, `COM10`. + * We have to listen on such changes and auto-reconnect the same board on another port. + * See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ + */ + protected latestValidBoardsConfig: + | RecursiveRequired + | undefined = undefined; + protected latestBoardsConfig: BoardsConfig.Config | undefined = undefined; + protected _boardsConfig: BoardsConfig.Config = {}; + protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only. + protected _availablePorts: Port[] = []; + protected _availableBoards: AvailableBoard[] = []; + + /** + * Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\ + * This even also fires, when the boards package was not available for the currently selected board, + * and the user installs the board package. Note: installing a board package will set the `fqbn` of the + * currently selected board.\ + * This even also emitted when the board package for the currently selected board was uninstalled. + */ + readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event; + readonly onAvailableBoardsChanged = + this.onAvailableBoardsChangedEmitter.event; + + onStart(): void { + this.notificationCenter.onAttachedBoardsChanged( + this.notifyAttachedBoardsChanged.bind(this) + ); + this.notificationCenter.onPlatformInstalled( + this.notifyPlatformInstalled.bind(this) + ); + this.notificationCenter.onPlatformUninstalled( + this.notifyPlatformUninstalled.bind(this) + ); + + Promise.all([ + this.boardsService.getAttachedBoards(), + this.boardsService.getAvailablePorts(), + this.loadState(), + ]).then(([attachedBoards, availablePorts]) => { + this._attachedBoards = attachedBoards; + this._availablePorts = availablePorts; + this.reconcileAvailableBoards().then(() => this.tryReconnect()); + }); + } + + protected notifyAttachedBoardsChanged( + event: AttachedBoardsChangeEvent + ): void { + if (!AttachedBoardsChangeEvent.isEmpty(event)) { + this.logger.info('Attached boards and available ports changed:'); + this.logger.info(AttachedBoardsChangeEvent.toString(event)); + this.logger.info('------------------------------------------'); } - - protected notifyAttachedBoardsChanged( - event: AttachedBoardsChangeEvent - ): void { - if (!AttachedBoardsChangeEvent.isEmpty(event)) { - this.logger.info('Attached boards and available ports changed:'); - this.logger.info(AttachedBoardsChangeEvent.toString(event)); - this.logger.info('------------------------------------------'); - } - this._attachedBoards = event.newState.boards; - this._availablePorts = event.newState.ports; - this.reconcileAvailableBoards().then(() => this.tryReconnect()); - } - - protected notifyPlatformInstalled(event: { item: BoardsPackage }): void { - this.logger.info('Boards package installed: ', JSON.stringify(event)); - const { selectedBoard } = this.boardsConfig; - const { installedVersion, id } = event.item; - if (selectedBoard) { - const installedBoard = event.item.boards.find( - ({ name }) => name === selectedBoard.name - ); - if ( - installedBoard && - (!selectedBoard.fqbn || - selectedBoard.fqbn === installedBoard.fqbn) - ) { - this.logger.info( - `Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]` - ); - this.boardsConfig = { - ...this.boardsConfig, - selectedBoard: installedBoard, - }; - return; - } - // The board name can change after install. - // This logic handles it "gracefully" by unselecting the board, so that we can avoid no FQBN is set error. - // https://github.com/arduino/arduino-cli/issues/620 - // https://github.com/arduino/arduino-pro-ide/issues/374 - if ( - BoardWithPackage.is(selectedBoard) && - selectedBoard.packageId === event.item.id && - !installedBoard - ) { - this.messageService - .warn( - `Could not find previously selected board '${selectedBoard.name}' in installed platform '${event.item.name}'. Please manually reselect the board you want to use. Do you want to reselect it now?`, - 'Reselect later', - 'Yes' - ) - .then(async (answer) => { - if (answer === 'Yes') { - this.commandService.executeCommand( - ArduinoCommands.OPEN_BOARDS_DIALOG.id, - selectedBoard.name - ); - } - }); - this.boardsConfig = {}; - return; + this._attachedBoards = event.newState.boards; + this._availablePorts = event.newState.ports; + this.reconcileAvailableBoards().then(() => this.tryReconnect()); + } + + protected notifyPlatformInstalled(event: { item: BoardsPackage }): void { + this.logger.info('Boards package installed: ', JSON.stringify(event)); + const { selectedBoard } = this.boardsConfig; + const { installedVersion, id } = event.item; + if (selectedBoard) { + const installedBoard = event.item.boards.find( + ({ name }) => name === selectedBoard.name + ); + if ( + installedBoard && + (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn) + ) { + this.logger.info( + `Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]` + ); + this.boardsConfig = { + ...this.boardsConfig, + selectedBoard: installedBoard, + }; + return; + } + // The board name can change after install. + // This logic handles it "gracefully" by unselecting the board, so that we can avoid no FQBN is set error. + // https://github.com/arduino/arduino-cli/issues/620 + // https://github.com/arduino/arduino-pro-ide/issues/374 + if ( + BoardWithPackage.is(selectedBoard) && + selectedBoard.packageId === event.item.id && + !installedBoard + ) { + this.messageService + .warn( + `Could not find previously selected board '${selectedBoard.name}' in installed platform '${event.item.name}'. Please manually reselect the board you want to use. Do you want to reselect it now?`, + 'Reselect later', + 'Yes' + ) + .then(async (answer) => { + if (answer === 'Yes') { + this.commandService.executeCommand( + ArduinoCommands.OPEN_BOARDS_DIALOG.id, + selectedBoard.name + ); } - // Trigger a board re-set. See: https://github.com/arduino/arduino-cli/issues/954 - // E.g: install `adafruit:avr`, then select `adafruit:avr:adafruit32u4` board, and finally install the required `arduino:avr` - this.boardsConfig = this.boardsConfig; - } + }); + this.boardsConfig = {}; + return; + } + // Trigger a board re-set. See: https://github.com/arduino/arduino-cli/issues/954 + // E.g: install `adafruit:avr`, then select `adafruit:avr:adafruit32u4` board, and finally install the required `arduino:avr` + this.boardsConfig = this.boardsConfig; } - - protected notifyPlatformUninstalled(event: { item: BoardsPackage }): void { - this.logger.info('Boards package uninstalled: ', JSON.stringify(event)); - const { selectedBoard } = this.boardsConfig; - if (selectedBoard && selectedBoard.fqbn) { - const uninstalledBoard = event.item.boards.find( - ({ name }) => name === selectedBoard.name + } + + protected notifyPlatformUninstalled(event: { item: BoardsPackage }): void { + this.logger.info('Boards package uninstalled: ', JSON.stringify(event)); + const { selectedBoard } = this.boardsConfig; + if (selectedBoard && selectedBoard.fqbn) { + const uninstalledBoard = event.item.boards.find( + ({ name }) => name === selectedBoard.name + ); + if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) { + // We should not unset the FQBN, if the selected board is an attached, recognized board. + // Attach Uno and install AVR, select Uno. Uninstall the AVR core while Uno is selected. We do not want to discard the FQBN of the Uno board. + // Dev note: We cannot assume the `selectedBoard` is a type of `AvailableBoard`. + // When the user selects an `AvailableBoard` it works, but between app start/stops, + // it is just a FQBN, so we need to find the `selected` board among the `AvailableBoards` + const selectedAvailableBoard = AvailableBoard.is(selectedBoard) + ? selectedBoard + : this._availableBoards.find((availableBoard) => + Board.sameAs(availableBoard, selectedBoard) ); - if ( - uninstalledBoard && - uninstalledBoard.fqbn === selectedBoard.fqbn - ) { - // We should not unset the FQBN, if the selected board is an attached, recognized board. - // Attach Uno and install AVR, select Uno. Uninstall the AVR core while Uno is selected. We do not want to discard the FQBN of the Uno board. - // Dev note: We cannot assume the `selectedBoard` is a type of `AvailableBoard`. - // When the user selects an `AvailableBoard` it works, but between app start/stops, - // it is just a FQBN, so we need to find the `selected` board among the `AvailableBoards` - const selectedAvailableBoard = AvailableBoard.is(selectedBoard) - ? selectedBoard - : this._availableBoards.find((availableBoard) => - Board.sameAs(availableBoard, selectedBoard) - ); - if ( - selectedAvailableBoard && - selectedAvailableBoard.selected && - selectedAvailableBoard.state === - AvailableBoard.State.recognized - ) { - return; - } - this.logger.info( - `Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.` - ); - const selectedBoardWithoutFqbn = { - name: selectedBoard.name, - // No FQBN - }; - this.boardsConfig = { - ...this.boardsConfig, - selectedBoard: selectedBoardWithoutFqbn, - }; - } - } - } - - protected async tryReconnect(): Promise { if ( - this.latestValidBoardsConfig && - !this.canUploadTo(this.boardsConfig) + selectedAvailableBoard && + selectedAvailableBoard.selected && + selectedAvailableBoard.state === AvailableBoard.State.recognized ) { - for (const board of this.availableBoards.filter( - ({ state }) => state !== AvailableBoard.State.incomplete - )) { - if ( - this.latestValidBoardsConfig.selectedBoard.fqbn === - board.fqbn && - this.latestValidBoardsConfig.selectedBoard.name === - board.name && - Port.sameAs( - this.latestValidBoardsConfig.selectedPort, - board.port - ) - ) { - this.boardsConfig = this.latestValidBoardsConfig; - return true; - } - } - // If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed. - // See documentation on `latestValidBoardsConfig`. - for (const board of this.availableBoards.filter( - ({ state }) => state !== AvailableBoard.State.incomplete - )) { - if ( - this.latestValidBoardsConfig.selectedBoard.fqbn === - board.fqbn && - this.latestValidBoardsConfig.selectedBoard.name === - board.name - ) { - this.boardsConfig = { - ...this.latestValidBoardsConfig, - selectedPort: board.port, - }; - return true; - } - } + return; } - return false; - } - - set boardsConfig(config: BoardsConfig.Config) { - this.doSetBoardsConfig(config); - this.saveState().finally(() => - this.reconcileAvailableBoards().finally(() => - this.onBoardsConfigChangedEmitter.fire(this._boardsConfig) - ) + this.logger.info( + `Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.` ); + const selectedBoardWithoutFqbn = { + name: selectedBoard.name, + // No FQBN + }; + this.boardsConfig = { + ...this.boardsConfig, + selectedBoard: selectedBoardWithoutFqbn, + }; + } } + } - get boardsConfig(): BoardsConfig.Config { - return this._boardsConfig; - } - - protected doSetBoardsConfig(config: BoardsConfig.Config): void { - this.logger.info('Board config changed: ', JSON.stringify(config)); - this._boardsConfig = config; - this.latestBoardsConfig = this._boardsConfig; - if (this.canUploadTo(this._boardsConfig)) { - this.latestValidBoardsConfig = this._boardsConfig; + protected async tryReconnect(): Promise { + if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) { + for (const board of this.availableBoards.filter( + ({ state }) => state !== AvailableBoard.State.incomplete + )) { + if ( + this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn && + this.latestValidBoardsConfig.selectedBoard.name === board.name && + Port.sameAs(this.latestValidBoardsConfig.selectedPort, board.port) + ) { + this.boardsConfig = this.latestValidBoardsConfig; + return true; } + } + // If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed. + // See documentation on `latestValidBoardsConfig`. + for (const board of this.availableBoards.filter( + ({ state }) => state !== AvailableBoard.State.incomplete + )) { + if ( + this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn && + this.latestValidBoardsConfig.selectedBoard.name === board.name + ) { + this.boardsConfig = { + ...this.latestValidBoardsConfig, + selectedPort: board.port, + }; + return true; + } + } } - - async searchBoards({ - query, - cores, - }: { - query?: string; - cores?: string[]; - }): Promise { - const boards = await this.boardsService.searchBoards({ query }); - return boards; + return false; + } + + set boardsConfig(config: BoardsConfig.Config) { + this.doSetBoardsConfig(config); + this.saveState().finally(() => + this.reconcileAvailableBoards().finally(() => + this.onBoardsConfigChangedEmitter.fire(this._boardsConfig) + ) + ); + } + + get boardsConfig(): BoardsConfig.Config { + return this._boardsConfig; + } + + protected doSetBoardsConfig(config: BoardsConfig.Config): void { + this.logger.info('Board config changed: ', JSON.stringify(config)); + this._boardsConfig = config; + this.latestBoardsConfig = this._boardsConfig; + if (this.canUploadTo(this._boardsConfig)) { + this.latestValidBoardsConfig = this._boardsConfig; } - - /** - * `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`. - */ - canVerify( - config: BoardsConfig.Config | undefined = this.boardsConfig, - options: { silent: boolean } = { silent: true } - ): config is BoardsConfig.Config & { selectedBoard: Board } { - if (!config) { - return false; - } - - if (!config.selectedBoard) { - if (!options.silent) { - this.messageService.warn('No boards selected.', { - timeout: 3000, - }); - } - return false; - } - - return true; + } + + async searchBoards({ + query, + cores, + }: { + query?: string; + cores?: string[]; + }): Promise { + const boards = await this.boardsService.searchBoards({ query }); + return boards; + } + + /** + * `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`. + */ + canVerify( + config: BoardsConfig.Config | undefined = this.boardsConfig, + options: { silent: boolean } = { silent: true } + ): config is BoardsConfig.Config & { selectedBoard: Board } { + if (!config) { + return false; } - /** - * `true` if `canVerify`, the board has an FQBN and the `config.selectedPort` is also set, hence can upload to board. Otherwise, `false`. - */ - canUploadTo( - config: BoardsConfig.Config | undefined = this.boardsConfig, - options: { silent: boolean } = { silent: true } - ): config is RecursiveRequired { - if (!this.canVerify(config, options)) { - return false; - } - - const { name } = config.selectedBoard; - if (!config.selectedPort) { - if (!options.silent) { - this.messageService.warn( - `No ports selected for board: '${name}'.`, - { timeout: 3000 } - ); - } - return false; - } - - if (!config.selectedBoard.fqbn) { - if (!options.silent) { - this.messageService.warn( - `The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, - { timeout: 3000 } - ); - } - return false; - } - - return true; + if (!config.selectedBoard) { + if (!options.silent) { + this.messageService.warn('No boards selected.', { + timeout: 3000, + }); + } + return false; } - get availableBoards(): AvailableBoard[] { - return this._availableBoards; + return true; + } + + /** + * `true` if `canVerify`, the board has an FQBN and the `config.selectedPort` is also set, hence can upload to board. Otherwise, `false`. + */ + canUploadTo( + config: BoardsConfig.Config | undefined = this.boardsConfig, + options: { silent: boolean } = { silent: true } + ): config is RecursiveRequired { + if (!this.canVerify(config, options)) { + return false; } - async waitUntilAvailable( - what: Board & { port: Port }, - timeout?: number - ): Promise { - const find = ( - needle: Board & { port: Port }, - haystack: AvailableBoard[] - ) => - haystack.find( - (board) => - Board.equals(needle, board) && - Port.equals(needle.port, board.port) - ); - const timeoutTask = - !!timeout && timeout > 0 - ? new Promise((_, reject) => - setTimeout( - () => - reject(new Error(`Timeout after ${timeout} ms.`)), - timeout - ) - ) - : new Promise(() => { - /* never */ - }); - const waitUntilTask = new Promise((resolve) => { - let candidate = find(what, this.availableBoards); - if (candidate) { - resolve(); - return; - } - const disposable = this.onAvailableBoardsChanged( - (availableBoards) => { - candidate = find(what, availableBoards); - if (candidate) { - disposable.dispose(); - resolve(); - } - } - ); + const { name } = config.selectedBoard; + if (!config.selectedPort) { + if (!options.silent) { + this.messageService.warn(`No ports selected for board: '${name}'.`, { + timeout: 3000, }); - return await Promise.race([waitUntilTask, timeoutTask]); + } + return false; } - protected async reconcileAvailableBoards(): Promise { - const attachedBoards = this._attachedBoards; - const availablePorts = this._availablePorts; - // Unset the port on the user's config, if it is not available anymore. - if ( - this.boardsConfig.selectedPort && - !availablePorts.some((port) => - Port.sameAs(port, this.boardsConfig.selectedPort) - ) - ) { - this.doSetBoardsConfig({ - selectedBoard: this.boardsConfig.selectedBoard, - selectedPort: undefined, - }); - this.onBoardsConfigChangedEmitter.fire(this._boardsConfig); - } - const boardsConfig = this.boardsConfig; - const currentAvailableBoards = this._availableBoards; - const availableBoards: AvailableBoard[] = []; - const availableBoardPorts = availablePorts.filter(Port.isBoardPort); - const attachedSerialBoards = attachedBoards.filter( - ({ port }) => !!port + if (!config.selectedBoard.fqbn) { + if (!options.silent) { + this.messageService.warn( + `The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, + { timeout: 3000 } ); + } + return false; + } - for (const boardPort of availableBoardPorts) { - let state = AvailableBoard.State.incomplete; // Initial pessimism. - let board = attachedSerialBoards.find(({ port }) => - Port.sameAs(boardPort, port) - ); - if (board) { - state = AvailableBoard.State.recognized; - } else { - // If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623 - // We still want to show it without the red X in the boards toolbar: https://github.com/arduino/arduino-pro-ide/issues/198#issuecomment-599355836 - const lastSelectedBoard = await this.getLastSelectedBoardOnPort( - boardPort - ); - if (lastSelectedBoard) { - board = { - ...lastSelectedBoard, - port: boardPort, - }; - state = AvailableBoard.State.guessed; - } - } - if (!board) { - availableBoards.push({ - name: 'Unknown', - port: boardPort, - state, - }); - } else { - const selected = BoardsConfig.Config.sameAs( - boardsConfig, - board - ); - availableBoards.push({ - ...board, - state, - selected, - port: boardPort, - }); - } - } - - if ( - boardsConfig.selectedBoard && - !availableBoards.some(({ selected }) => selected) - ) { - availableBoards.push({ - ...boardsConfig.selectedBoard, - port: boardsConfig.selectedPort, - selected: true, - state: AvailableBoard.State.incomplete, - }); + return true; + } + + get availableBoards(): AvailableBoard[] { + return this._availableBoards; + } + + async waitUntilAvailable( + what: Board & { port: Port }, + timeout?: number + ): Promise { + const find = (needle: Board & { port: Port }, haystack: AvailableBoard[]) => + haystack.find( + (board) => + Board.equals(needle, board) && Port.equals(needle.port, board.port) + ); + const timeoutTask = + !!timeout && timeout > 0 + ? new Promise((_, reject) => + setTimeout( + () => reject(new Error(`Timeout after ${timeout} ms.`)), + timeout + ) + ) + : new Promise(() => { + /* never */ + }); + const waitUntilTask = new Promise((resolve) => { + let candidate = find(what, this.availableBoards); + if (candidate) { + resolve(); + return; + } + const disposable = this.onAvailableBoardsChanged((availableBoards) => { + candidate = find(what, availableBoards); + if (candidate) { + disposable.dispose(); + resolve(); } - - const sortedAvailableBoards = availableBoards.sort( - AvailableBoard.compare + }); + }); + return await Promise.race([waitUntilTask, timeoutTask]); + } + + protected async reconcileAvailableBoards(): Promise { + const attachedBoards = this._attachedBoards; + const availablePorts = this._availablePorts; + // Unset the port on the user's config, if it is not available anymore. + if ( + this.boardsConfig.selectedPort && + !availablePorts.some((port) => + Port.sameAs(port, this.boardsConfig.selectedPort) + ) + ) { + this.doSetBoardsConfig({ + selectedBoard: this.boardsConfig.selectedBoard, + selectedPort: undefined, + }); + this.onBoardsConfigChangedEmitter.fire(this._boardsConfig); + } + const boardsConfig = this.boardsConfig; + const currentAvailableBoards = this._availableBoards; + const availableBoards: AvailableBoard[] = []; + const availableBoardPorts = availablePorts.filter(Port.isBoardPort); + const attachedSerialBoards = attachedBoards.filter(({ port }) => !!port); + + for (const boardPort of availableBoardPorts) { + let state = AvailableBoard.State.incomplete; // Initial pessimism. + let board = attachedSerialBoards.find(({ port }) => + Port.sameAs(boardPort, port) + ); + if (board) { + state = AvailableBoard.State.recognized; + } else { + // If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623 + // We still want to show it without the red X in the boards toolbar: https://github.com/arduino/arduino-pro-ide/issues/198#issuecomment-599355836 + const lastSelectedBoard = await this.getLastSelectedBoardOnPort( + boardPort ); - let hasChanged = - sortedAvailableBoards.length !== currentAvailableBoards.length; - for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) { - hasChanged = - AvailableBoard.compare( - sortedAvailableBoards[i], - currentAvailableBoards[i] - ) !== 0; - } - if (hasChanged) { - this._availableBoards = sortedAvailableBoards; - this.onAvailableBoardsChangedEmitter.fire(this._availableBoards); + if (lastSelectedBoard) { + board = { + ...lastSelectedBoard, + port: boardPort, + }; + state = AvailableBoard.State.guessed; } + } + if (!board) { + availableBoards.push({ + name: 'Unknown', + port: boardPort, + state, + }); + } else { + const selected = BoardsConfig.Config.sameAs(boardsConfig, board); + availableBoards.push({ + ...board, + state, + selected, + port: boardPort, + }); + } } - protected async getLastSelectedBoardOnPort( - port: Port | string | undefined - ): Promise { - if (!port) { - return undefined; - } - const key = this.getLastSelectedBoardOnPortKey(port); - return this.getData(key); + if ( + boardsConfig.selectedBoard && + !availableBoards.some(({ selected }) => selected) + ) { + availableBoards.push({ + ...boardsConfig.selectedBoard, + port: boardsConfig.selectedPort, + selected: true, + state: AvailableBoard.State.incomplete, + }); } - protected async saveState(): Promise { - // We save the port with the selected board name/FQBN, to be able to guess a better board name. - // Required when the attached board belongs to a 3rd party boards package, and neither the name, nor - // the FQBN can be retrieved with a `board list` command. - // https://github.com/arduino/arduino-cli/issues/623 - const { selectedBoard, selectedPort } = this.boardsConfig; - if (selectedBoard && selectedPort) { - const key = this.getLastSelectedBoardOnPortKey(selectedPort); - await this.setData(key, selectedBoard); - } - await Promise.all([ - this.setData( - 'latest-valid-boards-config', - this.latestValidBoardsConfig - ), - this.setData('latest-boards-config', this.latestBoardsConfig), - ]); + const sortedAvailableBoards = availableBoards.sort(AvailableBoard.compare); + let hasChanged = + sortedAvailableBoards.length !== currentAvailableBoards.length; + for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) { + hasChanged = + AvailableBoard.compare( + sortedAvailableBoards[i], + currentAvailableBoards[i] + ) !== 0; } - - protected getLastSelectedBoardOnPortKey(port: Port | string): string { - // TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`. - return `last-selected-board-on-port:${ - typeof port === 'string' ? port : Port.toString(port) - }`; + if (hasChanged) { + this._availableBoards = sortedAvailableBoards; + this.onAvailableBoardsChangedEmitter.fire(this._availableBoards); } + } - protected async loadState(): Promise { - const storedLatestValidBoardsConfig = await this.getData< - RecursiveRequired - >('latest-valid-boards-config'); - if (storedLatestValidBoardsConfig) { - this.latestValidBoardsConfig = storedLatestValidBoardsConfig; - if (this.canUploadTo(this.latestValidBoardsConfig)) { - this.boardsConfig = this.latestValidBoardsConfig; - } - } else { - // If we could not restore the latest valid config, try to restore something, the board at least. - let storedLatestBoardsConfig = await this.getData< - BoardsConfig.Config | undefined - >('latest-boards-config'); - // Try to get from the URL if it was not persisted. - if (!storedLatestBoardsConfig) { - storedLatestBoardsConfig = BoardsConfig.Config.getConfig( - new URL(window.location.href) - ); - } - if (storedLatestBoardsConfig) { - this.latestBoardsConfig = storedLatestBoardsConfig; - this.boardsConfig = this.latestBoardsConfig; - } - } + protected async getLastSelectedBoardOnPort( + port: Port | string | undefined + ): Promise { + if (!port) { + return undefined; } - - private setData(key: string, value: T): Promise { - return this.commandService.executeCommand( - StorageWrapper.Commands.SET_DATA.id, - key, - value - ); + const key = this.getLastSelectedBoardOnPortKey(port); + return this.getData(key); + } + + protected async saveState(): Promise { + // We save the port with the selected board name/FQBN, to be able to guess a better board name. + // Required when the attached board belongs to a 3rd party boards package, and neither the name, nor + // the FQBN can be retrieved with a `board list` command. + // https://github.com/arduino/arduino-cli/issues/623 + const { selectedBoard, selectedPort } = this.boardsConfig; + if (selectedBoard && selectedPort) { + const key = this.getLastSelectedBoardOnPortKey(selectedPort); + await this.setData(key, selectedBoard); } - - private getData(key: string): Promise { - return this.commandService.executeCommand( - StorageWrapper.Commands.GET_DATA.id, - key + await Promise.all([ + this.setData('latest-valid-boards-config', this.latestValidBoardsConfig), + this.setData('latest-boards-config', this.latestBoardsConfig), + ]); + } + + protected getLastSelectedBoardOnPortKey(port: Port | string): string { + // TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`. + return `last-selected-board-on-port:${ + typeof port === 'string' ? port : Port.toString(port) + }`; + } + + protected async loadState(): Promise { + const storedLatestValidBoardsConfig = await this.getData< + RecursiveRequired + >('latest-valid-boards-config'); + if (storedLatestValidBoardsConfig) { + this.latestValidBoardsConfig = storedLatestValidBoardsConfig; + if (this.canUploadTo(this.latestValidBoardsConfig)) { + this.boardsConfig = this.latestValidBoardsConfig; + } + } else { + // If we could not restore the latest valid config, try to restore something, the board at least. + let storedLatestBoardsConfig = await this.getData< + BoardsConfig.Config | undefined + >('latest-boards-config'); + // Try to get from the URL if it was not persisted. + if (!storedLatestBoardsConfig) { + storedLatestBoardsConfig = BoardsConfig.Config.getConfig( + new URL(window.location.href) ); + } + if (storedLatestBoardsConfig) { + this.latestBoardsConfig = storedLatestBoardsConfig; + this.boardsConfig = this.latestBoardsConfig; + } } + } + + private setData(key: string, value: T): Promise { + return this.commandService.executeCommand( + StorageWrapper.Commands.SET_DATA.id, + key, + value + ); + } + + private getData(key: string): Promise { + return this.commandService.executeCommand( + StorageWrapper.Commands.GET_DATA.id, + key + ); + } } /** @@ -566,66 +533,66 @@ export class BoardsServiceProvider implements FrontendApplicationContribution { * when it has the `port` set. */ export interface AvailableBoard extends Board { - readonly state: AvailableBoard.State; - readonly selected?: boolean; - readonly port?: Port; + readonly state: AvailableBoard.State; + readonly selected?: boolean; + readonly port?: Port; } export namespace AvailableBoard { - export enum State { - /** - * Retrieved from the CLI via the `board list` command. - */ - 'recognized', - /** - * Guessed the name/FQBN of the board from the available board ports (3rd party). - */ - 'guessed', - /** - * We do not know anything about this board, probably a 3rd party. The user has not selected a board for this port yet. - */ - 'incomplete', + export enum State { + /** + * Retrieved from the CLI via the `board list` command. + */ + 'recognized', + /** + * Guessed the name/FQBN of the board from the available board ports (3rd party). + */ + 'guessed', + /** + * We do not know anything about this board, probably a 3rd party. The user has not selected a board for this port yet. + */ + 'incomplete', + } + + export function is(board: any): board is AvailableBoard { + return Board.is(board) && 'state' in board; + } + + export function hasPort( + board: AvailableBoard + ): board is AvailableBoard & { port: Port } { + return !!board.port; + } + + export const compare = (left: AvailableBoard, right: AvailableBoard) => { + if (left.selected && !right.selected) { + return -1; } - - export function is(board: any): board is AvailableBoard { - return Board.is(board) && 'state' in board; + if (right.selected && !left.selected) { + return 1; } - - export function hasPort( - board: AvailableBoard - ): board is AvailableBoard & { port: Port } { - return !!board.port; + let result = naturalCompare(left.name, right.name); + if (result !== 0) { + return result; } - - export const compare = (left: AvailableBoard, right: AvailableBoard) => { - if (left.selected && !right.selected) { - return -1; - } - if (right.selected && !left.selected) { - return 1; - } - let result = naturalCompare(left.name, right.name); - if (result !== 0) { - return result; - } - if (left.fqbn && right.fqbn) { - result = naturalCompare(left.fqbn, right.fqbn); - if (result !== 0) { - return result; - } - } - if (left.port && right.port) { - result = Port.compare(left.port, right.port); - if (result !== 0) { - return result; - } - } - if (!!left.selected && !right.selected) { - return -1; - } - if (!!right.selected && !left.selected) { - return 1; - } - return left.state - right.state; - }; + if (left.fqbn && right.fqbn) { + result = naturalCompare(left.fqbn, right.fqbn); + if (result !== 0) { + return result; + } + } + if (left.port && right.port) { + result = Port.compare(left.port, right.port); + if (result !== 0) { + return result; + } + } + if (!!left.selected && !right.selected) { + return -1; + } + if (!!right.selected && !left.selected) { + return 1; + } + return left.state - right.state; + }; } diff --git a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx index 5c83a74d7..b3d399a44 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -6,232 +6,217 @@ import { Port } from '../../common/protocol'; import { BoardsConfig } from './boards-config'; import { ArduinoCommands } from '../arduino-commands'; import { - BoardsServiceProvider, - AvailableBoard, + BoardsServiceProvider, + AvailableBoard, } from './boards-service-provider'; export interface BoardsDropDownListCoords { - readonly top: number; - readonly left: number; - readonly width: number; - readonly paddingTop: number; + readonly top: number; + readonly left: number; + readonly width: number; + readonly paddingTop: number; } export namespace BoardsDropDown { - export interface Props { - readonly coords: BoardsDropDownListCoords | 'hidden'; - readonly items: Array< - AvailableBoard & { onClick: () => void; port: Port } - >; - readonly openBoardsConfig: () => void; - } + export interface Props { + readonly coords: BoardsDropDownListCoords | 'hidden'; + readonly items: Array void; port: Port }>; + readonly openBoardsConfig: () => void; + } } export class BoardsDropDown extends React.Component { - protected dropdownElement: HTMLElement; - - constructor(props: BoardsDropDown.Props) { - super(props); - - let list = document.getElementById('boards-dropdown-container'); - if (!list) { - list = document.createElement('div'); - list.id = 'boards-dropdown-container'; - document.body.appendChild(list); - this.dropdownElement = list; - } - } + protected dropdownElement: HTMLElement; - render(): React.ReactNode { - return ReactDOM.createPortal(this.renderNode(), this.dropdownElement); - } + constructor(props: BoardsDropDown.Props) { + super(props); - protected renderNode(): React.ReactNode { - const { coords, items } = this.props; - if (coords === 'hidden') { - return ''; - } - return ( -
- {this.renderItem({ - label: 'Select Other Board & Port', - onClick: () => this.props.openBoardsConfig(), - })} - {items - .map(({ name, port, selected, onClick }) => ({ - label: `${name} at ${Port.toString(port)}`, - selected, - onClick, - })) - .map(this.renderItem)} -
- ); + let list = document.getElementById('boards-dropdown-container'); + if (!list) { + list = document.createElement('div'); + list.id = 'boards-dropdown-container'; + document.body.appendChild(list); + this.dropdownElement = list; } + } - protected renderItem({ - label, - selected, - onClick, - }: { - label: string; - selected?: boolean; - onClick: () => void; - }): React.ReactNode { - return ( -
-
{label}
- {selected ? : ''} -
- ); + render(): React.ReactNode { + return ReactDOM.createPortal(this.renderNode(), this.dropdownElement); + } + + protected renderNode(): React.ReactNode { + const { coords, items } = this.props; + if (coords === 'hidden') { + return ''; } + return ( +
+ {this.renderItem({ + label: 'Select Other Board & Port', + onClick: () => this.props.openBoardsConfig(), + })} + {items + .map(({ name, port, selected, onClick }) => ({ + label: `${name} at ${Port.toString(port)}`, + selected, + onClick, + })) + .map(this.renderItem)} +
+ ); + } + + protected renderItem({ + label, + selected, + onClick, + }: { + label: string; + selected?: boolean; + onClick: () => void; + }): React.ReactNode { + return ( +
+
{label}
+ {selected ? : ''} +
+ ); + } } export class BoardsToolBarItem extends React.Component< - BoardsToolBarItem.Props, - BoardsToolBarItem.State + BoardsToolBarItem.Props, + BoardsToolBarItem.State > { - static TOOLBAR_ID: 'boards-toolbar'; - - protected readonly toDispose: DisposableCollection = - new DisposableCollection(); - - constructor(props: BoardsToolBarItem.Props) { - super(props); - - const { availableBoards } = props.boardsServiceClient; - this.state = { - availableBoards, - coords: 'hidden', - }; - - document.addEventListener('click', () => { - this.setState({ coords: 'hidden' }); - }); - } + static TOOLBAR_ID: 'boards-toolbar'; - componentDidMount() { - this.props.boardsServiceClient.onAvailableBoardsChanged( - (availableBoards) => this.setState({ availableBoards }) - ); - } + protected readonly toDispose: DisposableCollection = + new DisposableCollection(); - componentWillUnmount(): void { - this.toDispose.dispose(); - } + constructor(props: BoardsToolBarItem.Props) { + super(props); - protected readonly show = (event: React.MouseEvent) => { - const { currentTarget: element } = event; - if (element instanceof HTMLElement) { - if (this.state.coords === 'hidden') { - const rect = element.getBoundingClientRect(); - this.setState({ - coords: { - top: rect.top, - left: rect.left, - width: rect.width, - paddingTop: rect.height, - }, - }); - } else { - this.setState({ coords: 'hidden' }); - } - } - event.stopPropagation(); - event.nativeEvent.stopImmediatePropagation(); + const { availableBoards } = props.boardsServiceClient; + this.state = { + availableBoards, + coords: 'hidden', }; - render(): React.ReactNode { - const { coords, availableBoards } = this.state; - const boardsConfig = this.props.boardsServiceClient.boardsConfig; - const title = BoardsConfig.Config.toString(boardsConfig, { - default: 'no board selected', + document.addEventListener('click', () => { + this.setState({ coords: 'hidden' }); + }); + } + + componentDidMount() { + this.props.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => + this.setState({ availableBoards }) + ); + } + + componentWillUnmount(): void { + this.toDispose.dispose(); + } + + protected readonly show = (event: React.MouseEvent) => { + const { currentTarget: element } = event; + if (element instanceof HTMLElement) { + if (this.state.coords === 'hidden') { + const rect = element.getBoundingClientRect(); + this.setState({ + coords: { + top: rect.top, + left: rect.left, + width: rect.width, + paddingTop: rect.height, + }, }); - const decorator = (() => { - const selectedBoard = availableBoards.find( - ({ selected }) => selected - ); - if (!selectedBoard || !selectedBoard.port) { - return 'fa fa-times notAttached'; - } - if (selectedBoard.state === AvailableBoard.State.guessed) { - return 'fa fa-exclamation-triangle guessed'; - } - return ''; - })(); - - return ( - -
-
-
- -
-
- {title} -
-
- -
-
-
- ({ - ...board, - onClick: () => { - if ( - board.state === - AvailableBoard.State.incomplete - ) { - this.props.boardsServiceClient.boardsConfig = - { - selectedPort: board.port, - }; - this.openDialog(); - } else { - this.props.boardsServiceClient.boardsConfig = - { - selectedBoard: board, - selectedPort: board.port, - }; - } - }, - }))} - openBoardsConfig={this.openDialog} - > -
- ); - } - - protected openDialog = () => { - this.props.commands.executeCommand( - ArduinoCommands.OPEN_BOARDS_DIALOG.id - ); + } else { this.setState({ coords: 'hidden' }); - }; + } + } + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + }; + + render(): React.ReactNode { + const { coords, availableBoards } = this.state; + const boardsConfig = this.props.boardsServiceClient.boardsConfig; + const title = BoardsConfig.Config.toString(boardsConfig, { + default: 'no board selected', + }); + const decorator = (() => { + const selectedBoard = availableBoards.find(({ selected }) => selected); + if (!selectedBoard || !selectedBoard.port) { + return 'fa fa-times notAttached'; + } + if (selectedBoard.state === AvailableBoard.State.guessed) { + return 'fa fa-exclamation-triangle guessed'; + } + return ''; + })(); + + return ( + +
+
+
+ +
+
{title}
+
+ +
+
+
+ ({ + ...board, + onClick: () => { + if (board.state === AvailableBoard.State.incomplete) { + this.props.boardsServiceClient.boardsConfig = { + selectedPort: board.port, + }; + this.openDialog(); + } else { + this.props.boardsServiceClient.boardsConfig = { + selectedBoard: board, + selectedPort: board.port, + }; + } + }, + }))} + openBoardsConfig={this.openDialog} + > +
+ ); + } + + protected openDialog = () => { + this.props.commands.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id); + this.setState({ coords: 'hidden' }); + }; } export namespace BoardsToolBarItem { - export interface Props { - readonly boardsServiceClient: BoardsServiceProvider; - readonly commands: CommandRegistry; - } - - export interface State { - availableBoards: AvailableBoard[]; - coords: BoardsDropDownListCoords | 'hidden'; - } + export interface Props { + readonly boardsServiceClient: BoardsServiceProvider; + readonly commands: CommandRegistry; + } + + export interface State { + availableBoards: AvailableBoard[]; + coords: BoardsDropDownListCoords | 'hidden'; + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts index bc344623d..21aed8310 100644 --- a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts @@ -5,20 +5,20 @@ import { ListWidgetFrontendContribution } from '../widgets/component-list/list-w @injectable() export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution { - constructor() { - super({ - widgetId: BoardsListWidget.WIDGET_ID, - widgetName: BoardsListWidget.WIDGET_LABEL, - defaultWidgetOptions: { - area: 'left', - rank: 2, - }, - toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`, - toggleKeybinding: 'CtrlCmd+Shift+B', - }); - } + constructor() { + super({ + widgetId: BoardsListWidget.WIDGET_ID, + widgetName: BoardsListWidget.WIDGET_LABEL, + defaultWidgetOptions: { + area: 'left', + rank: 2, + }, + toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`, + toggleKeybinding: 'CtrlCmd+Shift+B', + }); + } - async initializeLayout(): Promise { - this.openView(); - } + async initializeLayout(): Promise { + this.openView(); + } } diff --git a/arduino-ide-extension/src/browser/contributions/about.ts b/arduino-ide-extension/src/browser/contributions/about.ts index 96753eb8a..f6e9c37b1 100644 --- a/arduino-ide-extension/src/browser/contributions/about.ts +++ b/arduino-ide-extension/src/browser/contributions/about.ts @@ -5,119 +5,113 @@ import { isOSX, isWindows } from '@theia/core/lib/common/os'; import { ClipboardService } from '@theia/core/lib/browser/clipboard-service'; import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; import { - Contribution, - Command, - MenuModelRegistry, - CommandRegistry, + Contribution, + Command, + MenuModelRegistry, + CommandRegistry, } from './contribution'; import { ArduinoMenus } from '../menu/arduino-menus'; import { ConfigService } from '../../common/protocol'; @injectable() export class About extends Contribution { - @inject(ClipboardService) - protected readonly clipboardService: ClipboardService; + @inject(ClipboardService) + protected readonly clipboardService: ClipboardService; - @inject(ConfigService) - protected readonly configService: ConfigService; + @inject(ConfigService) + protected readonly configService: ConfigService; - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(About.Commands.ABOUT_APP, { - execute: () => this.showAbout(), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(About.Commands.ABOUT_APP, { + execute: () => this.showAbout(), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, { - commandId: About.Commands.ABOUT_APP.id, - label: `About ${this.applicationName}`, - order: '0', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, { + commandId: About.Commands.ABOUT_APP.id, + label: `About ${this.applicationName}`, + order: '0', + }); + } - async showAbout(): Promise { - const { - version, - commit, - status: cliStatus, - } = await this.configService.getVersion(); - const buildDate = this.buildDate; - const detail = ( - showAll: boolean - ) => `Version: ${remote.app.getVersion()} + async showAbout(): Promise { + const { + version, + commit, + status: cliStatus, + } = await this.configService.getVersion(); + const buildDate = this.buildDate; + const detail = (showAll: boolean) => `Version: ${remote.app.getVersion()} Date: ${buildDate ? buildDate : 'dev build'}${ - buildDate && showAll ? ` (${this.ago(buildDate)})` : '' - } + buildDate && showAll ? ` (${this.ago(buildDate)})` : '' + } CLI Version: ${version}${cliStatus ? ` ${cliStatus}` : ''} [${commit}] ${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''} `; - const ok = 'OK'; - const copy = 'Copy'; - const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy]; - const { response } = await remote.dialog.showMessageBox( - remote.getCurrentWindow(), - { - message: `${this.applicationName}`, - title: `${this.applicationName}`, - type: 'info', - detail: detail(true), - buttons, - noLink: true, - defaultId: buttons.indexOf(ok), - cancelId: buttons.indexOf(ok), - } - ); + const ok = 'OK'; + const copy = 'Copy'; + const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy]; + const { response } = await remote.dialog.showMessageBox( + remote.getCurrentWindow(), + { + message: `${this.applicationName}`, + title: `${this.applicationName}`, + type: 'info', + detail: detail(true), + buttons, + noLink: true, + defaultId: buttons.indexOf(ok), + cancelId: buttons.indexOf(ok), + } + ); - if (buttons[response] === copy) { - await this.clipboardService.writeText(detail(false).trim()); - } + if (buttons[response] === copy) { + await this.clipboardService.writeText(detail(false).trim()); } + } - protected get applicationName(): string { - return FrontendApplicationConfigProvider.get().applicationName; - } + protected get applicationName(): string { + return FrontendApplicationConfigProvider.get().applicationName; + } - protected get buildDate(): string | undefined { - return FrontendApplicationConfigProvider.get().buildDate; - } + protected get buildDate(): string | undefined { + return FrontendApplicationConfigProvider.get().buildDate; + } - protected ago(isoTime: string): string { - const now = moment(Date.now()); - const other = moment(isoTime); - let result = now.diff(other, 'minute'); - if (result < 60) { - return result === 1 - ? `${result} minute ago` - : `${result} minute ago`; - } - result = now.diff(other, 'hour'); - if (result < 25) { - return result === 1 ? `${result} hour ago` : `${result} hours ago`; - } - result = now.diff(other, 'day'); - if (result < 8) { - return result === 1 ? `${result} day ago` : `${result} days ago`; - } - result = now.diff(other, 'week'); - if (result < 5) { - return result === 1 ? `${result} week ago` : `${result} weeks ago`; - } - result = now.diff(other, 'month'); - if (result < 13) { - return result === 1 - ? `${result} month ago` - : `${result} months ago`; - } - result = now.diff(other, 'year'); - return result === 1 ? `${result} year ago` : `${result} years ago`; + protected ago(isoTime: string): string { + const now = moment(Date.now()); + const other = moment(isoTime); + let result = now.diff(other, 'minute'); + if (result < 60) { + return result === 1 ? `${result} minute ago` : `${result} minute ago`; } + result = now.diff(other, 'hour'); + if (result < 25) { + return result === 1 ? `${result} hour ago` : `${result} hours ago`; + } + result = now.diff(other, 'day'); + if (result < 8) { + return result === 1 ? `${result} day ago` : `${result} days ago`; + } + result = now.diff(other, 'week'); + if (result < 5) { + return result === 1 ? `${result} week ago` : `${result} weeks ago`; + } + result = now.diff(other, 'month'); + if (result < 13) { + return result === 1 ? `${result} month ago` : `${result} months ago`; + } + result = now.diff(other, 'year'); + return result === 1 ? `${result} year ago` : `${result} years ago`; + } } export namespace About { - export namespace Commands { - export const ABOUT_APP: Command = { - id: 'arduino-about', - }; - } + export namespace Commands { + export const ABOUT_APP: Command = { + id: 'arduino-about', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/add-file.ts b/arduino-ide-extension/src/browser/contributions/add-file.ts index 75b742e5b..121047f95 100644 --- a/arduino-ide-extension/src/browser/contributions/add-file.ts +++ b/arduino-ide-extension/src/browser/contributions/add-file.ts @@ -2,74 +2,74 @@ import { inject, injectable } from 'inversify'; import { remote } from 'electron'; import { ArduinoMenus } from '../menu/arduino-menus'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, - URI, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, + URI, } from './contribution'; import { FileDialogService } from '@theia/filesystem/lib/browser'; @injectable() export class AddFile extends SketchContribution { - @inject(FileDialogService) - protected readonly fileDialogService: FileDialogService; + @inject(FileDialogService) + protected readonly fileDialogService: FileDialogService; - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(AddFile.Commands.ADD_FILE, { - execute: () => this.addFile(), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(AddFile.Commands.ADD_FILE, { + execute: () => this.addFile(), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { - commandId: AddFile.Commands.ADD_FILE.id, - label: 'Add File...', - order: '2', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { + commandId: AddFile.Commands.ADD_FILE.id, + label: 'Add File...', + order: '2', + }); + } - protected async addFile(): Promise { - const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { - return; - } - const toAddUri = await this.fileDialogService.showOpenDialog({ - title: 'Add File', - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false, - }); - if (!toAddUri) { - return; - } - const sketchUri = new URI(sketch.uri); - const filename = toAddUri.path.base; - const targetUri = sketchUri.resolve('data').resolve(filename); - const exists = await this.fileService.exists(targetUri); - if (exists) { - const { response } = await remote.dialog.showMessageBox({ - type: 'question', - title: 'Replace', - buttons: ['Cancel', 'OK'], - message: `Replace the existing version of ${filename}?`, - }); - if (response === 0) { - // Cancel - return; - } - } - await this.fileService.copy(toAddUri, targetUri, { overwrite: true }); - this.messageService.info('One file added to the sketch.', { - timeout: 2000, - }); + protected async addFile(): Promise { + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return; + } + const toAddUri = await this.fileDialogService.showOpenDialog({ + title: 'Add File', + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + }); + if (!toAddUri) { + return; } + const sketchUri = new URI(sketch.uri); + const filename = toAddUri.path.base; + const targetUri = sketchUri.resolve('data').resolve(filename); + const exists = await this.fileService.exists(targetUri); + if (exists) { + const { response } = await remote.dialog.showMessageBox({ + type: 'question', + title: 'Replace', + buttons: ['Cancel', 'OK'], + message: `Replace the existing version of ${filename}?`, + }); + if (response === 0) { + // Cancel + return; + } + } + await this.fileService.copy(toAddUri, targetUri, { overwrite: true }); + this.messageService.info('One file added to the sketch.', { + timeout: 2000, + }); + } } export namespace AddFile { - export namespace Commands { - export const ADD_FILE: Command = { - id: 'arduino-add-file', - }; - } + export namespace Commands { + export const ADD_FILE: Command = { + id: 'arduino-add-file', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/add-zip-library.ts b/arduino-ide-extension/src/browser/contributions/add-zip-library.ts index 5ba58119b..e955d8b93 100644 --- a/arduino-ide-extension/src/browser/contributions/add-zip-library.ts +++ b/arduino-ide-extension/src/browser/contributions/add-zip-library.ts @@ -7,133 +7,127 @@ import { ArduinoMenus } from '../menu/arduino-menus'; import { ResponseServiceImpl } from '../response-service-impl'; import { Installable, LibraryService } from '../../common/protocol'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, } from './contribution'; @injectable() export class AddZipLibrary extends SketchContribution { - @inject(EnvVariablesServer) - protected readonly envVariableServer: EnvVariablesServer; + @inject(EnvVariablesServer) + protected readonly envVariableServer: EnvVariablesServer; - @inject(ResponseServiceImpl) - protected readonly responseService: ResponseServiceImpl; + @inject(ResponseServiceImpl) + protected readonly responseService: ResponseServiceImpl; - @inject(LibraryService) - protected readonly libraryService: LibraryService; + @inject(LibraryService) + protected readonly libraryService: LibraryService; - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, { - execute: () => this.addZipLibrary(), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, { + execute: () => this.addZipLibrary(), + }); + } - registerMenus(registry: MenuModelRegistry): void { - const includeLibMenuPath = [ - ...ArduinoMenus.SKETCH__UTILS_GROUP, - '0_include', - ]; - // TODO: do we need it? calling `registerSubmenu` multiple times is noop, so it does not hurt. - registry.registerSubmenu(includeLibMenuPath, 'Include Library', { - order: '1', - }); - registry.registerMenuAction([...includeLibMenuPath, '1_install'], { - commandId: AddZipLibrary.Commands.ADD_ZIP_LIBRARY.id, - label: 'Add .ZIP Library...', - order: '1', - }); - } + registerMenus(registry: MenuModelRegistry): void { + const includeLibMenuPath = [ + ...ArduinoMenus.SKETCH__UTILS_GROUP, + '0_include', + ]; + // TODO: do we need it? calling `registerSubmenu` multiple times is noop, so it does not hurt. + registry.registerSubmenu(includeLibMenuPath, 'Include Library', { + order: '1', + }); + registry.registerMenuAction([...includeLibMenuPath, '1_install'], { + commandId: AddZipLibrary.Commands.ADD_ZIP_LIBRARY.id, + label: 'Add .ZIP Library...', + order: '1', + }); + } - async addZipLibrary(): Promise { - const homeUri = await this.envVariableServer.getHomeDirUri(); - const defaultPath = await this.fileService.fsPath(new URI(homeUri)); - const { canceled, filePaths } = await remote.dialog.showOpenDialog({ - title: "Select a zip file containing the library you'd like to add", - defaultPath, - properties: ['openFile'], - filters: [ - { - name: 'Library', - extensions: ['zip'], - }, - ], - }); - if (!canceled && filePaths.length) { - const zipUri = await this.fileSystemExt.getUri(filePaths[0]); - try { - await this.doInstall(zipUri); - } catch (error) { - if (error instanceof AlreadyInstalledError) { - const result = await new ConfirmDialog({ - msg: error.message, - title: 'Do you want to overwrite the existing library?', - ok: 'Yes', - cancel: 'No', - }).open(); - if (result) { - await this.doInstall(zipUri, true); - } - } - } + async addZipLibrary(): Promise { + const homeUri = await this.envVariableServer.getHomeDirUri(); + const defaultPath = await this.fileService.fsPath(new URI(homeUri)); + const { canceled, filePaths } = await remote.dialog.showOpenDialog({ + title: "Select a zip file containing the library you'd like to add", + defaultPath, + properties: ['openFile'], + filters: [ + { + name: 'Library', + extensions: ['zip'], + }, + ], + }); + if (!canceled && filePaths.length) { + const zipUri = await this.fileSystemExt.getUri(filePaths[0]); + try { + await this.doInstall(zipUri); + } catch (error) { + if (error instanceof AlreadyInstalledError) { + const result = await new ConfirmDialog({ + msg: error.message, + title: 'Do you want to overwrite the existing library?', + ok: 'Yes', + cancel: 'No', + }).open(); + if (result) { + await this.doInstall(zipUri, true); + } } + } } + } - private async doInstall( - zipUri: string, - overwrite?: boolean - ): Promise { - try { - await Installable.doWithProgress({ - messageService: this.messageService, - progressText: `Processing ${new URI(zipUri).path.base}`, - responseService: this.responseService, - run: () => - this.libraryService.installZip({ zipUri, overwrite }), - }); - this.messageService.info( - `Successfully installed library from ${ - new URI(zipUri).path.base - } archive`, - { timeout: 3000 } + private async doInstall(zipUri: string, overwrite?: boolean): Promise { + try { + await Installable.doWithProgress({ + messageService: this.messageService, + progressText: `Processing ${new URI(zipUri).path.base}`, + responseService: this.responseService, + run: () => this.libraryService.installZip({ zipUri, overwrite }), + }); + this.messageService.info( + `Successfully installed library from ${ + new URI(zipUri).path.base + } archive`, + { timeout: 3000 } + ); + } catch (error) { + if (error instanceof Error) { + const match = error.message.match(/library (.*?) already installed/); + if (match && match.length >= 2) { + const name = match[1].trim(); + if (name) { + throw new AlreadyInstalledError( + `A library folder named ${name} already exists. Do you want to overwrite it?`, + name + ); + } else { + throw new AlreadyInstalledError( + 'A library already exists. Do you want to overwrite it?' ); - } catch (error) { - if (error instanceof Error) { - const match = error.message.match( - /library (.*?) already installed/ - ); - if (match && match.length >= 2) { - const name = match[1].trim(); - if (name) { - throw new AlreadyInstalledError( - `A library folder named ${name} already exists. Do you want to overwrite it?`, - name - ); - } else { - throw new AlreadyInstalledError( - 'A library already exists. Do you want to overwrite it?' - ); - } - } - } - this.messageService.error(error.toString()); - throw error; + } } + } + this.messageService.error(error.toString()); + throw error; } + } } class AlreadyInstalledError extends Error { - constructor(message: string, readonly libraryName?: string) { - super(message); - Object.setPrototypeOf(this, AlreadyInstalledError.prototype); - } + constructor(message: string, readonly libraryName?: string) { + super(message); + Object.setPrototypeOf(this, AlreadyInstalledError.prototype); + } } export namespace AddZipLibrary { - export namespace Commands { - export const ADD_ZIP_LIBRARY: Command = { - id: 'arduino-add-zip-library', - }; - } + export namespace Commands { + export const ADD_ZIP_LIBRARY: Command = { + id: 'arduino-add-zip-library', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts index 95a6145e2..72f418120 100644 --- a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts @@ -4,65 +4,65 @@ import * as dateFormat from 'dateformat'; import URI from '@theia/core/lib/common/uri'; import { ArduinoMenus } from '../menu/arduino-menus'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, } from './contribution'; @injectable() export class ArchiveSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, { - execute: () => this.archiveSketch(), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, { + execute: () => this.archiveSketch(), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { - commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id, - label: 'Archive Sketch', - order: '1', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { + commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id, + label: 'Archive Sketch', + order: '1', + }); + } - protected async archiveSketch(): Promise { - const [sketch, config] = await Promise.all([ - this.sketchServiceClient.currentSketch(), - this.configService.getConfiguration(), - ]); - if (!sketch) { - return; - } - const archiveBasename = `${sketch.name}-${dateFormat( - new Date(), - 'yymmdd' - )}a.zip`; - const defaultPath = await this.fileService.fsPath( - new URI(config.sketchDirUri).resolve(archiveBasename) - ); - const { filePath, canceled } = await remote.dialog.showSaveDialog({ - title: 'Save sketch folder as...', - defaultPath, - }); - if (!filePath || canceled) { - return; - } - const destinationUri = await this.fileSystemExt.getUri(filePath); - if (!destinationUri) { - return; - } - await this.sketchService.archive(sketch, destinationUri.toString()); - this.messageService.info(`Created archive '${archiveBasename}'.`, { - timeout: 2000, - }); + protected async archiveSketch(): Promise { + const [sketch, config] = await Promise.all([ + this.sketchServiceClient.currentSketch(), + this.configService.getConfiguration(), + ]); + if (!sketch) { + return; + } + const archiveBasename = `${sketch.name}-${dateFormat( + new Date(), + 'yymmdd' + )}a.zip`; + const defaultPath = await this.fileService.fsPath( + new URI(config.sketchDirUri).resolve(archiveBasename) + ); + const { filePath, canceled } = await remote.dialog.showSaveDialog({ + title: 'Save sketch folder as...', + defaultPath, + }); + if (!filePath || canceled) { + return; } + const destinationUri = await this.fileSystemExt.getUri(filePath); + if (!destinationUri) { + return; + } + await this.sketchService.archive(sketch, destinationUri.toString()); + this.messageService.info(`Created archive '${archiveBasename}'.`, { + timeout: 2000, + }); + } } export namespace ArchiveSketch { - export namespace Commands { - export const ARCHIVE_SKETCH: Command = { - id: 'arduino-archive-sketch', - }; - } + export namespace Commands { + export const ARCHIVE_SKETCH: Command = { + id: 'arduino-archive-sketch', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/board-selection.ts b/arduino-ide-extension/src/browser/contributions/board-selection.ts index 0b89ffafb..cd76599ec 100644 --- a/arduino-ide-extension/src/browser/contributions/board-selection.ts +++ b/arduino-ide-extension/src/browser/contributions/board-selection.ts @@ -2,8 +2,8 @@ import { inject, injectable } from 'inversify'; import { remote } from 'electron'; import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { - DisposableCollection, - Disposable, + DisposableCollection, + Disposable, } from '@theia/core/lib/common/disposable'; import { firstToUpperCase } from '../../common/utils'; import { BoardsConfig } from '../boards/boards-config'; @@ -12,327 +12,302 @@ import { BoardsListWidget } from '../boards/boards-list-widget'; import { NotificationCenter } from '../notification-center'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { - ArduinoMenus, - PlaceholderMenuNode, - unregisterSubmenu, + ArduinoMenus, + PlaceholderMenuNode, + unregisterSubmenu, } from '../menu/arduino-menus'; import { - BoardsService, - InstalledBoardWithPackage, - AvailablePorts, - Port, + BoardsService, + InstalledBoardWithPackage, + AvailablePorts, + Port, } from '../../common/protocol'; import { SketchContribution, Command, CommandRegistry } from './contribution'; @injectable() export class BoardSelection extends SketchContribution { - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; + @inject(MainMenuManager) + protected readonly mainMenuManager: MainMenuManager; - @inject(MenuModelRegistry) - protected readonly menuModelRegistry: MenuModelRegistry; + @inject(MenuModelRegistry) + protected readonly menuModelRegistry: MenuModelRegistry; - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; - @inject(BoardsService) - protected readonly boardsService: BoardsService; + @inject(BoardsService) + protected readonly boardsService: BoardsService; - @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider; - protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection(); + protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, { - execute: async () => { - const { selectedBoard, selectedPort } = - this.boardsServiceProvider.boardsConfig; - if (!selectedBoard) { - this.messageService.info( - 'Please select a board to obtain board info.' - ); - return; - } - if (!selectedBoard.fqbn) { - this.messageService.info( - `The platform for the selected '${selectedBoard.name}' board is not installed.` - ); - return; - } - if (!selectedPort) { - this.messageService.info( - 'Please select a port to obtain board info.' - ); - return; - } - const boardDetails = await this.boardsService.getBoardDetails({ - fqbn: selectedBoard.fqbn, - }); - if (boardDetails) { - const { VID, PID } = boardDetails; - const detail = `BN: ${selectedBoard.name} + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, { + execute: async () => { + const { selectedBoard, selectedPort } = + this.boardsServiceProvider.boardsConfig; + if (!selectedBoard) { + this.messageService.info( + 'Please select a board to obtain board info.' + ); + return; + } + if (!selectedBoard.fqbn) { + this.messageService.info( + `The platform for the selected '${selectedBoard.name}' board is not installed.` + ); + return; + } + if (!selectedPort) { + this.messageService.info( + 'Please select a port to obtain board info.' + ); + return; + } + const boardDetails = await this.boardsService.getBoardDetails({ + fqbn: selectedBoard.fqbn, + }); + if (boardDetails) { + const { VID, PID } = boardDetails; + const detail = `BN: ${selectedBoard.name} VID: ${VID} PID: ${PID}`; - await remote.dialog.showMessageBox( - remote.getCurrentWindow(), - { - message: 'Board Info', - title: 'Board Info', - type: 'info', - detail, - buttons: ['OK'], - } - ); - } - }, - }); - } + await remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: 'Board Info', + title: 'Board Info', + type: 'info', + detail, + buttons: ['OK'], + }); + } + }, + }); + } - onStart(): void { - this.updateMenus(); - this.notificationCenter.onPlatformInstalled( - this.updateMenus.bind(this) - ); - this.notificationCenter.onPlatformUninstalled( - this.updateMenus.bind(this) - ); - this.boardsServiceProvider.onBoardsConfigChanged( - this.updateMenus.bind(this) - ); - this.boardsServiceProvider.onAvailableBoardsChanged( - this.updateMenus.bind(this) - ); - } + onStart(): void { + this.updateMenus(); + this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this)); + this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this)); + this.boardsServiceProvider.onBoardsConfigChanged( + this.updateMenus.bind(this) + ); + this.boardsServiceProvider.onAvailableBoardsChanged( + this.updateMenus.bind(this) + ); + } - protected async updateMenus(): Promise { - const [installedBoards, availablePorts, config] = await Promise.all([ - this.installedBoards(), - this.boardsService.getState(), - this.boardsServiceProvider.boardsConfig, - ]); - this.rebuildMenus(installedBoards, availablePorts, config); - } + protected async updateMenus(): Promise { + const [installedBoards, availablePorts, config] = await Promise.all([ + this.installedBoards(), + this.boardsService.getState(), + this.boardsServiceProvider.boardsConfig, + ]); + this.rebuildMenus(installedBoards, availablePorts, config); + } - protected rebuildMenus( - installedBoards: InstalledBoardWithPackage[], - availablePorts: AvailablePorts, - config: BoardsConfig.Config - ): void { - this.toDisposeBeforeMenuRebuild.dispose(); + protected rebuildMenus( + installedBoards: InstalledBoardWithPackage[], + availablePorts: AvailablePorts, + config: BoardsConfig.Config + ): void { + this.toDisposeBeforeMenuRebuild.dispose(); - // Boards submenu - const boardsSubmenuPath = [ - ...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, - '1_boards', - ]; - const boardsSubmenuLabel = config.selectedBoard?.name; - // Note: The submenu order starts from `100` because `Auto Format`, `Serial Monitor`, etc starts from `0` index. - // The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order. - this.menuModelRegistry.registerSubmenu( - boardsSubmenuPath, - `Board${!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''}`, - { order: '100' } - ); - this.toDisposeBeforeMenuRebuild.push( - Disposable.create(() => - unregisterSubmenu(boardsSubmenuPath, this.menuModelRegistry) - ) - ); + // Boards submenu + const boardsSubmenuPath = [ + ...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, + '1_boards', + ]; + const boardsSubmenuLabel = config.selectedBoard?.name; + // Note: The submenu order starts from `100` because `Auto Format`, `Serial Monitor`, etc starts from `0` index. + // The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order. + this.menuModelRegistry.registerSubmenu( + boardsSubmenuPath, + `Board${!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''}`, + { order: '100' } + ); + this.toDisposeBeforeMenuRebuild.push( + Disposable.create(() => + unregisterSubmenu(boardsSubmenuPath, this.menuModelRegistry) + ) + ); - // Ports submenu - const portsSubmenuPath = [ - ...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, - '2_ports', - ]; - const portsSubmenuLabel = config.selectedPort?.address; - this.menuModelRegistry.registerSubmenu( - portsSubmenuPath, - `Port${!!portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''}`, - { order: '101' } - ); - this.toDisposeBeforeMenuRebuild.push( - Disposable.create(() => - unregisterSubmenu(portsSubmenuPath, this.menuModelRegistry) - ) - ); + // Ports submenu + const portsSubmenuPath = [ + ...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, + '2_ports', + ]; + const portsSubmenuLabel = config.selectedPort?.address; + this.menuModelRegistry.registerSubmenu( + portsSubmenuPath, + `Port${!!portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''}`, + { order: '101' } + ); + this.toDisposeBeforeMenuRebuild.push( + Disposable.create(() => + unregisterSubmenu(portsSubmenuPath, this.menuModelRegistry) + ) + ); - const getBoardInfo = { - commandId: BoardSelection.Commands.GET_BOARD_INFO.id, - label: 'Get Board Info', - order: '103', - }; - this.menuModelRegistry.registerMenuAction( - ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, - getBoardInfo - ); - this.toDisposeBeforeMenuRebuild.push( - Disposable.create(() => - this.menuModelRegistry.unregisterMenuAction(getBoardInfo) - ) - ); + const getBoardInfo = { + commandId: BoardSelection.Commands.GET_BOARD_INFO.id, + label: 'Get Board Info', + order: '103', + }; + this.menuModelRegistry.registerMenuAction( + ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, + getBoardInfo + ); + this.toDisposeBeforeMenuRebuild.push( + Disposable.create(() => + this.menuModelRegistry.unregisterMenuAction(getBoardInfo) + ) + ); - const boardsManagerGroup = [...boardsSubmenuPath, '0_manager']; - const boardsPackagesGroup = [...boardsSubmenuPath, '1_packages']; + const boardsManagerGroup = [...boardsSubmenuPath, '0_manager']; + const boardsPackagesGroup = [...boardsSubmenuPath, '1_packages']; - this.menuModelRegistry.registerMenuAction(boardsManagerGroup, { - commandId: `${BoardsListWidget.WIDGET_ID}:toggle`, - label: 'Boards Manager...', - }); + this.menuModelRegistry.registerMenuAction(boardsManagerGroup, { + commandId: `${BoardsListWidget.WIDGET_ID}:toggle`, + label: 'Boards Manager...', + }); - // Installed boards - for (const board of installedBoards) { - const { packageId, packageName, fqbn, name } = board; + // Installed boards + for (const board of installedBoards) { + const { packageId, packageName, fqbn, name } = board; - // Platform submenu - const platformMenuPath = [...boardsPackagesGroup, packageId]; - // Note: Registering the same submenu twice is a noop. No need to group the boards per platform. - this.menuModelRegistry.registerSubmenu( - platformMenuPath, - packageName - ); + // Platform submenu + const platformMenuPath = [...boardsPackagesGroup, packageId]; + // Note: Registering the same submenu twice is a noop. No need to group the boards per platform. + this.menuModelRegistry.registerSubmenu(platformMenuPath, packageName); + + const id = `arduino-select-board--${fqbn}`; + const command = { id }; + const handler = { + execute: () => { + if ( + fqbn !== this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn + ) { + this.boardsServiceProvider.boardsConfig = { + selectedBoard: { + name, + fqbn, + port: this.boardsServiceProvider.boardsConfig.selectedBoard + ?.port, // TODO: verify! + }, + selectedPort: + this.boardsServiceProvider.boardsConfig.selectedPort, + }; + } + }, + isToggled: () => + fqbn === this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn, + }; - const id = `arduino-select-board--${fqbn}`; + // Board menu + const menuAction = { commandId: id, label: name }; + this.commandRegistry.registerCommand(command, handler); + this.toDisposeBeforeMenuRebuild.push( + Disposable.create(() => this.commandRegistry.unregisterCommand(command)) + ); + this.menuModelRegistry.registerMenuAction(platformMenuPath, menuAction); + // Note: we do not dispose the menu actions individually. Calling `unregisterSubmenu` on the parent will wipe the children menu nodes recursively. + } + + // Installed ports + const registerPorts = (ports: AvailablePorts) => { + const addresses = Object.keys(ports); + if (!addresses.length) { + return; + } + + // Register placeholder for protocol + const [port] = ports[addresses[0]]; + const protocol = port.protocol; + const menuPath = [...portsSubmenuPath, protocol]; + const placeholder = new PlaceholderMenuNode( + menuPath, + `${firstToUpperCase(port.protocol)} ports` + ); + this.menuModelRegistry.registerMenuNode(menuPath, placeholder); + this.toDisposeBeforeMenuRebuild.push( + Disposable.create(() => + this.menuModelRegistry.unregisterMenuNode(placeholder.id) + ) + ); + + for (const address of addresses) { + if (!!ports[address]) { + const [port, boards] = ports[address]; + if (!boards.length) { + boards.push({ + name: '', + }); + } + for (const { name, fqbn } of boards) { + const id = `arduino-select-port--${address}${ + fqbn ? `--${fqbn}` : '' + }`; const command = { id }; const handler = { - execute: () => { - if ( - fqbn !== - this.boardsServiceProvider.boardsConfig.selectedBoard - ?.fqbn - ) { - this.boardsServiceProvider.boardsConfig = { - selectedBoard: { - name, - fqbn, - port: this.boardsServiceProvider.boardsConfig - .selectedBoard?.port, // TODO: verify! - }, - selectedPort: - this.boardsServiceProvider.boardsConfig - .selectedPort, - }; - } - }, - isToggled: () => - fqbn === - this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn, + execute: () => { + if ( + !Port.equals( + port, + this.boardsServiceProvider.boardsConfig.selectedPort + ) + ) { + this.boardsServiceProvider.boardsConfig = { + selectedBoard: + this.boardsServiceProvider.boardsConfig.selectedBoard, + selectedPort: port, + }; + } + }, + isToggled: () => + Port.equals( + port, + this.boardsServiceProvider.boardsConfig.selectedPort + ), + }; + const label = `${address}${name ? ` (${name})` : ''}`; + const menuAction = { + commandId: id, + label, + order: `1${label}`, // `1` comes after the placeholder which has order `0` }; - - // Board menu - const menuAction = { commandId: id, label: name }; this.commandRegistry.registerCommand(command, handler); this.toDisposeBeforeMenuRebuild.push( - Disposable.create(() => - this.commandRegistry.unregisterCommand(command) - ) - ); - this.menuModelRegistry.registerMenuAction( - platformMenuPath, - menuAction + Disposable.create(() => + this.commandRegistry.unregisterCommand(command) + ) ); - // Note: we do not dispose the menu actions individually. Calling `unregisterSubmenu` on the parent will wipe the children menu nodes recursively. + this.menuModelRegistry.registerMenuAction(menuPath, menuAction); + } } + } + }; - // Installed ports - const registerPorts = (ports: AvailablePorts) => { - const addresses = Object.keys(ports); - if (!addresses.length) { - return; - } + const { serial, network, unknown } = + AvailablePorts.groupByProtocol(availablePorts); + registerPorts(serial); + registerPorts(network); + registerPorts(unknown); - // Register placeholder for protocol - const [port] = ports[addresses[0]]; - const protocol = port.protocol; - const menuPath = [...portsSubmenuPath, protocol]; - const placeholder = new PlaceholderMenuNode( - menuPath, - `${firstToUpperCase(port.protocol)} ports` - ); - this.menuModelRegistry.registerMenuNode(menuPath, placeholder); - this.toDisposeBeforeMenuRebuild.push( - Disposable.create(() => - this.menuModelRegistry.unregisterMenuNode(placeholder.id) - ) - ); - - for (const address of addresses) { - if (!!ports[address]) { - const [port, boards] = ports[address]; - if (!boards.length) { - boards.push({ - name: '', - }); - } - for (const { name, fqbn } of boards) { - const id = `arduino-select-port--${address}${ - fqbn ? `--${fqbn}` : '' - }`; - const command = { id }; - const handler = { - execute: () => { - if ( - !Port.equals( - port, - this.boardsServiceProvider.boardsConfig - .selectedPort - ) - ) { - this.boardsServiceProvider.boardsConfig = { - selectedBoard: - this.boardsServiceProvider - .boardsConfig.selectedBoard, - selectedPort: port, - }; - } - }, - isToggled: () => - Port.equals( - port, - this.boardsServiceProvider.boardsConfig - .selectedPort - ), - }; - const label = `${address}${name ? ` (${name})` : ''}`; - const menuAction = { - commandId: id, - label, - order: `1${label}`, // `1` comes after the placeholder which has order `0` - }; - this.commandRegistry.registerCommand(command, handler); - this.toDisposeBeforeMenuRebuild.push( - Disposable.create(() => - this.commandRegistry.unregisterCommand(command) - ) - ); - this.menuModelRegistry.registerMenuAction( - menuPath, - menuAction - ); - } - } - } - }; - - const { serial, network, unknown } = - AvailablePorts.groupByProtocol(availablePorts); - registerPorts(serial); - registerPorts(network); - registerPorts(unknown); + this.mainMenuManager.update(); + } - this.mainMenuManager.update(); - } - - protected async installedBoards(): Promise { - const allBoards = await this.boardsService.searchBoards({}); - return allBoards.filter(InstalledBoardWithPackage.is); - } + protected async installedBoards(): Promise { + const allBoards = await this.boardsService.searchBoards({}); + return allBoards.filter(InstalledBoardWithPackage.is); + } } export namespace BoardSelection { - export namespace Commands { - export const GET_BOARD_INFO: Command = { id: 'arduino-get-board-info' }; - } + export namespace Commands { + export const GET_BOARD_INFO: Command = { id: 'arduino-get-board-info' }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index c2d0e4855..0664476fc 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -6,87 +6,85 @@ import { BoardsDataStore } from '../boards/boards-data-store'; import { MonitorConnection } from '../monitor/monitor-connection'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, } from './contribution'; @injectable() export class BurnBootloader extends SketchContribution { - @inject(CoreService) - protected readonly coreService: CoreService; + @inject(CoreService) + protected readonly coreService: CoreService; - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; + @inject(MonitorConnection) + protected readonly monitorConnection: MonitorConnection; - @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; + @inject(BoardsDataStore) + protected readonly boardsDataStore: BoardsDataStore; - @inject(BoardsServiceProvider) - protected readonly boardsServiceClientImpl: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceClientImpl: BoardsServiceProvider; - @inject(OutputChannelManager) - protected readonly outputChannelManager: OutputChannelManager; + @inject(OutputChannelManager) + protected readonly outputChannelManager: OutputChannelManager; - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, { - execute: () => this.burnBootloader(), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, { + execute: () => this.burnBootloader(), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, { - commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id, - label: 'Burn Bootloader', - order: 'z99', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, { + commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id, + label: 'Burn Bootloader', + order: 'z99', + }); + } - async burnBootloader(): Promise { - const monitorConfig = this.monitorConnection.monitorConfig; - if (monitorConfig) { - await this.monitorConnection.disconnect(); - } - try { - const { boardsConfig } = this.boardsServiceClientImpl; - const port = boardsConfig.selectedPort?.address; - const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = - await Promise.all([ - this.boardsDataStore.appendConfigToFqbn( - boardsConfig.selectedBoard?.fqbn - ), - this.boardsDataStore.getData( - boardsConfig.selectedBoard?.fqbn - ), - this.preferences.get('arduino.upload.verify'), - this.preferences.get('arduino.upload.verbose'), - ]); - this.outputChannelManager.getChannel('Arduino').clear(); - await this.coreService.burnBootloader({ - fqbn, - programmer, - port, - verify, - verbose, - }); - this.messageService.info('Done burning bootloader.', { - timeout: 3000, - }); - } catch (e) { - this.messageService.error(e.toString()); - } finally { - if (monitorConfig) { - await this.monitorConnection.connect(monitorConfig); - } - } + async burnBootloader(): Promise { + const monitorConfig = this.monitorConnection.monitorConfig; + if (monitorConfig) { + await this.monitorConnection.disconnect(); } + try { + const { boardsConfig } = this.boardsServiceClientImpl; + const port = boardsConfig.selectedPort?.address; + const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = + await Promise.all([ + this.boardsDataStore.appendConfigToFqbn( + boardsConfig.selectedBoard?.fqbn + ), + this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn), + this.preferences.get('arduino.upload.verify'), + this.preferences.get('arduino.upload.verbose'), + ]); + this.outputChannelManager.getChannel('Arduino').clear(); + await this.coreService.burnBootloader({ + fqbn, + programmer, + port, + verify, + verbose, + }); + this.messageService.info('Done burning bootloader.', { + timeout: 3000, + }); + } catch (e) { + this.messageService.error(e.toString()); + } finally { + if (monitorConfig) { + await this.monitorConnection.connect(monitorConfig); + } + } + } } export namespace BurnBootloader { - export namespace Commands { - export const BURN_BOOTLOADER: Command = { - id: 'arduino-burn-bootloader', - }; - } + export namespace Commands { + export const BURN_BOOTLOADER: Command = { + id: 'arduino-burn-bootloader', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/close.ts b/arduino-ide-extension/src/browser/contributions/close.ts index a096b5579..ce15be86a 100644 --- a/arduino-ide-extension/src/browser/contributions/close.ts +++ b/arduino-ide-extension/src/browser/contributions/close.ts @@ -8,12 +8,12 @@ import { FrontendApplication } from '@theia/core/lib/browser/frontend-applicatio import { ArduinoMenus } from '../menu/arduino-menus'; import { SaveAsSketch } from './save-as-sketch'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - URI, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + URI, } from './contribution'; /** @@ -21,118 +21,112 @@ import { */ @injectable() export class Close extends SketchContribution { - @inject(EditorManager) - protected readonly editorManager: EditorManager; + @inject(EditorManager) + protected readonly editorManager: EditorManager; - protected shell: ApplicationShell; + protected shell: ApplicationShell; - onStart(app: FrontendApplication): void { - this.shell = app.shell; - } + onStart(app: FrontendApplication): void { + this.shell = app.shell; + } - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(Close.Commands.CLOSE, { - execute: async () => { - // Close current editor if closeable. - const { currentEditor } = this.editorManager; - if (currentEditor && currentEditor.title.closable) { - currentEditor.close(); - return; - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(Close.Commands.CLOSE, { + execute: async () => { + // Close current editor if closeable. + const { currentEditor } = this.editorManager; + if (currentEditor && currentEditor.title.closable) { + currentEditor.close(); + return; + } - // Close current widget from the main area if possible. - const { currentWidget } = this.shell; - if (currentWidget) { - const currentWidgetInMain = toArray( - this.shell.mainPanel.widgets() - ).find((widget) => widget === currentWidget); - if ( - currentWidgetInMain && - currentWidgetInMain.title.closable - ) { - return currentWidgetInMain.close(); - } - } + // Close current widget from the main area if possible. + const { currentWidget } = this.shell; + if (currentWidget) { + const currentWidgetInMain = toArray( + this.shell.mainPanel.widgets() + ).find((widget) => widget === currentWidget); + if (currentWidgetInMain && currentWidgetInMain.title.closable) { + return currentWidgetInMain.close(); + } + } - // Close the sketch (window). - const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { - return; - } - const isTemp = await this.sketchService.isTemp(sketch); - const uri = await this.sketchServiceClient.currentSketchFile(); - if (!uri) { - return; - } - if (isTemp && (await this.wasTouched(uri))) { - const { response } = await remote.dialog.showMessageBox({ - type: 'question', - buttons: ["Don't Save", 'Cancel', 'Save'], - message: - 'Do you want to save changes to this sketch before closing?', - detail: "If you don't save, your changes will be lost.", - }); - if (response === 1) { - // Cancel - return; - } - if (response === 2) { - // Save - const saved = await this.commandService.executeCommand( - SaveAsSketch.Commands.SAVE_AS_SKETCH.id, - { openAfterMove: false, execOnlyIfTemp: true } - ); - if (!saved) { - // If it was not saved, do bail the close. - return; - } - } - } - window.close(); - }, - }); - } + // Close the sketch (window). + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return; + } + const isTemp = await this.sketchService.isTemp(sketch); + const uri = await this.sketchServiceClient.currentSketchFile(); + if (!uri) { + return; + } + if (isTemp && (await this.wasTouched(uri))) { + const { response } = await remote.dialog.showMessageBox({ + type: 'question', + buttons: ["Don't Save", 'Cancel', 'Save'], + message: + 'Do you want to save changes to this sketch before closing?', + detail: "If you don't save, your changes will be lost.", + }); + if (response === 1) { + // Cancel + return; + } + if (response === 2) { + // Save + const saved = await this.commandService.executeCommand( + SaveAsSketch.Commands.SAVE_AS_SKETCH.id, + { openAfterMove: false, execOnlyIfTemp: true } + ); + if (!saved) { + // If it was not saved, do bail the close. + return; + } + } + } + window.close(); + }, + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { - commandId: Close.Commands.CLOSE.id, - label: 'Close', - order: '5', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { + commandId: Close.Commands.CLOSE.id, + label: 'Close', + order: '5', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: Close.Commands.CLOSE.id, - keybinding: 'CtrlCmd+W', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: Close.Commands.CLOSE.id, + keybinding: 'CtrlCmd+W', + }); + } - /** - * If the file was ever touched/modified. We get this based on the `version` of the monaco model. - */ - protected async wasTouched(uri: string): Promise { - const editorWidget = await this.editorManager.getByUri(new URI(uri)); - if (editorWidget) { - const { editor } = editorWidget; - if (editor instanceof MonacoEditor) { - const versionId = editor - .getControl() - .getModel() - ?.getVersionId(); - if (Number.isInteger(versionId) && versionId! > 1) { - return true; - } - } + /** + * If the file was ever touched/modified. We get this based on the `version` of the monaco model. + */ + protected async wasTouched(uri: string): Promise { + const editorWidget = await this.editorManager.getByUri(new URI(uri)); + if (editorWidget) { + const { editor } = editorWidget; + if (editor instanceof MonacoEditor) { + const versionId = editor.getControl().getModel()?.getVersionId(); + if (Number.isInteger(versionId) && versionId! > 1) { + return true; } - return false; + } } + return false; + } } export namespace Close { - export namespace Commands { - export const CLOSE: Command = { - id: 'arduino-close', - }; - } + export namespace Commands { + export const CLOSE: Command = { + id: 'arduino-close', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/contribution.ts b/arduino-ide-extension/src/browser/contributions/contribution.ts index a8c0c4003..14e6e9715 100644 --- a/arduino-ide-extension/src/browser/contributions/contribution.ts +++ b/arduino-ide-extension/src/browser/contributions/contribution.ts @@ -11,147 +11,144 @@ import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service import { open, OpenerService } from '@theia/core/lib/browser/opener-service'; import { OutputChannelManager } from '@theia/output/lib/common/output-channel'; import { - MenuModelRegistry, - MenuContribution, + MenuModelRegistry, + MenuContribution, } from '@theia/core/lib/common/menu'; import { - KeybindingRegistry, - KeybindingContribution, + KeybindingRegistry, + KeybindingContribution, } from '@theia/core/lib/browser/keybinding'; import { - TabBarToolbarContribution, - TabBarToolbarRegistry, + TabBarToolbarContribution, + TabBarToolbarRegistry, } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { - FrontendApplicationContribution, - FrontendApplication, + FrontendApplicationContribution, + FrontendApplication, } from '@theia/core/lib/browser/frontend-application'; import { - Command, - CommandRegistry, - CommandContribution, - CommandService, + Command, + CommandRegistry, + CommandContribution, + CommandService, } from '@theia/core/lib/common/command'; import { EditorMode } from '../editor-mode'; import { SettingsService } from '../settings'; import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl'; import { - SketchesService, - ConfigService, - FileSystemExt, - Sketch, + SketchesService, + ConfigService, + FileSystemExt, + Sketch, } from '../../common/protocol'; import { ArduinoPreferences } from '../arduino-preferences'; export { - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - TabBarToolbarRegistry, - URI, - Sketch, - open, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + TabBarToolbarRegistry, + URI, + Sketch, + open, }; @injectable() export abstract class Contribution - implements - CommandContribution, - MenuContribution, - KeybindingContribution, - TabBarToolbarContribution, - FrontendApplicationContribution + implements + CommandContribution, + MenuContribution, + KeybindingContribution, + TabBarToolbarContribution, + FrontendApplicationContribution { - @inject(ILogger) - protected readonly logger: ILogger; + @inject(ILogger) + protected readonly logger: ILogger; - @inject(MessageService) - protected readonly messageService: MessageService; + @inject(MessageService) + protected readonly messageService: MessageService; - @inject(CommandService) - protected readonly commandService: CommandService; + @inject(CommandService) + protected readonly commandService: CommandService; - @inject(WorkspaceService) - protected readonly workspaceService: WorkspaceService; + @inject(WorkspaceService) + protected readonly workspaceService: WorkspaceService; - @inject(EditorMode) - protected readonly editorMode: EditorMode; + @inject(EditorMode) + protected readonly editorMode: EditorMode; - @inject(LabelProvider) - protected readonly labelProvider: LabelProvider; + @inject(LabelProvider) + protected readonly labelProvider: LabelProvider; - @inject(SettingsService) - protected readonly settingsService: SettingsService; + @inject(SettingsService) + protected readonly settingsService: SettingsService; - onStart(app: FrontendApplication): MaybePromise {} + onStart(app: FrontendApplication): MaybePromise {} - registerCommands(registry: CommandRegistry): void {} + registerCommands(registry: CommandRegistry): void {} - registerMenus(registry: MenuModelRegistry): void {} + registerMenus(registry: MenuModelRegistry): void {} - registerKeybindings(registry: KeybindingRegistry): void {} + registerKeybindings(registry: KeybindingRegistry): void {} - registerToolbarItems(registry: TabBarToolbarRegistry): void {} + registerToolbarItems(registry: TabBarToolbarRegistry): void {} } @injectable() export abstract class SketchContribution extends Contribution { - @inject(FileService) - protected readonly fileService: FileService; - - @inject(FileSystemExt) - protected readonly fileSystemExt: FileSystemExt; - - @inject(ConfigService) - protected readonly configService: ConfigService; - - @inject(SketchesService) - protected readonly sketchService: SketchesService; - - @inject(OpenerService) - protected readonly openerService: OpenerService; - - @inject(SketchesServiceClientImpl) - protected readonly sketchServiceClient: SketchesServiceClientImpl; - - @inject(ArduinoPreferences) - protected readonly preferences: ArduinoPreferences; - - @inject(EditorManager) - protected readonly editorManager: EditorManager; - - @inject(OutputChannelManager) - protected readonly outputChannelManager: OutputChannelManager; - - protected async sourceOverride(): Promise> { - const override: Record = {}; - const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch) { - for (const editor of this.editorManager.all) { - const uri = editor.editor.uri; - if ( - Saveable.isDirty(editor) && - Sketch.isInSketch(uri, sketch) - ) { - override[uri.toString()] = editor.editor.document.getText(); - } - } + @inject(FileService) + protected readonly fileService: FileService; + + @inject(FileSystemExt) + protected readonly fileSystemExt: FileSystemExt; + + @inject(ConfigService) + protected readonly configService: ConfigService; + + @inject(SketchesService) + protected readonly sketchService: SketchesService; + + @inject(OpenerService) + protected readonly openerService: OpenerService; + + @inject(SketchesServiceClientImpl) + protected readonly sketchServiceClient: SketchesServiceClientImpl; + + @inject(ArduinoPreferences) + protected readonly preferences: ArduinoPreferences; + + @inject(EditorManager) + protected readonly editorManager: EditorManager; + + @inject(OutputChannelManager) + protected readonly outputChannelManager: OutputChannelManager; + + protected async sourceOverride(): Promise> { + const override: Record = {}; + const sketch = await this.sketchServiceClient.currentSketch(); + if (sketch) { + for (const editor of this.editorManager.all) { + const uri = editor.editor.uri; + if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) { + override[uri.toString()] = editor.editor.document.getText(); } - return override; + } } + return override; + } } export namespace Contribution { - export function configure( - bind: interfaces.Bind, - serviceIdentifier: typeof Contribution - ): void { - bind(serviceIdentifier).toSelf().inSingletonScope(); - bind(CommandContribution).toService(serviceIdentifier); - bind(MenuContribution).toService(serviceIdentifier); - bind(KeybindingContribution).toService(serviceIdentifier); - bind(TabBarToolbarContribution).toService(serviceIdentifier); - bind(FrontendApplicationContribution).toService(serviceIdentifier); - } + export function configure( + bind: interfaces.Bind, + serviceIdentifier: typeof Contribution + ): void { + bind(serviceIdentifier).toSelf().inSingletonScope(); + bind(CommandContribution).toService(serviceIdentifier); + bind(MenuContribution).toService(serviceIdentifier); + bind(KeybindingContribution).toService(serviceIdentifier); + bind(TabBarToolbarContribution).toService(serviceIdentifier); + bind(FrontendApplicationContribution).toService(serviceIdentifier); + } } diff --git a/arduino-ide-extension/src/browser/contributions/debug.ts b/arduino-ide-extension/src/browser/contributions/debug.ts index ff6f5825b..2c1c1f079 100644 --- a/arduino-ide-extension/src/browser/contributions/debug.ts +++ b/arduino-ide-extension/src/browser/contributions/debug.ts @@ -6,164 +6,159 @@ import { NotificationCenter } from '../notification-center'; import { Board, BoardsService, ExecutableService } from '../../common/protocol'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { - URI, - Command, - CommandRegistry, - SketchContribution, - TabBarToolbarRegistry, + URI, + Command, + CommandRegistry, + SketchContribution, + TabBarToolbarRegistry, } from './contribution'; @injectable() export class Debug extends SketchContribution { - @inject(HostedPluginSupport) - protected hostedPluginSupport: HostedPluginSupport; + @inject(HostedPluginSupport) + protected hostedPluginSupport: HostedPluginSupport; - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; - @inject(ExecutableService) - protected readonly executableService: ExecutableService; + @inject(ExecutableService) + protected readonly executableService: ExecutableService; - @inject(BoardsService) - protected readonly boardService: BoardsService; + @inject(BoardsService) + protected readonly boardService: BoardsService; - @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider; - /** - * If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled. - */ - protected _disabledMessages?: string = 'No board selected'; // Initial pessimism. - protected disabledMessageDidChangeEmitter = new Emitter< - string | undefined - >(); - protected onDisabledMessageDidChange = - this.disabledMessageDidChangeEmitter.event; + /** + * If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled. + */ + protected _disabledMessages?: string = 'No board selected'; // Initial pessimism. + protected disabledMessageDidChangeEmitter = new Emitter(); + protected onDisabledMessageDidChange = + this.disabledMessageDidChangeEmitter.event; - protected get disabledMessage(): string | undefined { - return this._disabledMessages; - } - protected set disabledMessage(message: string | undefined) { - this._disabledMessages = message; - this.disabledMessageDidChangeEmitter.fire(this._disabledMessages); - } + protected get disabledMessage(): string | undefined { + return this._disabledMessages; + } + protected set disabledMessage(message: string | undefined) { + this._disabledMessages = message; + this.disabledMessageDidChangeEmitter.fire(this._disabledMessages); + } + + protected readonly debugToolbarItem = { + id: Debug.Commands.START_DEBUGGING.id, + command: Debug.Commands.START_DEBUGGING.id, + tooltip: `${ + this.disabledMessage + ? `Debug - ${this.disabledMessage}` + : 'Start Debugging' + }`, + priority: 3, + onDidChange: this.onDisabledMessageDidChange as Event, + }; - protected readonly debugToolbarItem = { - id: Debug.Commands.START_DEBUGGING.id, - command: Debug.Commands.START_DEBUGGING.id, - tooltip: `${ - this.disabledMessage - ? `Debug - ${this.disabledMessage}` - : 'Start Debugging' - }`, - priority: 3, - onDidChange: this.onDisabledMessageDidChange as Event, + onStart(): void { + this.onDisabledMessageDidChange( + () => + (this.debugToolbarItem.tooltip = `${ + this.disabledMessage + ? `Debug - ${this.disabledMessage}` + : 'Start Debugging' + }`) + ); + const refreshState = async ( + board: Board | undefined = this.boardsServiceProvider.boardsConfig + .selectedBoard + ) => { + if (!board) { + this.disabledMessage = 'No board selected'; + return; + } + const fqbn = board.fqbn; + if (!fqbn) { + this.disabledMessage = `Platform is not installed for '${board.name}'`; + return; + } + const details = await this.boardService.getBoardDetails({ fqbn }); + if (!details) { + this.disabledMessage = `Platform is not installed for '${board.name}'`; + return; + } + const { debuggingSupported } = details; + if (!debuggingSupported) { + this.disabledMessage = `Debugging is not supported by '${board.name}'`; + } else { + this.disabledMessage = undefined; + } }; + this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => + refreshState(selectedBoard) + ); + this.notificationCenter.onPlatformInstalled(() => refreshState()); + this.notificationCenter.onPlatformUninstalled(() => refreshState()); + refreshState(); + } - onStart(): void { - this.onDisabledMessageDidChange( - () => - (this.debugToolbarItem.tooltip = `${ - this.disabledMessage - ? `Debug - ${this.disabledMessage}` - : 'Start Debugging' - }`) - ); - const refreshState = async ( - board: Board | undefined = this.boardsServiceProvider.boardsConfig - .selectedBoard - ) => { - if (!board) { - this.disabledMessage = 'No board selected'; - return; - } - const fqbn = board.fqbn; - if (!fqbn) { - this.disabledMessage = `Platform is not installed for '${board.name}'`; - return; - } - const details = await this.boardService.getBoardDetails({ fqbn }); - if (!details) { - this.disabledMessage = `Platform is not installed for '${board.name}'`; - return; - } - const { debuggingSupported } = details; - if (!debuggingSupported) { - this.disabledMessage = `Debugging is not supported by '${board.name}'`; - } else { - this.disabledMessage = undefined; - } - }; - this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => - refreshState(selectedBoard) - ); - this.notificationCenter.onPlatformInstalled(() => refreshState()); - this.notificationCenter.onPlatformUninstalled(() => refreshState()); - refreshState(); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(Debug.Commands.START_DEBUGGING, { + execute: () => this.startDebug(), + isVisible: (widget) => + ArduinoToolbar.is(widget) && widget.side === 'left', + isEnabled: () => !this.disabledMessage, + }); + } - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(Debug.Commands.START_DEBUGGING, { - execute: () => this.startDebug(), - isVisible: (widget) => - ArduinoToolbar.is(widget) && widget.side === 'left', - isEnabled: () => !this.disabledMessage, - }); - } + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem(this.debugToolbarItem); + } - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem(this.debugToolbarItem); + protected async startDebug( + board: Board | undefined = this.boardsServiceProvider.boardsConfig + .selectedBoard + ): Promise { + if (!board) { + return; } - - protected async startDebug( - board: Board | undefined = this.boardsServiceProvider.boardsConfig - .selectedBoard - ): Promise { - if (!board) { - return; - } - const { name, fqbn } = board; - if (!fqbn) { - return; - } - await this.hostedPluginSupport.didStart; - const [sketch, executables] = await Promise.all([ - this.sketchServiceClient.currentSketch(), - this.executableService.list(), - ]); - if (!sketch) { - return; - } - const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri( - sketch - ); - const [cliPath, sketchPath, configPath] = await Promise.all([ - this.fileService.fsPath(new URI(executables.cliUri)), - this.fileService.fsPath(new URI(sketch.uri)), - this.fileService.fsPath(new URI(ideTempFolderUri)), - ]); - const config = { - cliPath, - board: { - fqbn, - name, - }, - sketchPath, - configPath, - }; - return this.commandService.executeCommand( - 'arduino.debug.start', - config - ); + const { name, fqbn } = board; + if (!fqbn) { + return; + } + await this.hostedPluginSupport.didStart; + const [sketch, executables] = await Promise.all([ + this.sketchServiceClient.currentSketch(), + this.executableService.list(), + ]); + if (!sketch) { + return; } + const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri( + sketch + ); + const [cliPath, sketchPath, configPath] = await Promise.all([ + this.fileService.fsPath(new URI(executables.cliUri)), + this.fileService.fsPath(new URI(sketch.uri)), + this.fileService.fsPath(new URI(ideTempFolderUri)), + ]); + const config = { + cliPath, + board: { + fqbn, + name, + }, + sketchPath, + configPath, + }; + return this.commandService.executeCommand('arduino.debug.start', config); + } } export namespace Debug { - export namespace Commands { - export const START_DEBUGGING: Command = { - id: 'arduino-start-debug', - label: 'Start Debugging', - category: 'Arduino', - }; - } + export namespace Commands { + export const START_DEBUGGING: Command = { + id: 'arduino-start-debug', + label: 'Start Debugging', + category: 'Arduino', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts index f4886fb95..24e7a5dc0 100644 --- a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts +++ b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts @@ -4,11 +4,11 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service'; import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service'; import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service'; import { - Contribution, - Command, - MenuModelRegistry, - KeybindingRegistry, - CommandRegistry, + Contribution, + Command, + MenuModelRegistry, + KeybindingRegistry, + CommandRegistry, } from './contribution'; import { ArduinoMenus } from '../menu/arduino-menus'; @@ -16,312 +16,305 @@ import { ArduinoMenus } from '../menu/arduino-menus'; // Depends on https://github.com/eclipse-theia/theia/pull/7964 @injectable() export class EditContributions extends Contribution { - @inject(MonacoEditorService) - protected readonly codeEditorService: MonacoEditorService; + @inject(MonacoEditorService) + protected readonly codeEditorService: MonacoEditorService; - @inject(ClipboardService) - protected readonly clipboardService: ClipboardService; + @inject(ClipboardService) + protected readonly clipboardService: ClipboardService; - @inject(PreferenceService) - protected readonly preferences: PreferenceService; + @inject(PreferenceService) + protected readonly preferences: PreferenceService; - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(EditContributions.Commands.GO_TO_LINE, { - execute: () => this.run('editor.action.gotoLine'), - }); - registry.registerCommand(EditContributions.Commands.TOGGLE_COMMENT, { - execute: () => this.run('editor.action.commentLine'), - }); - registry.registerCommand(EditContributions.Commands.INDENT_LINES, { - execute: () => this.run('editor.action.indentLines'), - }); - registry.registerCommand(EditContributions.Commands.OUTDENT_LINES, { - execute: () => this.run('editor.action.outdentLines'), - }); - registry.registerCommand(EditContributions.Commands.FIND, { - execute: () => this.run('actions.find'), - }); - registry.registerCommand(EditContributions.Commands.FIND_NEXT, { - execute: () => this.run('actions.findWithSelection'), - }); - registry.registerCommand(EditContributions.Commands.FIND_PREVIOUS, { - execute: () => this.run('editor.action.nextMatchFindAction'), - }); - registry.registerCommand(EditContributions.Commands.USE_FOR_FIND, { - execute: () => - this.run('editor.action.previousSelectionMatchFindAction'), - }); - registry.registerCommand( - EditContributions.Commands.INCREASE_FONT_SIZE, - { - execute: async () => { - const settings = await this.settingsService.settings(); - if (settings.autoScaleInterface) { - settings.interfaceScale = settings.interfaceScale + 1; - } else { - settings.editorFontSize = settings.editorFontSize + 1; - } - await this.settingsService.update(settings); - await this.settingsService.save(); - }, - } - ); - registry.registerCommand( - EditContributions.Commands.DECREASE_FONT_SIZE, - { - execute: async () => { - const settings = await this.settingsService.settings(); - if (settings.autoScaleInterface) { - settings.interfaceScale = settings.interfaceScale - 1; - } else { - settings.editorFontSize = settings.editorFontSize - 1; - } - await this.settingsService.update(settings); - await this.settingsService.save(); - }, - } - ); - /* Tools */ registry.registerCommand( - EditContributions.Commands.AUTO_FORMAT, - { execute: () => this.run('editor.action.formatDocument') } - ); - registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, { - execute: async () => { - const value = await this.currentValue(); - if (value !== undefined) { - this.clipboardService.writeText(`[code] + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(EditContributions.Commands.GO_TO_LINE, { + execute: () => this.run('editor.action.gotoLine'), + }); + registry.registerCommand(EditContributions.Commands.TOGGLE_COMMENT, { + execute: () => this.run('editor.action.commentLine'), + }); + registry.registerCommand(EditContributions.Commands.INDENT_LINES, { + execute: () => this.run('editor.action.indentLines'), + }); + registry.registerCommand(EditContributions.Commands.OUTDENT_LINES, { + execute: () => this.run('editor.action.outdentLines'), + }); + registry.registerCommand(EditContributions.Commands.FIND, { + execute: () => this.run('actions.find'), + }); + registry.registerCommand(EditContributions.Commands.FIND_NEXT, { + execute: () => this.run('actions.findWithSelection'), + }); + registry.registerCommand(EditContributions.Commands.FIND_PREVIOUS, { + execute: () => this.run('editor.action.nextMatchFindAction'), + }); + registry.registerCommand(EditContributions.Commands.USE_FOR_FIND, { + execute: () => this.run('editor.action.previousSelectionMatchFindAction'), + }); + registry.registerCommand(EditContributions.Commands.INCREASE_FONT_SIZE, { + execute: async () => { + const settings = await this.settingsService.settings(); + if (settings.autoScaleInterface) { + settings.interfaceScale = settings.interfaceScale + 1; + } else { + settings.editorFontSize = settings.editorFontSize + 1; + } + await this.settingsService.update(settings); + await this.settingsService.save(); + }, + }); + registry.registerCommand(EditContributions.Commands.DECREASE_FONT_SIZE, { + execute: async () => { + const settings = await this.settingsService.settings(); + if (settings.autoScaleInterface) { + settings.interfaceScale = settings.interfaceScale - 1; + } else { + settings.editorFontSize = settings.editorFontSize - 1; + } + await this.settingsService.update(settings); + await this.settingsService.save(); + }, + }); + /* Tools */ registry.registerCommand( + EditContributions.Commands.AUTO_FORMAT, + { execute: () => this.run('editor.action.formatDocument') } + ); + registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, { + execute: async () => { + const value = await this.currentValue(); + if (value !== undefined) { + this.clipboardService.writeText(`[code] ${value} [/code]`); - } - }, - }); - registry.registerCommand(EditContributions.Commands.COPY_FOR_GITHUB, { - execute: async () => { - const value = await this.currentValue(); - if (value !== undefined) { - this.clipboardService.writeText(`\`\`\`cpp + } + }, + }); + registry.registerCommand(EditContributions.Commands.COPY_FOR_GITHUB, { + execute: async () => { + const value = await this.currentValue(); + if (value !== undefined) { + this.clipboardService.writeText(`\`\`\`cpp ${value} \`\`\``); - } - }, - }); - } + } + }, + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { - commandId: CommonCommands.CUT.id, - order: '0', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { - commandId: CommonCommands.COPY.id, - order: '1', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { - commandId: EditContributions.Commands.COPY_FOR_FORUM.id, - label: 'Copy for Forum', - order: '2', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { - commandId: EditContributions.Commands.COPY_FOR_GITHUB.id, - label: 'Copy for GitHub', - order: '3', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { - commandId: CommonCommands.PASTE.id, - order: '4', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { - commandId: CommonCommands.SELECT_ALL.id, - order: '5', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { - commandId: EditContributions.Commands.GO_TO_LINE.id, - label: 'Go to Line...', - order: '6', - }); + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { + commandId: CommonCommands.CUT.id, + order: '0', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { + commandId: CommonCommands.COPY.id, + order: '1', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { + commandId: EditContributions.Commands.COPY_FOR_FORUM.id, + label: 'Copy for Forum', + order: '2', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { + commandId: EditContributions.Commands.COPY_FOR_GITHUB.id, + label: 'Copy for GitHub', + order: '3', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { + commandId: CommonCommands.PASTE.id, + order: '4', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { + commandId: CommonCommands.SELECT_ALL.id, + order: '5', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { + commandId: EditContributions.Commands.GO_TO_LINE.id, + label: 'Go to Line...', + order: '6', + }); - registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, { - commandId: EditContributions.Commands.TOGGLE_COMMENT.id, - label: 'Comment/Uncomment', - order: '0', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, { - commandId: EditContributions.Commands.INDENT_LINES.id, - label: 'Increase Indent', - order: '1', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, { - commandId: EditContributions.Commands.OUTDENT_LINES.id, - label: 'Decrease Indent', - order: '2', - }); + registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, { + commandId: EditContributions.Commands.TOGGLE_COMMENT.id, + label: 'Comment/Uncomment', + order: '0', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, { + commandId: EditContributions.Commands.INDENT_LINES.id, + label: 'Increase Indent', + order: '1', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, { + commandId: EditContributions.Commands.OUTDENT_LINES.id, + label: 'Decrease Indent', + order: '2', + }); - registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, { - commandId: EditContributions.Commands.INCREASE_FONT_SIZE.id, - label: 'Increase Font Size', - order: '0', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, { - commandId: EditContributions.Commands.DECREASE_FONT_SIZE.id, - label: 'Decrease Font Size', - order: '1', - }); + registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, { + commandId: EditContributions.Commands.INCREASE_FONT_SIZE.id, + label: 'Increase Font Size', + order: '0', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, { + commandId: EditContributions.Commands.DECREASE_FONT_SIZE.id, + label: 'Decrease Font Size', + order: '1', + }); - registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { - commandId: EditContributions.Commands.FIND.id, - label: 'Find', - order: '0', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { - commandId: EditContributions.Commands.FIND_NEXT.id, - label: 'Find Next', - order: '1', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { - commandId: EditContributions.Commands.FIND_PREVIOUS.id, - label: 'Find Previous', - order: '2', - }); - registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { - commandId: EditContributions.Commands.USE_FOR_FIND.id, - label: 'Use Selection for Find', // XXX: The Java IDE uses `Use Selection For Find`. - order: '3', - }); + registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { + commandId: EditContributions.Commands.FIND.id, + label: 'Find', + order: '0', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { + commandId: EditContributions.Commands.FIND_NEXT.id, + label: 'Find Next', + order: '1', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { + commandId: EditContributions.Commands.FIND_PREVIOUS.id, + label: 'Find Previous', + order: '2', + }); + registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, { + commandId: EditContributions.Commands.USE_FOR_FIND.id, + label: 'Use Selection for Find', // XXX: The Java IDE uses `Use Selection For Find`. + order: '3', + }); - // `Tools` - registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { - commandId: EditContributions.Commands.AUTO_FORMAT.id, - label: 'Auto Format', // XXX: The Java IDE uses `Use Selection For Find`. - order: '0', - }); - } + // `Tools` + registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { + commandId: EditContributions.Commands.AUTO_FORMAT.id, + label: 'Auto Format', // XXX: The Java IDE uses `Use Selection For Find`. + order: '0', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: EditContributions.Commands.COPY_FOR_FORUM.id, - keybinding: 'CtrlCmd+Shift+C', - when: 'editorFocus', - }); - registry.registerKeybinding({ - command: EditContributions.Commands.COPY_FOR_GITHUB.id, - keybinding: 'CtrlCmd+Alt+C', - when: 'editorFocus', - }); - registry.registerKeybinding({ - command: EditContributions.Commands.GO_TO_LINE.id, - keybinding: 'CtrlCmd+L', - when: 'editorFocus', - }); + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: EditContributions.Commands.COPY_FOR_FORUM.id, + keybinding: 'CtrlCmd+Shift+C', + when: 'editorFocus', + }); + registry.registerKeybinding({ + command: EditContributions.Commands.COPY_FOR_GITHUB.id, + keybinding: 'CtrlCmd+Alt+C', + when: 'editorFocus', + }); + registry.registerKeybinding({ + command: EditContributions.Commands.GO_TO_LINE.id, + keybinding: 'CtrlCmd+L', + when: 'editorFocus', + }); - registry.registerKeybinding({ - command: EditContributions.Commands.TOGGLE_COMMENT.id, - keybinding: 'CtrlCmd+/', - when: 'editorFocus', - }); + registry.registerKeybinding({ + command: EditContributions.Commands.TOGGLE_COMMENT.id, + keybinding: 'CtrlCmd+/', + when: 'editorFocus', + }); - registry.registerKeybinding({ - command: EditContributions.Commands.INCREASE_FONT_SIZE.id, - keybinding: 'CtrlCmd+=', - }); - registry.registerKeybinding({ - command: EditContributions.Commands.DECREASE_FONT_SIZE.id, - keybinding: 'CtrlCmd+-', - }); + registry.registerKeybinding({ + command: EditContributions.Commands.INCREASE_FONT_SIZE.id, + keybinding: 'CtrlCmd+=', + }); + registry.registerKeybinding({ + command: EditContributions.Commands.DECREASE_FONT_SIZE.id, + keybinding: 'CtrlCmd+-', + }); - registry.registerKeybinding({ - command: EditContributions.Commands.FIND.id, - keybinding: 'CtrlCmd+F', - }); - registry.registerKeybinding({ - command: EditContributions.Commands.FIND_NEXT.id, - keybinding: 'CtrlCmd+G', - }); - registry.registerKeybinding({ - command: EditContributions.Commands.FIND_PREVIOUS.id, - keybinding: 'CtrlCmd+Shift+G', - }); - registry.registerKeybinding({ - command: EditContributions.Commands.USE_FOR_FIND.id, - keybinding: 'CtrlCmd+E', - }); + registry.registerKeybinding({ + command: EditContributions.Commands.FIND.id, + keybinding: 'CtrlCmd+F', + }); + registry.registerKeybinding({ + command: EditContributions.Commands.FIND_NEXT.id, + keybinding: 'CtrlCmd+G', + }); + registry.registerKeybinding({ + command: EditContributions.Commands.FIND_PREVIOUS.id, + keybinding: 'CtrlCmd+Shift+G', + }); + registry.registerKeybinding({ + command: EditContributions.Commands.USE_FOR_FIND.id, + keybinding: 'CtrlCmd+E', + }); - // `Tools` - registry.registerKeybinding({ - command: EditContributions.Commands.AUTO_FORMAT.id, - keybinding: 'CtrlCmd+T', - }); - } + // `Tools` + registry.registerKeybinding({ + command: EditContributions.Commands.AUTO_FORMAT.id, + keybinding: 'CtrlCmd+T', + }); + } - protected async current(): Promise { - return ( - this.codeEditorService.getFocusedCodeEditor() || - this.codeEditorService.getActiveCodeEditor() - ); - } + protected async current(): Promise { + return ( + this.codeEditorService.getFocusedCodeEditor() || + this.codeEditorService.getActiveCodeEditor() + ); + } - protected async currentValue(): Promise { - const currentEditor = await this.current(); - if (currentEditor) { - const selection = currentEditor.getSelection(); - if (!selection || selection.isEmpty()) { - return currentEditor.getValue(); - } - return currentEditor.getModel()?.getValueInRange(selection); - } - return undefined; + protected async currentValue(): Promise { + const currentEditor = await this.current(); + if (currentEditor) { + const selection = currentEditor.getSelection(); + if (!selection || selection.isEmpty()) { + return currentEditor.getValue(); + } + return currentEditor.getModel()?.getValueInRange(selection); } + return undefined; + } - protected async run(commandId: string): Promise { - const editor = await this.current(); - if (editor) { - const action = editor.getAction(commandId); - if (action) { - return action.run(); - } - } + protected async run(commandId: string): Promise { + const editor = await this.current(); + if (editor) { + const action = editor.getAction(commandId); + if (action) { + return action.run(); + } } + } } export namespace EditContributions { - export namespace Commands { - export const COPY_FOR_FORUM: Command = { - id: 'arduino-copy-for-forum', - }; - export const COPY_FOR_GITHUB: Command = { - id: 'arduino-copy-for-github', - }; - export const GO_TO_LINE: Command = { - id: 'arduino-go-to-line', - }; - export const TOGGLE_COMMENT: Command = { - id: 'arduino-toggle-comment', - }; - export const INDENT_LINES: Command = { - id: 'arduino-indent-lines', - }; - export const OUTDENT_LINES: Command = { - id: 'arduino-outdent-lines', - }; - export const FIND: Command = { - id: 'arduino-find', - }; - export const FIND_NEXT: Command = { - id: 'arduino-find-next', - }; - export const FIND_PREVIOUS: Command = { - id: 'arduino-find-previous', - }; - export const USE_FOR_FIND: Command = { - id: 'arduino-for-find', - }; - export const INCREASE_FONT_SIZE: Command = { - id: 'arduino-increase-font-size', - }; - export const DECREASE_FONT_SIZE: Command = { - id: 'arduino-decrease-font-size', - }; - export const AUTO_FORMAT: Command = { - id: 'arduino-auto-format', // `Auto Format` should belong to `Tool`. - }; - } + export namespace Commands { + export const COPY_FOR_FORUM: Command = { + id: 'arduino-copy-for-forum', + }; + export const COPY_FOR_GITHUB: Command = { + id: 'arduino-copy-for-github', + }; + export const GO_TO_LINE: Command = { + id: 'arduino-go-to-line', + }; + export const TOGGLE_COMMENT: Command = { + id: 'arduino-toggle-comment', + }; + export const INDENT_LINES: Command = { + id: 'arduino-indent-lines', + }; + export const OUTDENT_LINES: Command = { + id: 'arduino-outdent-lines', + }; + export const FIND: Command = { + id: 'arduino-find', + }; + export const FIND_NEXT: Command = { + id: 'arduino-find-next', + }; + export const FIND_PREVIOUS: Command = { + id: 'arduino-find-previous', + }; + export const USE_FOR_FIND: Command = { + id: 'arduino-for-find', + }; + export const INCREASE_FONT_SIZE: Command = { + id: 'arduino-increase-font-size', + }; + export const DECREASE_FONT_SIZE: Command = { + id: 'arduino-decrease-font-size', + }; + export const AUTO_FORMAT: Command = { + id: 'arduino-auto-format', // `Auto Format` should belong to `Tool`. + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/examples.ts b/arduino-ide-extension/src/browser/contributions/examples.ts index 37a8dd4f0..383270d1d 100644 --- a/arduino-ide-extension/src/browser/contributions/examples.ts +++ b/arduino-ide-extension/src/browser/contributions/examples.ts @@ -2,13 +2,13 @@ import * as PQueue from 'p-queue'; import { inject, injectable, postConstruct } from 'inversify'; import { CommandHandler } from '@theia/core/lib/common/command'; import { - MenuPath, - CompositeMenuNode, - SubMenuOptions, + MenuPath, + CompositeMenuNode, + SubMenuOptions, } from '@theia/core/lib/common/menu'; import { - Disposable, - DisposableCollection, + Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; import { OpenSketch } from './open-sketch'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; @@ -16,243 +16,231 @@ import { MainMenuManager } from '../../common/main-menu-manager'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { ExamplesService } from '../../common/protocol/examples-service'; import { - SketchContribution, - CommandRegistry, - MenuModelRegistry, + SketchContribution, + CommandRegistry, + MenuModelRegistry, } from './contribution'; import { NotificationCenter } from '../notification-center'; import { Board, Sketch, SketchContainer } from '../../common/protocol'; @injectable() export abstract class Examples extends SketchContribution { - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; - - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; - - @inject(MainMenuManager) - protected readonly menuManager: MainMenuManager; - - @inject(ExamplesService) - protected readonly examplesService: ExamplesService; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - - protected readonly toDispose = new DisposableCollection(); - - @postConstruct() - init(): void { - this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => - this.handleBoardChanged(selectedBoard) - ); - } - - protected handleBoardChanged(board: Board | undefined): void { - // NOOP + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; + + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; + + @inject(MainMenuManager) + protected readonly menuManager: MainMenuManager; + + @inject(ExamplesService) + protected readonly examplesService: ExamplesService; + + @inject(BoardsServiceProvider) + protected readonly boardsServiceClient: BoardsServiceProvider; + + protected readonly toDispose = new DisposableCollection(); + + @postConstruct() + init(): void { + this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => + this.handleBoardChanged(selectedBoard) + ); + } + + protected handleBoardChanged(board: Board | undefined): void { + // NOOP + } + + registerMenus(registry: MenuModelRegistry): void { + try { + // This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222. + const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1; + const menuId = ArduinoMenus.FILE__EXAMPLES_SUBMENU[index]; + const groupPath = + index === 0 ? [] : ArduinoMenus.FILE__EXAMPLES_SUBMENU.slice(0, index); + const parent: CompositeMenuNode = (registry as any).findGroup(groupPath); + const examples = new CompositeMenuNode(menuId, '', { order: '4' }); + parent.addNode(examples); + } catch (e) { + console.error(e); + console.warn('Could not patch menu ordering.'); } - - registerMenus(registry: MenuModelRegistry): void { - try { - // This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222. - const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1; - const menuId = ArduinoMenus.FILE__EXAMPLES_SUBMENU[index]; - const groupPath = - index === 0 - ? [] - : ArduinoMenus.FILE__EXAMPLES_SUBMENU.slice(0, index); - const parent: CompositeMenuNode = (registry as any).findGroup( - groupPath - ); - const examples = new CompositeMenuNode(menuId, '', { order: '4' }); - parent.addNode(examples); - } catch (e) { - console.error(e); - console.warn('Could not patch menu ordering.'); + // Registering the same submenu multiple times has no side-effect. + // TODO: unregister submenu? https://github.com/eclipse-theia/theia/issues/7300 + registry.registerSubmenu(ArduinoMenus.FILE__EXAMPLES_SUBMENU, 'Examples', { + order: '4', + }); + } + + registerRecursively( + sketchContainerOrPlaceholder: + | SketchContainer + | (Sketch | SketchContainer)[] + | string, + menuPath: MenuPath, + pushToDispose: DisposableCollection = new DisposableCollection(), + subMenuOptions?: SubMenuOptions | undefined + ): void { + 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 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); + } } - // Registering the same submenu multiple times has no side-effect. - // TODO: unregister submenu? https://github.com/eclipse-theia/theia/issues/7300 - registry.registerSubmenu( - ArduinoMenus.FILE__EXAMPLES_SUBMENU, - 'Examples', - { order: '4' } + } + children.forEach((child) => + this.registerRecursively(child, submenuPath, pushToDispose) + ); + for (const sketch of sketches) { + const { uri } = sketch; + const commandId = `arduino-open-example-${submenuPath.join( + ':' + )}--${uri}`; + const command = { id: commandId }; + const handler = this.createHandler(uri); + pushToDispose.push( + this.commandRegistry.registerCommand(command, handler) ); + this.menuRegistry.registerMenuAction(submenuPath, { + commandId, + label: sketch.name, + order: sketch.name.toLocaleLowerCase(), + }); + pushToDispose.push( + Disposable.create(() => + this.menuRegistry.unregisterMenuAction(command) + ) + ); + } } - - registerRecursively( - sketchContainerOrPlaceholder: - | SketchContainer - | (Sketch | SketchContainer)[] - | string, - menuPath: MenuPath, - pushToDispose: DisposableCollection = new DisposableCollection(), - subMenuOptions?: SubMenuOptions | undefined - ): void { - 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 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; - const commandId = `arduino-open-example-${submenuPath.join( - ':' - )}--${uri}`; - const command = { id: commandId }; - const handler = this.createHandler(uri); - pushToDispose.push( - this.commandRegistry.registerCommand(command, handler) - ); - this.menuRegistry.registerMenuAction(submenuPath, { - commandId, - label: sketch.name, - order: sketch.name.toLocaleLowerCase(), - }); - pushToDispose.push( - Disposable.create(() => - this.menuRegistry.unregisterMenuAction(command) - ) - ); - } - } - } - - protected createHandler(uri: string): CommandHandler { - return { - execute: async () => { - const sketch = await this.sketchService.cloneExample(uri); - return this.commandService.executeCommand( - OpenSketch.Commands.OPEN_SKETCH.id, - sketch - ); - }, - }; - } + } + + protected createHandler(uri: string): CommandHandler { + return { + execute: async () => { + const sketch = await this.sketchService.cloneExample(uri); + return this.commandService.executeCommand( + OpenSketch.Commands.OPEN_SKETCH.id, + sketch + ); + }, + }; + } } @injectable() export class BuiltInExamples extends Examples { - onStart(): void { - this.register(); // no `await` + onStart(): void { + this.register(); // no `await` + } + + protected async register(): Promise { + let sketchContainers: SketchContainer[] | undefined; + try { + 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; } - - protected async register(): Promise { - let sketchContainers: SketchContainer[] | undefined; - try { - 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', ...sketchContainers]) { - this.registerRecursively( - container, - ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, - this.toDispose - ); - } - this.menuManager.update(); + this.toDispose.dispose(); + for (const container of ['Built-in examples', ...sketchContainers]) { + this.registerRecursively( + container, + ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, + this.toDispose + ); } + this.menuManager.update(); + } } @injectable() export class LibraryExamples extends Examples { - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; - - protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); - - onStart(): void { - this.register(); // no `await` - this.notificationCenter.onLibraryInstalled(() => this.register()); - this.notificationCenter.onLibraryUninstalled(() => this.register()); - } - - protected handleBoardChanged(board: Board | undefined): void { - this.register(board); - } - - protected async register( - board: Board | undefined = this.boardsServiceClient.boardsConfig - .selectedBoard - ): Promise { - return this.queue.add(async () => { - this.toDispose.dispose(); - const fqbn = board?.fqbn; - const name = board?.name; - // Shows all examples when no board is selected, or the platform of the currently selected board is not installed. - const { user, current, any } = await this.examplesService.installed( - { fqbn } - ); - if (user.length) { - (user as any).unshift('Examples from Custom Libraries'); - } - if (name && fqbn && current.length) { - (current as any).unshift(`Examples for ${name}`); - } - if (any.length) { - (any as any).unshift('Examples for any board'); - } - for (const container of user) { - this.registerRecursively( - container, - ArduinoMenus.EXAMPLES__USER_LIBS_GROUP, - this.toDispose - ); - } - for (const container of current) { - this.registerRecursively( - container, - ArduinoMenus.EXAMPLES__CURRENT_BOARD_GROUP, - this.toDispose - ); - } - for (const container of any) { - this.registerRecursively( - container, - ArduinoMenus.EXAMPLES__ANY_BOARD_GROUP, - this.toDispose - ); - } - this.menuManager.update(); - }); - } + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; + + protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); + + onStart(): void { + this.register(); // no `await` + this.notificationCenter.onLibraryInstalled(() => this.register()); + this.notificationCenter.onLibraryUninstalled(() => this.register()); + } + + protected handleBoardChanged(board: Board | undefined): void { + this.register(board); + } + + protected async register( + board: Board | undefined = this.boardsServiceClient.boardsConfig + .selectedBoard + ): Promise { + return this.queue.add(async () => { + this.toDispose.dispose(); + const fqbn = board?.fqbn; + const name = board?.name; + // Shows all examples when no board is selected, or the platform of the currently selected board is not installed. + const { user, current, any } = await this.examplesService.installed({ + fqbn, + }); + if (user.length) { + (user as any).unshift('Examples from Custom Libraries'); + } + if (name && fqbn && current.length) { + (current as any).unshift(`Examples for ${name}`); + } + if (any.length) { + (any as any).unshift('Examples for any board'); + } + for (const container of user) { + this.registerRecursively( + container, + ArduinoMenus.EXAMPLES__USER_LIBS_GROUP, + this.toDispose + ); + } + for (const container of current) { + this.registerRecursively( + container, + ArduinoMenus.EXAMPLES__CURRENT_BOARD_GROUP, + this.toDispose + ); + } + for (const container of any) { + this.registerRecursively( + container, + ArduinoMenus.EXAMPLES__ANY_BOARD_GROUP, + this.toDispose + ); + } + this.menuManager.update(); + }); + } } diff --git a/arduino-ide-extension/src/browser/contributions/help.ts b/arduino-ide-extension/src/browser/contributions/help.ts index fee91c9b2..fc6a07644 100644 --- a/arduino-ide-extension/src/browser/contributions/help.ts +++ b/arduino-ide-extension/src/browser/contributions/help.ts @@ -6,167 +6,160 @@ import { CommandHandler } from '@theia/core/lib/common/command'; import { QuickInputService } from '@theia/core/lib/browser/quick-open/quick-input-service'; import { ArduinoMenus } from '../menu/arduino-menus'; import { - Contribution, - Command, - MenuModelRegistry, - CommandRegistry, - KeybindingRegistry, + Contribution, + Command, + MenuModelRegistry, + CommandRegistry, + KeybindingRegistry, } from './contribution'; @injectable() export class Help extends Contribution { - @inject(EditorManager) - protected readonly editorManager: EditorManager; + @inject(EditorManager) + protected readonly editorManager: EditorManager; - @inject(WindowService) - protected readonly windowService: WindowService; + @inject(WindowService) + protected readonly windowService: WindowService; - @inject(QuickInputService) - protected readonly quickInputService: QuickInputService; + @inject(QuickInputService) + protected readonly quickInputService: QuickInputService; - registerCommands(registry: CommandRegistry): void { - const open = (url: string) => - this.windowService.openNewWindow(url, { external: true }); - const createOpenHandler = (url: string) => - { - execute: () => open(url), - }; - registry.registerCommand( - Help.Commands.GETTING_STARTED, - createOpenHandler('https://www.arduino.cc/en/Guide') - ); - registry.registerCommand( - Help.Commands.ENVIRONMENT, - createOpenHandler('https://www.arduino.cc/en/Guide/Environment') - ); - registry.registerCommand( - Help.Commands.TROUBLESHOOTING, - createOpenHandler('https://support.arduino.cc/hc/en-us') - ); - registry.registerCommand( - Help.Commands.REFERENCE, - createOpenHandler('https://www.arduino.cc/reference/en/') - ); - registry.registerCommand(Help.Commands.FIND_IN_REFERENCE, { - execute: async () => { - let searchFor: string | undefined = undefined; - const { currentEditor } = this.editorManager; - if ( - currentEditor && - currentEditor.editor instanceof MonacoEditor - ) { - const codeEditor = currentEditor.editor.getControl(); - const selection = codeEditor.getSelection(); - const model = codeEditor.getModel(); - if ( - model && - selection && - !monaco.Range.isEmpty(selection) - ) { - searchFor = model.getValueInRange(selection); - } - } - if (!searchFor) { - searchFor = await this.quickInputService.open({ - prompt: 'Search on Arduino.cc', - placeHolder: 'Type a keyword', - }); - } - if (searchFor) { - return open( - `https://www.arduino.cc/search?q=${encodeURIComponent( - searchFor - )}&tab=reference` - ); - } - }, - }); - registry.registerCommand( - Help.Commands.FAQ, - createOpenHandler('https://support.arduino.cc/hc/en-us') - ); - registry.registerCommand( - Help.Commands.VISIT_ARDUINO, - createOpenHandler('https://www.arduino.cc/') - ); - } + registerCommands(registry: CommandRegistry): void { + const open = (url: string) => + this.windowService.openNewWindow(url, { external: true }); + const createOpenHandler = (url: string) => + { + execute: () => open(url), + }; + registry.registerCommand( + Help.Commands.GETTING_STARTED, + createOpenHandler('https://www.arduino.cc/en/Guide') + ); + registry.registerCommand( + Help.Commands.ENVIRONMENT, + createOpenHandler('https://www.arduino.cc/en/Guide/Environment') + ); + registry.registerCommand( + Help.Commands.TROUBLESHOOTING, + createOpenHandler('https://support.arduino.cc/hc/en-us') + ); + registry.registerCommand( + Help.Commands.REFERENCE, + createOpenHandler('https://www.arduino.cc/reference/en/') + ); + registry.registerCommand(Help.Commands.FIND_IN_REFERENCE, { + execute: async () => { + let searchFor: string | undefined = undefined; + const { currentEditor } = this.editorManager; + if (currentEditor && currentEditor.editor instanceof MonacoEditor) { + const codeEditor = currentEditor.editor.getControl(); + const selection = codeEditor.getSelection(); + const model = codeEditor.getModel(); + if (model && selection && !monaco.Range.isEmpty(selection)) { + searchFor = model.getValueInRange(selection); + } + } + if (!searchFor) { + searchFor = await this.quickInputService.open({ + prompt: 'Search on Arduino.cc', + placeHolder: 'Type a keyword', + }); + } + if (searchFor) { + return open( + `https://www.arduino.cc/search?q=${encodeURIComponent( + searchFor + )}&tab=reference` + ); + } + }, + }); + registry.registerCommand( + Help.Commands.FAQ, + createOpenHandler('https://support.arduino.cc/hc/en-us') + ); + registry.registerCommand( + Help.Commands.VISIT_ARDUINO, + createOpenHandler('https://www.arduino.cc/') + ); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { - commandId: Help.Commands.GETTING_STARTED.id, - order: '0', - }); - registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { - commandId: Help.Commands.ENVIRONMENT.id, - order: '1', - }); - registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { - commandId: Help.Commands.TROUBLESHOOTING.id, - order: '2', - }); - registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { - commandId: Help.Commands.REFERENCE.id, - order: '3', - }); + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { + commandId: Help.Commands.GETTING_STARTED.id, + order: '0', + }); + registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { + commandId: Help.Commands.ENVIRONMENT.id, + order: '1', + }); + registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { + commandId: Help.Commands.TROUBLESHOOTING.id, + order: '2', + }); + registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, { + commandId: Help.Commands.REFERENCE.id, + order: '3', + }); - registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, { - commandId: Help.Commands.FIND_IN_REFERENCE.id, - order: '4', - }); - registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, { - commandId: Help.Commands.FAQ.id, - order: '5', - }); - registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, { - commandId: Help.Commands.VISIT_ARDUINO.id, - order: '6', - }); - } + registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, { + commandId: Help.Commands.FIND_IN_REFERENCE.id, + order: '4', + }); + registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, { + commandId: Help.Commands.FAQ.id, + order: '5', + }); + registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, { + commandId: Help.Commands.VISIT_ARDUINO.id, + order: '6', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: Help.Commands.FIND_IN_REFERENCE.id, - keybinding: 'CtrlCmd+Shift+F', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: Help.Commands.FIND_IN_REFERENCE.id, + keybinding: 'CtrlCmd+Shift+F', + }); + } } export namespace Help { - export namespace Commands { - export const GETTING_STARTED: Command = { - id: 'arduino-getting-started', - label: 'Getting Started', - category: 'Arduino', - }; - export const ENVIRONMENT: Command = { - id: 'arduino-environment', - label: 'Environment', - category: 'Arduino', - }; - export const TROUBLESHOOTING: Command = { - id: 'arduino-troubleshooting', - label: 'Troubleshooting', - category: 'Arduino', - }; - export const REFERENCE: Command = { - id: 'arduino-reference', - label: 'Reference', - category: 'Arduino', - }; - export const FIND_IN_REFERENCE: Command = { - id: 'arduino-find-in-reference', - label: 'Find in Reference', - category: 'Arduino', - }; - export const FAQ: Command = { - id: 'arduino-faq', - label: 'Frequently Asked Questions', - category: 'Arduino', - }; - export const VISIT_ARDUINO: Command = { - id: 'arduino-visit-arduino', - label: 'Visit Arduino.cc', - category: 'Arduino', - }; - } + export namespace Commands { + export const GETTING_STARTED: Command = { + id: 'arduino-getting-started', + label: 'Getting Started', + category: 'Arduino', + }; + export const ENVIRONMENT: Command = { + id: 'arduino-environment', + label: 'Environment', + category: 'Arduino', + }; + export const TROUBLESHOOTING: Command = { + id: 'arduino-troubleshooting', + label: 'Troubleshooting', + category: 'Arduino', + }; + export const REFERENCE: Command = { + id: 'arduino-reference', + label: 'Reference', + category: 'Arduino', + }; + export const FIND_IN_REFERENCE: Command = { + id: 'arduino-find-in-reference', + label: 'Find in Reference', + category: 'Arduino', + }; + export const FAQ: Command = { + id: 'arduino-faq', + label: 'Frequently Asked Questions', + category: 'Arduino', + }; + export const VISIT_ARDUINO: Command = { + id: 'arduino-visit-arduino', + label: 'Visit Arduino.cc', + category: 'Arduino', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/include-library.ts b/arduino-ide-extension/src/browser/contributions/include-library.ts index b83b060c6..5ee7c8b41 100644 --- a/arduino-ide-extension/src/browser/contributions/include-library.ts +++ b/arduino-ide-extension/src/browser/contributions/include-library.ts @@ -5,8 +5,8 @@ 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, + Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; import { LibraryPackage, LibraryService } from '../../common/protocol'; @@ -18,209 +18,198 @@ import { NotificationCenter } from '../notification-center'; @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(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceClient: BoardsServiceProvider; - - @inject(LibraryService) - protected readonly libraryService: LibraryService; - - protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); - protected readonly toDispose = new DisposableCollection(); - - onStart(): void { - this.updateMenuActions(); - this.boardsServiceClient.onBoardsConfigChanged(() => - this.updateMenuActions() - ); - this.notificationCenter.onLibraryInstalled(() => - this.updateMenuActions() - ); - this.notificationCenter.onLibraryUninstalled(() => - this.updateMenuActions() - ); + @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(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; + + @inject(BoardsServiceProvider) + protected readonly boardsServiceClient: BoardsServiceProvider; + + @inject(LibraryService) + protected readonly libraryService: LibraryService; + + protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); + protected readonly toDispose = new DisposableCollection(); + + onStart(): void { + this.updateMenuActions(); + this.boardsServiceClient.onBoardsConfigChanged(() => + this.updateMenuActions() + ); + this.notificationCenter.onLibraryInstalled(() => this.updateMenuActions()); + this.notificationCenter.onLibraryUninstalled(() => + this.updateMenuActions() + ); + } + + registerMenus(registry: MenuModelRegistry): void { + // `Include Library` submenu + const includeLibMenuPath = [ + ...ArduinoMenus.SKETCH__UTILS_GROUP, + '0_include', + ]; + registry.registerSubmenu(includeLibMenuPath, 'Include Library', { + order: '1', + }); + // `Manage Libraries...` group. + registry.registerMenuAction([...includeLibMenuPath, '0_manage'], { + commandId: `${LibraryListWidget.WIDGET_ID}:toggle`, + label: 'Manage Libraries...', + }); + } + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, { + execute: async (arg) => { + if (LibraryPackage.is(arg)) { + this.includeLibrary(arg); + } + }, + }); + } + + 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; + // Show all libraries, when no board is selected. + // Otherwise, show libraries only for the selected board. + libraries.push(...(await this.libraryService.list({ fqbn }))); + + const includeLibMenuPath = [ + ...ArduinoMenus.SKETCH__UTILS_GROUP, + '0_include', + ]; + // `Add .ZIP Library...` + // TODO: implement it + + // `Arduino libraries` + const packageMenuPath = [...includeLibMenuPath, '2_arduino']; + const userMenuPath = [...includeLibMenuPath, '3_contributed']; + const { user, rest } = LibraryPackage.groupByLocation(libraries); + if (rest.length) { + (rest as any).unshift('Arduino libraries'); + } + if (user.length) { + (user as any).unshift('Contributed libraries'); + } + + for (const library of user) { + this.toDispose.push(this.registerLibrary(library, userMenuPath)); + } + for (const library of rest) { + this.toDispose.push(this.registerLibrary(library, packageMenuPath)); + } + + this.mainMenuManager.update(); + }); + } + + protected registerLibrary( + libraryOrPlaceholder: LibraryPackage | string, + menuPath: MenuPath + ): Disposable { + if (typeof libraryOrPlaceholder === 'string') { + const placeholder = new PlaceholderMenuNode( + menuPath, + libraryOrPlaceholder + ); + this.menuRegistry.registerMenuNode(menuPath, placeholder); + return Disposable.create(() => + this.menuRegistry.unregisterMenuNode(placeholder.id) + ); } - - registerMenus(registry: MenuModelRegistry): void { - // `Include Library` submenu - const includeLibMenuPath = [ - ...ArduinoMenus.SKETCH__UTILS_GROUP, - '0_include', - ]; - registry.registerSubmenu(includeLibMenuPath, 'Include Library', { - order: '1', - }); - // `Manage Libraries...` group. - registry.registerMenuAction([...includeLibMenuPath, '0_manage'], { - commandId: `${LibraryListWidget.WIDGET_ID}:toggle`, - label: 'Manage Libraries...', - }); + const commandId = `arduino-include-library--${libraryOrPlaceholder.name}:${libraryOrPlaceholder.author}`; + const command = { id: commandId }; + const handler = { + execute: () => + this.commandRegistry.executeCommand( + IncludeLibrary.Commands.INCLUDE_LIBRARY.id, + libraryOrPlaceholder + ), + }; + const menuAction = { commandId, label: libraryOrPlaceholder.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 { + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return; } - - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, { - execute: async (arg) => { - if (LibraryPackage.is(arg)) { - this.includeLibrary(arg); - } - }, - }); + // 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(); + } } - 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; - // Show all libraries, when no board is selected. - // Otherwise, show libraries only for the selected board. - libraries.push(...(await this.libraryService.list({ fqbn }))); - - const includeLibMenuPath = [ - ...ArduinoMenus.SKETCH__UTILS_GROUP, - '0_include', - ]; - // `Add .ZIP Library...` - // TODO: implement it - - // `Arduino libraries` - const packageMenuPath = [...includeLibMenuPath, '2_arduino']; - const userMenuPath = [...includeLibMenuPath, '3_contributed']; - const { user, rest } = LibraryPackage.groupByLocation(libraries); - if (rest.length) { - (rest as any).unshift('Arduino libraries'); - } - if (user.length) { - (user as any).unshift('Contributed libraries'); - } - - for (const library of user) { - this.toDispose.push( - this.registerLibrary(library, userMenuPath) - ); - } - for (const library of rest) { - this.toDispose.push( - this.registerLibrary(library, packageMenuPath) - ); - } - - this.mainMenuManager.update(); - }); + if (!codeEditor) { + const widget = await this.editorManager.open(new URI(sketch.mainFileUri)); + if (widget.editor instanceof MonacoEditor) { + codeEditor = widget.editor.getControl(); + } } - protected registerLibrary( - libraryOrPlaceholder: LibraryPackage | string, - menuPath: MenuPath - ): Disposable { - if (typeof libraryOrPlaceholder === 'string') { - const placeholder = new PlaceholderMenuNode( - menuPath, - libraryOrPlaceholder - ); - this.menuRegistry.registerMenuNode(menuPath, placeholder); - return Disposable.create(() => - this.menuRegistry.unregisterMenuNode(placeholder.id) - ); - } - const commandId = `arduino-include-library--${libraryOrPlaceholder.name}:${libraryOrPlaceholder.author}`; - const command = { id: commandId }; - const handler = { - execute: () => - this.commandRegistry.executeCommand( - IncludeLibrary.Commands.INCLUDE_LIBRARY.id, - libraryOrPlaceholder - ), - }; - const menuAction = { commandId, label: libraryOrPlaceholder.name }; - this.menuRegistry.registerMenuAction(menuPath, menuAction); - return new DisposableCollection( - this.commandRegistry.registerCommand(command, handler), - Disposable.create(() => - this.menuRegistry.unregisterMenuAction(menuAction) - ) - ); + if (!codeEditor) { + return; } - protected async includeLibrary(library: LibraryPackage): Promise { - 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. + 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. + } } export namespace IncludeLibrary { - export namespace Commands { - export const INCLUDE_LIBRARY: Command = { - id: 'arduino-include-library', - }; - } + export namespace Commands { + export const INCLUDE_LIBRARY: Command = { + id: 'arduino-include-library', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/new-sketch.ts b/arduino-ide-extension/src/browser/contributions/new-sketch.ts index 3a231a41f..73b4e9827 100644 --- a/arduino-ide-extension/src/browser/contributions/new-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/new-sketch.ts @@ -2,70 +2,69 @@ import { injectable } from 'inversify'; import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { - SketchContribution, - URI, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - TabBarToolbarRegistry, + SketchContribution, + URI, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + TabBarToolbarRegistry, } from './contribution'; @injectable() export class NewSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(NewSketch.Commands.NEW_SKETCH, { - execute: () => this.newSketch(), - }); - registry.registerCommand(NewSketch.Commands.NEW_SKETCH__TOOLBAR, { - isVisible: (widget) => - ArduinoToolbar.is(widget) && widget.side === 'left', - execute: () => - registry.executeCommand(NewSketch.Commands.NEW_SKETCH.id), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(NewSketch.Commands.NEW_SKETCH, { + execute: () => this.newSketch(), + }); + registry.registerCommand(NewSketch.Commands.NEW_SKETCH__TOOLBAR, { + isVisible: (widget) => + ArduinoToolbar.is(widget) && widget.side === 'left', + execute: () => registry.executeCommand(NewSketch.Commands.NEW_SKETCH.id), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { - commandId: NewSketch.Commands.NEW_SKETCH.id, - label: 'New', - order: '0', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { + commandId: NewSketch.Commands.NEW_SKETCH.id, + label: 'New', + order: '0', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: NewSketch.Commands.NEW_SKETCH.id, - keybinding: 'CtrlCmd+N', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: NewSketch.Commands.NEW_SKETCH.id, + keybinding: 'CtrlCmd+N', + }); + } - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem({ - id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, - command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, - tooltip: 'New', - priority: 3, - }); - } + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem({ + id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, + command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, + tooltip: 'New', + priority: 3, + }); + } - async newSketch(): Promise { - try { - const sketch = await this.sketchService.createNewSketch(); - this.workspaceService.open(new URI(sketch.uri)); - } catch (e) { - await this.messageService.error(e.toString()); - } + async newSketch(): Promise { + try { + const sketch = await this.sketchService.createNewSketch(); + this.workspaceService.open(new URI(sketch.uri)); + } catch (e) { + await this.messageService.error(e.toString()); } + } } export namespace NewSketch { - export namespace Commands { - export const NEW_SKETCH: Command = { - id: 'arduino-new-sketch', - }; - export const NEW_SKETCH__TOOLBAR: Command = { - id: 'arduino-new-sketch--toolbar', - }; - } + export namespace Commands { + export const NEW_SKETCH: Command = { + id: 'arduino-new-sketch', + }; + export const NEW_SKETCH__TOOLBAR: Command = { + id: 'arduino-new-sketch--toolbar', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts index 6a6be49ea..418b4dd27 100644 --- a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts @@ -1,14 +1,14 @@ import { inject, injectable } from 'inversify'; import { WorkspaceServer } from '@theia/workspace/lib/common/workspace-protocol'; import { - Disposable, - DisposableCollection, + Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; import { - SketchContribution, - CommandRegistry, - MenuModelRegistry, - Sketch, + SketchContribution, + CommandRegistry, + MenuModelRegistry, + Sketch, } from './contribution'; import { ArduinoMenus } from '../menu/arduino-menus'; import { MainMenuManager } from '../../common/main-menu-manager'; @@ -17,78 +17,78 @@ import { NotificationCenter } from '../notification-center'; @injectable() export class OpenRecentSketch extends SketchContribution { - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; + @inject(MainMenuManager) + protected readonly mainMenuManager: MainMenuManager; - @inject(WorkspaceServer) - protected readonly workspaceServer: WorkspaceServer; + @inject(WorkspaceServer) + protected readonly workspaceServer: WorkspaceServer; - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; - protected toDisposeBeforeRegister = new Map(); + protected toDisposeBeforeRegister = new Map(); - onStart(): void { - const refreshMenu = (sketches: Sketch[]) => { - this.register(sketches); - this.mainMenuManager.update(); - }; - this.notificationCenter.onRecentSketchesChanged(({ sketches }) => - refreshMenu(sketches) - ); - this.sketchService.recentlyOpenedSketches().then(refreshMenu); - } + onStart(): void { + const refreshMenu = (sketches: Sketch[]) => { + this.register(sketches); + this.mainMenuManager.update(); + }; + this.notificationCenter.onRecentSketchesChanged(({ sketches }) => + refreshMenu(sketches) + ); + this.sketchService.recentlyOpenedSketches().then(refreshMenu); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerSubmenu( - ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, - 'Open Recent', - { order: '2' } - ); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerSubmenu( + ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, + 'Open Recent', + { order: '2' } + ); + } - protected register(sketches: Sketch[]): void { - const order = 0; - for (const sketch of sketches) { - const { uri } = sketch; - const toDispose = this.toDisposeBeforeRegister.get(uri); - if (toDispose) { - toDispose.dispose(); - } - const command = { id: `arduino-open-recent--${uri}` }; - const handler = { - execute: () => - this.commandRegistry.executeCommand( - OpenSketch.Commands.OPEN_SKETCH.id, - sketch - ), - }; - this.commandRegistry.registerCommand(command, handler); - this.menuRegistry.registerMenuAction( - ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, - { - commandId: command.id, - label: sketch.name, - order: String(order), - } - ); - this.toDisposeBeforeRegister.set( - sketch.uri, - new DisposableCollection( - Disposable.create(() => - this.commandRegistry.unregisterCommand(command) - ), - Disposable.create(() => - this.menuRegistry.unregisterMenuAction(command) - ) - ) - ); + protected register(sketches: Sketch[]): void { + const order = 0; + for (const sketch of sketches) { + const { uri } = sketch; + const toDispose = this.toDisposeBeforeRegister.get(uri); + if (toDispose) { + toDispose.dispose(); + } + const command = { id: `arduino-open-recent--${uri}` }; + const handler = { + execute: () => + this.commandRegistry.executeCommand( + OpenSketch.Commands.OPEN_SKETCH.id, + sketch + ), + }; + this.commandRegistry.registerCommand(command, handler); + this.menuRegistry.registerMenuAction( + ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, + { + commandId: command.id, + label: sketch.name, + order: String(order), } + ); + this.toDisposeBeforeRegister.set( + sketch.uri, + new DisposableCollection( + Disposable.create(() => + this.commandRegistry.unregisterCommand(command) + ), + Disposable.create(() => + this.menuRegistry.unregisterMenuAction(command) + ) + ) + ); } + } } diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts b/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts index b43a93b82..874fb465f 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts @@ -3,54 +3,54 @@ import { remote } from 'electron'; import URI from '@theia/core/lib/common/uri'; import { ArduinoMenus } from '../menu/arduino-menus'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, } from './contribution'; @injectable() export class OpenSketchExternal extends SketchContribution { - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, { - execute: () => this.openExternal(), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, { + execute: () => this.openExternal(), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { - commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, - label: 'Show Sketch Folder', - order: '0', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { + commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, + label: 'Show Sketch Folder', + order: '0', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, - keybinding: 'CtrlCmd+Alt+K', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, + keybinding: 'CtrlCmd+Alt+K', + }); + } - protected async openExternal(): Promise { - const uri = await this.sketchServiceClient.currentSketchFile(); - if (uri) { - const exists = this.fileService.exists(new URI(uri)); - if (exists) { - const fsPath = await this.fileService.fsPath(new URI(uri)); - if (fsPath) { - remote.shell.showItemInFolder(fsPath); - } - } + protected async openExternal(): Promise { + const uri = await this.sketchServiceClient.currentSketchFile(); + if (uri) { + const exists = this.fileService.exists(new URI(uri)); + if (exists) { + const fsPath = await this.fileService.fsPath(new URI(uri)); + if (fsPath) { + remote.shell.showItemInFolder(fsPath); } + } } + } } export namespace OpenSketchExternal { - export namespace Commands { - export const OPEN_EXTERNAL: Command = { - id: 'arduino-open-sketch-external', - }; - } + export namespace Commands { + export const OPEN_EXTERNAL: Command = { + id: 'arduino-open-sketch-external', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-sketch.ts index 300f7fba6..f2fe31b82 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch.ts @@ -3,20 +3,20 @@ import { remote } from 'electron'; import { MaybePromise } from '@theia/core/lib/common/types'; import { Widget, ContextMenuRenderer } from '@theia/core/lib/browser'; import { - Disposable, - DisposableCollection, + Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { - SketchContribution, - Sketch, - URI, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - TabBarToolbarRegistry, + SketchContribution, + Sketch, + URI, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + TabBarToolbarRegistry, } from './contribution'; import { ExamplesService } from '../../common/protocol/examples-service'; import { BuiltInExamples } from './examples'; @@ -25,204 +25,194 @@ import { SketchContainer } from '../../common/protocol'; @injectable() export class OpenSketch extends SketchContribution { - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; - @inject(ContextMenuRenderer) - protected readonly contextMenuRenderer: ContextMenuRenderer; + @inject(ContextMenuRenderer) + protected readonly contextMenuRenderer: ContextMenuRenderer; - @inject(BuiltInExamples) - protected readonly builtInExamples: BuiltInExamples; + @inject(BuiltInExamples) + protected readonly builtInExamples: BuiltInExamples; - @inject(ExamplesService) - protected readonly examplesService: ExamplesService; + @inject(ExamplesService) + protected readonly examplesService: ExamplesService; - @inject(Sketchbook) - protected readonly sketchbook: Sketchbook; + @inject(Sketchbook) + protected readonly sketchbook: Sketchbook; - protected readonly toDispose = new DisposableCollection(); + protected readonly toDispose = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, { - execute: (arg) => - Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(), + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, { + execute: (arg) => + Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(), + }); + registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH__TOOLBAR, { + isVisible: (widget) => + ArduinoToolbar.is(widget) && widget.side === 'left', + execute: async (_: Widget, target: EventTarget) => { + const container = await this.sketchService.getSketches({ + exclude: ['**/hardware/**'], }); - registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH__TOOLBAR, { - isVisible: (widget) => - ArduinoToolbar.is(widget) && widget.side === 'left', - execute: async (_: Widget, target: EventTarget) => { - const container = await this.sketchService.getSketches({ - exclude: ['**/hardware/**'], - }); - if (SketchContainer.isEmpty(container)) { - this.openSketch(); - } else { - this.toDispose.dispose(); - if (!(target instanceof HTMLElement)) { - return; - } - const { parentElement } = target; - if (!parentElement) { - return; - } + if (SketchContainer.isEmpty(container)) { + this.openSketch(); + } else { + this.toDispose.dispose(); + if (!(target instanceof HTMLElement)) { + return; + } + const { parentElement } = target; + if (!parentElement) { + return; + } - this.menuRegistry.registerMenuAction( - ArduinoMenus.OPEN_SKETCH__CONTEXT__OPEN_GROUP, - { - commandId: OpenSketch.Commands.OPEN_SKETCH.id, - label: 'Open...', - } - ); - 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.toDispose - ); - } - } catch (e) { - console.error( - 'Error when collecting built-in examples.', - e - ); - } - const options = { - menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT, - anchor: { - x: parentElement.getBoundingClientRect().left, - y: - parentElement.getBoundingClientRect().top + - parentElement.offsetHeight, - }, - }; - this.contextMenuRenderer.render(options); - } + this.menuRegistry.registerMenuAction( + ArduinoMenus.OPEN_SKETCH__CONTEXT__OPEN_GROUP, + { + commandId: OpenSketch.Commands.OPEN_SKETCH.id, + label: 'Open...', + } + ); + 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.toDispose + ); + } + } catch (e) { + console.error('Error when collecting built-in examples.', e); + } + const options = { + menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT, + anchor: { + x: parentElement.getBoundingClientRect().left, + y: + parentElement.getBoundingClientRect().top + + parentElement.offsetHeight, }, - }); - } + }; + this.contextMenuRenderer.render(options); + } + }, + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { - commandId: OpenSketch.Commands.OPEN_SKETCH.id, - label: 'Open...', - order: '1', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { + commandId: OpenSketch.Commands.OPEN_SKETCH.id, + label: 'Open...', + order: '1', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: OpenSketch.Commands.OPEN_SKETCH.id, - keybinding: 'CtrlCmd+O', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: OpenSketch.Commands.OPEN_SKETCH.id, + keybinding: 'CtrlCmd+O', + }); + } - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem({ - id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, - command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, - tooltip: 'Open', - priority: 4, - }); - } + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem({ + id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, + command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, + tooltip: 'Open', + priority: 4, + }); + } - async openSketch( - toOpen: MaybePromise = this.selectSketch() - ): Promise { - const sketch = await toOpen; - if (sketch) { - this.workspaceService.open(new URI(sketch.uri)); - } + async openSketch( + toOpen: MaybePromise = this.selectSketch() + ): Promise { + const sketch = await toOpen; + if (sketch) { + this.workspaceService.open(new URI(sketch.uri)); } + } - protected async selectSketch(): Promise { - const config = await this.configService.getConfiguration(); - const defaultPath = await this.fileService.fsPath( - new URI(config.sketchDirUri) - ); - const { filePaths } = await remote.dialog.showOpenDialog({ - defaultPath, - properties: ['createDirectory', 'openFile'], - filters: [ - { - name: 'Sketch', - extensions: ['ino', 'pde'], - }, - ], - }); - if (!filePaths.length) { - return undefined; - } - if (filePaths.length > 1) { - this.logger.warn( - `Multiple sketches were selected: ${filePaths}. Using the first one.` - ); - } - const sketchFilePath = filePaths[0]; - const sketchFileUri = await this.fileSystemExt.getUri(sketchFilePath); - const sketch = await this.sketchService.getSketchFolder(sketchFileUri); - if (sketch) { - return sketch; - } - if (Sketch.isSketchFile(sketchFileUri)) { - const name = new URI(sketchFileUri).path.name; - const nameWithExt = this.labelProvider.getName( - new URI(sketchFileUri) - ); - const { response } = await remote.dialog.showMessageBox({ - title: 'Moving', - type: 'question', - buttons: ['Cancel', 'OK'], - message: `The file "${nameWithExt}" needs to be inside a sketch folder named as "${name}".\nCreate this folder, move the file, and continue?`, - }); - if (response === 1) { - // OK - const newSketchUri = new URI(sketchFileUri).parent.resolve( - name - ); - const exists = await this.fileService.exists(newSketchUri); - if (exists) { - await remote.dialog.showMessageBox({ - type: 'error', - title: 'Error', - message: `A folder named "${name}" already exists. Can't open sketch.`, - }); - return undefined; - } - await this.fileService.createFolder(newSketchUri); - await this.fileService.move( - new URI(sketchFileUri), - new URI(newSketchUri.resolve(nameWithExt).toString()) - ); - return this.sketchService.getSketchFolder( - newSketchUri.toString() - ); - } + protected async selectSketch(): Promise { + const config = await this.configService.getConfiguration(); + const defaultPath = await this.fileService.fsPath( + new URI(config.sketchDirUri) + ); + const { filePaths } = await remote.dialog.showOpenDialog({ + defaultPath, + properties: ['createDirectory', 'openFile'], + filters: [ + { + name: 'Sketch', + extensions: ['ino', 'pde'], + }, + ], + }); + if (!filePaths.length) { + return undefined; + } + if (filePaths.length > 1) { + this.logger.warn( + `Multiple sketches were selected: ${filePaths}. Using the first one.` + ); + } + const sketchFilePath = filePaths[0]; + const sketchFileUri = await this.fileSystemExt.getUri(sketchFilePath); + const sketch = await this.sketchService.getSketchFolder(sketchFileUri); + if (sketch) { + return sketch; + } + if (Sketch.isSketchFile(sketchFileUri)) { + const name = new URI(sketchFileUri).path.name; + const nameWithExt = this.labelProvider.getName(new URI(sketchFileUri)); + const { response } = await remote.dialog.showMessageBox({ + title: 'Moving', + type: 'question', + buttons: ['Cancel', 'OK'], + message: `The file "${nameWithExt}" needs to be inside a sketch folder named as "${name}".\nCreate this folder, move the file, and continue?`, + }); + if (response === 1) { + // OK + const newSketchUri = new URI(sketchFileUri).parent.resolve(name); + const exists = await this.fileService.exists(newSketchUri); + if (exists) { + await remote.dialog.showMessageBox({ + type: 'error', + title: 'Error', + message: `A folder named "${name}" already exists. Can't open sketch.`, + }); + return undefined; } + await this.fileService.createFolder(newSketchUri); + await this.fileService.move( + new URI(sketchFileUri), + new URI(newSketchUri.resolve(nameWithExt).toString()) + ); + return this.sketchService.getSketchFolder(newSketchUri.toString()); + } } + } } export namespace OpenSketch { - export namespace Commands { - export const OPEN_SKETCH: Command = { - id: 'arduino-open-sketch', - }; - export const OPEN_SKETCH__TOOLBAR: Command = { - id: 'arduino-open-sketch--toolbar', - }; - } + export namespace Commands { + export const OPEN_SKETCH: Command = { + id: 'arduino-open-sketch', + }; + export const OPEN_SKETCH__TOOLBAR: Command = { + id: 'arduino-open-sketch--toolbar', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/quit-app.ts b/arduino-ide-extension/src/browser/contributions/quit-app.ts index e2ceaf32c..457f9ff36 100644 --- a/arduino-ide-extension/src/browser/contributions/quit-app.ts +++ b/arduino-ide-extension/src/browser/contributions/quit-app.ts @@ -2,49 +2,49 @@ import { injectable } from 'inversify'; import { remote } from 'electron'; import { isOSX } from '@theia/core/lib/common/os'; import { - Contribution, - Command, - MenuModelRegistry, - KeybindingRegistry, - CommandRegistry, + Contribution, + Command, + MenuModelRegistry, + KeybindingRegistry, + CommandRegistry, } from './contribution'; import { ArduinoMenus } from '../menu/arduino-menus'; @injectable() export class QuitApp extends Contribution { - registerCommands(registry: CommandRegistry): void { - if (!isOSX) { - registry.registerCommand(QuitApp.Commands.QUIT_APP, { - execute: () => remote.app.quit(), - }); - } + registerCommands(registry: CommandRegistry): void { + if (!isOSX) { + registry.registerCommand(QuitApp.Commands.QUIT_APP, { + execute: () => remote.app.quit(), + }); } + } - registerMenus(registry: MenuModelRegistry): void { - // On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it. - if (!isOSX) { - registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, { - commandId: QuitApp.Commands.QUIT_APP.id, - label: 'Quit', - order: '0', - }); - } + registerMenus(registry: MenuModelRegistry): void { + // On macOS we will get the `Quit ${YOUR_APP_NAME}` menu item natively, no need to duplicate it. + if (!isOSX) { + registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, { + commandId: QuitApp.Commands.QUIT_APP.id, + label: 'Quit', + order: '0', + }); } + } - registerKeybindings(registry: KeybindingRegistry): void { - if (!isOSX) { - registry.registerKeybinding({ - command: QuitApp.Commands.QUIT_APP.id, - keybinding: 'CtrlCmd+Q', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + if (!isOSX) { + registry.registerKeybinding({ + command: QuitApp.Commands.QUIT_APP.id, + keybinding: 'CtrlCmd+Q', + }); } + } } export namespace QuitApp { - export namespace Commands { - export const QUIT_APP: Command = { - id: 'arduino-quit-app', - }; - } + export namespace Commands { + export const QUIT_APP: Command = { + id: 'arduino-quit-app', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts index a270a2dc8..4f24d5dbf 100644 --- a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts @@ -3,129 +3,126 @@ import { remote } from 'electron'; import * as dateFormat from 'dateformat'; import { ArduinoMenus } from '../menu/arduino-menus'; import { - SketchContribution, - URI, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, + SketchContribution, + URI, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, } from './contribution'; @injectable() export class SaveAsSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, { - execute: (args) => this.saveAs(args), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, { + execute: (args) => this.saveAs(args), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { - commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, - label: 'Save As...', - order: '7', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { + commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, + label: 'Save As...', + order: '7', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, - keybinding: 'CtrlCmd+Shift+S', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, + keybinding: 'CtrlCmd+Shift+S', + }); + } - /** - * Resolves `true` if the sketch was successfully saved as something. - */ - async saveAs( - { - execOnlyIfTemp, - openAfterMove, - wipeOriginal, - }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT - ): Promise { - const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { - return false; - } + /** + * Resolves `true` if the sketch was successfully saved as something. + */ + async saveAs( + { + execOnlyIfTemp, + openAfterMove, + wipeOriginal, + }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT + ): Promise { + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return false; + } - const isTemp = await this.sketchService.isTemp(sketch); - if (!isTemp && !!execOnlyIfTemp) { - return false; - } + const isTemp = await this.sketchService.isTemp(sketch); + if (!isTemp && !!execOnlyIfTemp) { + return false; + } - // If target does not exist, propose a `directories.user`/${sketch.name} path - // If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss} - const sketchDirUri = new URI( - (await this.configService.getConfiguration()).sketchDirUri - ); - const exists = await this.fileService.exists( - sketchDirUri.resolve(sketch.name) - ); - const defaultUri = exists - ? sketchDirUri.resolve( - sketchDirUri - .resolve( - `${sketch.name}_copy_${dateFormat( - new Date(), - 'yyyymmddHHMMss' - )}` - ) - .toString() - ) - : sketchDirUri.resolve(sketch.name); - const defaultPath = await this.fileService.fsPath(defaultUri); - const { filePath, canceled } = await remote.dialog.showSaveDialog({ - title: 'Save sketch folder as...', - defaultPath, - }); - if (!filePath || canceled) { - return false; - } - const destinationUri = await this.fileSystemExt.getUri(filePath); - if (!destinationUri) { - return false; - } - const workspaceUri = await this.sketchService.copy(sketch, { - destinationUri, - }); - if (workspaceUri && openAfterMove) { - if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) { - try { - await this.fileService.delete(new URI(sketch.uri), { - recursive: true, - }); - } catch { - /* NOOP: from time to time, it's not possible to wipe the old resource from the temp dir on Windows */ - } - } - this.workspaceService.open(new URI(workspaceUri), { - preserveWindow: true, - }); + // If target does not exist, propose a `directories.user`/${sketch.name} path + // If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss} + const sketchDirUri = new URI( + (await this.configService.getConfiguration()).sketchDirUri + ); + const exists = await this.fileService.exists( + sketchDirUri.resolve(sketch.name) + ); + const defaultUri = exists + ? sketchDirUri.resolve( + sketchDirUri + .resolve( + `${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}` + ) + .toString() + ) + : sketchDirUri.resolve(sketch.name); + const defaultPath = await this.fileService.fsPath(defaultUri); + const { filePath, canceled } = await remote.dialog.showSaveDialog({ + title: 'Save sketch folder as...', + defaultPath, + }); + if (!filePath || canceled) { + return false; + } + const destinationUri = await this.fileSystemExt.getUri(filePath); + if (!destinationUri) { + return false; + } + const workspaceUri = await this.sketchService.copy(sketch, { + destinationUri, + }); + if (workspaceUri && openAfterMove) { + if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) { + try { + await this.fileService.delete(new URI(sketch.uri), { + recursive: true, + }); + } catch { + /* NOOP: from time to time, it's not possible to wipe the old resource from the temp dir on Windows */ } - return !!workspaceUri; + } + this.workspaceService.open(new URI(workspaceUri), { + preserveWindow: true, + }); } + return !!workspaceUri; + } } export namespace SaveAsSketch { - export namespace Commands { - export const SAVE_AS_SKETCH: Command = { - id: 'arduino-save-as-sketch', - }; - } - export interface Options { - readonly execOnlyIfTemp?: boolean; - readonly openAfterMove?: boolean; - /** - * Ignored if `openAfterMove` is `false`. - */ - readonly wipeOriginal?: boolean; - } - export namespace Options { - export const DEFAULT: Options = { - execOnlyIfTemp: false, - openAfterMove: true, - wipeOriginal: false, - }; - } + export namespace Commands { + export const SAVE_AS_SKETCH: Command = { + id: 'arduino-save-as-sketch', + }; + } + export interface Options { + readonly execOnlyIfTemp?: boolean; + readonly openAfterMove?: boolean; + /** + * Ignored if `openAfterMove` is `false`. + */ + readonly wipeOriginal?: boolean; + } + export namespace Options { + export const DEFAULT: Options = { + execOnlyIfTemp: false, + openAfterMove: true, + wipeOriginal: false, + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/save-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-sketch.ts index b5b382b1e..228a3ac66 100644 --- a/arduino-ide-extension/src/browser/contributions/save-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-sketch.ts @@ -3,64 +3,64 @@ import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribu import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - TabBarToolbarRegistry, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + TabBarToolbarRegistry, } from './contribution'; @injectable() export class SaveSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, { - execute: () => this.saveSketch(), - }); - registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH__TOOLBAR, { - isVisible: (widget) => - ArduinoToolbar.is(widget) && widget.side === 'left', - execute: () => - registry.executeCommand(SaveSketch.Commands.SAVE_SKETCH.id), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, { + execute: () => this.saveSketch(), + }); + registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH__TOOLBAR, { + isVisible: (widget) => + ArduinoToolbar.is(widget) && widget.side === 'left', + execute: () => + registry.executeCommand(SaveSketch.Commands.SAVE_SKETCH.id), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { - commandId: SaveSketch.Commands.SAVE_SKETCH.id, - label: 'Save', - order: '6', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { + commandId: SaveSketch.Commands.SAVE_SKETCH.id, + label: 'Save', + order: '6', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: SaveSketch.Commands.SAVE_SKETCH.id, - keybinding: 'CtrlCmd+S', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: SaveSketch.Commands.SAVE_SKETCH.id, + keybinding: 'CtrlCmd+S', + }); + } - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem({ - id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, - command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, - tooltip: 'Save', - priority: 5, - }); - } + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem({ + id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, + command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, + tooltip: 'Save', + priority: 5, + }); + } - async saveSketch(): Promise { - return this.commandService.executeCommand(CommonCommands.SAVE_ALL.id); - } + async saveSketch(): Promise { + return this.commandService.executeCommand(CommonCommands.SAVE_ALL.id); + } } export namespace SaveSketch { - export namespace Commands { - export const SAVE_SKETCH: Command = { - id: 'arduino-save-sketch', - }; - export const SAVE_SKETCH__TOOLBAR: Command = { - id: 'arduino-save-sketch--toolbar', - }; - } + export namespace Commands { + export const SAVE_SKETCH: Command = { + id: 'arduino-save-sketch', + }; + export const SAVE_SKETCH__TOOLBAR: Command = { + id: 'arduino-save-sketch--toolbar', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/settings.ts b/arduino-ide-extension/src/browser/contributions/settings.ts index 9878843b4..fe9b669e6 100644 --- a/arduino-ide-extension/src/browser/contributions/settings.ts +++ b/arduino-ide-extension/src/browser/contributions/settings.ts @@ -1,68 +1,65 @@ import { inject, injectable } from 'inversify'; import { - Command, - MenuModelRegistry, - CommandRegistry, - SketchContribution, - KeybindingRegistry, + Command, + MenuModelRegistry, + CommandRegistry, + SketchContribution, + KeybindingRegistry, } from './contribution'; import { ArduinoMenus } from '../menu/arduino-menus'; import { Settings as Preferences, SettingsDialog } from '../settings'; @injectable() export class Settings extends SketchContribution { - @inject(SettingsDialog) - protected readonly settingsDialog: SettingsDialog; + @inject(SettingsDialog) + protected readonly settingsDialog: SettingsDialog; - protected settingsOpened = false; + protected settingsOpened = false; - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(Settings.Commands.OPEN, { - execute: async () => { - let settings: Preferences | undefined = undefined; - try { - this.settingsOpened = true; - settings = await this.settingsDialog.open(); - } finally { - this.settingsOpened = false; - } - if (settings) { - await this.settingsService.update(settings); - await this.settingsService.save(); - } else { - await this.settingsService.reset(); - } - }, - isEnabled: () => !this.settingsOpened, - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(Settings.Commands.OPEN, { + execute: async () => { + let settings: Preferences | undefined = undefined; + try { + this.settingsOpened = true; + settings = await this.settingsDialog.open(); + } finally { + this.settingsOpened = false; + } + if (settings) { + await this.settingsService.update(settings); + await this.settingsService.save(); + } else { + await this.settingsService.reset(); + } + }, + isEnabled: () => !this.settingsOpened, + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, { - commandId: Settings.Commands.OPEN.id, - label: 'Preferences...', - order: '0', - }); - registry.registerSubmenu( - ArduinoMenus.FILE__ADVANCED_SUBMENU, - 'Advanced' - ); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, { + commandId: Settings.Commands.OPEN.id, + label: 'Preferences...', + order: '0', + }); + registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced'); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: Settings.Commands.OPEN.id, - keybinding: 'CtrlCmd+,', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: Settings.Commands.OPEN.id, + keybinding: 'CtrlCmd+,', + }); + } } export namespace Settings { - export namespace Commands { - export const OPEN: Command = { - id: 'arduino-settings-open', - label: 'Open Preferences...', - category: 'Arduino', - }; - } + export namespace Commands { + export const OPEN: Command = { + id: 'arduino-settings-open', + label: 'Open Preferences...', + category: 'Arduino', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/sketch-control.ts b/arduino-ide-extension/src/browser/contributions/sketch-control.ts index 94f540ebb..8b04eaa3f 100644 --- a/arduino-ide-extension/src/browser/contributions/sketch-control.ts +++ b/arduino-ide-extension/src/browser/contributions/sketch-control.ts @@ -4,18 +4,18 @@ import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shel import { WorkspaceCommands } from '@theia/workspace/lib/browser'; import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer'; import { - Disposable, - DisposableCollection, + Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; import { - URI, - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - TabBarToolbarRegistry, - open, + URI, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + TabBarToolbarRegistry, + open, } from './contribution'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; @@ -24,260 +24,254 @@ import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider'; @injectable() export class SketchControl extends SketchContribution { - @inject(ApplicationShell) - protected readonly shell: ApplicationShell; + @inject(ApplicationShell) + protected readonly shell: ApplicationShell; - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; - @inject(ContextMenuRenderer) - protected readonly contextMenuRenderer: ContextMenuRenderer; + @inject(ContextMenuRenderer) + protected readonly contextMenuRenderer: ContextMenuRenderer; - @inject(EditorManager) - protected readonly editorManager: EditorManager; + @inject(EditorManager) + protected readonly editorManager: EditorManager; - @inject(SketchesServiceClientImpl) - protected readonly sketchesServiceClient: SketchesServiceClientImpl; + @inject(SketchesServiceClientImpl) + protected readonly sketchesServiceClient: SketchesServiceClientImpl; - @inject(LocalCacheFsProvider) - protected readonly localCacheFsProvider: LocalCacheFsProvider; + @inject(LocalCacheFsProvider) + protected readonly localCacheFsProvider: LocalCacheFsProvider; - protected readonly toDisposeBeforeCreateNewContextMenu = - new DisposableCollection(); + protected readonly toDisposeBeforeCreateNewContextMenu = + new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { - registry.registerCommand( - SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR, - { - isVisible: (widget) => - this.shell.getWidgets('main').indexOf(widget) !== -1, - execute: async () => { - this.toDisposeBeforeCreateNewContextMenu.dispose(); - const sketch = - await this.sketchServiceClient.currentSketch(); - if (!sketch) { - return; - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand( + SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR, + { + isVisible: (widget) => + this.shell.getWidgets('main').indexOf(widget) !== -1, + execute: async () => { + this.toDisposeBeforeCreateNewContextMenu.dispose(); + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return; + } - const target = document.getElementById( - SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id - ); - if (!(target instanceof HTMLElement)) { - return; - } - const { parentElement } = target; - if (!parentElement) { - return; - } + const target = document.getElementById( + SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id + ); + if (!(target instanceof HTMLElement)) { + return; + } + const { parentElement } = target; + if (!parentElement) { + return; + } - const { mainFileUri, rootFolderFileUris } = - await this.sketchService.loadSketch(sketch.uri); - const uris = [mainFileUri, ...rootFolderFileUris]; + const { mainFileUri, rootFolderFileUris } = + await this.sketchService.loadSketch(sketch.uri); + const uris = [mainFileUri, ...rootFolderFileUris]; - const currentSketch = - await this.sketchesServiceClient.currentSketch(); - const parentsketchUri = this.editorManager.currentEditor - ?.getResourceUri() - ?.toString(); - const parentsketch = - await this.sketchService.getSketchFolder( - parentsketchUri || '' - ); + const currentSketch = + await this.sketchesServiceClient.currentSketch(); + const parentsketchUri = this.editorManager.currentEditor + ?.getResourceUri() + ?.toString(); + const parentsketch = await this.sketchService.getSketchFolder( + parentsketchUri || '' + ); - // if the current file is in the current opened sketch, show extra menus - if ( - currentSketch && - parentsketch && - parentsketch.uri === currentSketch.uri && - (await this.allowRename(parentsketch.uri)) - ) { - this.menuRegistry.registerMenuAction( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, - { - commandId: WorkspaceCommands.FILE_RENAME.id, - label: 'Rename', - order: '1', - } - ); - this.toDisposeBeforeCreateNewContextMenu.push( - Disposable.create(() => - this.menuRegistry.unregisterMenuAction( - WorkspaceCommands.FILE_RENAME - ) - ) - ); - } else { - const renamePlaceholder = new PlaceholderMenuNode( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, - 'Rename' - ); - this.menuRegistry.registerMenuNode( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, - renamePlaceholder - ); - this.toDisposeBeforeCreateNewContextMenu.push( - Disposable.create(() => - this.menuRegistry.unregisterMenuNode( - renamePlaceholder.id - ) - ) - ); - } + // if the current file is in the current opened sketch, show extra menus + if ( + currentSketch && + parentsketch && + parentsketch.uri === currentSketch.uri && + (await this.allowRename(parentsketch.uri)) + ) { + this.menuRegistry.registerMenuAction( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, + { + commandId: WorkspaceCommands.FILE_RENAME.id, + label: 'Rename', + order: '1', + } + ); + this.toDisposeBeforeCreateNewContextMenu.push( + Disposable.create(() => + this.menuRegistry.unregisterMenuAction( + WorkspaceCommands.FILE_RENAME + ) + ) + ); + } else { + const renamePlaceholder = new PlaceholderMenuNode( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, + 'Rename' + ); + this.menuRegistry.registerMenuNode( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, + renamePlaceholder + ); + this.toDisposeBeforeCreateNewContextMenu.push( + Disposable.create(() => + this.menuRegistry.unregisterMenuNode(renamePlaceholder.id) + ) + ); + } - if ( - currentSketch && - parentsketch && - parentsketch.uri === currentSketch.uri && - (await this.allowDelete(parentsketch.uri)) - ) { - this.menuRegistry.registerMenuAction( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, - { - commandId: WorkspaceCommands.FILE_DELETE.id, // TODO: customize delete. Wipe sketch if deleting main file. Close window. - label: 'Delete', - order: '2', - } - ); - this.toDisposeBeforeCreateNewContextMenu.push( - Disposable.create(() => - this.menuRegistry.unregisterMenuAction( - WorkspaceCommands.FILE_DELETE - ) - ) - ); - } else { - const deletePlaceholder = new PlaceholderMenuNode( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, - 'Delete' - ); - this.menuRegistry.registerMenuNode( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, - deletePlaceholder - ); - this.toDisposeBeforeCreateNewContextMenu.push( - Disposable.create(() => - this.menuRegistry.unregisterMenuNode( - deletePlaceholder.id - ) - ) - ); - } + if ( + currentSketch && + parentsketch && + parentsketch.uri === currentSketch.uri && + (await this.allowDelete(parentsketch.uri)) + ) { + this.menuRegistry.registerMenuAction( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, + { + commandId: WorkspaceCommands.FILE_DELETE.id, // TODO: customize delete. Wipe sketch if deleting main file. Close window. + label: 'Delete', + order: '2', + } + ); + this.toDisposeBeforeCreateNewContextMenu.push( + Disposable.create(() => + this.menuRegistry.unregisterMenuAction( + WorkspaceCommands.FILE_DELETE + ) + ) + ); + } else { + const deletePlaceholder = new PlaceholderMenuNode( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, + 'Delete' + ); + this.menuRegistry.registerMenuNode( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, + deletePlaceholder + ); + this.toDisposeBeforeCreateNewContextMenu.push( + Disposable.create(() => + this.menuRegistry.unregisterMenuNode(deletePlaceholder.id) + ) + ); + } - for (let i = 0; i < uris.length; i++) { - const uri = new URI(uris[i]); + for (let i = 0; i < uris.length; i++) { + const uri = new URI(uris[i]); - // focus on the opened sketch - const command = { - id: `arduino-focus-file--${uri.toString()}`, - }; - const handler = { - execute: () => open(this.openerService, uri), - }; - this.toDisposeBeforeCreateNewContextMenu.push( - registry.registerCommand(command, handler) - ); - this.menuRegistry.registerMenuAction( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP, - { - commandId: command.id, - label: this.labelProvider.getName(uri), - order: `${i}`, - } - ); - this.toDisposeBeforeCreateNewContextMenu.push( - Disposable.create(() => - this.menuRegistry.unregisterMenuAction(command) - ) - ); - } - const options = { - menuPath: ArduinoMenus.SKETCH_CONTROL__CONTEXT, - anchor: { - x: parentElement.getBoundingClientRect().left, - y: - parentElement.getBoundingClientRect().top + - parentElement.offsetHeight, - }, - }; - this.contextMenuRenderer.render(options); - }, - } - ); - } + // focus on the opened sketch + const command = { + id: `arduino-focus-file--${uri.toString()}`, + }; + const handler = { + execute: () => open(this.openerService, uri), + }; + this.toDisposeBeforeCreateNewContextMenu.push( + registry.registerCommand(command, handler) + ); + this.menuRegistry.registerMenuAction( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP, + { + commandId: command.id, + label: this.labelProvider.getName(uri), + order: `${i}`, + } + ); + this.toDisposeBeforeCreateNewContextMenu.push( + Disposable.create(() => + this.menuRegistry.unregisterMenuAction(command) + ) + ); + } + const options = { + menuPath: ArduinoMenus.SKETCH_CONTROL__CONTEXT, + anchor: { + x: parentElement.getBoundingClientRect().left, + y: + parentElement.getBoundingClientRect().top + + parentElement.offsetHeight, + }, + }; + this.contextMenuRenderer.render(options); + }, + } + ); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, - { - commandId: WorkspaceCommands.NEW_FILE.id, - label: 'New Tab', - order: '0', - } - ); + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, + { + commandId: WorkspaceCommands.NEW_FILE.id, + label: 'New Tab', + order: '0', + } + ); - registry.registerMenuAction( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP, - { - commandId: CommonCommands.PREVIOUS_TAB.id, - label: 'Previous Tab', - order: '0', - } - ); - registry.registerMenuAction( - ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP, - { - commandId: CommonCommands.NEXT_TAB.id, - label: 'Next Tab', - order: '0', - } - ); - } + registry.registerMenuAction( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP, + { + commandId: CommonCommands.PREVIOUS_TAB.id, + label: 'Previous Tab', + order: '0', + } + ); + registry.registerMenuAction( + ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP, + { + commandId: CommonCommands.NEXT_TAB.id, + label: 'Next Tab', + order: '0', + } + ); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: WorkspaceCommands.NEW_FILE.id, - keybinding: 'CtrlCmd+Shift+N', - }); - registry.registerKeybinding({ - command: CommonCommands.PREVIOUS_TAB.id, - keybinding: 'CtrlCmd+Alt+Left', // TODO: check why electron does not show the keybindings in the UI. - }); - registry.registerKeybinding({ - command: CommonCommands.NEXT_TAB.id, - keybinding: 'CtrlCmd+Alt+Right', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: WorkspaceCommands.NEW_FILE.id, + keybinding: 'CtrlCmd+Shift+N', + }); + registry.registerKeybinding({ + command: CommonCommands.PREVIOUS_TAB.id, + keybinding: 'CtrlCmd+Alt+Left', // TODO: check why electron does not show the keybindings in the UI. + }); + registry.registerKeybinding({ + command: CommonCommands.NEXT_TAB.id, + keybinding: 'CtrlCmd+Alt+Right', + }); + } - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem({ - id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, - command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, - }); - } + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem({ + id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, + command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, + }); + } - protected async isCloudSketch(uri: string) { - const cloudCacheLocation = this.localCacheFsProvider.from(new URI(uri)); + protected async isCloudSketch(uri: string) { + const cloudCacheLocation = this.localCacheFsProvider.from(new URI(uri)); - if (cloudCacheLocation) { - return true; - } - return false; + if (cloudCacheLocation) { + return true; } + return false; + } - protected async allowRename(uri: string) { - return !this.isCloudSketch(uri); - } + protected async allowRename(uri: string) { + return !this.isCloudSketch(uri); + } - protected async allowDelete(uri: string) { - return !this.isCloudSketch(uri); - } + protected async allowDelete(uri: string) { + return !this.isCloudSketch(uri); + } } export namespace SketchControl { - export namespace Commands { - export const OPEN_SKETCH_CONTROL__TOOLBAR: Command = { - id: 'arduino-open-sketch-control--toolbar', - iconClass: 'fa fa-caret-down', - }; - } + export namespace Commands { + export const OPEN_SKETCH_CONTROL__TOOLBAR: Command = { + id: 'arduino-open-sketch-control--toolbar', + iconClass: 'fa fa-caret-down', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/sketchbook.ts b/arduino-ide-extension/src/browser/contributions/sketchbook.ts index 8b5976d68..7ec593815 100644 --- a/arduino-ide-extension/src/browser/contributions/sketchbook.ts +++ b/arduino-ide-extension/src/browser/contributions/sketchbook.ts @@ -10,57 +10,57 @@ import { OpenSketch } from './open-sketch'; @injectable() export class Sketchbook extends Examples { - @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + @inject(CommandRegistry) + protected readonly commandRegistry: CommandRegistry; - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; + @inject(MainMenuManager) + protected readonly mainMenuManager: MainMenuManager; - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter; - onStart(): void { - this.sketchService.getSketches({}).then((container) => { - this.register(container); - this.mainMenuManager.update(); - }); - this.sketchServiceClient.onSketchbookDidChange(() => { - this.sketchService.getSketches({}).then((container) => { - this.register(container); - this.mainMenuManager.update(); - }); - }); - } + onStart(): void { + this.sketchService.getSketches({}).then((container) => { + this.register(container); + this.mainMenuManager.update(); + }); + this.sketchServiceClient.onSketchbookDidChange(() => { + this.sketchService.getSketches({}).then((container) => { + this.register(container); + this.mainMenuManager.update(); + }); + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerSubmenu( - ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, - 'Sketchbook', - { order: '3' } - ); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerSubmenu( + ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, + 'Sketchbook', + { order: '3' } + ); + } - protected register(container: SketchContainer): void { - this.toDispose.dispose(); - this.registerRecursively( - [...container.children, ...container.sketches], - ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, - this.toDispose - ); - } + protected register(container: SketchContainer): void { + this.toDispose.dispose(); + this.registerRecursively( + [...container.children, ...container.sketches], + ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, + this.toDispose + ); + } - protected createHandler(uri: string): CommandHandler { - return { - execute: async () => { - const sketch = await this.sketchService.loadSketch(uri); - return this.commandService.executeCommand( - OpenSketch.Commands.OPEN_SKETCH.id, - sketch - ); - }, - }; - } + protected createHandler(uri: string): CommandHandler { + return { + execute: async () => { + const sketch = await this.sketchService.loadSketch(uri); + return this.commandService.executeCommand( + OpenSketch.Commands.OPEN_SKETCH.id, + sketch + ); + }, + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 5332dbea3..a704c97ab 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -7,205 +7,200 @@ import { BoardsDataStore } from '../boards/boards-data-store'; import { MonitorConnection } from '../monitor/monitor-connection'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - TabBarToolbarRegistry, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + TabBarToolbarRegistry, } from './contribution'; @injectable() export class UploadSketch extends SketchContribution { - @inject(CoreService) - protected readonly coreService: CoreService; - - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; - - @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceClientImpl: BoardsServiceProvider; - - protected readonly onDidChangeEmitter = new Emitter>(); - readonly onDidChange = this.onDidChangeEmitter.event; - - protected uploadInProgress = false; - - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { - execute: () => this.uploadSketch(), - isEnabled: () => !this.uploadInProgress, - }); - registry.registerCommand( - UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER, - { - execute: () => this.uploadSketch(true), - isEnabled: () => !this.uploadInProgress, - } - ); - registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR, { - isVisible: (widget) => - ArduinoToolbar.is(widget) && widget.side === 'left', - isEnabled: () => !this.uploadInProgress, - isToggled: () => this.uploadInProgress, - execute: () => - registry.executeCommand(UploadSketch.Commands.UPLOAD_SKETCH.id), - }); + @inject(CoreService) + protected readonly coreService: CoreService; + + @inject(MonitorConnection) + protected readonly monitorConnection: MonitorConnection; + + @inject(BoardsDataStore) + protected readonly boardsDataStore: BoardsDataStore; + + @inject(BoardsServiceProvider) + protected readonly boardsServiceClientImpl: BoardsServiceProvider; + + protected readonly onDidChangeEmitter = new Emitter>(); + readonly onDidChange = this.onDidChangeEmitter.event; + + protected uploadInProgress = false; + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { + execute: () => this.uploadSketch(), + isEnabled: () => !this.uploadInProgress, + }); + registry.registerCommand( + UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER, + { + execute: () => this.uploadSketch(true), + isEnabled: () => !this.uploadInProgress, + } + ); + registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR, { + isVisible: (widget) => + ArduinoToolbar.is(widget) && widget.side === 'left', + isEnabled: () => !this.uploadInProgress, + isToggled: () => this.uploadInProgress, + execute: () => + registry.executeCommand(UploadSketch.Commands.UPLOAD_SKETCH.id), + }); + } + + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { + commandId: UploadSketch.Commands.UPLOAD_SKETCH.id, + label: 'Upload', + order: '1', + }); + registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { + commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id, + label: 'Upload Using Programmer', + order: '2', + }); + } + + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: UploadSketch.Commands.UPLOAD_SKETCH.id, + keybinding: 'CtrlCmd+U', + }); + registry.registerKeybinding({ + command: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id, + keybinding: 'CtrlCmd+Shift+U', + }); + } + + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem({ + id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, + command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, + tooltip: 'Upload', + priority: 1, + onDidChange: this.onDidChange, + }); + } + + async uploadSketch(usingProgrammer = false): Promise { + // even with buttons disabled, better to double check if an upload is already in progress + if (this.uploadInProgress) { + return; } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { - commandId: UploadSketch.Commands.UPLOAD_SKETCH.id, - label: 'Upload', - order: '1', - }); - registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { - commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id, - label: 'Upload Using Programmer', - order: '2', - }); + // toggle the toolbar button and menu item state. + // uploadInProgress will be set to false whether the upload fails or not + this.uploadInProgress = true; + this.onDidChangeEmitter.fire(); + const sketch = await this.sketchServiceClient.currentSketch(); + if (!sketch) { + return; } - - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: UploadSketch.Commands.UPLOAD_SKETCH.id, - keybinding: 'CtrlCmd+U', - }); - registry.registerKeybinding({ - command: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id, - keybinding: 'CtrlCmd+Shift+U', - }); - } - - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem({ - id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, - command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, - tooltip: 'Upload', - priority: 1, - onDidChange: this.onDidChange, - }); + let shouldAutoConnect = false; + const monitorConfig = this.monitorConnection.monitorConfig; + if (monitorConfig) { + await this.monitorConnection.disconnect(); + if (this.monitorConnection.autoConnect) { + shouldAutoConnect = true; + } + this.monitorConnection.autoConnect = false; } - - async uploadSketch(usingProgrammer = false): Promise { - // even with buttons disabled, better to double check if an upload is already in progress - if (this.uploadInProgress) { - return; - } - - // toggle the toolbar button and menu item state. - // uploadInProgress will be set to false whether the upload fails or not - this.uploadInProgress = true; - this.onDidChangeEmitter.fire(); - const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { - return; - } - let shouldAutoConnect = false; - const monitorConfig = this.monitorConnection.monitorConfig; - if (monitorConfig) { - await this.monitorConnection.disconnect(); - if (this.monitorConnection.autoConnect) { - shouldAutoConnect = true; - } - this.monitorConnection.autoConnect = false; - } + try { + const { boardsConfig } = this.boardsServiceClientImpl; + const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] = + await Promise.all([ + this.boardsDataStore.appendConfigToFqbn( + boardsConfig.selectedBoard?.fqbn + ), + this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn), + this.preferences.get('arduino.upload.verify'), + this.preferences.get('arduino.upload.verbose'), + this.sourceOverride(), + ]); + + let options: CoreService.Upload.Options | undefined = undefined; + const sketchUri = sketch.uri; + const optimizeForDebug = this.editorMode.compileForDebug; + const { selectedPort } = boardsConfig; + const port = selectedPort?.address; + + if (usingProgrammer) { + const programmer = selectedProgrammer; + options = { + sketchUri, + fqbn, + optimizeForDebug, + programmer, + port, + verbose, + verify, + sourceOverride, + }; + } else { + options = { + sketchUri, + fqbn, + optimizeForDebug, + port, + verbose, + verify, + sourceOverride, + }; + } + this.outputChannelManager.getChannel('Arduino').clear(); + if (usingProgrammer) { + await this.coreService.uploadUsingProgrammer(options); + } else { + await this.coreService.upload(options); + } + this.messageService.info('Done uploading.', { timeout: 3000 }); + } catch (e) { + this.messageService.error(e.toString()); + } finally { + this.uploadInProgress = false; + this.onDidChangeEmitter.fire(); + + if (monitorConfig) { + const { board, port } = monitorConfig; try { - const { boardsConfig } = this.boardsServiceClientImpl; - const [ - fqbn, - { selectedProgrammer }, - verify, - verbose, - sourceOverride, - ] = await Promise.all([ - this.boardsDataStore.appendConfigToFqbn( - boardsConfig.selectedBoard?.fqbn - ), - this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn), - this.preferences.get('arduino.upload.verify'), - this.preferences.get('arduino.upload.verbose'), - this.sourceOverride(), - ]); - - let options: CoreService.Upload.Options | undefined = undefined; - const sketchUri = sketch.uri; - const optimizeForDebug = this.editorMode.compileForDebug; - const { selectedPort } = boardsConfig; - const port = selectedPort?.address; - - if (usingProgrammer) { - const programmer = selectedProgrammer; - options = { - sketchUri, - fqbn, - optimizeForDebug, - programmer, - port, - verbose, - verify, - sourceOverride, - }; - } else { - options = { - sketchUri, - fqbn, - optimizeForDebug, - port, - verbose, - verify, - sourceOverride, - }; - } - this.outputChannelManager.getChannel('Arduino').clear(); - if (usingProgrammer) { - await this.coreService.uploadUsingProgrammer(options); - } else { - await this.coreService.upload(options); - } - this.messageService.info('Done uploading.', { timeout: 3000 }); - } catch (e) { - this.messageService.error(e.toString()); - } finally { - this.uploadInProgress = false; - this.onDidChangeEmitter.fire(); - - if (monitorConfig) { - const { board, port } = monitorConfig; - try { - await this.boardsServiceClientImpl.waitUntilAvailable( - Object.assign(board, { port }), - 10_000 - ); - if (shouldAutoConnect) { - // Enabling auto-connect will trigger a connect. - this.monitorConnection.autoConnect = true; - } else { - await this.monitorConnection.connect(monitorConfig); - } - } catch (waitError) { - this.messageService.error( - `Could not reconnect to serial monitor. ${waitError.toString()}` - ); - } - } + await this.boardsServiceClientImpl.waitUntilAvailable( + Object.assign(board, { port }), + 10_000 + ); + if (shouldAutoConnect) { + // Enabling auto-connect will trigger a connect. + this.monitorConnection.autoConnect = true; + } else { + await this.monitorConnection.connect(monitorConfig); + } + } catch (waitError) { + this.messageService.error( + `Could not reconnect to serial monitor. ${waitError.toString()}` + ); } + } } + } } export namespace UploadSketch { - export namespace Commands { - export const UPLOAD_SKETCH: Command = { - id: 'arduino-upload-sketch', - }; - export const UPLOAD_SKETCH_USING_PROGRAMMER: Command = { - id: 'arduino-upload-sketch-using-programmer', - }; - export const UPLOAD_SKETCH_TOOLBAR: Command = { - id: 'arduino-upload-sketch--toolbar', - }; - } + export namespace Commands { + export const UPLOAD_SKETCH: Command = { + id: 'arduino-upload-sketch', + }; + export const UPLOAD_SKETCH_USING_PROGRAMMER: Command = { + id: 'arduino-upload-sketch-using-programmer', + }; + export const UPLOAD_SKETCH_TOOLBAR: Command = { + id: 'arduino-upload-sketch--toolbar', + }; + } } diff --git a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts index 55609bf68..92e61df62 100644 --- a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts @@ -6,140 +6,138 @@ import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { BoardsDataStore } from '../boards/boards-data-store'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { - SketchContribution, - Command, - CommandRegistry, - MenuModelRegistry, - KeybindingRegistry, - TabBarToolbarRegistry, + SketchContribution, + Command, + CommandRegistry, + MenuModelRegistry, + KeybindingRegistry, + TabBarToolbarRegistry, } from './contribution'; @injectable() export class VerifySketch extends SketchContribution { - @inject(CoreService) - protected readonly coreService: CoreService; + @inject(CoreService) + protected readonly coreService: CoreService; - @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; + @inject(BoardsDataStore) + protected readonly boardsDataStore: BoardsDataStore; - @inject(BoardsServiceProvider) - protected readonly boardsServiceClientImpl: BoardsServiceProvider; + @inject(BoardsServiceProvider) + protected readonly boardsServiceClientImpl: BoardsServiceProvider; - protected readonly onDidChangeEmitter = new Emitter>(); - readonly onDidChange = this.onDidChangeEmitter.event; + protected readonly onDidChangeEmitter = new Emitter>(); + readonly onDidChange = this.onDidChangeEmitter.event; - protected verifyInProgress = false; + protected verifyInProgress = false; - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, { - execute: () => this.verifySketch(), - isEnabled: () => !this.verifyInProgress, - }); - registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, { - execute: () => this.verifySketch(true), - isEnabled: () => !this.verifyInProgress, - }); - registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, { - isVisible: (widget) => - ArduinoToolbar.is(widget) && widget.side === 'left', - isEnabled: () => !this.verifyInProgress, - isToggled: () => this.verifyInProgress, - execute: () => - registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id), - }); - } + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, { + execute: () => this.verifySketch(), + isEnabled: () => !this.verifyInProgress, + }); + registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, { + execute: () => this.verifySketch(true), + isEnabled: () => !this.verifyInProgress, + }); + registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, { + isVisible: (widget) => + ArduinoToolbar.is(widget) && widget.side === 'left', + isEnabled: () => !this.verifyInProgress, + isToggled: () => this.verifyInProgress, + execute: () => + registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id), + }); + } - registerMenus(registry: MenuModelRegistry): void { - registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { - commandId: VerifySketch.Commands.VERIFY_SKETCH.id, - label: 'Verify/Compile', - order: '0', - }); - registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { - commandId: VerifySketch.Commands.EXPORT_BINARIES.id, - label: 'Export compiled Binary', - order: '3', - }); - } + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { + commandId: VerifySketch.Commands.VERIFY_SKETCH.id, + label: 'Verify/Compile', + order: '0', + }); + registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { + commandId: VerifySketch.Commands.EXPORT_BINARIES.id, + label: 'Export compiled Binary', + order: '3', + }); + } - registerKeybindings(registry: KeybindingRegistry): void { - registry.registerKeybinding({ - command: VerifySketch.Commands.VERIFY_SKETCH.id, - keybinding: 'CtrlCmd+R', - }); - registry.registerKeybinding({ - command: VerifySketch.Commands.EXPORT_BINARIES.id, - keybinding: 'CtrlCmd+Alt+S', - }); - } + registerKeybindings(registry: KeybindingRegistry): void { + registry.registerKeybinding({ + command: VerifySketch.Commands.VERIFY_SKETCH.id, + keybinding: 'CtrlCmd+R', + }); + registry.registerKeybinding({ + command: VerifySketch.Commands.EXPORT_BINARIES.id, + keybinding: 'CtrlCmd+Alt+S', + }); + } - registerToolbarItems(registry: TabBarToolbarRegistry): void { - registry.registerItem({ - id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, - command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, - tooltip: 'Verify', - priority: 0, - onDidChange: this.onDidChange, - }); - } + registerToolbarItems(registry: TabBarToolbarRegistry): void { + registry.registerItem({ + id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, + command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, + tooltip: 'Verify', + priority: 0, + onDidChange: this.onDidChange, + }); + } - async verifySketch(exportBinaries?: boolean): Promise { - // even with buttons disabled, better to double check if a verify is already in progress - if (this.verifyInProgress) { - return; - } + async verifySketch(exportBinaries?: boolean): Promise { + // even with buttons disabled, better to double check if a verify is already in progress + if (this.verifyInProgress) { + return; + } - // toggle the toolbar button and menu item state. - // verifyInProgress will be set to false whether the compilation fails or not - this.verifyInProgress = true; - this.onDidChangeEmitter.fire(); - const sketch = await this.sketchServiceClient.currentSketch(); + // toggle the toolbar button and menu item state. + // verifyInProgress will be set to false whether the compilation fails or not + this.verifyInProgress = true; + this.onDidChangeEmitter.fire(); + const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { - return; - } - try { - const { boardsConfig } = this.boardsServiceClientImpl; - const [fqbn, sourceOverride] = await Promise.all([ - this.boardsDataStore.appendConfigToFqbn( - boardsConfig.selectedBoard?.fqbn - ), - this.sourceOverride(), - ]); - const verbose = this.preferences.get('arduino.compile.verbose'); - const compilerWarnings = this.preferences.get( - 'arduino.compile.warnings' - ); - this.outputChannelManager.getChannel('Arduino').clear(); - await this.coreService.compile({ - sketchUri: sketch.uri, - fqbn, - optimizeForDebug: this.editorMode.compileForDebug, - verbose, - exportBinaries, - sourceOverride, - compilerWarnings, - }); - this.messageService.info('Done compiling.', { timeout: 3000 }); - } catch (e) { - this.messageService.error(e.toString()); - } finally { - this.verifyInProgress = false; - this.onDidChangeEmitter.fire(); - } + if (!sketch) { + return; + } + try { + const { boardsConfig } = this.boardsServiceClientImpl; + const [fqbn, sourceOverride] = await Promise.all([ + this.boardsDataStore.appendConfigToFqbn( + boardsConfig.selectedBoard?.fqbn + ), + this.sourceOverride(), + ]); + const verbose = this.preferences.get('arduino.compile.verbose'); + const compilerWarnings = this.preferences.get('arduino.compile.warnings'); + this.outputChannelManager.getChannel('Arduino').clear(); + await this.coreService.compile({ + sketchUri: sketch.uri, + fqbn, + optimizeForDebug: this.editorMode.compileForDebug, + verbose, + exportBinaries, + sourceOverride, + compilerWarnings, + }); + this.messageService.info('Done compiling.', { timeout: 3000 }); + } catch (e) { + this.messageService.error(e.toString()); + } finally { + this.verifyInProgress = false; + this.onDidChangeEmitter.fire(); } + } } export namespace VerifySketch { - export namespace Commands { - export const VERIFY_SKETCH: Command = { - id: 'arduino-verify-sketch', - }; - export const EXPORT_BINARIES: Command = { - id: 'arduino-export-binaries', - }; - export const VERIFY_SKETCH_TOOLBAR: Command = { - id: 'arduino-verify-sketch--toolbar', - }; - } + export namespace Commands { + export const VERIFY_SKETCH: Command = { + id: 'arduino-verify-sketch', + }; + export const EXPORT_BINARIES: Command = { + id: 'arduino-export-binaries', + }; + export const VERIFY_SKETCH_TOOLBAR: Command = { + id: 'arduino-verify-sketch--toolbar', + }; + } } diff --git a/arduino-ide-extension/src/browser/create/create-api.ts b/arduino-ide-extension/src/browser/create/create-api.ts index a464b2416..f8ea5a6f1 100644 --- a/arduino-ide-extension/src/browser/create/create-api.ts +++ b/arduino-ide-extension/src/browser/create/create-api.ts @@ -5,12 +5,12 @@ import { AuthenticationClientService } from '../auth/authentication-client-servi import { ArduinoPreferences } from '../arduino-preferences'; export interface ResponseResultProvider { - (response: Response): Promise; + (response: Response): Promise; } export namespace ResponseResultProvider { - export const NOOP: ResponseResultProvider = async () => undefined; - export const TEXT: ResponseResultProvider = (response) => response.text(); - export const JSON: ResponseResultProvider = (response) => response.json(); + export const NOOP: ResponseResultProvider = async () => undefined; + export const TEXT: ResponseResultProvider = (response) => response.text(); + export const JSON: ResponseResultProvider = (response) => response.json(); } type ResourceType = 'f' | 'd'; @@ -19,499 +19,486 @@ export let sketchCache: Create.Sketch[] = []; @injectable() export class CreateApi { - protected authenticationService: AuthenticationClientService; - protected arduinoPreferences: ArduinoPreferences; - - public init( - authenticationService: AuthenticationClientService, - arduinoPreferences: ArduinoPreferences - ): CreateApi { - this.authenticationService = authenticationService; - this.arduinoPreferences = arduinoPreferences; - - return this; - } - - public sketchCompareByPath = (param: string) => { - return (sketch: Create.Sketch) => { - const [, spath] = splitSketchPath(sketch.path); - return param === spath; - }; + protected authenticationService: AuthenticationClientService; + protected arduinoPreferences: ArduinoPreferences; + + public init( + authenticationService: AuthenticationClientService, + arduinoPreferences: ArduinoPreferences + ): CreateApi { + this.authenticationService = authenticationService; + this.arduinoPreferences = arduinoPreferences; + + return this; + } + + public sketchCompareByPath = (param: string) => { + return (sketch: Create.Sketch) => { + const [, spath] = splitSketchPath(sketch.path); + return param === spath; }; - - async findSketchInCache( - compareFn: (sketch: Create.Sketch) => boolean, - trustCache = true - ): Promise { - const sketch = sketchCache.find((sketch) => compareFn(sketch)); - if (trustCache) { - return Promise.resolve(sketch); - } - return await this.sketch({ id: sketch?.id }); + }; + + async findSketchInCache( + compareFn: (sketch: Create.Sketch) => boolean, + trustCache = true + ): Promise { + const sketch = sketchCache.find((sketch) => compareFn(sketch)); + if (trustCache) { + return Promise.resolve(sketch); } - - getSketchSecretStat(sketch: Create.Sketch): Create.Resource { - return { - href: `${sketch.href}${posix.sep}${Create.arduino_secrets_file}`, - modified_at: sketch.modified_at, - name: `${Create.arduino_secrets_file}`, - path: `${sketch.path}${posix.sep}${Create.arduino_secrets_file}`, - mimetype: 'text/x-c++src; charset=utf-8', - type: 'file', - sketchId: sketch.id, - }; - } - - async sketch(opt: { - id?: string; - path?: string; - }): Promise { - let url; - if (opt.id) { - url = new URL(`${this.domain()}/sketches/byID/${opt.id}`); - } else if (opt.path) { - url = new URL(`${this.domain()}/sketches/byPath${opt.path}`); - } else { - return; - } - - url.searchParams.set('user_id', 'me'); - const headers = await this.headers(); - const result = await this.run(url, { - method: 'GET', - headers, - }); - return result; - } - - async sketches(): Promise { - const url = new URL(`${this.domain()}/sketches`); - url.searchParams.set('user_id', 'me'); - const headers = await this.headers(); - const result = await this.run<{ sketches: Create.Sketch[] }>(url, { - method: 'GET', - headers, - }); - sketchCache = result.sketches; - return result.sketches; - } - - async createSketch( - posixPath: string, - content: string = CreateApi.defaultInoContent - ): Promise { - const url = new URL(`${this.domain()}/sketches`); - const headers = await this.headers(); - const payload = { - ino: btoa(content), - path: posixPath, - user_id: 'me', - }; - const init = { - method: 'PUT', - body: JSON.stringify(payload), - headers, - }; - const result = await this.run(url, init); - return result; + return await this.sketch({ id: sketch?.id }); + } + + getSketchSecretStat(sketch: Create.Sketch): Create.Resource { + return { + href: `${sketch.href}${posix.sep}${Create.arduino_secrets_file}`, + modified_at: sketch.modified_at, + name: `${Create.arduino_secrets_file}`, + path: `${sketch.path}${posix.sep}${Create.arduino_secrets_file}`, + mimetype: 'text/x-c++src; charset=utf-8', + type: 'file', + sketchId: sketch.id, + }; + } + + async sketch(opt: { + id?: string; + path?: string; + }): Promise { + let url; + if (opt.id) { + url = new URL(`${this.domain()}/sketches/byID/${opt.id}`); + } else if (opt.path) { + url = new URL(`${this.domain()}/sketches/byPath${opt.path}`); + } else { + return; } - async readDirectory( - posixPath: string, - options: { recursive?: boolean; match?: string; secrets?: boolean } = {} - ): Promise { - const url = new URL( - `${this.domain()}/files/d/$HOME/sketches_v2${posixPath}` - ); - if (options.recursive) { - url.searchParams.set('deep', 'true'); - } - if (options.match) { - url.searchParams.set('name_like', options.match); - } - const headers = await this.headers(); - - const sketchProm = options.secrets - ? this.sketches() - : Promise.resolve(sketchCache); - - return Promise.all([ - this.run(url, { - method: 'GET', - headers, - }), - sketchProm, - ]) - .then(async ([result, sketches]) => { - if (options.secrets) { - // for every sketch with secrets, create a fake arduino_secrets.h - result.forEach(async (res) => { - if (res.type !== 'sketch') { - return; - } - - const [, spath] = createPaths.splitSketchPath(res.path); - const sketch = await this.findSketchInCache( - this.sketchCompareByPath(spath) - ); - if ( - sketch && - sketch.secrets && - sketch.secrets.length > 0 - ) { - result.push(this.getSketchSecretStat(sketch)); - } - }); - - if (posixPath !== posix.sep) { - const sketch = await this.findSketchInCache( - this.sketchCompareByPath(posixPath) - ); - if ( - sketch && - sketch.secrets && - sketch.secrets.length > 0 - ) { - result.push(this.getSketchSecretStat(sketch)); - } - } - } - const sketchesMap: Record = - sketches.reduce((prev, curr) => { - return { ...prev, [curr.path]: curr }; - }, {}); - - // add the sketch id and isPublic to the resource - return result.map((resource) => { - return { - ...resource, - sketchId: sketchesMap[resource.path]?.id || '', - isPublic: - sketchesMap[resource.path]?.is_public || false, - }; - }); - }) - .catch((reason) => { - if (reason?.status === 404) return [] as Create.Resource[]; - else throw reason; - }); + url.searchParams.set('user_id', 'me'); + const headers = await this.headers(); + const result = await this.run(url, { + method: 'GET', + headers, + }); + return result; + } + + async sketches(): Promise { + const url = new URL(`${this.domain()}/sketches`); + url.searchParams.set('user_id', 'me'); + const headers = await this.headers(); + const result = await this.run<{ sketches: Create.Sketch[] }>(url, { + method: 'GET', + headers, + }); + sketchCache = result.sketches; + return result.sketches; + } + + async createSketch( + posixPath: string, + content: string = CreateApi.defaultInoContent + ): Promise { + const url = new URL(`${this.domain()}/sketches`); + const headers = await this.headers(); + const payload = { + ino: btoa(content), + path: posixPath, + user_id: 'me', + }; + const init = { + method: 'PUT', + body: JSON.stringify(payload), + headers, + }; + const result = await this.run(url, init); + return result; + } + + async readDirectory( + posixPath: string, + options: { recursive?: boolean; match?: string; secrets?: boolean } = {} + ): Promise { + const url = new URL( + `${this.domain()}/files/d/$HOME/sketches_v2${posixPath}` + ); + if (options.recursive) { + url.searchParams.set('deep', 'true'); } - - async createDirectory(posixPath: string): Promise { - const url = new URL( - `${this.domain()}/files/d/$HOME/sketches_v2${posixPath}` - ); - const headers = await this.headers(); - await this.run(url, { - method: 'POST', - headers, - }); + if (options.match) { + url.searchParams.set('name_like', options.match); } + const headers = await this.headers(); + + const sketchProm = options.secrets + ? this.sketches() + : Promise.resolve(sketchCache); + + return Promise.all([ + this.run(url, { + method: 'GET', + headers, + }), + sketchProm, + ]) + .then(async ([result, sketches]) => { + if (options.secrets) { + // for every sketch with secrets, create a fake arduino_secrets.h + result.forEach(async (res) => { + if (res.type !== 'sketch') { + return; + } - async stat(posixPath: string): Promise { - // The root is a directory read. - if (posixPath === '/') { - throw new Error('Stating the root is not supported'); - } - // The RESTful API has different endpoints for files and directories. - // The RESTful API does not provide specific error codes, only HTP 500. - // We query the parent directory and look for the file with the last segment. - const parentPosixPath = createPaths.parentPosix(posixPath); - const basename = createPaths.basename(posixPath); - - let resources; - if (basename === Create.arduino_secrets_file) { + const [, spath] = createPaths.splitSketchPath(res.path); const sketch = await this.findSketchInCache( - this.sketchCompareByPath(parentPosixPath) + this.sketchCompareByPath(spath) ); - resources = sketch ? [this.getSketchSecretStat(sketch)] : []; - } else { - resources = await this.readDirectory(parentPosixPath, { - match: basename, - }); - } - - resources.sort((left, right) => left.path.length - right.path.length); - const resource = resources.find(({ name }) => name === basename); - if (!resource) { - throw new CreateError(`Not found: ${posixPath}.`, 404); - } - return resource; - } - - private async toggleSecretsInclude( - path: string, - data: string, - mode: 'add' | 'remove' - ) { - const includeString = `#include "${Create.arduino_secrets_file}"`; - const includeRegexp = new RegExp(includeString + '\\s*', 'g'); - - const basename = createPaths.basename(path); - if (mode === 'add') { - const doesIncludeSecrets = includeRegexp.test(data); - - if (doesIncludeSecrets) { - return data; + if (sketch && sketch.secrets && sketch.secrets.length > 0) { + result.push(this.getSketchSecretStat(sketch)); } + }); - const sketch = await this.findSketchInCache((sketch) => { - const [, spath] = splitSketchPath(sketch.path); - return spath === createPaths.parentPosix(path); - }, true); - - if ( - sketch && - (sketch.name + '.ino' === basename || - sketch.name + '.pde' === basename) && - sketch.secrets && - sketch.secrets.length > 0 - ) { - return includeString + '\n' + data; - } - } else if (mode === 'remove') { - return data.replace(includeRegexp, ''); - } - return data; - } - - async readFile(posixPath: string): Promise { - const basename = createPaths.basename(posixPath); - - if (basename === Create.arduino_secrets_file) { - const parentPosixPath = createPaths.parentPosix(posixPath); + if (posixPath !== posix.sep) { const sketch = await this.findSketchInCache( - this.sketchCompareByPath(parentPosixPath), - false + this.sketchCompareByPath(posixPath) ); - - let file = ''; - if (sketch && sketch.secrets) { - for (const item of sketch?.secrets) { - file += `#define ${item.name} "${item.value}"\r\n`; - } + if (sketch && sketch.secrets && sketch.secrets.length > 0) { + result.push(this.getSketchSecretStat(sketch)); } - return file; + } } - - const url = new URL( - `${this.domain()}/files/f/$HOME/sketches_v2${posixPath}` + const sketchesMap: Record = sketches.reduce( + (prev, curr) => { + return { ...prev, [curr.path]: curr }; + }, + {} ); - const headers = await this.headers(); - const result = await this.run<{ data: string; path: string }>(url, { - method: 'GET', - headers, + + // add the sketch id and isPublic to the resource + return result.map((resource) => { + return { + ...resource, + sketchId: sketchesMap[resource.path]?.id || '', + isPublic: sketchesMap[resource.path]?.is_public || false, + }; }); - let { data } = result; + }) + .catch((reason) => { + if (reason?.status === 404) return [] as Create.Resource[]; + else throw reason; + }); + } + + async createDirectory(posixPath: string): Promise { + const url = new URL( + `${this.domain()}/files/d/$HOME/sketches_v2${posixPath}` + ); + const headers = await this.headers(); + await this.run(url, { + method: 'POST', + headers, + }); + } + + async stat(posixPath: string): Promise { + // The root is a directory read. + if (posixPath === '/') { + throw new Error('Stating the root is not supported'); + } + // The RESTful API has different endpoints for files and directories. + // The RESTful API does not provide specific error codes, only HTP 500. + // We query the parent directory and look for the file with the last segment. + const parentPosixPath = createPaths.parentPosix(posixPath); + const basename = createPaths.basename(posixPath); + + let resources; + if (basename === Create.arduino_secrets_file) { + const sketch = await this.findSketchInCache( + this.sketchCompareByPath(parentPosixPath) + ); + resources = sketch ? [this.getSketchSecretStat(sketch)] : []; + } else { + resources = await this.readDirectory(parentPosixPath, { + match: basename, + }); + } - // add includes to main arduino file - data = await this.toggleSecretsInclude(posixPath, atob(data), 'add'); + resources.sort((left, right) => left.path.length - right.path.length); + const resource = resources.find(({ name }) => name === basename); + if (!resource) { + throw new CreateError(`Not found: ${posixPath}.`, 404); + } + return resource; + } + + private async toggleSecretsInclude( + path: string, + data: string, + mode: 'add' | 'remove' + ) { + const includeString = `#include "${Create.arduino_secrets_file}"`; + const includeRegexp = new RegExp(includeString + '\\s*', 'g'); + + const basename = createPaths.basename(path); + if (mode === 'add') { + const doesIncludeSecrets = includeRegexp.test(data); + + if (doesIncludeSecrets) { return data; + } + + const sketch = await this.findSketchInCache((sketch) => { + const [, spath] = splitSketchPath(sketch.path); + return spath === createPaths.parentPosix(path); + }, true); + + if ( + sketch && + (sketch.name + '.ino' === basename || + sketch.name + '.pde' === basename) && + sketch.secrets && + sketch.secrets.length > 0 + ) { + return includeString + '\n' + data; + } + } else if (mode === 'remove') { + return data.replace(includeRegexp, ''); } - - async writeFile( - posixPath: string, - content: string | Uint8Array - ): Promise { - const basename = createPaths.basename(posixPath); - - if (basename === Create.arduino_secrets_file) { - const parentPosixPath = createPaths.parentPosix(posixPath); - const sketch = await this.findSketchInCache( - this.sketchCompareByPath(parentPosixPath) - ); - if (sketch) { - const url = new URL(`${this.domain()}/sketches/${sketch.id}`); - const headers = await this.headers(); - - // parse the secret file - const secrets = ( - typeof content === 'string' - ? content - : new TextDecoder().decode(content) - ) - .split(/\r?\n/) - .reduce((prev, curr) => { - // check if the line contains a secret - const secret = curr.split('SECRET_')[1] || null; - if (!secret) { - return prev; - } - const regexp = /(\S*)\s+([\S\s]*)/g; - const tokens = regexp.exec(secret) || []; - const name = - tokens[1].length > 0 ? `SECRET_${tokens[1]}` : ''; - - let value = ''; - if (tokens[2].length > 0) { - value = JSON.parse( - JSON.stringify( - tokens[2] - .replace(/^['"]?/g, '') - .replace(/['"]?$/g, '') - ) - ); - } - - if (name.length === 0) { - return prev; - } - - return [...prev, { name, value }]; - }, []); - - const payload = { - id: sketch.id, - libraries: sketch.libraries, - secrets: { data: secrets }, - }; - - // replace the sketch in the cache, so other calls will not overwrite each other - sketchCache = sketchCache.filter((skt) => skt.id !== sketch.id); - sketchCache.push({ ...sketch, secrets }); - - const init = { - method: 'POST', - body: JSON.stringify(payload), - headers, - }; - await this.run(url, init); - } - return; + return data; + } + + async readFile(posixPath: string): Promise { + const basename = createPaths.basename(posixPath); + + if (basename === Create.arduino_secrets_file) { + const parentPosixPath = createPaths.parentPosix(posixPath); + const sketch = await this.findSketchInCache( + this.sketchCompareByPath(parentPosixPath), + false + ); + + let file = ''; + if (sketch && sketch.secrets) { + for (const item of sketch?.secrets) { + file += `#define ${item.name} "${item.value}"\r\n`; } + } + return file; + } - const url = new URL( - `${this.domain()}/files/f/$HOME/sketches_v2${posixPath}` - ); + const url = new URL( + `${this.domain()}/files/f/$HOME/sketches_v2${posixPath}` + ); + const headers = await this.headers(); + const result = await this.run<{ data: string; path: string }>(url, { + method: 'GET', + headers, + }); + let { data } = result; + + // add includes to main arduino file + data = await this.toggleSecretsInclude(posixPath, atob(data), 'add'); + return data; + } + + async writeFile( + posixPath: string, + content: string | Uint8Array + ): Promise { + const basename = createPaths.basename(posixPath); + + if (basename === Create.arduino_secrets_file) { + const parentPosixPath = createPaths.parentPosix(posixPath); + const sketch = await this.findSketchInCache( + this.sketchCompareByPath(parentPosixPath) + ); + if (sketch) { + const url = new URL(`${this.domain()}/sketches/${sketch.id}`); const headers = await this.headers(); - let data: string = - typeof content === 'string' - ? content - : new TextDecoder().decode(content); - data = await this.toggleSecretsInclude(posixPath, data, 'remove'); - - const payload = { data: btoa(data) }; - const init = { - method: 'POST', - body: JSON.stringify(payload), - headers, - }; - await this.run(url, init); - } - - async deleteFile(posixPath: string): Promise { - await this.delete(posixPath, 'f'); - } + // parse the secret file + const secrets = ( + typeof content === 'string' + ? content + : new TextDecoder().decode(content) + ) + .split(/\r?\n/) + .reduce((prev, curr) => { + // check if the line contains a secret + const secret = curr.split('SECRET_')[1] || null; + if (!secret) { + return prev; + } + const regexp = /(\S*)\s+([\S\s]*)/g; + const tokens = regexp.exec(secret) || []; + const name = tokens[1].length > 0 ? `SECRET_${tokens[1]}` : ''; + + let value = ''; + if (tokens[2].length > 0) { + value = JSON.parse( + JSON.stringify( + tokens[2].replace(/^['"]?/g, '').replace(/['"]?$/g, '') + ) + ); + } - async deleteDirectory(posixPath: string): Promise { - await this.delete(posixPath, 'd'); - } + if (name.length === 0) { + return prev; + } - private async delete(posixPath: string, type: ResourceType): Promise { - const url = new URL( - `${this.domain()}/files/${type}/$HOME/sketches_v2${posixPath}` - ); - const headers = await this.headers(); - await this.run(url, { - method: 'DELETE', - headers, - }); - } + return [...prev, { name, value }]; + }, []); - async rename(fromPosixPath: string, toPosixPath: string): Promise { - const url = new URL(`${this.domain('v3')}/files/mv`); - const headers = await this.headers(); const payload = { - from: `$HOME/sketches_v2${fromPosixPath}`, - to: `$HOME/sketches_v2${toPosixPath}`, - }; - const init = { - method: 'POST', - body: JSON.stringify(payload), - headers, + id: sketch.id, + libraries: sketch.libraries, + secrets: { data: secrets }, }; - await this.run(url, init, ResponseResultProvider.NOOP); - } - - async editSketch({ - id, - params, - }: { - id: string; - params: Record; - }): Promise { - const url = new URL(`${this.domain()}/sketches/${id}`); - const headers = await this.headers(); - const result = await this.run(url, { - method: 'POST', - body: JSON.stringify({ id, ...params }), - headers, - }); - return result; - } + // replace the sketch in the cache, so other calls will not overwrite each other + sketchCache = sketchCache.filter((skt) => skt.id !== sketch.id); + sketchCache.push({ ...sketch, secrets }); - async copy(fromPosixPath: string, toPosixPath: string): Promise { - const payload = { - from: `$HOME/sketches_v2${fromPosixPath}`, - to: `$HOME/sketches_v2${toPosixPath}`, - }; - const url = new URL(`${this.domain('v3')}/files/cp`); - const headers = await this.headers(); const init = { - method: 'POST', - body: JSON.stringify(payload), - headers, + method: 'POST', + body: JSON.stringify(payload), + headers, }; - await this.run(url, init, ResponseResultProvider.NOOP); + await this.run(url, init); + } + return; } - private async run( - requestInfo: RequestInfo | URL, - init: RequestInit | undefined, - resultProvider: ResponseResultProvider = ResponseResultProvider.JSON - ): Promise { - const response = await fetch( - requestInfo instanceof URL ? requestInfo.toString() : requestInfo, - init - ); - if (!response.ok) { - let details: string | undefined = undefined; - try { - details = await response.json(); - } catch (e) { - console.error('Cloud not get the error details.', e); - } - const { statusText, status } = response; - throw new CreateError(statusText, status, details); - } - const result = await resultProvider(response); - return result; - } + const url = new URL( + `${this.domain()}/files/f/$HOME/sketches_v2${posixPath}` + ); + const headers = await this.headers(); - private async headers(): Promise> { - const token = await this.token(); - return { - 'content-type': 'application/json', - accept: 'application/json', - authorization: `Bearer ${token}`, - }; - } + let data: string = + typeof content === 'string' ? content : new TextDecoder().decode(content); + data = await this.toggleSecretsInclude(posixPath, data, 'remove'); - private domain(apiVersion = 'v2'): string { - const endpoint = - this.arduinoPreferences['arduino.cloud.sketchSyncEnpoint']; - return `${endpoint}/${apiVersion}`; + const payload = { data: btoa(data) }; + const init = { + method: 'POST', + body: JSON.stringify(payload), + headers, + }; + await this.run(url, init); + } + + async deleteFile(posixPath: string): Promise { + await this.delete(posixPath, 'f'); + } + + async deleteDirectory(posixPath: string): Promise { + await this.delete(posixPath, 'd'); + } + + private async delete(posixPath: string, type: ResourceType): Promise { + const url = new URL( + `${this.domain()}/files/${type}/$HOME/sketches_v2${posixPath}` + ); + const headers = await this.headers(); + await this.run(url, { + method: 'DELETE', + headers, + }); + } + + async rename(fromPosixPath: string, toPosixPath: string): Promise { + const url = new URL(`${this.domain('v3')}/files/mv`); + const headers = await this.headers(); + const payload = { + from: `$HOME/sketches_v2${fromPosixPath}`, + to: `$HOME/sketches_v2${toPosixPath}`, + }; + const init = { + method: 'POST', + body: JSON.stringify(payload), + headers, + }; + await this.run(url, init, ResponseResultProvider.NOOP); + } + + async editSketch({ + id, + params, + }: { + id: string; + params: Record; + }): Promise { + const url = new URL(`${this.domain()}/sketches/${id}`); + + const headers = await this.headers(); + const result = await this.run(url, { + method: 'POST', + body: JSON.stringify({ id, ...params }), + headers, + }); + return result; + } + + async copy(fromPosixPath: string, toPosixPath: string): Promise { + const payload = { + from: `$HOME/sketches_v2${fromPosixPath}`, + to: `$HOME/sketches_v2${toPosixPath}`, + }; + const url = new URL(`${this.domain('v3')}/files/cp`); + const headers = await this.headers(); + const init = { + method: 'POST', + body: JSON.stringify(payload), + headers, + }; + await this.run(url, init, ResponseResultProvider.NOOP); + } + + private async run( + requestInfo: RequestInfo | URL, + init: RequestInit | undefined, + resultProvider: ResponseResultProvider = ResponseResultProvider.JSON + ): Promise { + const response = await fetch( + requestInfo instanceof URL ? requestInfo.toString() : requestInfo, + init + ); + if (!response.ok) { + let details: string | undefined = undefined; + try { + details = await response.json(); + } catch (e) { + console.error('Cloud not get the error details.', e); + } + const { statusText, status } = response; + throw new CreateError(statusText, status, details); } + const result = await resultProvider(response); + return result; + } + + private async headers(): Promise> { + const token = await this.token(); + return { + 'content-type': 'application/json', + accept: 'application/json', + authorization: `Bearer ${token}`, + }; + } - private async token(): Promise { - return this.authenticationService.session?.accessToken || ''; - } + private domain(apiVersion = 'v2'): string { + const endpoint = this.arduinoPreferences['arduino.cloud.sketchSyncEnpoint']; + return `${endpoint}/${apiVersion}`; + } + + private async token(): Promise { + return this.authenticationService.session?.accessToken || ''; + } } export namespace CreateApi { - export const defaultInoContent = `/* + export const defaultInoContent = `/* */ @@ -527,73 +514,73 @@ void loop() { } export namespace Create { - export interface Sketch { - readonly name: string; - readonly path: string; - readonly modified_at: string; - readonly created_at: string; - - readonly secrets?: { name: string; value: string }[]; - - readonly id: string; - readonly is_public: boolean; - // readonly board_fqbn: '', - // readonly board_name: '', - // readonly board_type: 'serial' | 'network' | 'cloud' | '', - readonly href?: string; - readonly libraries: string[]; - // readonly tutorials: string[] | null; - // readonly types: string[] | null; - // readonly user_id: string; + export interface Sketch { + readonly name: string; + readonly path: string; + readonly modified_at: string; + readonly created_at: string; + + readonly secrets?: { name: string; value: string }[]; + + readonly id: string; + readonly is_public: boolean; + // readonly board_fqbn: '', + // readonly board_name: '', + // readonly board_type: 'serial' | 'network' | 'cloud' | '', + readonly href?: string; + readonly libraries: string[]; + // readonly tutorials: string[] | null; + // readonly types: string[] | null; + // readonly user_id: string; + } + + export type ResourceType = 'sketch' | 'folder' | 'file'; + export const arduino_secrets_file = 'arduino_secrets.h'; + export interface Resource { + readonly name: string; + /** + * Note: this path is **not** the POSIX path we use. It has the leading segments with the `user_id`. + */ + readonly path: string; + readonly type: ResourceType; + readonly sketchId: string; + readonly modified_at: string; // As an ISO-8601 formatted string: `YYYY-MM-DDTHH:mm:ss.sssZ` + readonly children?: number; // For 'sketch' and 'folder' types. + readonly size?: number; // For 'sketch' type only. + readonly isPublic?: boolean; // For 'sketch' type only. + + readonly mimetype?: string; // For 'file' type. + readonly href?: string; + } + export namespace Resource { + export function is(arg: any): arg is Resource { + return ( + !!arg && + 'name' in arg && + typeof arg['name'] === 'string' && + 'path' in arg && + typeof arg['path'] === 'string' && + 'type' in arg && + typeof arg['type'] === 'string' && + 'modified_at' in arg && + typeof arg['modified_at'] === 'string' && + (arg['type'] === 'sketch' || + arg['type'] === 'folder' || + arg['type'] === 'file') + ); } + } - export type ResourceType = 'sketch' | 'folder' | 'file'; - export const arduino_secrets_file = 'arduino_secrets.h'; - export interface Resource { - readonly name: string; - /** - * Note: this path is **not** the POSIX path we use. It has the leading segments with the `user_id`. - */ - readonly path: string; - readonly type: ResourceType; - readonly sketchId: string; - readonly modified_at: string; // As an ISO-8601 formatted string: `YYYY-MM-DDTHH:mm:ss.sssZ` - readonly children?: number; // For 'sketch' and 'folder' types. - readonly size?: number; // For 'sketch' type only. - readonly isPublic?: boolean; // For 'sketch' type only. - - readonly mimetype?: string; // For 'file' type. - readonly href?: string; - } - export namespace Resource { - export function is(arg: any): arg is Resource { - return ( - !!arg && - 'name' in arg && - typeof arg['name'] === 'string' && - 'path' in arg && - typeof arg['path'] === 'string' && - 'type' in arg && - typeof arg['type'] === 'string' && - 'modified_at' in arg && - typeof arg['modified_at'] === 'string' && - (arg['type'] === 'sketch' || - arg['type'] === 'folder' || - arg['type'] === 'file') - ); - } - } - - export type RawResource = Omit; + export type RawResource = Omit; } export class CreateError extends Error { - constructor( - message: string, - readonly status: number, - readonly details?: string - ) { - super(message); - Object.setPrototypeOf(this, CreateError.prototype); - } + constructor( + message: string, + readonly status: number, + readonly details?: string + ) { + super(message); + Object.setPrototypeOf(this, CreateError.prototype); + } } diff --git a/arduino-ide-extension/src/browser/create/create-fs-provider.ts b/arduino-ide-extension/src/browser/create/create-fs-provider.ts index e459b6dc1..79484cc84 100644 --- a/arduino-ide-extension/src/browser/create/create-fs-provider.ts +++ b/arduino-ide-extension/src/browser/create/create-fs-provider.ts @@ -2,26 +2,26 @@ import { inject, injectable } from 'inversify'; import URI from '@theia/core/lib/common/uri'; import { Event } from '@theia/core/lib/common/event'; import { - Disposable, - DisposableCollection, + Disposable, + DisposableCollection, } from '@theia/core/lib/common/disposable'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { - Stat, - FileType, - FileChange, - FileWriteOptions, - FileDeleteOptions, - FileOverwriteOptions, - FileSystemProvider, - FileSystemProviderError, - FileSystemProviderErrorCode, - FileSystemProviderCapabilities, - WatchOptions, + Stat, + FileType, + FileChange, + FileWriteOptions, + FileDeleteOptions, + FileOverwriteOptions, + FileSystemProvider, + FileSystemProviderError, + FileSystemProviderErrorCode, + FileSystemProviderCapabilities, + WatchOptions, } from '@theia/filesystem/lib/common/files'; import { - FileService, - FileServiceContribution, + FileService, + FileServiceContribution, } from '@theia/filesystem/lib/browser/file-service'; import { AuthenticationClientService } from '../auth/authentication-client-service'; import { Create, CreateApi } from './create-api'; @@ -33,176 +33,174 @@ export const REMOTE_ONLY_FILES = ['sketch.json']; @injectable() export class CreateFsProvider - implements - FileSystemProvider, - FrontendApplicationContribution, - FileServiceContribution + implements + FileSystemProvider, + FrontendApplicationContribution, + FileServiceContribution { - @inject(AuthenticationClientService) - protected readonly authenticationService: AuthenticationClientService; - - @inject(CreateApi) - protected readonly createApi: CreateApi; - - @inject(SketchesService) - protected readonly sketchesService: SketchesService; - - @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; - - protected readonly toDispose = new DisposableCollection(); - - readonly onFileWatchError: Event = Event.None; - readonly onDidChangeFile: Event = Event.None; - readonly onDidChangeCapabilities: Event = Event.None; - readonly capabilities: FileSystemProviderCapabilities = - FileSystemProviderCapabilities.FileReadWrite | - FileSystemProviderCapabilities.PathCaseSensitive | - FileSystemProviderCapabilities.Access; - - onStop(): void { - this.toDispose.dispose(); - } - - registerFileSystemProviders(service: FileService): void { - service.onWillActivateFileSystemProvider((event) => { - if (event.scheme === CreateUri.scheme) { - event.waitUntil( - (async () => { - service.registerProvider(CreateUri.scheme, this); - })() - ); - } - }); - } - - watch(uri: URI, opts: WatchOptions): Disposable { - return Disposable.NULL; - } - - async stat(uri: URI): Promise { - if (CreateUri.equals(CreateUri.root, uri)) { - this.getCreateApi; // This will throw when not logged in. - return { - type: FileType.Directory, - ctime: 0, - mtime: 0, - size: 0, - }; - } - const resource = await this.getCreateApi.stat(uri.path.toString()); - const mtime = Date.parse(resource.modified_at); - return { - type: this.toFileType(resource.type), - ctime: mtime, - mtime, - size: 0, - }; - } - - async mkdir(uri: URI): Promise { - await this.getCreateApi.createDirectory(uri.path.toString()); - } - - async readdir(uri: URI): Promise<[string, FileType][]> { - const resources = await this.getCreateApi.readDirectory( - uri.path.toString(), - { - secrets: true, - } + @inject(AuthenticationClientService) + protected readonly authenticationService: AuthenticationClientService; + + @inject(CreateApi) + protected readonly createApi: CreateApi; + + @inject(SketchesService) + protected readonly sketchesService: SketchesService; + + @inject(ArduinoPreferences) + protected readonly arduinoPreferences: ArduinoPreferences; + + protected readonly toDispose = new DisposableCollection(); + + readonly onFileWatchError: Event = Event.None; + readonly onDidChangeFile: Event = Event.None; + readonly onDidChangeCapabilities: Event = Event.None; + readonly capabilities: FileSystemProviderCapabilities = + FileSystemProviderCapabilities.FileReadWrite | + FileSystemProviderCapabilities.PathCaseSensitive | + FileSystemProviderCapabilities.Access; + + onStop(): void { + this.toDispose.dispose(); + } + + registerFileSystemProviders(service: FileService): void { + service.onWillActivateFileSystemProvider((event) => { + if (event.scheme === CreateUri.scheme) { + event.waitUntil( + (async () => { + service.registerProvider(CreateUri.scheme, this); + })() ); - return resources - .filter((res) => !REMOTE_ONLY_FILES.includes(res.name)) - .map(({ name, type }) => [name, this.toFileType(type)]); + } + }); + } + + watch(uri: URI, opts: WatchOptions): Disposable { + return Disposable.NULL; + } + + async stat(uri: URI): Promise { + if (CreateUri.equals(CreateUri.root, uri)) { + this.getCreateApi; // This will throw when not logged in. + return { + type: FileType.Directory, + ctime: 0, + mtime: 0, + size: 0, + }; } - - async delete(uri: URI, opts: FileDeleteOptions): Promise { - return; - - if (!opts.recursive) { - throw new Error( - 'Arduino Create file-system provider does not support non-recursive deletion.' - ); - } - const stat = await this.stat(uri); - if (!stat) { - throw new FileSystemProviderError( - 'File not found.', - FileSystemProviderErrorCode.FileNotFound - ); - } - switch (stat.type) { - case FileType.Directory: { - await this.getCreateApi.deleteDirectory(uri.path.toString()); - break; - } - case FileType.File: { - await this.getCreateApi.deleteFile(uri.path.toString()); - break; - } - default: { - throw new FileSystemProviderError( - `Unexpected file type '${ - stat.type - }' for resource: ${uri.toString()}`, - FileSystemProviderErrorCode.Unknown - ); - } - } - } - - async rename( - oldUri: URI, - newUri: URI, - options: FileOverwriteOptions - ): Promise { - await this.getCreateApi.rename( - oldUri.path.toString(), - newUri.path.toString() - ); + const resource = await this.getCreateApi.stat(uri.path.toString()); + const mtime = Date.parse(resource.modified_at); + return { + type: this.toFileType(resource.type), + ctime: mtime, + mtime, + size: 0, + }; + } + + async mkdir(uri: URI): Promise { + await this.getCreateApi.createDirectory(uri.path.toString()); + } + + async readdir(uri: URI): Promise<[string, FileType][]> { + const resources = await this.getCreateApi.readDirectory( + uri.path.toString(), + { + secrets: true, + } + ); + return resources + .filter((res) => !REMOTE_ONLY_FILES.includes(res.name)) + .map(({ name, type }) => [name, this.toFileType(type)]); + } + + async delete(uri: URI, opts: FileDeleteOptions): Promise { + return; + + if (!opts.recursive) { + throw new Error( + 'Arduino Create file-system provider does not support non-recursive deletion.' + ); } - - async readFile(uri: URI): Promise { - const content = await this.getCreateApi.readFile(uri.path.toString()); - return new TextEncoder().encode(content); + const stat = await this.stat(uri); + if (!stat) { + throw new FileSystemProviderError( + 'File not found.', + FileSystemProviderErrorCode.FileNotFound + ); } - - async writeFile( - uri: URI, - content: Uint8Array, - options: FileWriteOptions - ): Promise { - await this.getCreateApi.writeFile(uri.path.toString(), content); + switch (stat.type) { + case FileType.Directory: { + await this.getCreateApi.deleteDirectory(uri.path.toString()); + break; + } + case FileType.File: { + await this.getCreateApi.deleteFile(uri.path.toString()); + break; + } + default: { + throw new FileSystemProviderError( + `Unexpected file type '${stat.type}' for resource: ${uri.toString()}`, + FileSystemProviderErrorCode.Unknown + ); + } } - - async access(uri: URI, mode?: number): Promise { - this.getCreateApi; // Will throw if not logged in. + } + + async rename( + oldUri: URI, + newUri: URI, + options: FileOverwriteOptions + ): Promise { + await this.getCreateApi.rename( + oldUri.path.toString(), + newUri.path.toString() + ); + } + + async readFile(uri: URI): Promise { + const content = await this.getCreateApi.readFile(uri.path.toString()); + return new TextEncoder().encode(content); + } + + async writeFile( + uri: URI, + content: Uint8Array, + options: FileWriteOptions + ): Promise { + await this.getCreateApi.writeFile(uri.path.toString(), content); + } + + async access(uri: URI, mode?: number): Promise { + this.getCreateApi; // Will throw if not logged in. + } + + public toFileType(type: Create.ResourceType): FileType { + switch (type) { + case 'file': + return FileType.File; + case 'sketch': + case 'folder': + return FileType.Directory; + default: + return FileType.Unknown; } - - public toFileType(type: Create.ResourceType): FileType { - switch (type) { - case 'file': - return FileType.File; - case 'sketch': - case 'folder': - return FileType.Directory; - default: - return FileType.Unknown; - } + } + + private get getCreateApi(): CreateApi { + const { session } = this.authenticationService; + if (!session) { + throw new FileSystemProviderError( + 'Not logged in.', + FileSystemProviderErrorCode.NoPermissions + ); } - private get getCreateApi(): CreateApi { - const { session } = this.authenticationService; - if (!session) { - throw new FileSystemProviderError( - 'Not logged in.', - FileSystemProviderErrorCode.NoPermissions - ); - } - - return this.createApi.init( - this.authenticationService, - this.arduinoPreferences - ); - } + return this.createApi.init( + this.authenticationService, + this.arduinoPreferences + ); + } } diff --git a/arduino-ide-extension/src/browser/create/create-paths.ts b/arduino-ide-extension/src/browser/create/create-paths.ts index 0fccb0c05..03b5ed974 100644 --- a/arduino-ide-extension/src/browser/create/create-paths.ts +++ b/arduino-ide-extension/src/browser/create/create-paths.ts @@ -2,22 +2,22 @@ export const posix = { sep: '/' }; // TODO: poor man's `path.join(path, '..')` in the browser. export function parentPosix(path: string): string { - const segments = path.split(posix.sep) || []; - segments.pop(); - let modified = segments.join(posix.sep); - if (path.charAt(path.length - 1) === posix.sep) { - modified += posix.sep; - } - return modified; + const segments = path.split(posix.sep) || []; + segments.pop(); + let modified = segments.join(posix.sep); + if (path.charAt(path.length - 1) === posix.sep) { + modified += posix.sep; + } + return modified; } export function basename(path: string): string { - const segments = path.split(posix.sep) || []; - return segments.pop()!; + const segments = path.split(posix.sep) || []; + return segments.pop()!; } export function posixSegments(posixPath: string): string[] { - return posixPath.split(posix.sep).filter((segment) => !!segment); + return posixPath.split(posix.sep).filter((segment) => !!segment); } /** @@ -32,28 +32,28 @@ export function posixSegments(posixPath: string): string[] { * ``` */ export function splitSketchPath( - raw: string, - sep = '/sketches_v2/' + raw: string, + sep = '/sketches_v2/' ): [string, string] { - if (!sep) { - throw new Error('Invalid separator. Cannot be zero length.'); - } - const index = raw.indexOf(sep); - if (index === -1) { - throw new Error(`Invalid path pattern. Raw path was '${raw}'.`); - } - const createRoot = raw.substring(0, index + sep.length - 1); // TODO: validate the `createRoot` format. - const posixPath = raw.substr(index + sep.length - 1); - if (!posixPath) { - throw new Error(`Could not extract POSIX path from '${raw}'.`); - } - return [createRoot, posixPath]; + if (!sep) { + throw new Error('Invalid separator. Cannot be zero length.'); + } + const index = raw.indexOf(sep); + if (index === -1) { + throw new Error(`Invalid path pattern. Raw path was '${raw}'.`); + } + const createRoot = raw.substring(0, index + sep.length - 1); // TODO: validate the `createRoot` format. + const posixPath = raw.substr(index + sep.length - 1); + if (!posixPath) { + throw new Error(`Could not extract POSIX path from '${raw}'.`); + } + return [createRoot, posixPath]; } export function toPosixPath(raw: string): string { - if (raw === posix.sep) { - return posix.sep; // Handles the root resource case. - } - const [, posixPath] = splitSketchPath(raw); - return posixPath; + if (raw === posix.sep) { + return posix.sep; // Handles the root resource case. + } + const [, posixPath] = splitSketchPath(raw); + return posixPath; } diff --git a/arduino-ide-extension/src/browser/create/create-uri.ts b/arduino-ide-extension/src/browser/create/create-uri.ts index b9a5d176f..e5ddb25d6 100644 --- a/arduino-ide-extension/src/browser/create/create-uri.ts +++ b/arduino-ide-extension/src/browser/create/create-uri.ts @@ -4,36 +4,34 @@ import { Create } from './create-api'; import { toPosixPath, parentPosix, posix } from './create-paths'; export namespace CreateUri { - export const scheme = 'arduino-create'; - export const root = toUri(posix.sep); + export const scheme = 'arduino-create'; + export const root = toUri(posix.sep); - export function toUri(posixPathOrResource: string | Create.Resource): URI { - const posixPath = - typeof posixPathOrResource === 'string' - ? posixPathOrResource - : toPosixPath(posixPathOrResource.path); - return new URI( - Uri.parse(posixPath).with({ scheme, authority: 'create' }) - ); - } + export function toUri(posixPathOrResource: string | Create.Resource): URI { + const posixPath = + typeof posixPathOrResource === 'string' + ? posixPathOrResource + : toPosixPath(posixPathOrResource.path); + return new URI(Uri.parse(posixPath).with({ scheme, authority: 'create' })); + } - export function is(uri: URI): boolean { - return uri.scheme === scheme; - } + export function is(uri: URI): boolean { + return uri.scheme === scheme; + } - export function equals(left: URI, right: URI): boolean { - return is(left) && is(right) && left.toString() === right.toString(); - } + export function equals(left: URI, right: URI): boolean { + return is(left) && is(right) && left.toString() === right.toString(); + } - export function parent(uri: URI): URI { - if (!is(uri)) { - throw new Error( - `Invalid URI scheme. Expected '${scheme}' got '${uri.scheme}' instead.` - ); - } - if (equals(uri, root)) { - return uri; - } - return toUri(parentPosix(uri.path.toString())); + export function parent(uri: URI): URI { + if (!is(uri)) { + throw new Error( + `Invalid URI scheme. Expected '${scheme}' got '${uri.scheme}' instead.` + ); + } + if (equals(uri, root)) { + return uri; } + return toUri(parentPosix(uri.path.toString())); + } } diff --git a/arduino-ide-extension/src/browser/dialogs.ts/cloud-share-sketch-dialog.tsx b/arduino-ide-extension/src/browser/dialogs.ts/cloud-share-sketch-dialog.tsx index 60f53eeb7..ceda82d8d 100644 --- a/arduino-ide-extension/src/browser/dialogs.ts/cloud-share-sketch-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs.ts/cloud-share-sketch-dialog.tsx @@ -4,172 +4,172 @@ import { Widget } from '@phosphor/widgets'; import { Message } from '@phosphor/messaging'; import { clipboard } from 'electron'; import { - AbstractDialog, - ReactWidget, - DialogProps, + AbstractDialog, + ReactWidget, + DialogProps, } from '@theia/core/lib/browser'; import { CreateApi } from '../create/create-api'; const RadioButton = (props: { - id: string; - changed: (evt: React.BaseSyntheticEvent) => void; - value: string; - isSelected: boolean; - isDisabled: boolean; - label: string; + id: string; + changed: (evt: React.BaseSyntheticEvent) => void; + value: string; + isSelected: boolean; + isDisabled: boolean; + label: string; }) => { - return ( -

- - -

- ); + return ( +

+ + +

+ ); }; export const ShareSketchComponent = ({ - treeNode, - createApi, - domain = 'https://create.arduino.cc', + treeNode, + createApi, + domain = 'https://create.arduino.cc', }: { - treeNode: any; - createApi: CreateApi; - domain?: string; + treeNode: any; + createApi: CreateApi; + domain?: string; }): React.ReactElement => { - // const [publicVisibility, setPublicVisibility] = React.useState( - // treeNode.isPublic - // ); + // const [publicVisibility, setPublicVisibility] = React.useState( + // treeNode.isPublic + // ); - const [loading, setloading] = React.useState(false); + const [loading, setloading] = React.useState(false); - const radioChangeHandler = async (event: React.BaseSyntheticEvent) => { - setloading(true); - const sketch = await createApi.editSketch({ - id: treeNode.sketchId, - params: { - is_public: event.target.value === 'private' ? false : true, - }, - }); - // setPublicVisibility(sketch.is_public); - treeNode.isPublic = sketch.is_public; - setloading(false); - }; + const radioChangeHandler = async (event: React.BaseSyntheticEvent) => { + setloading(true); + const sketch = await createApi.editSketch({ + id: treeNode.sketchId, + params: { + is_public: event.target.value === 'private' ? false : true, + }, + }); + // setPublicVisibility(sketch.is_public); + treeNode.isPublic = sketch.is_public; + setloading(false); + }; - const sketchLink = `${domain}/editor/_/${treeNode.sketchId}/preview`; - const embedLink = ``; + const sketchLink = `${domain}/editor/_/${treeNode.sketchId}/preview`; + const embedLink = ``; - return ( -
-

Choose visibility of your Sketch:

- +

Choose visibility of your Sketch:

+ + + + {treeNode.isPublic && ( +
+

Link:

+
+ - clipboard.writeText(sketchLink)} + value="copy" + className="theia-button secondary" + > + Copy + +
+

Embed:

+
+