diff --git a/.gitignore b/.gitignore index fdab6fb0e..5d9bf1ea6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ yarn*.log plugins # the config files for the CLI arduino-ide-extension/data/cli/config +# content trace files for electron +electron-app/traces diff --git a/.prettierrc b/.prettierrc index b20f01f1c..47e5c04d7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,6 @@ "singleQuote": true, "tabWidth": 2, "useTabs": false, - "printWidth": 80 + "printWidth": 80, + "endOfLine": "auto" } diff --git a/.vscode/launch.json b/.vscode/launch.json index d6ed25954..802b130d6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,44 @@ { "version": "0.2.0", "configurations": [ + { + "type": "node", + "request": "launch", + "name": "App (Electron) [Dev]", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd", + }, + "cwd": "${workspaceFolder}/electron-app", + "args": [ + ".", + "--log-level=debug", + "--hostname=localhost", + "--no-cluster", + "--app-project-path=${workspaceRoot}/electron-app", + "--remote-debugging-port=9222", + "--no-app-auto-install", + "--plugins=local-dir:../plugins", + "--hosted-plugin-inspect=9339", + "--nosplash", + "--content-trace", + "--open-devtools" + ], + "env": { + "NODE_ENV": "development" + }, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/electron-app/src-gen/backend/*.js", + "${workspaceRoot}/electron-app/src-gen/frontend/*.js", + "${workspaceRoot}/electron-app/lib/**/*.js", + "${workspaceRoot}/arduino-ide-extension/lib/**/*.js", + "${workspaceRoot}/node_modules/@theia/**/*.js" + ], + "smartStep": true, + "internalConsoleOptions": "openOnSessionStart", + "outputCapture": "std" + }, { "type": "node", "request": "launch", @@ -10,7 +48,6 @@ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd", }, "cwd": "${workspaceFolder}/electron-app", - "protocol": "inspector", "args": [ ".", "--log-level=debug", @@ -78,7 +115,6 @@ { "type": "node", "request": "launch", - "protocol": "inspector", "name": "Run Test [current]", "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", "args": [ diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 5db304aa6..789fd1007 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -21,14 +21,13 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "@grpc/grpc-js": "^1.3.7", + "@grpc/grpc-js": "^1.6.7", "@theia/application-package": "1.25.0", "@theia/core": "1.25.0", "@theia/editor": "1.25.0", "@theia/editor-preview": "1.25.0", "@theia/electron": "1.25.0", "@theia/filesystem": "1.25.0", - "@theia/git": "1.25.0", "@theia/keymaps": "1.25.0", "@theia/markers": "1.25.0", "@theia/monaco": "1.25.0", @@ -45,7 +44,7 @@ "@types/btoa": "^1.2.3", "@types/dateformat": "^3.0.1", "@types/deepmerge": "^2.2.0", - "@types/glob": "^5.0.35", + "@types/glob": "^7.2.0", "@types/google-protobuf": "^3.7.2", "@types/js-yaml": "^3.12.2", "@types/keytar": "^4.4.0", @@ -63,14 +62,15 @@ "atob": "^2.1.2", "auth0-js": "^9.14.0", "btoa": "^1.2.1", - "css-element-queries": "^1.2.0", "dateformat": "^3.0.3", "deepmerge": "2.0.1", + "deep-equals": "^0.0.2", + "deep-object-diff": "^1.1.7", + "deep-sort-object": "^1.0.2", "electron-updater": "^4.6.5", - "fuzzy": "^0.1.3", + "fast-safe-stringify": "^2.1.1", "glob": "^7.1.6", - "google-protobuf": "^3.11.4", - "grpc": "^1.24.11", + "google-protobuf": "^3.20.1", "hash.js": "^1.1.7", "is-valid-path": "^0.1.1", "js-yaml": "^3.13.1", @@ -91,6 +91,7 @@ "semver": "^7.3.2", "string-natural-compare": "^2.0.3", "temp": "^0.9.1", + "temp-dir": "^2.0.0", "tree-kill": "^1.2.1", "upath": "^1.1.2", "url": "^0.11.0", @@ -157,10 +158,10 @@ ], "arduino": { "cli": { - "version": "0.21.0" + "version": "20220527" }, "fwuploader": { - "version": "2.0.0" + "version": "2.2.0" }, "clangd": { "version": "13.0.0" diff --git a/arduino-ide-extension/scripts/download-examples.js b/arduino-ide-extension/scripts/download-examples.js index a393c5552..c7bcf9865 100644 --- a/arduino-ide-extension/scripts/download-examples.js +++ b/arduino-ide-extension/scripts/download-examples.js @@ -4,30 +4,93 @@ const version = '1.9.1'; (async () => { + const os = require('os'); + const { promises: fs } = require('fs'); + const path = require('path'); + const shell = require('shelljs'); + const { v4 } = require('uuid'); - const os = require('os'); - const path = require('path'); - const shell = require('shelljs'); - const { v4 } = require('uuid'); + const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`); + if (shell.mkdir('-p', repository).code !== 0) { + shell.exit(1); + } - const repository = path.join(os.tmpdir(), `${v4()}-arduino-examples`); - if (shell.mkdir('-p', repository).code !== 0) { - shell.exit(1); - process.exit(1); - } - - if (shell.exec(`git clone https://github.com/arduino/arduino-examples.git ${repository}`).code !== 0) { - shell.exit(1); - process.exit(1); - } + if ( + shell.exec( + `git clone https://github.com/arduino/arduino-examples.git ${repository}` + ).code !== 0 + ) { + shell.exit(1); + } - if (shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`).code !== 0) { - shell.exit(1); - process.exit(1); - } + if ( + shell.exec(`git -C ${repository} checkout tags/${version} -b ${version}`) + .code !== 0 + ) { + shell.exit(1); + } - const destination = path.join(__dirname, '..', 'Examples'); - shell.mkdir('-p', destination); - shell.cp('-fR', path.join(repository, 'examples', '*'), destination); + const destination = path.join(__dirname, '..', 'Examples'); + shell.mkdir('-p', destination); + shell.cp('-fR', path.join(repository, 'examples', '*'), destination); + const isSketch = async (pathLike) => { + try { + const names = await fs.readdir(pathLike); + const dirName = path.basename(pathLike); + return names.indexOf(`${dirName}.ino`) !== -1; + } catch (e) { + if (e.code === 'ENOTDIR') { + return false; + } + throw e; + } + }; + const examples = []; + const categories = await fs.readdir(destination); + const visit = async (pathLike, container) => { + const stat = await fs.lstat(pathLike); + if (stat.isDirectory()) { + if (await isSketch(pathLike)) { + container.sketches.push({ + name: path.basename(pathLike), + relativePath: path.relative(destination, pathLike), + }); + } else { + const names = await fs.readdir(pathLike); + for (const name of names) { + const childPath = path.join(pathLike, name); + if (await isSketch(childPath)) { + container.sketches.push({ + name, + relativePath: path.relative(destination, childPath), + }); + } else { + const child = { + label: name, + children: [], + sketches: [], + }; + container.children.push(child); + await visit(childPath, child); + } + } + } + } + }; + for (const category of categories) { + const example = { + label: category, + children: [], + sketches: [], + }; + await visit(path.join(destination, category), example); + examples.push(example); + } + await fs.writeFile( + path.join(destination, 'examples.json'), + JSON.stringify(examples, null, 2), + { encoding: 'utf8' } + ); + shell.echo(`Generated output to ${path.join(destination, 'examples.json')}`); })(); diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 25e5d220a..24e712671 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import * as React from '@theia/core/shared/react'; import * as remote from '@theia/core/electron-shared/@electron/remote'; import { @@ -7,6 +11,7 @@ import { ExecutableService, Sketch, LibraryService, + ArduinoDaemon, } from '../common/protocol'; import { Mutex } from 'async-mutex'; import { @@ -44,19 +49,13 @@ import { EditorManager, EditorOpenerOptions, } from '@theia/editor/lib/browser'; -import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution'; import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu'; -import { FileNavigatorCommands, FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution'; -import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution'; -import { OutputContribution } from '@theia/output/lib/browser/output-contribution'; -import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution'; -import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; +import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution'; import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileChangeType } from '@theia/filesystem/lib/browser'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; -import { ConfigService } from '../common/protocol/config-service'; import { ArduinoCommands } from './arduino-commands'; import { BoardsConfig } from './boards/boards-config'; import { BoardsConfigDialog } from './boards/boards-config-dialog'; @@ -67,9 +66,11 @@ import { ArduinoMenus } from './menu/arduino-menus'; import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; import { ArduinoToolbar } from './toolbar/arduino-toolbar'; import { ArduinoPreferences } from './arduino-preferences'; -import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../common/protocol/sketches-service-client-impl'; import { SaveAsSketch } from './contributions/save-as-sketch'; -import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution'; import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog'; import { IDEUpdater } from '../common/protocol/ide-updater'; import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution'; @@ -84,93 +85,73 @@ export class ArduinoFrontendContribution TabBarToolbarContribution, CommandContribution, MenuContribution, - ColorContribution { + ColorContribution +{ @inject(ILogger) - protected logger: ILogger; + private readonly logger: ILogger; @inject(MessageService) - protected readonly messageService: MessageService; + private readonly messageService: MessageService; @inject(BoardsService) - protected readonly boardsService: BoardsService; + private readonly boardsService: BoardsService; @inject(LibraryService) - protected readonly libraryService: LibraryService; + private readonly libraryService: LibraryService; @inject(BoardsServiceProvider) - protected readonly boardsServiceClientImpl: BoardsServiceProvider; + private readonly boardsServiceClientImpl: BoardsServiceProvider; @inject(EditorManager) - protected readonly editorManager: EditorManager; + private readonly editorManager: EditorManager; @inject(FileService) - protected readonly fileService: FileService; + private readonly fileService: FileService; @inject(SketchesService) - protected readonly sketchService: SketchesService; + private readonly sketchService: SketchesService; @inject(BoardsConfigDialog) - protected readonly boardsConfigDialog: BoardsConfigDialog; + private readonly boardsConfigDialog: BoardsConfigDialog; @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + private readonly commandRegistry: CommandRegistry; @inject(StatusBar) - protected readonly statusBar: StatusBar; - - @inject(FileNavigatorContribution) - protected readonly fileNavigatorContributions: FileNavigatorContribution; - - @inject(OutputContribution) - protected readonly outputContribution: OutputContribution; - - @inject(OutlineViewContribution) - protected readonly outlineContribution: OutlineViewContribution; - - @inject(ProblemContribution) - protected readonly problemContribution: ProblemContribution; - - @inject(ScmContribution) - protected readonly scmContribution: ScmContribution; - - @inject(SearchInWorkspaceFrontendContribution) - protected readonly siwContribution: SearchInWorkspaceFrontendContribution; - - @inject(SketchbookWidgetContribution) - protected readonly sketchbookWidgetContribution: SketchbookWidgetContribution; + private readonly statusBar: StatusBar; @inject(EditorMode) - protected readonly editorMode: EditorMode; - - @inject(ConfigService) - protected readonly configService: ConfigService; + private readonly editorMode: EditorMode; @inject(HostedPluginSupport) - protected hostedPluginSupport: HostedPluginSupport; + private readonly hostedPluginSupport: HostedPluginSupport; @inject(ExecutableService) - protected executableService: ExecutableService; + private readonly executableService: ExecutableService; @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + private readonly arduinoPreferences: ArduinoPreferences; @inject(SketchesServiceClientImpl) - protected readonly sketchServiceClient: SketchesServiceClientImpl; + private readonly sketchServiceClient: SketchesServiceClientImpl; @inject(FrontendApplicationStateService) - protected readonly appStateService: FrontendApplicationStateService; + private readonly appStateService: FrontendApplicationStateService; @inject(LocalStorageService) - protected readonly localStorageService: LocalStorageService; + private readonly localStorageService: LocalStorageService; @inject(FileSystemFrontendContribution) - protected readonly fileSystemFrontendContribution: FileSystemFrontendContribution; + private readonly fileSystemFrontendContribution: FileSystemFrontendContribution; @inject(IDEUpdater) - protected readonly updater: IDEUpdater; + private readonly updater: IDEUpdater; @inject(IDEUpdaterDialog) - protected readonly updaterDialog: IDEUpdaterDialog; + private readonly updaterDialog: IDEUpdaterDialog; + + @inject(ArduinoDaemon) + private readonly daemon: ArduinoDaemon; protected invalidConfigPopup: | Promise @@ -241,7 +222,10 @@ export class ArduinoFrontendContribution updateStatusBar(this.boardsServiceClientImpl.boardsConfig); this.appStateService.reachedState('ready').then(async () => { const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch && !(await this.sketchService.isTemp(sketch))) { + if ( + CurrentSketch.isValid(sketch) && + !(await this.sketchService.isTemp(sketch)) + ) { this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri))); this.toDisposeOnStop.push( this.fileService.onDidFilesChange(async (event) => { @@ -267,21 +251,6 @@ export class ArduinoFrontendContribution } async onStart(app: FrontendApplication): Promise { - // 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); - } - } - this.updater .init( this.arduinoPreferences.get('arduino.ide.updateChannel'), @@ -345,16 +314,18 @@ export class ArduinoFrontendContribution app.shell.leftPanelHandler.removeBottomMenu('settings-menu'); - this.fileSystemFrontendContribution.onDidChangeEditorFile(e => { - if (e.type === FileChangeType.DELETED) { - const editorWidget = e.editor; - if (SaveableWidget.is(editorWidget)) { - editorWidget.closeWithoutSaving(); - } else { - editorWidget.close(); + this.fileSystemFrontendContribution.onDidChangeEditorFile( + ({ type, editor }) => { + if (type === FileChangeType.DELETED) { + const editorWidget = editor; + if (SaveableWidget.is(editorWidget)) { + editorWidget.closeWithoutSaving(); + } else { + editorWidget.close(); + } } } - }); + ); } onStop(): void { @@ -369,6 +340,10 @@ export class ArduinoFrontendContribution ): Promise { const release = await this.languageServerStartMutex.acquire(); try { + const port = await this.daemon.tryGetPort(); + if (!port) { + return; + } await this.hostedPluginSupport.didStart; const details = await this.boardsService.getBoardDetails({ fqbn }); if (!details) { @@ -404,7 +379,7 @@ export class ArduinoFrontendContribution let currentSketchPath: string | undefined = undefined; if (log) { const currentSketch = await this.sketchServiceClient.currentSketch(); - if (currentSketch) { + if (CurrentSketch.isValid(currentSketch)) { currentSketchPath = await this.fileService.fsPath( new URI(currentSketch.uri) ); @@ -416,8 +391,6 @@ export class ArduinoFrontendContribution this.fileService.fsPath(new URI(lsUri)), ]); - const config = await this.configService.getConfiguration(); - this.languageServerFqbn = await Promise.race([ new Promise((_, reject) => setTimeout( @@ -429,7 +402,7 @@ export class ArduinoFrontendContribution 'arduino.languageserver.start', { lsPath, - cliDaemonAddr: `localhost:${config.daemon.port}`, // TODO: verify if this port is coming from the BE + cliDaemonAddr: `localhost:${port}`, clangdPath, log: currentSketchPath ? currentSketchPath : log, cliDaemonInstance: '1', @@ -495,13 +468,13 @@ export class ArduinoFrontendContribution EditorCommands.SPLIT_EDITOR_UP, EditorCommands.SPLIT_EDITOR_VERTICAL, EditorCommands.SPLIT_EDITOR_HORIZONTAL, - FileNavigatorCommands.REVEAL_IN_NAVIGATOR + FileNavigatorCommands.REVEAL_IN_NAVIGATOR, ]) { registry.unregisterCommand(command); } } - registerMenus(registry: MenuModelRegistry) { + registerMenus(registry: MenuModelRegistry): void { const menuId = (menuPath: string[]): string => { const index = menuPath.length - 1; const menuId = menuPath[index]; @@ -570,7 +543,7 @@ export class ArduinoFrontendContribution uri: string, forceOpen = false, options?: EditorOpenerOptions | undefined - ): Promise { + ): Promise { const widget = this.editorManager.all.find( (widget) => widget.editor.uri.toString() === uri ); 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 355b4e8a9..2e52f6940 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -48,9 +48,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from 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, @@ -88,7 +86,6 @@ import { TabBarDecoratorService } from './theia/core/tab-bar-decorator'; import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser'; import { ProblemManager } from './theia/markers/problem-manager'; import { BoardsAutoInstaller } from './boards/boards-auto-installer'; -import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer'; import { EditorMode } from './editor-mode'; import { ListItemRenderer } from './widgets/component-list/list-item-renderer'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; @@ -138,7 +135,6 @@ import { PreferencesContribution } from './theia/preferences/preferences-contrib import { QuitApp } from './contributions/quit-app'; import { SketchControl } from './contributions/sketch-control'; import { Settings } from './contributions/settings'; -import { KeybindingRegistry } from './theia/core/keybindings'; import { WorkspaceCommandContribution } from './theia/workspace/workspace-commands'; import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handler'; @@ -275,8 +271,14 @@ import { IDEUpdaterDialogWidget, } from './dialogs/ide-updater/ide-updater-dialog'; import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider'; - -const ElementQueries = require('css-element-queries/src/ElementQueries'); +import { DefaultJsonSchemaContribution } from './theia/core/json-schema-store'; +import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store'; +import { EditorNavigationContribution } from './theia/editor/editor-navigation-contribution'; +import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution'; +import { PreferenceTreeGenerator } from './theia/preferences/preference-tree-generator'; +import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator'; +import { AboutDialog } from './theia/core/about-dialog'; +import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog'; MonacoThemingService.register({ id: 'arduino-theme', @@ -286,8 +288,6 @@ MonacoThemingService.register({ }); export default new ContainerModule((bind, unbind, isBound, rebind) => { - ElementQueries.listen(); - ElementQueries.init(); // Commands and toolbar items bind(ArduinoFrontendContribution).toSelf().inSingletonScope(); @@ -477,7 +477,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TheiaPreferencesContribution) .to(PreferencesContribution) .inSingletonScope(); - rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope(); rebind(TheiaWorkspaceCommandContribution) .to(WorkspaceCommandContribution) .inSingletonScope(); @@ -542,10 +541,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { 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); @@ -682,6 +677,26 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(NavigatorTabBarDecorator).toSelf().inSingletonScope(); rebind(TheiaNavigatorTabBarDecorator).toService(NavigatorTabBarDecorator); + // Do not fetch the `catalog.json` from Azure on FE load. + bind(DefaultJsonSchemaContribution).toSelf().inSingletonScope(); + rebind(TheiaDefaultJsonSchemaContribution).toService( + DefaultJsonSchemaContribution + ); + + // Do not block the app startup when initializing the editor navigation history. + bind(EditorNavigationContribution).toSelf().inSingletonScope(); + rebind(TheiaEditorNavigationContribution).toService( + EditorNavigationContribution + ); + + // IDE2 does not use the Theia preferences widget, no need to create and sync the underlying tree model. + bind(PreferenceTreeGenerator).toSelf().inSingletonScope(); + rebind(TheiaPreferenceTreeGenerator).toService(PreferenceTreeGenerator); + + // IDE2 has a custom about dialog, so there is no need to load the Theia extensions on FE load + bind(AboutDialog).toSelf().inSingletonScope(); + rebind(TheiaAboutDialog).toService(AboutDialog); + // To avoid running `Save All` when there are no dirty editors before starting the debug session. bind(DebugSessionManager).toSelf().inSingletonScope(); rebind(TheiaDebugSessionManager).toService(DebugSessionManager); 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 80cbf49fe..7ad65697a 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 @@ -55,12 +55,13 @@ export class BoardsConfigDialogWidget extends ReactWidget { onConfigChange={this.fireConfigChanged} onFocusNodeSet={this.setFocusNode} onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event} + onAppStateDidChange={this.notificationCenter.onAppStateDidChange} /> ); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); if (this.focusNode instanceof HTMLInputElement) { this.focusNode.select(); 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 8bf143ab2..d5db717c8 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts @@ -26,7 +26,7 @@ export class BoardsConfigDialog extends AbstractDialog { constructor( @inject(BoardsConfigDialogProps) - protected readonly props: BoardsConfigDialogProps + protected override readonly props: BoardsConfigDialogProps ) { super(props); @@ -52,7 +52,7 @@ export class BoardsConfigDialog extends AbstractDialog { /** * Pass in an empty string if you want to reset the search term. Using `undefined` has no effect. */ - async open( + override async open( query: string | undefined = undefined ): Promise { if (typeof query === 'string') { @@ -95,7 +95,7 @@ export class BoardsConfigDialog extends AbstractDialog { return head; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -110,23 +110,23 @@ export class BoardsConfigDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message) { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { if (event.target instanceof HTMLTextAreaElement) { return false; } } - protected isValid(value: BoardsConfig.Config): DialogError { + protected override isValid(value: BoardsConfig.Config): DialogError { if (!value.selectedBoard) { if (value.selectedPort) { return nls.localize( diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx index cd8ae9110..1a80ced5d 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx @@ -16,6 +16,7 @@ import { } from './boards-service-provider'; import { naturalCompare } from '../../common/utils'; import { nls } from '@theia/core/lib/common'; +import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state'; export namespace BoardsConfig { export interface Config { @@ -29,6 +30,7 @@ export namespace BoardsConfig { readonly onConfigChange: (config: Config) => void; readonly onFocusNodeSet: (element: HTMLElement | undefined) => void; readonly onFilteredTextDidChangeEvent: Event; + readonly onAppStateDidChange: Event; } export interface State extends Config { @@ -47,7 +49,7 @@ export abstract class Item extends React.Component<{ missing?: boolean; details?: string; }> { - render(): React.ReactNode { + override render(): React.ReactNode { const { selected, label, missing, details } = this.props; const classNames = ['item']; if (selected) { @@ -99,14 +101,18 @@ export class BoardsConfig extends React.Component< }; } - componentDidMount() { - this.updateBoards(); - this.updatePorts( - this.props.boardsServiceProvider.availableBoards - .map(({ port }) => port) - .filter(notEmpty) - ); + override componentDidMount(): void { this.toDispose.pushAll([ + this.props.onAppStateDidChange((state) => { + if (state === 'ready') { + this.updateBoards(); + this.updatePorts( + this.props.boardsServiceProvider.availableBoards + .map(({ port }) => port) + .filter(notEmpty) + ); + } + }), this.props.notificationCenter.onAttachedBoardsChanged((event) => this.updatePorts( event.newState.ports, @@ -141,11 +147,11 @@ export class BoardsConfig extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } - protected fireConfigChanged() { + protected fireConfigChanged(): void { const { selectedBoard, selectedPort } = this.state; this.props.onConfigChange({ selectedBoard, selectedPort }); } @@ -250,7 +256,7 @@ export class BoardsConfig extends React.Component< this.props.onFocusNodeSet(element || undefined); }; - render(): React.ReactNode { + override render(): React.ReactNode { return (
{this.renderContainer('boards', this.renderBoards.bind(this))} 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 a3f2ec208..5f1c42e51 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 @@ -13,6 +13,7 @@ import { BoardsDataStore } from './boards-data-store'; import { MainMenuManager } from '../../common/main-menu-manager'; import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus'; import { nls } from '@theia/core/lib/common'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class BoardsDataMenuUpdater implements FrontendApplicationContribution { @@ -31,11 +32,20 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution { @inject(BoardsServiceProvider) protected readonly boardsServiceClient: BoardsServiceProvider; + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); protected readonly toDisposeOnBoardChange = new DisposableCollection(); async onStart(): Promise { - this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard); + this.appStateService + .reachedState('ready') + .then(() => + this.updateMenuActions( + this.boardsServiceClient.boardsConfig.selectedBoard + ) + ); this.boardsDataStore.onChanged(() => this.updateMenuActions( this.boardsServiceClient.boardsConfig.selectedBoard 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 da8a03875..ca2508fb9 100644 --- a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts +++ b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts @@ -30,7 +30,7 @@ export class BoardsListWidget extends ListWidget { } @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.toDispose.pushAll([ this.notificationCenter.onPlatformInstalled(() => @@ -42,7 +42,7 @@ export class BoardsListWidget extends ListWidget { ]); } - protected async install({ + protected override async install({ item, progressId, version, @@ -63,7 +63,7 @@ export class BoardsListWidget extends ListWidget { ); } - protected async uninstall({ + protected override async uninstall({ item, progressId, }: { 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 b94e2a620..cc7cd24da 100644 --- a/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-toolbar-item.tsx @@ -41,7 +41,7 @@ export class BoardsDropDown extends React.Component { } } - render(): React.ReactNode { + override render(): React.ReactNode { return ReactDOM.createPortal(this.renderNode(), this.dropdownElement); } @@ -130,13 +130,13 @@ export class BoardsToolBarItem extends React.Component< }); } - componentDidMount() { + override componentDidMount(): void { this.props.boardsServiceClient.onAvailableBoardsChanged((availableBoards) => this.setState({ availableBoards }) ); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } @@ -161,7 +161,7 @@ export class BoardsToolBarItem extends React.Component< event.nativeEvent.stopImmediatePropagation(); }; - render(): React.ReactNode { + override render(): React.ReactNode { const { coords, availableBoards } = this.state; const boardsConfig = this.props.boardsServiceClient.boardsConfig; const title = BoardsConfig.Config.toString(boardsConfig, { 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 815148714..af31aff6e 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 @@ -18,7 +18,7 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont }); } - async initializeLayout(): Promise { + override 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 761352e55..f3a50fc54 100644 --- a/arduino-ide-extension/src/browser/contributions/about.ts +++ b/arduino-ide-extension/src/browser/contributions/about.ts @@ -22,13 +22,13 @@ export class About extends Contribution { @inject(ConfigService) protected readonly configService: ConfigService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(About.Commands.ABOUT_APP, { execute: () => this.showAbout(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, { commandId: About.Commands.ABOUT_APP.id, label: nls.localize( diff --git a/arduino-ide-extension/src/browser/contributions/add-file.ts b/arduino-ide-extension/src/browser/contributions/add-file.ts index a6abaf78b..b7cb48f73 100644 --- a/arduino-ide-extension/src/browser/contributions/add-file.ts +++ b/arduino-ide-extension/src/browser/contributions/add-file.ts @@ -10,19 +10,20 @@ import { } from './contribution'; import { FileDialogService } from '@theia/filesystem/lib/browser'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class AddFile extends SketchContribution { @inject(FileDialogService) protected readonly fileDialogService: FileDialogService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(AddFile.Commands.ADD_FILE, { execute: () => this.addFile(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { commandId: AddFile.Commands.ADD_FILE.id, label: nls.localize('arduino/contributions/addFile', 'Add File') + '...', @@ -32,7 +33,7 @@ export class AddFile extends SketchContribution { protected async addFile(): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const toAddUri = await this.fileDialogService.showOpenDialog({ 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 d3f9a6e93..927af4868 100644 --- a/arduino-ide-extension/src/browser/contributions/add-zip-library.ts +++ b/arduino-ide-extension/src/browser/contributions/add-zip-library.ts @@ -28,13 +28,13 @@ export class AddZipLibrary extends SketchContribution { @inject(LibraryService) protected readonly libraryService: LibraryService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, { execute: () => this.addZipLibrary(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { const includeLibMenuPath = [ ...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include', diff --git a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts index 698264fe7..abe22d77f 100644 --- a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts @@ -10,16 +10,17 @@ import { MenuModelRegistry, } from './contribution'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class ArchiveSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, { execute: () => this.archiveSketch(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id, label: nls.localize('arduino/sketch/archiveSketch', 'Archive Sketch'), @@ -32,7 +33,7 @@ export class ArchiveSketch extends SketchContribution { this.sketchServiceClient.currentSketch(), this.configService.getConfiguration(), ]); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const archiveBasename = `${sketch.name}-${dateFormat( diff --git a/arduino-ide-extension/src/browser/contributions/board-selection.ts b/arduino-ide-extension/src/browser/contributions/board-selection.ts index 0a4669955..16b025662 100644 --- a/arduino-ide-extension/src/browser/contributions/board-selection.ts +++ b/arduino-ide-extension/src/browser/contributions/board-selection.ts @@ -47,7 +47,7 @@ export class BoardSelection extends SketchContribution { protected readonly toDisposeBeforeMenuRebuild = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, { execute: async () => { const { selectedBoard, selectedPort } = @@ -100,21 +100,22 @@ PID: ${PID}`; }); } - 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) + override onStart(): void { + this.notificationCenter.onPlatformInstalled(() => this.updateMenus()); + this.notificationCenter.onPlatformUninstalled(() => this.updateMenus()); + this.boardsServiceProvider.onBoardsConfigChanged(() => this.updateMenus()); + this.boardsServiceProvider.onAvailableBoardsChanged(() => + this.updateMenus() ); - this.boardsServiceProvider.onAvailablePortsChanged( - this.updateMenus.bind(this) + this.boardsServiceProvider.onAvailablePortsChanged(() => + this.updateMenus() ); } + override async onReady(): Promise { + this.updateMenus(); + } + protected async updateMenus(): Promise { const [installedBoards, availablePorts, config] = await Promise.all([ this.installedBoards(), diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index e7267ae34..1acda7d15 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -28,15 +28,15 @@ export class BurnBootloader extends SketchContribution { protected readonly boardsServiceClientImpl: BoardsServiceProvider; @inject(OutputChannelManager) - protected readonly outputChannelManager: OutputChannelManager; + protected override readonly outputChannelManager: OutputChannelManager; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, { execute: () => this.burnBootloader(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, { commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id, label: nls.localize( diff --git a/arduino-ide-extension/src/browser/contributions/close.ts b/arduino-ide-extension/src/browser/contributions/close.ts index f27b832cf..ffae12539 100644 --- a/arduino-ide-extension/src/browser/contributions/close.ts +++ b/arduino-ide-extension/src/browser/contributions/close.ts @@ -16,6 +16,7 @@ import { URI, } from './contribution'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; /** * Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window. @@ -23,15 +24,15 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class Close extends SketchContribution { @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; protected shell: ApplicationShell; - onStart(app: FrontendApplication): void { + override onStart(app: FrontendApplication): void { this.shell = app.shell; } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(Close.Commands.CLOSE, { execute: async () => { // Close current editor if closeable. @@ -54,7 +55,7 @@ export class Close extends SketchContribution { // Close the sketch (window). const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const isTemp = await this.sketchService.isTemp(sketch); @@ -106,7 +107,7 @@ export class Close extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: Close.Commands.CLOSE.id, label: nls.localize('vscode/editor.contribution/close', 'Close'), @@ -114,7 +115,7 @@ export class Close extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: Close.Commands.CLOSE.id, keybinding: 'CtrlCmd+W', diff --git a/arduino-ide-extension/src/browser/contributions/contribution.ts b/arduino-ide-extension/src/browser/contributions/contribution.ts index 8f7ff5ba7..1597cac28 100644 --- a/arduino-ide-extension/src/browser/contributions/contribution.ts +++ b/arduino-ide-extension/src/browser/contributions/contribution.ts @@ -1,4 +1,9 @@ -import { inject, injectable, interfaces } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + interfaces, + postConstruct, +} from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { ILogger } from '@theia/core/lib/common/logger'; import { Saveable } from '@theia/core/lib/browser/saveable'; @@ -34,7 +39,10 @@ import { } from '@theia/core/lib/common/command'; import { EditorMode } from '../editor-mode'; import { SettingsService } from '../dialogs/settings/settings'; -import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../common/protocol/sketches-service-client-impl'; import { SketchesService, ConfigService, @@ -42,6 +50,7 @@ import { Sketch, } from '../../common/protocol'; import { ArduinoPreferences } from '../arduino-preferences'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; export { Command, @@ -84,15 +93,31 @@ export abstract class Contribution @inject(SettingsService) protected readonly settingsService: SettingsService; + @inject(FrontendApplicationStateService) + protected readonly appStateService: FrontendApplicationStateService; + + @postConstruct() + protected init(): void { + this.appStateService.reachedState('ready').then(() => this.onReady()); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars onStart(app: FrontendApplication): MaybePromise {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerCommands(registry: CommandRegistry): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerMenus(registry: MenuModelRegistry): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerKeybindings(registry: KeybindingRegistry): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function, unused-imports/no-unused-vars registerToolbarItems(registry: TabBarToolbarRegistry): void {} + + // eslint-disable-next-line @typescript-eslint/no-empty-function + onReady(): MaybePromise {} } @injectable() @@ -127,7 +152,7 @@ export abstract class SketchContribution extends Contribution { protected async sourceOverride(): Promise> { const override: Record = {}; const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch) { + if (CurrentSketch.isValid(sketch)) { for (const editor of this.editorManager.all) { const uri = editor.editor.uri; if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) { @@ -140,7 +165,7 @@ export abstract class SketchContribution extends Contribution { } export namespace Contribution { - export function configure( + export function configure( bind: interfaces.Bind, serviceIdentifier: typeof Contribution ): void { diff --git a/arduino-ide-extension/src/browser/contributions/debug.ts b/arduino-ide-extension/src/browser/contributions/debug.ts index 0a2614ad4..b1550ce32 100644 --- a/arduino-ide-extension/src/browser/contributions/debug.ts +++ b/arduino-ide-extension/src/browser/contributions/debug.ts @@ -12,7 +12,8 @@ import { SketchContribution, TabBarToolbarRegistry, } from './contribution'; -import { nls } from '@theia/core/lib/common'; +import { MaybePromise, nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class Debug extends SketchContribution { @@ -66,7 +67,7 @@ export class Debug extends SketchContribution { onDidChange: this.onDisabledMessageDidChange as Event, }; - onStart(): void { + override onStart(): void { this.onDisabledMessageDidChange( () => (this.debugToolbarItem.tooltip = `${ @@ -79,55 +80,18 @@ export class Debug extends SketchContribution { : Debug.Commands.START_DEBUGGING.label }`) ); - const refreshState = async ( - board: Board | undefined = this.boardsServiceProvider.boardsConfig - .selectedBoard - ) => { - if (!board) { - this.disabledMessage = nls.localize( - 'arduino/common/noBoardSelected', - 'No board selected' - ); - return; - } - const fqbn = board.fqbn; - if (!fqbn) { - this.disabledMessage = nls.localize( - 'arduino/debug/noPlatformInstalledFor', - "Platform is not installed for '{0}'", - board.name - ); - return; - } - const details = await this.boardService.getBoardDetails({ fqbn }); - if (!details) { - this.disabledMessage = nls.localize( - 'arduino/debug/noPlatformInstalledFor', - "Platform is not installed for '{0}'", - board.name - ); - return; - } - const { debuggingSupported } = details; - if (!debuggingSupported) { - this.disabledMessage = nls.localize( - 'arduino/debug/debuggingNotSupported', - "Debugging is not supported by '{0}'", - board.name - ); - } else { - this.disabledMessage = undefined; - } - }; this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => - refreshState(selectedBoard) + this.refreshState(selectedBoard) ); - this.notificationCenter.onPlatformInstalled(() => refreshState()); - this.notificationCenter.onPlatformUninstalled(() => refreshState()); - refreshState(); + this.notificationCenter.onPlatformInstalled(() => this.refreshState()); + this.notificationCenter.onPlatformUninstalled(() => this.refreshState()); + } + + override onReady(): MaybePromise { + this.refreshState(); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(Debug.Commands.START_DEBUGGING, { execute: () => this.startDebug(), isVisible: (widget) => @@ -136,10 +100,51 @@ export class Debug extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem(this.debugToolbarItem); } + private async refreshState( + board: Board | undefined = this.boardsServiceProvider.boardsConfig + .selectedBoard + ): Promise { + if (!board) { + this.disabledMessage = nls.localize( + 'arduino/common/noBoardSelected', + 'No board selected' + ); + return; + } + const fqbn = board.fqbn; + if (!fqbn) { + this.disabledMessage = nls.localize( + 'arduino/debug/noPlatformInstalledFor', + "Platform is not installed for '{0}'", + board.name + ); + return; + } + const details = await this.boardService.getBoardDetails({ fqbn }); + if (!details) { + this.disabledMessage = nls.localize( + 'arduino/debug/noPlatformInstalledFor', + "Platform is not installed for '{0}'", + board.name + ); + return; + } + const { debuggingSupported } = details; + if (!debuggingSupported) { + this.disabledMessage = nls.localize( + 'arduino/debug/debuggingNotSupported', + "Debugging is not supported by '{0}'", + board.name + ); + } else { + this.disabledMessage = undefined; + } + } + protected async startDebug( board: Board | undefined = this.boardsServiceProvider.boardsConfig .selectedBoard @@ -156,7 +161,7 @@ export class Debug extends SketchContribution { this.sketchServiceClient.currentSketch(), this.executableService.list(), ]); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri( diff --git a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts index 3fee31490..6b77d5163 100644 --- a/arduino-ide-extension/src/browser/contributions/edit-contributions.ts +++ b/arduino-ide-extension/src/browser/contributions/edit-contributions.ts @@ -28,7 +28,7 @@ export class EditContributions extends Contribution { @inject(PreferenceService) protected readonly preferences: PreferenceService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(EditContributions.Commands.GO_TO_LINE, { execute: () => this.run('editor.action.gotoLine'), }); @@ -93,7 +93,7 @@ ${value} }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, { commandId: CommonCommands.CUT.id, order: '0', @@ -201,7 +201,7 @@ ${value} }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: EditContributions.Commands.COPY_FOR_FORUM.id, keybinding: 'CtrlCmd+Shift+C', diff --git a/arduino-ide-extension/src/browser/contributions/examples.ts b/arduino-ide-extension/src/browser/contributions/examples.ts index 7a0e04daa..17368feab 100644 --- a/arduino-ide-extension/src/browser/contributions/examples.ts +++ b/arduino-ide-extension/src/browser/contributions/examples.ts @@ -1,5 +1,5 @@ import * as PQueue from 'p-queue'; -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { CommandHandler } from '@theia/core/lib/common/command'; import { MenuPath, @@ -21,7 +21,7 @@ import { MenuModelRegistry, } from './contribution'; import { NotificationCenter } from '../notification-center'; -import { Board, Sketch, SketchContainer } from '../../common/protocol'; +import { Board, SketchRef, SketchContainer } from '../../common/protocol'; import { nls } from '@theia/core/lib/common'; @injectable() @@ -43,8 +43,8 @@ export abstract class Examples extends SketchContribution { protected readonly toDispose = new DisposableCollection(); - @postConstruct() - init(): void { + protected override init(): void { + super.init(); this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard) ); @@ -54,7 +54,7 @@ export abstract class Examples extends SketchContribution { // NOOP } - registerMenus(registry: MenuModelRegistry): void { + override 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; @@ -82,7 +82,7 @@ export abstract class Examples extends SketchContribution { registerRecursively( sketchContainerOrPlaceholder: | SketchContainer - | (Sketch | SketchContainer)[] + | (SketchRef | SketchContainer)[] | string, menuPath: MenuPath, pushToDispose: DisposableCollection = new DisposableCollection(), @@ -100,7 +100,7 @@ export abstract class Examples extends SketchContribution { ) ); } else { - const sketches: Sketch[] = []; + const sketches: SketchRef[] = []; const children: SketchContainer[] = []; let submenuPath = menuPath; @@ -161,7 +161,7 @@ export abstract class Examples extends SketchContribution { @injectable() export class BuiltInExamples extends Examples { - onStart(): void { + override async onReady(): Promise { this.register(); // no `await` } @@ -201,13 +201,16 @@ export class LibraryExamples extends Examples { protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); - onStart(): void { - this.register(); // no `await` + override onStart(): void { this.notificationCenter.onLibraryInstalled(() => this.register()); this.notificationCenter.onLibraryUninstalled(() => this.register()); } - protected handleBoardChanged(board: Board | undefined): void { + override async onReady(): Promise { + this.register(); // no `await` + } + + protected override handleBoardChanged(board: Board | undefined): void { this.register(board); } diff --git a/arduino-ide-extension/src/browser/contributions/help.ts b/arduino-ide-extension/src/browser/contributions/help.ts index 63c3da323..36e09f52e 100644 --- a/arduino-ide-extension/src/browser/contributions/help.ts +++ b/arduino-ide-extension/src/browser/contributions/help.ts @@ -28,7 +28,7 @@ export class Help extends Contribution { @inject(QuickInputService) protected readonly quickInputService: QuickInputService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { const open = (url: string) => this.windowService.openNewWindow(url, { external: true }); const createOpenHandler = (url: string) => @@ -92,7 +92,7 @@ export class Help extends Contribution { ); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.unregisterMenuAction({ commandId: ElectronCommands.TOGGLE_DEVELOPER_TOOLS.id, }); @@ -136,7 +136,7 @@ export class Help extends Contribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: Help.Commands.FIND_IN_REFERENCE.id, keybinding: 'CtrlCmd+Shift+F', diff --git a/arduino-ide-extension/src/browser/contributions/include-library.ts b/arduino-ide-extension/src/browser/contributions/include-library.ts index f8f07e7a9..7347c3fa9 100644 --- a/arduino-ide-extension/src/browser/contributions/include-library.ts +++ b/arduino-ide-extension/src/browser/contributions/include-library.ts @@ -17,6 +17,7 @@ import { SketchContribution, Command, CommandRegistry } from './contribution'; import { NotificationCenter } from '../notification-center'; import { nls } from '@theia/core/lib/common'; import * as monaco from '@theia/monaco-editor-core'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class IncludeLibrary extends SketchContribution { @@ -30,7 +31,7 @@ export class IncludeLibrary extends SketchContribution { protected readonly mainMenuManager: MainMenuManager; @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; @@ -44,8 +45,7 @@ export class IncludeLibrary extends SketchContribution { protected readonly queue = new PQueue({ autoStart: true, concurrency: 1 }); protected readonly toDispose = new DisposableCollection(); - onStart(): void { - this.updateMenuActions(); + override onStart(): void { this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions() ); @@ -55,7 +55,11 @@ export class IncludeLibrary extends SketchContribution { ); } - registerMenus(registry: MenuModelRegistry): void { + override async onReady(): Promise { + this.updateMenuActions(); + } + + override registerMenus(registry: MenuModelRegistry): void { // `Include Library` submenu const includeLibMenuPath = [ ...ArduinoMenus.SKETCH__UTILS_GROUP, @@ -78,7 +82,7 @@ export class IncludeLibrary extends SketchContribution { }); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, { execute: async (arg) => { if (LibraryPackage.is(arg)) { @@ -169,7 +173,7 @@ export class IncludeLibrary extends SketchContribution { protected async includeLibrary(library: LibraryPackage): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } // If the current editor is one of the additional files from the sketch, we use that. diff --git a/arduino-ide-extension/src/browser/contributions/new-sketch.ts b/arduino-ide-extension/src/browser/contributions/new-sketch.ts index bc6cdcf14..685ae7e2b 100644 --- a/arduino-ide-extension/src/browser/contributions/new-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/new-sketch.ts @@ -14,7 +14,7 @@ import { @injectable() export class NewSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(NewSketch.Commands.NEW_SKETCH, { execute: () => this.newSketch(), }); @@ -25,7 +25,7 @@ export class NewSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: NewSketch.Commands.NEW_SKETCH.id, label: nls.localize('arduino/sketch/new', 'New'), @@ -33,14 +33,14 @@ export class NewSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: NewSketch.Commands.NEW_SKETCH.id, keybinding: 'CtrlCmd+N', }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id, 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 d5f4ddbcc..dfedf5d8c 100644 --- a/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-recent-sketch.ts @@ -35,18 +35,19 @@ export class OpenRecentSketch extends SketchContribution { protected toDisposeBeforeRegister = new Map(); - onStart(): void { - const refreshMenu = (sketches: Sketch[]) => { - this.register(sketches); - this.mainMenuManager.update(); - }; + override onStart(): void { this.notificationCenter.onRecentSketchesChanged(({ sketches }) => - refreshMenu(sketches) + this.refreshMenu(sketches) ); - this.sketchService.recentlyOpenedSketches().then(refreshMenu); } - registerMenus(registry: MenuModelRegistry): void { + override async onReady(): Promise { + this.sketchService + .recentlyOpenedSketches() + .then((sketches) => this.refreshMenu(sketches)); + } + + override registerMenus(registry: MenuModelRegistry): void { registry.registerSubmenu( ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, nls.localize('arduino/sketch/openRecent', 'Open Recent'), @@ -54,6 +55,11 @@ export class OpenRecentSketch extends SketchContribution { ); } + private refreshMenu(sketches: Sketch[]): void { + this.register(sketches); + this.mainMenuManager.update(); + } + protected register(sketches: Sketch[]): void { const order = 0; for (const sketch of sketches) { 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 4a753082a..03207126f 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch-external.ts @@ -13,13 +13,13 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class OpenSketchExternal extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, { execute: () => this.openExternal(), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, { commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, label: nls.localize('arduino/sketch/showFolder', 'Show Sketch Folder'), @@ -27,7 +27,7 @@ export class OpenSketchExternal extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id, keybinding: 'CtrlCmd+Alt+K', diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-sketch.ts index fefbbbb6c..f110addc3 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch.ts @@ -43,7 +43,7 @@ export class OpenSketch extends SketchContribution { protected readonly toDispose = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, { execute: (arg) => Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(), @@ -116,7 +116,7 @@ export class OpenSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: OpenSketch.Commands.OPEN_SKETCH.id, label: nls.localize('vscode/workspaceActions/openFileFolder', 'Open...'), @@ -124,14 +124,14 @@ export class OpenSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: OpenSketch.Commands.OPEN_SKETCH.id, keybinding: 'CtrlCmd+O', }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id, diff --git a/arduino-ide-extension/src/browser/contributions/quit-app.ts b/arduino-ide-extension/src/browser/contributions/quit-app.ts index d12e5fdfa..17a7874dd 100644 --- a/arduino-ide-extension/src/browser/contributions/quit-app.ts +++ b/arduino-ide-extension/src/browser/contributions/quit-app.ts @@ -13,7 +13,7 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class QuitApp extends Contribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { if (!isOSX) { registry.registerCommand(QuitApp.Commands.QUIT_APP, { execute: () => remote.app.quit(), @@ -21,7 +21,7 @@ export class QuitApp extends Contribution { } } - registerMenus(registry: MenuModelRegistry): void { + override 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, { @@ -32,7 +32,7 @@ export class QuitApp extends Contribution { } } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { if (!isOSX) { registry.registerKeybinding({ command: QuitApp.Commands.QUIT_APP.id, 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 cafb51b29..6aa63f30e 100644 --- a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts @@ -14,6 +14,7 @@ import { nls } from '@theia/core/lib/common'; import { ApplicationShell, NavigatableWidget, Saveable } from '@theia/core/lib/browser'; import { EditorManager } from '@theia/editor/lib/browser'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class SaveAsSketch extends SketchContribution { @@ -22,18 +23,18 @@ export class SaveAsSketch extends SketchContribution { protected readonly applicationShell: ApplicationShell; @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; @inject(WindowService) protected readonly windowService: WindowService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, { execute: (args) => this.saveAs(args), }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, label: nls.localize('vscode/fileCommands/saveAs', 'Save As...'), @@ -41,7 +42,7 @@ export class SaveAsSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id, keybinding: 'CtrlCmd+Shift+S', @@ -59,7 +60,7 @@ export class SaveAsSketch extends SketchContribution { }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT ): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return false; } diff --git a/arduino-ide-extension/src/browser/contributions/save-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-sketch.ts index 2792fd8eb..2c1ab550e 100644 --- a/arduino-ide-extension/src/browser/contributions/save-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-sketch.ts @@ -12,10 +12,11 @@ import { TabBarToolbarRegistry, } from './contribution'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class SaveSketch extends SketchContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, { execute: () => this.saveSketch(), }); @@ -27,7 +28,7 @@ export class SaveSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, { commandId: SaveSketch.Commands.SAVE_SKETCH.id, label: nls.localize('vscode/fileCommands/save', 'Save'), @@ -35,14 +36,14 @@ export class SaveSketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: SaveSketch.Commands.SAVE_SKETCH.id, keybinding: 'CtrlCmd+S', }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id, @@ -53,7 +54,7 @@ export class SaveSketch extends SketchContribution { async saveSketch(): Promise { const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } const isTemp = await this.sketchService.isTemp(sketch); diff --git a/arduino-ide-extension/src/browser/contributions/settings.ts b/arduino-ide-extension/src/browser/contributions/settings.ts index a6321c511..32030809e 100644 --- a/arduino-ide-extension/src/browser/contributions/settings.ts +++ b/arduino-ide-extension/src/browser/contributions/settings.ts @@ -18,7 +18,7 @@ export class Settings extends SketchContribution { protected settingsOpened = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(Settings.Commands.OPEN, { execute: async () => { let settings: Preferences | undefined = undefined; @@ -39,7 +39,7 @@ export class Settings extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, { commandId: Settings.Commands.OPEN.id, label: @@ -52,7 +52,7 @@ export class Settings extends SketchContribution { registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced'); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: Settings.Commands.OPEN.id, keybinding: 'CtrlCmd+,', diff --git a/arduino-ide-extension/src/browser/contributions/sketch-control.ts b/arduino-ide-extension/src/browser/contributions/sketch-control.ts index fa4974ce3..ea376fea1 100644 --- a/arduino-ide-extension/src/browser/contributions/sketch-control.ts +++ b/arduino-ide-extension/src/browser/contributions/sketch-control.ts @@ -19,7 +19,10 @@ import { } from './contribution'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; -import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../common/protocol/sketches-service-client-impl'; import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider'; import { nls } from '@theia/core/lib/common'; @@ -35,7 +38,7 @@ export class SketchControl extends SketchContribution { protected readonly contextMenuRenderer: ContextMenuRenderer; @inject(EditorManager) - protected readonly editorManager: EditorManager; + protected override readonly editorManager: EditorManager; @inject(SketchesServiceClientImpl) protected readonly sketchesServiceClient: SketchesServiceClientImpl; @@ -46,7 +49,7 @@ export class SketchControl extends SketchContribution { protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection(); - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand( SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR, { @@ -55,7 +58,7 @@ export class SketchControl extends SketchContribution { execute: async () => { this.toDisposeBeforeCreateNewContextMenu.dispose(); const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } @@ -70,25 +73,22 @@ export class SketchControl extends SketchContribution { return; } - const { mainFileUri, rootFolderFileUris } = - await this.sketchService.loadSketch(sketch.uri); + const { mainFileUri, rootFolderFileUris } = sketch; const uris = [mainFileUri, ...rootFolderFileUris]; - const currentSketch = - await this.sketchesServiceClient.currentSketch(); - const parentsketchUri = this.editorManager.currentEditor + const parentSketchUri = this.editorManager.currentEditor ?.getResourceUri() ?.toString(); - const parentsketch = await this.sketchService.getSketchFolder( - parentsketchUri || '' + 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 && - this.allowRename(parentsketch.uri) + sketch && + parentSketch && + parentSketch.uri === sketch.uri && + this.allowRename(parentSketch.uri) ) { this.menuRegistry.registerMenuAction( ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, @@ -122,10 +122,10 @@ export class SketchControl extends SketchContribution { } if ( - currentSketch && - parentsketch && - parentsketch.uri === currentSketch.uri && - this.allowDelete(parentsketch.uri) + sketch && + parentSketch && + parentSketch.uri === sketch.uri && + this.allowDelete(parentSketch.uri) ) { this.menuRegistry.registerMenuAction( ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, @@ -200,7 +200,7 @@ export class SketchControl extends SketchContribution { ); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction( ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, { @@ -228,7 +228,7 @@ export class SketchControl extends SketchContribution { ); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: WorkspaceCommands.NEW_FILE.id, keybinding: 'CtrlCmd+Shift+N', @@ -243,7 +243,7 @@ export class SketchControl extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id, diff --git a/arduino-ide-extension/src/browser/contributions/sketchbook.ts b/arduino-ide-extension/src/browser/contributions/sketchbook.ts index 2b57cb171..80dc99065 100644 --- a/arduino-ide-extension/src/browser/contributions/sketchbook.ts +++ b/arduino-ide-extension/src/browser/contributions/sketchbook.ts @@ -12,10 +12,10 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class Sketchbook extends Examples { @inject(CommandRegistry) - protected readonly commandRegistry: CommandRegistry; + protected override readonly commandRegistry: CommandRegistry; @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; + protected override readonly menuRegistry: MenuModelRegistry; @inject(MainMenuManager) protected readonly mainMenuManager: MainMenuManager; @@ -23,11 +23,7 @@ export class Sketchbook extends Examples { @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; - onStart(): void { - this.sketchService.getSketches({}).then((container) => { - this.register(container); - this.mainMenuManager.update(); - }); + override onStart(): void { this.sketchServiceClient.onSketchbookDidChange(() => { this.sketchService.getSketches({}).then((container) => { this.register(container); @@ -36,7 +32,14 @@ export class Sketchbook extends Examples { }); } - registerMenus(registry: MenuModelRegistry): void { + override async onReady(): Promise { + this.sketchService.getSketches({}).then((container) => { + this.register(container); + this.mainMenuManager.update(); + }); + } + + override registerMenus(registry: MenuModelRegistry): void { registry.registerSubmenu( ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, nls.localize('arduino/sketch/sketchbook', 'Sketchbook'), @@ -53,7 +56,7 @@ export class Sketchbook extends Examples { ); } - protected createHandler(uri: string): CommandHandler { + protected override createHandler(uri: string): CommandHandler { return { execute: async () => { const sketch = await this.sketchService.loadSketch(uri); diff --git a/arduino-ide-extension/src/browser/contributions/upload-certificate.ts b/arduino-ide-extension/src/browser/contributions/upload-certificate.ts index b57f8d036..91292eb49 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-certificate.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-certificate.ts @@ -39,7 +39,7 @@ export class UploadCertificate extends Contribution { protected dialogOpened = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadCertificate.Commands.OPEN, { execute: async () => { try { @@ -93,7 +93,7 @@ export class UploadCertificate extends Contribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, { commandId: UploadCertificate.Commands.OPEN.id, label: UploadCertificate.Commands.OPEN.label, diff --git a/arduino-ide-extension/src/browser/contributions/upload-firmware.ts b/arduino-ide-extension/src/browser/contributions/upload-firmware.ts index 8b3f4f94f..6fc566904 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-firmware.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-firmware.ts @@ -16,7 +16,7 @@ export class UploadFirmware extends Contribution { protected dialogOpened = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadFirmware.Commands.OPEN, { execute: async () => { try { @@ -30,7 +30,7 @@ export class UploadFirmware extends Contribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.TOOLS__FIRMWARE_UPLOADER_GROUP, { commandId: UploadFirmware.Commands.OPEN.id, label: UploadFirmware.Commands.OPEN.label, diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 1a198d4b1..f3478b1ea 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -1,4 +1,4 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/lib/common/event'; import { BoardUserField, CoreService } from '../../common/protocol'; import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus'; @@ -16,6 +16,7 @@ import { } from './contribution'; import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog'; import { DisposableCollection, nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class UploadSketch extends SketchContribution { @@ -47,8 +48,8 @@ export class UploadSketch extends SketchContribution { protected readonly menuActionsDisposables = new DisposableCollection(); - @postConstruct() - protected init(): void { + protected override init(): void { + super.init(); this.boardsServiceClientImpl.onBoardsConfigChanged(async () => { const userFields = await this.boardsServiceClientImpl.selectedBoardUserFields(); @@ -72,7 +73,7 @@ export class UploadSketch extends SketchContribution { return fqbn + '|' + address; } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, { execute: async () => { const key = this.selectedFqbnAddress(); @@ -134,7 +135,7 @@ export class UploadSketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { this.menuActionsDisposables.dispose(); this.menuActionsDisposables.push( @@ -177,7 +178,7 @@ export class UploadSketch extends SketchContribution { ); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: UploadSketch.Commands.UPLOAD_SKETCH.id, keybinding: 'CtrlCmd+U', @@ -188,7 +189,7 @@ export class UploadSketch extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id, @@ -209,7 +210,7 @@ export class UploadSketch extends SketchContribution { this.uploadInProgress = true; this.onDidChangeEmitter.fire(); const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } diff --git a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts index 4ebc5cff0..b22f5998a 100644 --- a/arduino-ide-extension/src/browser/contributions/verify-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/verify-sketch.ts @@ -14,6 +14,7 @@ import { TabBarToolbarRegistry, } from './contribution'; import { nls } from '@theia/core/lib/common'; +import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; @injectable() export class VerifySketch extends SketchContribution { @@ -31,7 +32,7 @@ export class VerifySketch extends SketchContribution { protected verifyInProgress = false; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, { execute: () => this.verifySketch(), isEnabled: () => !this.verifyInProgress, @@ -50,7 +51,7 @@ export class VerifySketch extends SketchContribution { }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { commandId: VerifySketch.Commands.VERIFY_SKETCH.id, label: nls.localize('arduino/sketch/verifyOrCompile', 'Verify/Compile'), @@ -66,7 +67,7 @@ export class VerifySketch extends SketchContribution { }); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.registerKeybinding({ command: VerifySketch.Commands.VERIFY_SKETCH.id, keybinding: 'CtrlCmd+R', @@ -77,7 +78,7 @@ export class VerifySketch extends SketchContribution { }); } - registerToolbarItems(registry: TabBarToolbarRegistry): void { + override registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id, @@ -99,7 +100,7 @@ export class VerifySketch extends SketchContribution { this.onDidChangeEmitter.fire(); const sketch = await this.sketchServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } try { diff --git a/arduino-ide-extension/src/browser/create/create-api.ts b/arduino-ide-extension/src/browser/create/create-api.ts index 19fa98ddc..1e8740a96 100644 --- a/arduino-ide-extension/src/browser/create/create-api.ts +++ b/arduino-ide-extension/src/browser/create/create-api.ts @@ -117,11 +117,11 @@ export class CreateApi { headers, }) ).sketches; - if (partialSketches.length != 0) { + if (partialSketches.length !== 0) { result.sketches = result.sketches.concat(partialSketches); } currentOffset = currentOffset + limit; - } while (partialSketches.length != 0); + } while (partialSketches.length !== 0); result.sketches.forEach((sketch) => this.sketchCache.addSketch(sketch)); return result.sketches; diff --git a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx index 77bc89240..4a9d2f4da 100644 --- a/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/certificate-uploader/certificate-uploader-dialog.tsx @@ -139,7 +139,7 @@ export class UploadCertificateDialog extends AbstractDialog { constructor( @inject(UploadCertificateDialogProps) - protected readonly props: UploadCertificateDialogProps + protected override readonly props: UploadCertificateDialogProps ) { super({ title: nls.localize( @@ -155,7 +155,7 @@ export class UploadCertificateDialog extends AbstractDialog { return; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -165,21 +165,21 @@ export class UploadCertificateDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { return false; } - close(): void { + override close(): void { if (this.busy) { return; } diff --git a/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx index 9e5b678af..dbbad77e6 100644 --- a/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/cloud-share-sketch-dialog.tsx @@ -149,7 +149,7 @@ export class ShareSketchDialog extends AbstractDialog { constructor( @inject(ShareSketchDialogProps) - protected readonly props: ShareSketchDialogProps + protected override readonly props: ShareSketchDialogProps ) { super({ title: props.title }); this.contentNode.classList.add('arduino-share-sketch-dialog'); @@ -159,7 +159,7 @@ export class ShareSketchDialog extends AbstractDialog { get value(): void { return; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -168,12 +168,12 @@ export class ShareSketchDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } diff --git a/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts b/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts index 474573ff8..a7982aca5 100644 --- a/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts +++ b/arduino-ide-extension/src/browser/dialogs/do-not-ask-again-dialog.ts @@ -19,7 +19,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog { constructor( @inject(DoNotAskAgainDialogProps) - protected readonly props: DoNotAskAgainDialogProps + protected override readonly props: DoNotAskAgainDialogProps ) { super(props); this.controlPanel.removeChild(this.errorMessageNode); @@ -42,7 +42,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog { this.doNotAskAgainCheckbox.type = 'checkbox'; } - protected async accept(): Promise { + protected override async accept(): Promise { if (!this.resolve) { return; } @@ -65,7 +65,7 @@ export class DoNotAskAgainConfirmDialog extends ConfirmDialog { } } - protected setErrorMessage(error: DialogError): void { + protected override setErrorMessage(error: DialogError): void { if (this.acceptButton) { this.acceptButton.disabled = !DialogError.getResult(error); } diff --git a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx index 9dd8a9b6b..dd966422e 100644 --- a/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/firmware-uploader/firmware-uploader-dialog.tsx @@ -15,6 +15,7 @@ import { } from '../../../common/protocol/arduino-firmware-uploader'; import { FirmwareUploaderComponent } from './firmware-uploader-component'; import { UploadFirmware } from '../../contributions/upload-firmware'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class UploadFirmwareDialogWidget extends ReactWidget { @@ -24,6 +25,9 @@ export class UploadFirmwareDialogWidget extends ReactWidget { @inject(ArduinoFirmwareUploader) protected readonly arduinoFirmwareUploader: ArduinoFirmwareUploader; + @inject(FrontendApplicationStateService) + private readonly appStatusService: FrontendApplicationStateService; + protected updatableFqbns: string[] = []; protected availableBoards: AvailableBoard[] = []; protected isOpen = new Object(); @@ -38,7 +42,8 @@ export class UploadFirmwareDialogWidget extends ReactWidget { @postConstruct() protected init(): void { - this.arduinoFirmwareUploader.updatableBoards().then((fqbns) => { + this.appStatusService.reachedState('ready').then(async () => { + const fqbns = await this.arduinoFirmwareUploader.updatableBoards(); this.updatableFqbns = fqbns; this.update(); }); @@ -56,7 +61,7 @@ export class UploadFirmwareDialogWidget extends ReactWidget { .finally(() => this.busyCallback(false)); } - onCloseRequest(msg: Message): void { + protected override onCloseRequest(msg: Message): void { super.onCloseRequest(msg); this.isOpen = new Object(); } @@ -88,7 +93,7 @@ export class UploadFirmwareDialog extends AbstractDialog { constructor( @inject(UploadFirmwareDialogProps) - protected readonly props: UploadFirmwareDialogProps + protected override readonly props: UploadFirmwareDialogProps ) { super({ title: UploadFirmware.Commands.OPEN.label || '' }); this.contentNode.classList.add('firmware-uploader-dialog'); @@ -99,7 +104,7 @@ export class UploadFirmwareDialog extends AbstractDialog { return; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -109,21 +114,21 @@ export class UploadFirmwareDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { return false; } - close(): void { + override close(): void { if (this.busy) { return; } diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx index 215e00f31..2b0b952bd 100644 --- a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx @@ -70,7 +70,7 @@ export class IDEUpdaterDialogWidget extends ReactWidget { this.close(); } - close(): void { + override close(): void { super.close(); this.onClose(); } @@ -122,7 +122,7 @@ export class IDEUpdaterDialog extends AbstractDialog { constructor( @inject(IDEUpdaterDialogProps) - protected readonly props: IDEUpdaterDialogProps + protected override readonly props: IDEUpdaterDialogProps ) { super({ title: nls.localize( @@ -138,7 +138,7 @@ export class IDEUpdaterDialog extends AbstractDialog { return this.widget.updateInfo; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -147,7 +147,7 @@ export class IDEUpdaterDialog extends AbstractDialog { this.update(); } - async open( + override async open( data: UpdateInfo | undefined = undefined ): Promise { if (data && data.version) { @@ -156,17 +156,17 @@ export class IDEUpdaterDialog extends AbstractDialog { } } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - close(): void { + override close(): void { this.widget.dispose(); super.close(); } diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx index 33272a32f..86d0d6847 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx @@ -32,7 +32,7 @@ export class SettingsComponent extends React.Component< super(props); } - componentDidUpdate( + override componentDidUpdate( _: SettingsComponent.Props, prevState: SettingsComponent.State ): void { @@ -49,7 +49,7 @@ export class SettingsComponent extends React.Component< } } - componentDidMount(): void { + override componentDidMount(): void { this.props.settingsService .settings() .then((settings) => @@ -67,11 +67,11 @@ export class SettingsComponent extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } - render(): React.ReactNode { + override render(): React.ReactNode { if (!this.state) { return
; } diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx index 498de0cfa..9c51479c9 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx @@ -56,7 +56,7 @@ export class SettingsDialog extends AbstractDialog> { constructor( @inject(SettingsDialogProps) - protected readonly props: SettingsDialogProps + protected override readonly props: SettingsDialogProps ) { super(props); this.contentNode.classList.add('arduino-settings-dialog'); @@ -73,7 +73,7 @@ export class SettingsDialog extends AbstractDialog> { ); } - protected async isValid(settings: Promise): Promise { + protected override async isValid(settings: Promise): Promise { const result = await this.settingsService.validate(settings); if (typeof result === 'string') { return result; @@ -85,7 +85,7 @@ export class SettingsDialog extends AbstractDialog> { return this.settingsService.settings(); } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -97,12 +97,12 @@ export class SettingsDialog extends AbstractDialog> { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); // calling settingsService.reset() in order to reload the settings from the preferenceService @@ -172,17 +172,17 @@ export class AdditionalUrlsDialog extends AbstractDialog { return AdditionalUrls.parse(this.textArea.value, 'newline'); } - protected onAfterAttach(message: Message): void { + protected override onAfterAttach(message: Message): void { super.onAfterAttach(message); this.addUpdateListener(this.textArea, 'input'); } - protected onActivateRequest(message: Message): void { + protected override onActivateRequest(message: Message): void { super.onActivateRequest(message); this.textArea.focus(); } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { if (event.target instanceof HTMLInputElement) { return super.handleEnter(event); } diff --git a/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx index 19a7aee37..8835fd355 100644 --- a/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/user-fields/user-fields-dialog.tsx @@ -61,7 +61,7 @@ export class UserFieldsDialog extends AbstractDialog { constructor( @inject(UserFieldsDialogProps) - protected readonly props: UserFieldsDialogProps + protected override readonly props: UserFieldsDialogProps ) { super({ title: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label || '', @@ -83,7 +83,7 @@ export class UserFieldsDialog extends AbstractDialog { return this.widget.currentUserFields; } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { if (this.widget.isAttached) { Widget.detach(this.widget); } @@ -92,17 +92,17 @@ export class UserFieldsDialog extends AbstractDialog { this.update(); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); this.widget.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); this.widget.activate(); } - protected async accept(): Promise { + protected override async accept(): Promise { // If the user presses enter and at least // a field is empty don't accept the input for (const field of this.value) { @@ -113,7 +113,7 @@ export class UserFieldsDialog extends AbstractDialog { return super.accept(); } - close(): void { + override close(): void { this.widget.resetUserFieldsValue(); this.widget.close(); super.close(); diff --git a/arduino-ide-extension/src/browser/library/library-list-widget.ts b/arduino-ide-extension/src/browser/library/library-list-widget.ts index 033e1802c..dc82199e8 100644 --- a/arduino-ide-extension/src/browser/library/library-list-widget.ts +++ b/arduino-ide-extension/src/browser/library/library-list-widget.ts @@ -38,7 +38,7 @@ export class LibraryListWidget extends ListWidget { } @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.toDispose.pushAll([ this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)), @@ -48,7 +48,7 @@ export class LibraryListWidget extends ListWidget { ]); } - protected async install({ + protected override async install({ item, progressId, version, @@ -158,7 +158,7 @@ export class LibraryListWidget extends ListWidget { } } - protected async uninstall({ + protected override async uninstall({ item, progressId, }: { @@ -199,7 +199,7 @@ class MessageBoxDialog extends AbstractDialog { }); } - protected onCloseRequest(message: Message): void { + protected override onCloseRequest(message: Message): void { super.onCloseRequest(message); this.accept(); } @@ -217,7 +217,7 @@ class MessageBoxDialog extends AbstractDialog { return message; } - protected handleEnter(event: KeyboardEvent): boolean | void { + protected override handleEnter(event: KeyboardEvent): boolean | void { this.response = 0; super.handleEnter(event); } diff --git a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts index 1a06e8472..37a3b0679 100644 --- a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts @@ -28,7 +28,7 @@ export class LibraryListWidgetFrontendContribution this.openView(); } - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { if (this.toggleCommand) { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: this.toggleCommand.id, diff --git a/arduino-ide-extension/src/browser/notification-center.ts b/arduino-ide-extension/src/browser/notification-center.ts index ab52f27ab..b6ad3b4b6 100644 --- a/arduino-ide-extension/src/browser/notification-center.ts +++ b/arduino-ide-extension/src/browser/notification-center.ts @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { Emitter } from '@theia/core/lib/common/event'; import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; @@ -14,6 +18,10 @@ import { Config, Sketch, } from '../common/protocol'; +import { + FrontendApplicationStateService, + FrontendApplicationState, +} from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class NotificationCenter @@ -22,8 +30,11 @@ export class NotificationCenter @inject(NotificationServiceServer) protected readonly server: JsonRpcProxy; + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + protected readonly indexUpdatedEmitter = new Emitter(); - protected readonly daemonStartedEmitter = new Emitter(); + protected readonly daemonStartedEmitter = new Emitter(); protected readonly daemonStoppedEmitter = new Emitter(); protected readonly configChangedEmitter = new Emitter<{ config: Config | undefined; @@ -45,6 +56,8 @@ export class NotificationCenter protected readonly recentSketchesChangedEmitter = new Emitter<{ sketches: Sketch[]; }>(); + private readonly onAppStateDidChangeEmitter = + new Emitter(); protected readonly toDispose = new DisposableCollection( this.indexUpdatedEmitter, @@ -68,10 +81,16 @@ export class NotificationCenter readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event; readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event; readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event; + readonly onAppStateDidChange = this.onAppStateDidChangeEmitter.event; @postConstruct() protected init(): void { this.server.setClient(this); + this.toDispose.push( + this.appStateService.onStateChanged((state) => + this.onAppStateDidChangeEmitter.fire(state) + ) + ); } onStop(): void { @@ -82,8 +101,8 @@ export class NotificationCenter this.indexUpdatedEmitter.fire(); } - notifyDaemonStarted(): void { - this.daemonStartedEmitter.fire(); + notifyDaemonStarted(port: string): void { + this.daemonStartedEmitter.fire(port); } notifyDaemonStopped(): void { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index 43b5a0a12..c29f7ca3e 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -62,7 +62,7 @@ export class MonitorViewContribution }); } - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { if (this.toggleCommand) { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: this.toggleCommand.id, @@ -95,7 +95,7 @@ export class MonitorViewContribution }); } - registerCommands(commands: CommandRegistry): void { + override registerCommands(commands: CommandRegistry): void { commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, { isEnabled: (widget) => widget instanceof MonitorWidget, isVisible: (widget) => widget instanceof MonitorWidget, diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index cbe94e58b..24d6449e7 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -75,21 +75,21 @@ export class MonitorWidget extends ReactWidget { this.update(); } - dispose(): void { + override dispose(): void { super.dispose(); } - protected onAfterAttach(msg: Message): void { + protected override onAfterAttach(msg: Message): void { super.onAfterAttach(msg); this.serialConnection.openWSToBE(); } - onCloseRequest(msg: Message): void { + protected override onCloseRequest(msg: Message): void { this.closing = true; super.onCloseRequest(msg); } - protected onUpdateRequest(msg: Message): void { + protected override onUpdateRequest(msg: Message): void { // TODO: `this.isAttached` // See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713 if (!this.closing && this.isAttached) { @@ -97,13 +97,13 @@ export class MonitorWidget extends ReactWidget { } } - protected onResize(msg: Widget.ResizeMessage): void { + protected override onResize(msg: Widget.ResizeMessage): void { super.onResize(msg); this.widgetHeight = msg.height; this.update(); } - protected onActivateRequest(msg: Message): void { + protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); (this.focusNode || this.node).focus(); } diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index 15215307d..8000d9fe4 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -32,7 +32,7 @@ export class SerialMonitorSendInput extends React.Component< this.onKeyDown = this.onKeyDown.bind(this); } - componentDidMount(): void { + override componentDidMount(): void { this.props.serialConnection.isBESerialConnected().then((connected) => { this.setState({ connected }); }); @@ -50,12 +50,12 @@ export class SerialMonitorSendInput extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout? this.toDisposeBeforeUnmount.dispose(); } - render(): React.ReactNode { + override render(): React.ReactNode { return ( { @@ -87,7 +87,7 @@ export class SerialMonitorOutput extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { // TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout? this.toDisposeBeforeUnmount.dispose(); } diff --git a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index da2d7384a..c2f23ae8f 100644 --- a/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -45,7 +45,7 @@ export class PlotterFrontendContribution extends Contribution { @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; - onStart(app: FrontendApplication): MaybePromise { + override onStart(app: FrontendApplication): MaybePromise { this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString(); ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { @@ -56,13 +56,13 @@ export class PlotterFrontendContribution extends Contribution { return super.onStart(app); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { execute: this.connect.bind(this), }); } - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { commandId: SerialPlotterContribution.Commands.OPEN.id, label: SerialPlotterContribution.Commands.OPEN.label, diff --git a/arduino-ide-extension/src/browser/theia/core/about-dialog.ts b/arduino-ide-extension/src/browser/theia/core/about-dialog.ts new file mode 100644 index 000000000..3e89c5105 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/core/about-dialog.ts @@ -0,0 +1,10 @@ +import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog'; +import { duration } from '../../../common/decorators'; + +export class AboutDialog extends TheiaAboutDialog { + @duration({ name: 'theia-about#init' }) + protected override async init(): Promise { + // NOOP + // IDE2 has a custom about dialog, so it does not make sense to collect Theia extensions at startup time. + } +} diff --git a/arduino-ide-extension/src/browser/theia/core/application-shell.ts b/arduino-ide-extension/src/browser/theia/core/application-shell.ts index 8af61bdf2..eff6a3a04 100644 --- a/arduino-ide-extension/src/browser/theia/core/application-shell.ts +++ b/arduino-ide-extension/src/browser/theia/core/application-shell.ts @@ -15,7 +15,7 @@ import { } from '@theia/core/lib/browser'; import { Sketch } from '../../../common/protocol'; import { SaveAsSketch } from '../../contributions/save-as-sketch'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; import { nls } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; @@ -33,7 +33,7 @@ export class ApplicationShell extends TheiaApplicationShell { @inject(ConnectionStatusService) protected readonly connectionStatusService: ConnectionStatusService; - protected track(widget: Widget): void { + protected override track(widget: Widget): void { super.track(widget); if (widget instanceof OutputWidget) { widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700 @@ -41,7 +41,7 @@ export class ApplicationShell extends TheiaApplicationShell { if (widget instanceof EditorWidget) { // Make the editor un-closeable asynchronously. this.sketchesServiceClient.currentSketch().then((sketch) => { - if (sketch) { + if (CurrentSketch.isValid(sketch)) { if (!this.isSketchFile(widget.editor.uri, sketch.uri)) { return; } @@ -61,7 +61,7 @@ export class ApplicationShell extends TheiaApplicationShell { return false; } - async addWidget( + override async addWidget( widget: Widget, options: Readonly = {} ): Promise { @@ -87,19 +87,19 @@ export class ApplicationShell extends TheiaApplicationShell { return super.addWidget(widget, { ...options, ref }); } - handleEvent(): boolean { + override handleEvent(): boolean { // NOOP, dragging has been disabled - return false + return false; } // Avoid hiding top panel as we use it for arduino toolbar - protected createTopPanel(): Panel { + protected override createTopPanel(): Panel { const topPanel = super.createTopPanel(); topPanel.show(); return topPanel; } - async saveAll(): Promise { + override async saveAll(): Promise { if ( this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE ) { diff --git a/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts b/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts index 2898dd7c4..737ca98ac 100644 --- a/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts +++ b/arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts @@ -12,12 +12,12 @@ export class BrowserMainMenuFactory { protected menuBar: MenuBarWidget | undefined; - createMenuBar(): MenuBarWidget { + override createMenuBar(): MenuBarWidget { this.menuBar = super.createMenuBar(); return this.menuBar; } - update() { + update(): void { if (this.menuBar) { this.menuBar.clearMenus(); this.fillMenuBar(this.menuBar); diff --git a/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts b/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts index e82fd719e..ee234fbdb 100644 --- a/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts +++ b/arduino-ide-extension/src/browser/theia/core/browser-menu-plugin.ts @@ -4,7 +4,7 @@ import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser @injectable() export class ArduinoMenuContribution extends BrowserMenuBarContribution { - onStart(app: FrontendApplication): void { + override onStart(app: FrontendApplication): void { const menu = this.factory.createMenuBar(); app.shell.addWidget(menu, { area: 'top' }); } diff --git a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts index 4687dfde3..8a0d30b5d 100644 --- a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts @@ -8,7 +8,7 @@ import { CommandRegistry } from '@theia/core/lib/common/command'; @injectable() export class CommonFrontendContribution extends TheiaCommonFrontendContribution { - registerCommands(commandRegistry: CommandRegistry): void { + override registerCommands(commandRegistry: CommandRegistry): void { super.registerCommands(commandRegistry); for (const command of [ @@ -26,7 +26,7 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution } } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); for (const command of [ CommonCommands.SAVE, diff --git a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts index 5117d2730..ae997183f 100644 --- a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts +++ b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { Disposable } from '@theia/core/lib/common/disposable'; import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar'; import { @@ -18,18 +22,22 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; - protected isRunning = false; + protected connectedPort: string | undefined; @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { this.schedulePing(); try { - this.isRunning = await this.daemon.isRunning(); + this.connectedPort = await this.daemon.tryGetPort(); } catch {} - this.notificationCenter.onDaemonStarted(() => (this.isRunning = true)); - this.notificationCenter.onDaemonStopped(() => (this.isRunning = false)); + this.notificationCenter.onDaemonStarted( + (port) => (this.connectedPort = port) + ); + this.notificationCenter.onDaemonStopped( + () => (this.connectedPort = undefined) + ); this.wsConnectionProvider.onIncomingMessageActivity(() => { - this.updateStatus(this.isRunning); + this.updateStatus(!!this.connectedPort); this.schedulePing(); }); } @@ -43,32 +51,36 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon @inject(NotificationCenter) protected readonly notificationCenter: NotificationCenter; - protected isRunning = false; + protected connectedPort: string | undefined; @postConstruct() protected async init(): Promise { try { - this.isRunning = await this.daemon.isRunning(); + this.connectedPort = await this.daemon.tryGetPort(); } catch {} - this.notificationCenter.onDaemonStarted(() => (this.isRunning = true)); - this.notificationCenter.onDaemonStopped(() => (this.isRunning = false)); + this.notificationCenter.onDaemonStarted( + (port) => (this.connectedPort = port) + ); + this.notificationCenter.onDaemonStopped( + () => (this.connectedPort = undefined) + ); } - protected onStateChange(state: ConnectionStatus): void { - if (!this.isRunning && state === ConnectionStatus.ONLINE) { + protected override onStateChange(state: ConnectionStatus): void { + if (!this.connectedPort && state === ConnectionStatus.ONLINE) { return; } super.onStateChange(state); } - protected handleOffline(): void { + protected override handleOffline(): void { this.statusBar.setElement('connection-status', { alignment: StatusBarAlignment.LEFT, - text: this.isRunning + text: this.connectedPort ? nls.localize('theia/core/offline', 'Offline') : '$(bolt) ' + nls.localize('theia/core/daemonOffline', 'CLI Daemon Offline'), - tooltip: this.isRunning + tooltip: this.connectedPort ? nls.localize( 'theia/core/cannotConnectBackend', 'Cannot connect to the backend.' diff --git a/arduino-ide-extension/src/browser/theia/core/frontend-application.ts b/arduino-ide-extension/src/browser/theia/core/frontend-application.ts index 9c5234c46..ba6b2f8bc 100644 --- a/arduino-ide-extension/src/browser/theia/core/frontend-application.ts +++ b/arduino-ide-extension/src/browser/theia/core/frontend-application.ts @@ -20,22 +20,22 @@ export class FrontendApplication extends TheiaFrontendApplication { @inject(SketchesService) protected readonly sketchesService: SketchesService; - protected async initializeLayout(): Promise { + protected override async initializeLayout(): Promise { await super.initializeLayout(); - const roots = await this.workspaceService.roots; - for (const root of roots) { - const exists = await this.fileService.exists(root.resource); - if (exists) { - this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu + this.workspaceService.roots.then(async (roots) => { + for (const root of roots) { await this.commandService.executeCommand( ArduinoCommands.OPEN_SKETCH_FILES.id, root.resource ); + this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu } - } + }); } - protected getStartupIndicator(host: HTMLElement): HTMLElement | undefined { + protected override getStartupIndicator( + host: HTMLElement + ): HTMLElement | undefined { let startupElement = this.doGetStartupIndicator(host, 'old-theia-preload'); // https://github.com/eclipse-theia/theia/pull/10761#issuecomment-1131476318 if (!startupElement) { startupElement = this.doGetStartupIndicator(host, 'theia-preload'); // We show the new Theia spinner in dev mode. diff --git a/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts b/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts new file mode 100644 index 000000000..8d9f96313 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/core/json-schema-store.ts @@ -0,0 +1,11 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { DefaultJsonSchemaContribution as TheiaDefaultJsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store'; + +@injectable() +export class DefaultJsonSchemaContribution extends TheiaDefaultJsonSchemaContribution { + override async registerSchemas(): Promise { + // NOOP + // Do not fetch the https://www.schemastore.org/api/json/catalog.json on every single browser window load. + // If the schemas are required in the future, we should fetch the `catalog.json` on build time and load it. + } +} diff --git a/arduino-ide-extension/src/browser/theia/core/keybindings.ts b/arduino-ide-extension/src/browser/theia/core/keybindings.ts deleted file mode 100644 index b6d55f564..000000000 --- a/arduino-ide-extension/src/browser/theia/core/keybindings.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { Command } from '@theia/core/lib/common/command'; -import { Keybinding } from '@theia/core/lib/common/keybinding'; -import { - KeybindingRegistry as TheiaKeybindingRegistry, - KeybindingScope, -} from '@theia/core/lib/browser/keybinding'; - -@injectable() -export class KeybindingRegistry extends TheiaKeybindingRegistry { - // https://github.com/eclipse-theia/theia/issues/8209 - unregisterKeybinding(key: string): void; - unregisterKeybinding(keybinding: Keybinding): void; - unregisterKeybinding(command: Command): void; - unregisterKeybinding(arg: string | Keybinding | Command): void { - const keymap = this.keymaps[KeybindingScope.DEFAULT]; - const filter = Command.is(arg) - ? ({ command }: Keybinding) => command === arg.id - : ({ keybinding }: Keybinding) => - Keybinding.is(arg) - ? keybinding === arg.keybinding - : keybinding === arg; - for (const binding of keymap.filter(filter)) { - const idx = keymap.indexOf(binding); - if (idx !== -1) { - keymap.splice(idx, 1); - } - } - } -} diff --git a/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts b/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts deleted file mode 100644 index 510a1944f..000000000 --- a/arduino-ide-extension/src/browser/theia/core/shell-layout-restorer.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; -import { ShellLayoutRestorer as TheiaShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer'; - -@injectable() -export class ShellLayoutRestorer extends TheiaShellLayoutRestorer { - // Workaround for https://github.com/eclipse-theia/theia/issues/6579. - async storeLayoutAsync(app: FrontendApplication): Promise { - if (this.shouldStoreLayout) { - try { - this.logger.info('>>> Storing the layout...'); - const layoutData = app.shell.getLayoutData(); - const serializedLayoutData = this.deflate(layoutData); - await this.storageService.setData( - this.storageKey, - serializedLayoutData - ); - this.logger.info('<<< The layout has been successfully stored.'); - } catch (error) { - await this.storageService.setData(this.storageKey, undefined); - this.logger.error('Error during serialization of layout data', error); - } - } - } - - async restoreLayout(app: FrontendApplication): Promise { - this.logger.info('>>> Restoring the layout state...'); - const serializedLayoutData = await this.storageService.getData( - this.storageKey - ); - if (serializedLayoutData === undefined) { - this.logger.info('<<< Nothing to restore.'); - return false; - } - - const layoutData = await this.inflate(serializedLayoutData); - // workaround to remove duplicated tabs - const filesUri: string[] = []; - if ((layoutData as any)?.mainPanel?.main?.widgets) { - (layoutData as any).mainPanel.main.widgets = ( - layoutData as any - ).mainPanel.main.widgets.filter((widget: any) => { - const uri = widget.getResourceUri().toString(); - if (filesUri.includes(uri)) { - return false; - } - filesUri.push(uri); - return true; - }); - } - - await app.shell.setLayoutData(layoutData); - this.logger.info('<<< The layout has been successfully restored.'); - return true; - } -} diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts b/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts index ecf9199d0..ea1f29eed 100644 --- a/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts +++ b/arduino-ide-extension/src/browser/theia/core/tab-bar-decorator.ts @@ -27,7 +27,7 @@ export class TabBarDecoratorService extends TheiaTabBarDecoratorService { ); } - getDecorations(title: Title): WidgetDecoration.Data[] { + override getDecorations(title: Title): WidgetDecoration.Data[] { if (title.owner instanceof EditorWidget) { const editor = title.owner.editor; if (this.dataDirUri && this.dataDirUri.isEqualOrParent(editor.uri)) { diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx b/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx index a57b658ba..42e086d2b 100644 --- a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx +++ b/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx @@ -12,7 +12,7 @@ export class TabBarToolbar extends TheiaTabBarToolbar { * Copied over from Theia. Added an ID to the parent of the toolbar item (`--container`). * CSS3 does not support parent selectors but we want to style the parent of the toolbar item. */ - protected renderItem(item: TabBarToolbarItem): React.ReactNode { + protected override renderItem(item: TabBarToolbarItem): React.ReactNode { let innerText = ''; const classNames = []; if (item.text) { diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bars.ts b/arduino-ide-extension/src/browser/theia/core/tab-bars.ts index 03a518c70..c6adbc236 100644 --- a/arduino-ide-extension/src/browser/theia/core/tab-bars.ts +++ b/arduino-ide-extension/src/browser/theia/core/tab-bars.ts @@ -3,7 +3,7 @@ import { Saveable } from '@theia/core/lib/browser/saveable'; import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars'; export class TabBarRenderer extends TheiaTabBarRenderer { - createTabClass(data: TabBar.IRenderData): string { + override createTabClass(data: TabBar.IRenderData): string { let className = super.createTabClass(data); if (!data.title.closable && Saveable.isDirty(data.title.owner)) { className += ' p-mod-closable'; diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts index dc3be2836..0059f433c 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts @@ -7,7 +7,10 @@ import { DebugConfiguration } from '@theia/debug/lib/common/debug-common'; import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model'; import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { SketchesService } from '../../../common/protocol'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { DebugConfigurationModel } from './debug-configuration-model'; import { FileOperationError, @@ -36,7 +39,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager { } @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { super.init(); this.appStateService.reachedState('ready').then(async () => { const tempContent = await this.getTempLaunchJsonContent(); @@ -73,7 +76,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager { }); } - protected updateModels = debounce(async () => { + protected override updateModels = debounce(async () => { await this.appStateService.reachedState('ready'); const roots = await this.workspaceService.roots; const toDelete = new Set(this.models.keys()); @@ -113,7 +116,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager { (TheiaDebugConfigurationModel.JsonContent & { uri: URI }) | URI | undefined > { const sketch = await this.sketchesServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return undefined; } const uri = await this.sketchesService.getIdeTempFolderUri(sketch); diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts index f1ccf01d1..225a003c1 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts @@ -6,8 +6,8 @@ import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/ export class DebugConfigurationModel extends TheiaDebugConfigurationModel { constructor( - readonly workspaceFolderUri: string, - protected readonly preferences: PreferenceService, + override readonly workspaceFolderUri: string, + protected override readonly preferences: PreferenceService, protected readonly config: DebugConfiguration[], protected configUri: URI | undefined, protected readonly onConfigDidChange: Event @@ -25,7 +25,7 @@ export class DebugConfigurationModel extends TheiaDebugConfigurationModel { this.reconcile(); } - protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent { + protected override parseConfigurations(): TheiaDebugConfigurationModel.JsonContent { return { uri: this.configUri, configurations: this.config, diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts b/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts index 1060b112c..00e351ece 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-frontend-application-contribution.ts @@ -13,7 +13,7 @@ export class DebugFrontendApplicationContribution extends TheiaDebugFrontendAppl this.options.defaultWidgetOptions.rank = 4; } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); unregisterSubmenu(DebugMenus.DEBUG, registry); } diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts deleted file mode 100644 index 14c5a3d9b..000000000 --- a/arduino-ide-extension/src/browser/theia/debug/debug-hover-source.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { - ExpressionItem, - DebugVariable, -} from '@theia/debug/lib/browser/console/debug-console-items'; -import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source'; - -// TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/. -@injectable() -export class DebugHoverSource extends TheiaDebugHoverSource { - async evaluate2( - expression: string - ): Promise { - const evaluated = await this.doEvaluate(expression); - const elements = evaluated && (await evaluated.getElements()); - this._expression = evaluated; - this.elements = elements ? [...elements] : []; - this.fireDidChange(); - return evaluated; - } -} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts b/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts deleted file mode 100644 index 437555bdb..000000000 --- a/arduino-ide-extension/src/browser/theia/debug/debug-hover-widget.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { injectable, interfaces, Container } from '@theia/core/shared/inversify'; -import { Widget } from '@theia/core/shared/@phosphor/widgets'; -import { SourceTreeWidget } from '@theia/core/lib/browser/source-tree'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; -import { DebugEditor } from '@theia/debug/lib/browser/editor/debug-editor'; -import { DebugVariable } from '@theia/debug/lib/browser/console/debug-console-items'; -import { DebugExpressionProvider } from '@theia/debug/lib/browser/editor/debug-expression-provider'; -import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source'; -import { - DebugHoverWidget as TheiaDebugHoverWidget, - ShowDebugHoverOptions, -} from '@theia/debug/lib/browser/editor/debug-hover-widget'; -import { DebugHoverSource } from './debug-hover-source'; - -export function createDebugHoverWidgetContainer( - parent: interfaces.Container, - editor: DebugEditor -): Container { - const child = SourceTreeWidget.createContainer(parent, { - virtualized: false, - }); - child.bind(DebugEditor).toConstantValue(editor); - child.bind(TheiaDebugHoverSource).toSelf(); - child.bind(DebugHoverSource).toSelf(); - child.rebind(TheiaDebugHoverSource).to(DebugHoverSource); - child.unbind(SourceTreeWidget); - child.bind(DebugExpressionProvider).toSelf(); - child.bind(TheiaDebugHoverWidget).toSelf(); - child.bind(DebugHoverWidget).toSelf(); - child.rebind(TheiaDebugHoverWidget).to(DebugHoverWidget); - return child; -} - -// TODO: remove patch after https://github.com/eclipse-theia/theia/pull/9256/ -@injectable() -export class DebugHoverWidget extends TheiaDebugHoverWidget { - protected async doShow( - options: ShowDebugHoverOptions | undefined = this.options - ): Promise { - if (!this.isEditorFrame()) { - this.hide(); - return; - } - if (!options) { - this.hide(); - return; - } - if (this.options && this.options.selection.equalsRange(options.selection)) { - return; - } - if (!this.isAttached) { - Widget.attach(this, this.contentNode); - } - - this.options = options; - const matchingExpression = this.expressionProvider.get( - this.editor.getControl().getModel()!, - options.selection - ); - if (!matchingExpression) { - this.hide(); - return; - } - const toFocus = new DisposableCollection(); - if (this.options.focus === true) { - toFocus.push( - this.model.onNodeRefreshed(() => { - toFocus.dispose(); - this.activate(); - }) - ); - } - const expression = await (this.hoverSource as DebugHoverSource).evaluate2( - matchingExpression - ); - if (!expression || !expression.value) { - toFocus.dispose(); - this.hide(); - return; - } - - this.contentNode.hidden = false; - ['number', 'boolean', 'string'].forEach((token) => - this.titleNode.classList.remove(token) - ); - this.domNode.classList.remove('complex-value'); - if (expression.hasElements) { - this.domNode.classList.add('complex-value'); - } else { - this.contentNode.hidden = true; - if ( - expression.type === 'number' || - expression.type === 'boolean' || - expression.type === 'string' - ) { - this.titleNode.classList.add(expression.type); - } else if (!isNaN(+expression.value)) { - this.titleNode.classList.add('number'); - } else if (DebugVariable.booleanRegex.test(expression.value)) { - this.titleNode.classList.add('boolean'); - } else if (DebugVariable.stringRegex.test(expression.value)) { - this.titleNode.classList.add('string'); - } - } - - // super.show(); // Here we cannot call `super.show()` but have to call `show` on the `Widget` prototype. - Widget.prototype.show.call(this); - await new Promise((resolve) => { - setTimeout( - () => - window.requestAnimationFrame(() => { - this.editor.getControl().layoutContentWidget(this); - resolve(); - }), - 0 - ); - }); - } -} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts index 16345af8c..6eb2ebdeb 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts @@ -7,7 +7,7 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class DebugSessionManager extends TheiaDebugSessionManager { - async start(options: DebugSessionOptions): Promise { + override async start(options: DebugSessionOptions): Promise { return this.progressService.withProgress( nls.localize('theia/debug/start', 'Start...'), 'debug', @@ -76,7 +76,7 @@ export class DebugSessionManager extends TheiaDebugSessionManager { } ); } - async terminateSession(session?: DebugSession): Promise { + override async terminateSession(session?: DebugSession): Promise { if (!session) { this.updateCurrentSession(this._currentSession); session = this._currentSession; diff --git a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts index ab479cf13..b93131c7f 100644 --- a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts +++ b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts @@ -8,7 +8,7 @@ import { @injectable() export abstract class AbstractDialog extends TheiaAbstractDialog { - constructor(@inject(DialogProps) protected readonly props: DialogProps) { + constructor(@inject(DialogProps) protected override readonly props: DialogProps) { super(props); this.closeCrossNode.classList.remove(...codiconArray('close')); diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-command.ts b/arduino-ide-extension/src/browser/theia/editor/editor-command.ts index 5b98de336..9f4a3ffc5 100644 --- a/arduino-ide-extension/src/browser/theia/editor/editor-command.ts +++ b/arduino-ide-extension/src/browser/theia/editor/editor-command.ts @@ -4,7 +4,7 @@ import { EditorCommandContribution as TheiaEditorCommandContribution } from '@th @injectable() export class EditorCommandContribution extends TheiaEditorCommandContribution { @postConstruct() - protected init(): void { + protected override init(): void { // Workaround for https://github.com/eclipse-theia/theia/issues/8722. this.editorPreferences.onPreferenceChanged( ({ preferenceName, newValue, oldValue }) => { diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts b/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts new file mode 100644 index 000000000..ebd90f1bf --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/editor/editor-navigation-contribution.ts @@ -0,0 +1,11 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { EditorNavigationContribution as TheiaEditorNavigationContribution } from '@theia/editor/lib/browser/editor-navigation-contribution'; + +@injectable() +export class EditorNavigationContribution extends TheiaEditorNavigationContribution { + override async onStart(): Promise { + // No await. + // Restore the navigation history asynchronously. + super.onStart(); + } +} diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts index be497e688..dd00e1879 100644 --- a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts +++ b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts @@ -3,7 +3,10 @@ import URI from '@theia/core/lib/common/uri'; import { EditorWidget } from '@theia/editor/lib/browser'; import { LabelProvider } from '@theia/core/lib/browser'; import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { SketchesService, Sketch } from '../../../common/protocol'; import { nls } from '@theia/core/lib/common'; @@ -16,9 +19,9 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory { protected readonly sketchesServiceClient: SketchesServiceClientImpl; @inject(LabelProvider) - protected readonly labelProvider: LabelProvider; + protected override readonly labelProvider: LabelProvider; - protected async createEditor(uri: URI): Promise { + protected override async createEditor(uri: URI): Promise { const widget = await super.createEditor(uri); return this.maybeUpdateCaption(widget); } @@ -28,7 +31,7 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory { ): Promise { const sketch = await this.sketchesServiceClient.currentSketch(); const { uri } = widget.editor; - if (sketch && Sketch.isInSketch(uri, sketch)) { + if (CurrentSketch.isValid(sketch) && Sketch.isInSketch(uri, sketch)) { const isTemp = await this.sketchesService.isTemp(sketch); if (isTemp) { widget.title.caption = nls.localize( diff --git a/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts index d993c70e6..13914d025 100644 --- a/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/keymaps/keymaps-frontend-contribution.ts @@ -9,7 +9,7 @@ import { nls } from '@theia/core/lib/common'; @injectable() export class KeymapsFrontendContribution extends TheiaKeymapsFrontendContribution { - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, { commandId: KeymapsCommands.OPEN_KEYMAPS.id, label: nls.localize( diff --git a/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts b/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts index c837f5b73..d10f6adba 100644 --- a/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/markers/problem-contribution.ts @@ -6,15 +6,15 @@ import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/ @injectable() export class ProblemContribution extends TheiaProblemContribution { - async initializeLayout(app: FrontendApplication): Promise { + override async initializeLayout(app: FrontendApplication): Promise { // NOOP } - protected setStatusBarElement(problemStat: ProblemStat): void { + protected override setStatusBarElement(problemStat: ProblemStat): void { // NOOP } - registerKeybindings(keybindings: KeybindingRegistry): void { + override registerKeybindings(keybindings: KeybindingRegistry): void { if (this.toggleCommand) { keybindings.registerKeybinding({ command: this.toggleCommand.id, diff --git a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts index 3b3912464..0e830fd54 100644 --- a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts +++ b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts @@ -17,7 +17,7 @@ export class ProblemManager extends TheiaProblemManager { protected dataDirUri: URI | undefined; @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.configService .getConfiguration() @@ -27,7 +27,7 @@ export class ProblemManager extends TheiaProblemManager { ); } - setMarkers( + override setMarkers( uri: URI, owner: string, data: Diagnostic[] diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx index 0e3bc4d52..b4b83bc77 100644 --- a/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx +++ b/arduino-ide-extension/src/browser/theia/messages/notification-center-component.tsx @@ -7,7 +7,7 @@ import { codicon } from '@theia/core/lib/browser'; const PerfectScrollbar = require('react-perfect-scrollbar'); export class NotificationCenterComponent extends TheiaNotificationCenterComponent { - render(): React.ReactNode { + override render(): React.ReactNode { const empty = this.state.notifications.length === 0; const title = empty ? nls.localize( diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx index 9eaca094c..18b967836 100644 --- a/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx +++ b/arduino-ide-extension/src/browser/theia/messages/notification-component.tsx @@ -4,7 +4,7 @@ import { nls } from '@theia/core/lib/common'; import { codicon } from '@theia/core/lib/browser'; export class NotificationComponent extends TheiaNotificationComponent { - render(): React.ReactNode { + override render(): React.ReactNode { const { messageId, message, type, collapsed, expandable, source, actions } = this.props.notification; return ( diff --git a/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx b/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx index 6f46c8e49..393dd9b5a 100644 --- a/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx +++ b/arduino-ide-extension/src/browser/theia/messages/notification-toasts-component.tsx @@ -3,7 +3,7 @@ import { NotificationComponent } from './notification-component'; import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component'; export class NotificationToastsComponent extends TheiaNotificationToastsComponent { - render(): React.ReactNode { + override render(): React.ReactNode { return (
{ diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts index fa7ee0673..fca883087 100644 --- a/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-status-bar-contribution.ts @@ -3,7 +3,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from @injectable() export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution { - protected setConfigTabSizeWidget() {} + protected override setConfigTabSizeWidget() {} - protected setLineEndingWidget() {} + protected override setLineEndingWidget() {} } diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts index a7a36adc0..7a8ece561 100644 --- a/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts +++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-text-model-service.ts @@ -13,7 +13,7 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService { @inject(SketchesServiceClientImpl) protected readonly sketchesServiceClient: SketchesServiceClientImpl; - protected async createModel(resource: Resource): Promise { + protected override async createModel(resource: Resource): Promise { const factory = this.factories .getContributions() .find(({ scheme }) => resource.uri.scheme === scheme); @@ -33,7 +33,7 @@ export class MonacoTextModelService extends TheiaMonacoTextModelService { // https://github.com/eclipse-theia/theia/pull/8491 class SilentMonacoEditorModel extends MonacoEditorModel { - protected trace(loggable: Loggable): void { + protected override trace(loggable: Loggable): void { if (this.logger) { this.logger.trace((log: Log) => loggable((message, ...params) => @@ -46,24 +46,24 @@ class SilentMonacoEditorModel extends MonacoEditorModel { class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel { constructor( - protected readonly resource: Resource, - protected readonly m2p: MonacoToProtocolConverter, - protected readonly p2m: ProtocolToMonacoConverter, - protected readonly logger?: ILogger, - protected readonly editorPreferences?: EditorPreferences, + protected override readonly resource: Resource, + protected override readonly m2p: MonacoToProtocolConverter, + protected override readonly p2m: ProtocolToMonacoConverter, + protected override readonly logger?: ILogger, + protected override readonly editorPreferences?: EditorPreferences, protected readonly _readOnly?: boolean ) { super(resource, m2p, p2m, logger, editorPreferences); } - get readOnly(): boolean { + override get readOnly(): boolean { if (typeof this._readOnly === 'boolean') { return this._readOnly; } return this.resource.saveContents === undefined; } - protected setDirty(dirty: boolean): void { + protected override setDirty(dirty: boolean): void { if (this._readOnly === true) { // NOOP return; diff --git a/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts b/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts index 86207b82a..23d46aaa1 100644 --- a/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/navigator/navigator-contribution.ts @@ -13,14 +13,14 @@ import { WorkspacePreferences } from '@theia/workspace/lib/browser/workspace-pre export class FileNavigatorContribution extends TheiaFileNavigatorContribution { constructor( @inject(FileNavigatorPreferences) - protected readonly fileNavigatorPreferences: FileNavigatorPreferences, - @inject(OpenerService) protected readonly openerService: OpenerService, + protected override readonly fileNavigatorPreferences: FileNavigatorPreferences, + @inject(OpenerService) protected override readonly openerService: OpenerService, @inject(FileNavigatorFilter) - protected readonly fileNavigatorFilter: FileNavigatorFilter, + protected override readonly fileNavigatorFilter: FileNavigatorFilter, @inject(WorkspaceService) - protected readonly workspaceService: WorkspaceService, + protected override readonly workspaceService: WorkspaceService, @inject(WorkspacePreferences) - protected readonly workspacePreferences: WorkspacePreferences + protected override readonly workspacePreferences: WorkspacePreferences ) { super( fileNavigatorPreferences, @@ -32,11 +32,11 @@ export class FileNavigatorContribution extends TheiaFileNavigatorContribution { this.options.defaultWidgetOptions.rank = 1; } - async initializeLayout(app: FrontendApplication): Promise { + override async initializeLayout(app: FrontendApplication): Promise { // NOOP } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { super.registerKeybindings(registry); [WorkspaceCommands.FILE_RENAME, WorkspaceCommands.FILE_DELETE].forEach( registry.unregisterKeybinding.bind(registry) diff --git a/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts b/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts index d046f1cfc..b5b791bc4 100644 --- a/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts +++ b/arduino-ide-extension/src/browser/theia/navigator/navigator-tab-bar-decorator.ts @@ -8,11 +8,11 @@ import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@thei */ @injectable() export class NavigatorTabBarDecorator extends TheiaNavigatorTabBarDecorator { - onStart(): void { + override onStart(): void { // NOOP } - decorate(): WidgetDecoration.Data[] { + override decorate(): WidgetDecoration.Data[] { // Does not decorate anything. return []; } diff --git a/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts b/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts index 6dc60aba5..c0232d395 100644 --- a/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/outline/outline-contribution.ts @@ -12,7 +12,7 @@ export class OutlineViewContribution extends TheiaOutlineViewContribution { }; } - async initializeLayout(app: FrontendApplication): Promise { + override async initializeLayout(app: FrontendApplication): Promise { // NOOP } } diff --git a/arduino-ide-extension/src/browser/theia/output/output-channel.ts b/arduino-ide-extension/src/browser/theia/output/output-channel.ts index 15b9daff3..b928dce06 100644 --- a/arduino-ide-extension/src/browser/theia/output/output-channel.ts +++ b/arduino-ide-extension/src/browser/theia/output/output-channel.ts @@ -11,7 +11,7 @@ import { @injectable() export class OutputChannelManager extends TheiaOutputChannelManager { - getChannel(name: string): TheiaOutputChannel { + override getChannel(name: string): TheiaOutputChannel { const existing = this.channels.get(name); if (existing) { return existing; @@ -43,7 +43,7 @@ export class OutputChannelManager extends TheiaOutputChannelManager { } export class OutputChannel extends TheiaOutputChannel { - dispose(): void { + override dispose(): void { super.dispose(); if ((this as any).disposed) { const textModifyQueue: PQueue = (this as any).textModifyQueue; diff --git a/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts b/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts index 91971e3b5..cf0e524a6 100644 --- a/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/output/output-toolbar-contribution.ts @@ -8,7 +8,7 @@ import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@th @injectable() export class OutputToolbarContribution extends TheiaOutputToolbarContribution { - async registerToolbarItems(registry: TabBarToolbarRegistry): Promise { + override async registerToolbarItems(registry: TabBarToolbarRegistry): Promise { await super.registerToolbarItems(registry); // Why is it async? // It's a hack. Currently, it's not possible to unregister a toolbar contribution via API. ( diff --git a/arduino-ide-extension/src/browser/theia/output/output-widget.ts b/arduino-ide-extension/src/browser/theia/output/output-widget.ts index a975e6a5b..6d6b684d0 100644 --- a/arduino-ide-extension/src/browser/theia/output/output-widget.ts +++ b/arduino-ide-extension/src/browser/theia/output/output-widget.ts @@ -6,7 +6,7 @@ import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/out // Remove this module after ATL-222 and the Theia update. @injectable() export class OutputWidget extends TheiaOutputWidget { - protected onAfterShow(msg: Message): void { + protected override onAfterShow(msg: Message): void { super.onAfterShow(msg); this.onResize(Widget.ResizeMessage.UnknownSize); } diff --git a/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts b/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts index 09a79cf3f..1a5bdf12c 100644 --- a/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts +++ b/arduino-ide-extension/src/browser/theia/plugin-ext/output-channel-registry-main.ts @@ -7,9 +7,9 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl } f @injectable() export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMainImpl { @inject(CommandService) - protected readonly commandService: CommandService; + protected override readonly commandService: CommandService; - $append( + override $append( name: string, text: string, pluginInfo: PluginInfo @@ -21,17 +21,17 @@ export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMai return Promise.resolve(); } - $clear(name: string): PromiseLike { + override $clear(name: string): PromiseLike { this.commandService.executeCommand(OutputCommands.CLEAR.id, { name }); return Promise.resolve(); } - $dispose(name: string): PromiseLike { + override $dispose(name: string): PromiseLike { this.commandService.executeCommand(OutputCommands.DISPOSE.id, { name }); return Promise.resolve(); } - async $reveal(name: string, preserveFocus: boolean): Promise { + override async $reveal(name: string, preserveFocus: boolean): Promise { const options = { preserveFocus }; this.commandService.executeCommand(OutputCommands.SHOW.id, { name, @@ -39,7 +39,7 @@ export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMai }); } - $close(name: string): PromiseLike { + override $close(name: string): PromiseLike { this.commandService.executeCommand(OutputCommands.HIDE.id, { name }); return Promise.resolve(); } diff --git a/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts b/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts new file mode 100644 index 000000000..0dcb7af66 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/preferences/preference-tree-generator.ts @@ -0,0 +1,17 @@ +import { CompositeTreeNode } from '@theia/core/lib/browser/tree/tree'; +import { injectable } from '@theia/core/shared/inversify'; +import { PreferenceTreeGenerator as TheiaPreferenceTreeGenerator } from '@theia/preferences/lib/browser/util/preference-tree-generator'; + +@injectable() +export class PreferenceTreeGenerator extends TheiaPreferenceTreeGenerator { + protected override async init(): Promise { + // The IDE2 does not use the default Theia preferences UI. + // There is no need to create and keep the the tree model synchronized when there is no UI for it. + } + + // Just returns with the empty root. + override generateTree(): CompositeTreeNode { + this._root = this.createRootNode(); + return this._root; + } +} diff --git a/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts b/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts index 4fdd770b3..4aaf87e97 100644 --- a/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/preferences/preferences-contribution.ts @@ -6,7 +6,7 @@ import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/ @injectable() export class PreferencesContribution extends TheiaPreferencesContribution { - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); // The settings group: preferences, CLI config is not part of the `File` menu on macOS. // On Windows and Linux, we rebind it to `Preferences...`. It is safe to remove here. @@ -16,7 +16,7 @@ export class PreferencesContribution extends TheiaPreferencesContribution { ); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id); } } diff --git a/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts b/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts index 47cad0ea9..cc759db5b 100644 --- a/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/scm/scm-contribution.ts @@ -4,11 +4,11 @@ import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar'; @injectable() export class ScmContribution extends TheiaScmContribution { - async initializeLayout(): Promise { + override async initializeLayout(): Promise { // NOOP } - protected setStatusBarEntry(id: string, entry: StatusBarEntry): void { + protected override setStatusBarEntry(id: string, entry: StatusBarEntry): void { // NOOP } } diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts index 81d35d0be..34afd4d22 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-factory.ts @@ -8,7 +8,7 @@ import { @injectable() export class SearchInWorkspaceFactory extends TheiaSearchInWorkspaceFactory { - async createWidget(): Promise { + override async createWidget(): Promise { const viewContainer = await super.createWidget(); viewContainer.setTitleOptions({ ...SEARCH_VIEW_CONTAINER_TITLE_OPTIONS, diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts index cda7ebc23..2204848fa 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-frontend-contribution.ts @@ -13,12 +13,12 @@ export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspac this.options.defaultWidgetOptions.rank = 5; } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET); } - registerKeybindings(keybindings: KeybindingRegistry): void { + override registerKeybindings(keybindings: KeybindingRegistry): void { super.registerKeybindings(keybindings); keybindings.unregisterKeybinding(SearchInWorkspaceCommands.OPEN_SIW_WIDGET); } diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts index 5362be736..e831cd402 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-result-tree-widget.ts @@ -11,7 +11,7 @@ import { MEMORY_TEXT } from '@theia/core/lib/common/resource'; */ @injectable() export class SearchInWorkspaceResultTreeWidget extends TheiaSearchInWorkspaceResultTreeWidget { - protected async createReplacePreview( + protected override async createReplacePreview( node: SearchInWorkspaceFileNode ): Promise { const fileUri = new URI(node.fileUri).withScheme('file'); diff --git a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx index 58c35f5f6..cae633024 100644 --- a/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx +++ b/arduino-ide-extension/src/browser/theia/search-in-workspace/search-in-workspace-widget.tsx @@ -9,12 +9,12 @@ import { SearchInWorkspaceWidget as TheiaSearchInWorkspaceWidget } from '@theia/ @injectable() export class SearchInWorkspaceWidget extends TheiaSearchInWorkspaceWidget { @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.title.iconClass = 'fa fa-arduino-search'; } - protected renderGlobField(kind: 'include' | 'exclude'): React.ReactNode { + protected override renderGlobField(kind: 'include' | 'exclude'): React.ReactNode { const currentValue = this.searchInWorkspaceOptions[kind]; const value = (currentValue && currentValue.join(', ')) || ''; return ( diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts index 34b64ab43..5b864732b 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-commands.ts @@ -12,7 +12,10 @@ import { } from '@theia/workspace/lib/browser/workspace-commands'; import { Sketch, SketchesService } from '../../../common/protocol'; import { WorkspaceInputDialog } from './workspace-input-dialog'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { SaveAsSketch } from '../../contributions/save-as-sketch'; import { SingleTextInputDialog } from '@theia/core/lib/browser'; import { nls } from '@theia/core/lib/common'; @@ -28,7 +31,7 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut @inject(SketchesService) protected readonly sketchService: SketchesService; - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { super.registerCommands(registry); registry.unregisterCommand(WorkspaceCommands.NEW_FILE); registry.registerCommand( @@ -75,7 +78,7 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut } } - protected async validateFileName( + protected override async validateFileName( name: string, parent: FileStat, recursive = false @@ -129,15 +132,15 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut return; } const sketch = await this.sketchesServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } // file belongs to another sketch, do not allow rename - const parentsketch = await this.sketchService.getSketchFolder( + const parentSketch = await this.sketchService.getSketchFolder( uri.toString() ); - if (parentsketch && parentsketch.uri !== sketch.uri) { + if (parentSketch && parentSketch.uri !== sketch.uri) { return; } diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts index 47f93232e..e3461c379 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-delete-handler.ts @@ -2,7 +2,10 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import * as remote from '@theia/core/electron-shared/@electron/remote'; import URI from '@theia/core/lib/common/uri'; import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { nls } from '@theia/core/lib/common'; @injectable() @@ -10,9 +13,9 @@ export class WorkspaceDeleteHandler extends TheiaWorkspaceDeleteHandler { @inject(SketchesServiceClientImpl) protected readonly sketchesServiceClient: SketchesServiceClientImpl; - async execute(uris: URI[]): Promise { + override async execute(uris: URI[]): Promise { const sketch = await this.sketchesServiceClient.currentSketch(); - if (!sketch) { + if (!CurrentSketch.isValid(sketch)) { return; } // Deleting the main sketch file. diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts index 63f9eb034..4574fedfc 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts @@ -10,7 +10,7 @@ import { WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution } f @injectable() export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContribution { - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { super.registerCommands(registry); // TODO: instead of blacklisting commands to remove, it would be more robust to whitelist the ones we want to keep const commands = new Set(registry.commands); @@ -28,9 +28,9 @@ export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContrib .forEach(registry.unregisterCommand.bind(registry)); } - registerMenus(_: MenuModelRegistry): void {} + override registerMenus(_: MenuModelRegistry): void {} - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { super.registerKeybindings(registry); [ WorkspaceCommands.NEW_FILE, @@ -44,7 +44,7 @@ export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContrib @injectable() export class ArduinoFileMenuContribution extends FileMenuContribution { - registerMenus(_: MenuModelRegistry): void { + override registerMenus(_: MenuModelRegistry): void { // NOOP } } diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts index af5a99dbe..d70d7e27d 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-input-dialog.ts @@ -13,8 +13,8 @@ export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog { constructor( @inject(WorkspaceInputDialogProps) - protected readonly props: WorkspaceInputDialogProps, - @inject(LabelProvider) protected readonly labelProvider: LabelProvider + protected override readonly props: WorkspaceInputDialogProps, + @inject(LabelProvider) protected override readonly labelProvider: LabelProvider ) { super(props, labelProvider); this.appendCloseButton( @@ -22,18 +22,18 @@ export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog { ); } - protected appendParentPath(): void { + protected override appendParentPath(): void { // NOOP } - isValid(value: string, mode: DialogMode): MaybePromise { + override isValid(value: string, mode: DialogMode): MaybePromise { if (value !== '') { this.wasTouched = true; } return super.isValid(value, mode); } - protected setErrorMessage(error: DialogError): void { + protected override setErrorMessage(error: DialogError): void { if (this.acceptButton) { this.acceptButton.disabled = !DialogError.getResult(error); } diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts index 958ae0a6f..27f08f7f3 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts @@ -7,19 +7,16 @@ import { MessageService } from '@theia/core/lib/common/message-service'; import { ApplicationServer } from '@theia/core/lib/common/application-protocol'; import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; import { FocusTracker, Widget } from '@theia/core/lib/browser'; +import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { ConfigService } from '../../../common/protocol/config-service'; import { SketchesService, Sketch, - SketchContainer, } from '../../../common/protocol/sketches-service'; -import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver'; import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { BoardsConfig } from '../../boards/boards-config'; -import { nls } from '@theia/core/lib/common'; -import { URI as VSCodeUri } from '@theia/core/shared/vscode-uri'; @injectable() export class WorkspaceService extends TheiaWorkspaceService { @@ -30,10 +27,10 @@ export class WorkspaceService extends TheiaWorkspaceService { protected readonly configService: ConfigService; @inject(LabelProvider) - protected readonly labelProvider: LabelProvider; + protected override readonly labelProvider: LabelProvider; @inject(MessageService) - protected readonly messageService: MessageService; + protected override readonly messageService: MessageService; @inject(ApplicationServer) protected readonly applicationServer: ApplicationServer; @@ -44,12 +41,9 @@ export class WorkspaceService extends TheiaWorkspaceService { @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; - private application: FrontendApplication; - private workspaceUri?: Promise; private version?: string; async onStart(application: FrontendApplication): Promise { - this.application = application; const info = await this.applicationServer.getApplicationInfo(); this.version = info?.version; application.shell.onDidChangeCurrentWidget( @@ -61,53 +55,80 @@ export class WorkspaceService extends TheiaWorkspaceService { this.onCurrentWidgetChange({ newValue, oldValue: null }); } - protected getDefaultWorkspaceUri(): Promise { - if (this.workspaceUri) { - // Avoid creating a new sketch twice - return this.workspaceUri; + // Was copied from the Theia implementation. + // Unlike the default behavior, IDE2 does not check the existence of the workspace before open. + protected override async doGetDefaultWorkspaceUri(): Promise< + string | undefined + > { + // If an empty window is explicitly requested do not restore a previous workspace. + // Note: `window.location.hash` includes leading "#" if non-empty. + if (window.location.hash === `#${DEFAULT_WINDOW_HASH}`) { + window.location.hash = ''; + return undefined; + } + + // Prefer the workspace path specified as the URL fragment, if present. + if (window.location.hash.length > 1) { + // Remove the leading # and decode the URI. + const wpPath = decodeURI(window.location.hash.substring(1)); + const workspaceUri = new URI().withPath(wpPath).withScheme('file'); + // ### Customization! Here, we do no check if the workspace exists. + return workspaceUri.toString(); + } else { + // Else, ask the server for its suggested workspace (usually the one + // specified on the CLI, or the most recent). + // ### Customization! the default workspace server will create a new sketch and will return with its URI if no recent workspaces are available. + return this.server.getMostRecentlyUsedWorkspace(); } - this.workspaceUri = (async () => { - try { - const hash = window.location.hash; - const [recentWorkspacesPaths, recentSketches] = await Promise.all([ - this.server.getRecentWorkspaces(), - this.sketchService - .getSketches({}) - .then((container) => - SketchContainer.toArray(container).map((s) => s.uri) - ), - ]); - // On Dindows, `getRecentWorkspaces` returns only file paths, not URIs as expected by the `isValid` method. - const recentWorkspaces = recentWorkspacesPaths.map((e) => - VSCodeUri.file(e).toString() - ); - const toOpen = await new ArduinoWorkspaceRootResolver({ - isValid: this.isValid.bind(this), - }).resolve({ hash, recentWorkspaces, recentSketches }); - if (toOpen) { - const { uri } = toOpen; - await this.server.setMostRecentlyUsedWorkspace(uri); - return toOpen.uri; - } - return (await this.sketchService.createNewSketch()).uri; - } catch (err) { - this.appStateService - .reachedState('ready') - .then(() => this.application.shell.update()); - this.logger.fatal(`Failed to determine the sketch directory: ${err}`); - this.messageService.error( - nls.localize( - 'theia/workspace/sketchDirectoryError', - 'There was an error creating the sketch directory. See the log for more details. The application will probably not work as expected.' - ) - ); - return super.getDefaultWorkspaceUri(); - } - })(); - return this.workspaceUri; } - protected openNewWindow(workspacePath: string): void { + // protected override getDefaultWorkspaceUri(): Promise { + // if (this.workspaceUri) { + // // Avoid creating a new sketch twice + // return this.workspaceUri; + // } + // this.workspaceUri = (async () => { + // try { + // const hash = window.location.hash; + // const [recentWorkspacesPaths, recentSketches] = await Promise.all([ + // this.server.getRecentWorkspaces(), + // this.sketchService + // .getSketches({}) + // .then((container) => + // SketchContainer.toArray(container).map((s) => s.uri) + // ), + // ]); + // // On Dindows, `getRecentWorkspaces` returns only file paths, not URIs as expected by the `isValid` method. + // const recentWorkspaces = recentWorkspacesPaths.map((e) => + // VSCodeUri.file(e).toString() + // ); + // const toOpen = await new ArduinoWorkspaceRootResolver({ + // isValid: this.isValid.bind(this), + // }).resolve({ hash, recentWorkspaces, recentSketches }); + // if (toOpen) { + // const { uri } = toOpen; + // await this.server.setMostRecentlyUsedWorkspace(uri); + // return toOpen.uri; + // } + // return (await this.sketchService.createNewSketch()).uri; + // } catch (err) { + // this.appStateService + // .reachedState('ready') + // .then(() => this.application.shell.update()); + // this.logger.fatal(`Failed to determine the sketch directory: ${err}`); + // this.messageService.error( + // nls.localize( + // 'theia/workspace/sketchDirectoryError', + // 'There was an error creating the sketch directory. See the log for more details. The application will probably not work as expected.' + // ) + // ); + // return super.getDefaultWorkspaceUri(); + // } + // })(); + // return this.workspaceUri; + // } + + protected override openNewWindow(workspacePath: string): void { const { boardsConfig } = this.boardsServiceProvider; const url = BoardsConfig.Config.setConfig( boardsConfig, @@ -117,13 +138,13 @@ export class WorkspaceService extends TheiaWorkspaceService { this.windowService.openNewWindow(url.toString()); } - private async isValid(uri: string): Promise { - const exists = await this.fileService.exists(new URI(uri)); - if (!exists) { - return false; - } - return this.sketchService.isSketchFolder(uri); - } + // private async isValid(uri: string): Promise { + // const exists = await this.fileService.exists(new URI(uri)); + // if (!exists) { + // return false; + // } + // return this.sketchService.isSketchFolder(uri); + // } protected onCurrentWidgetChange({ newValue, @@ -146,7 +167,7 @@ export class WorkspaceService extends TheiaWorkspaceService { } } - protected formatTitle(title?: string): string { + protected override formatTitle(title?: string): string { const version = this.version ? ` ${this.version}` : ''; const name = `${this.applicationName} ${version}`; return title ? `${title} | ${name}` : name; diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts index a91949ba5..1ca0b7b85 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-variable-contribution.ts @@ -1,8 +1,15 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { WorkspaceVariableContribution as TheiaWorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution'; import { Sketch } from '../../../common/protocol'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; @injectable() export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContribution { @@ -12,14 +19,15 @@ export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContrib protected currentSketch?: Sketch; @postConstruct() - protected init(): void { - this.sketchesServiceClient - .currentSketch() - .then() - .then((sketch) => (this.currentSketch = sketch)); + protected override init(): void { + this.sketchesServiceClient.currentSketch().then((sketch) => { + if (CurrentSketch.isValid(sketch)) { + this.currentSketch = sketch; + } + }); } - getResourceUri(): URI | undefined { + override getResourceUri(): URI | undefined { const resourceUri = super.getResourceUri(); // https://github.com/arduino/arduino-ide/issues/46 // `currentWidget` can be an editor representing a file outside of the workspace. The current sketch should be a fallback. diff --git a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts index 9180be90e..09c125e9a 100644 --- a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts +++ b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar-contribution.ts @@ -19,7 +19,7 @@ export class ArduinoToolbarContainer extends Widget { this.toolbars = toolbars; } - onAfterAttach(msg: Message) { + override onAfterAttach(msg: Message) { for (const toolbar of this.toolbars) { Widget.attach(toolbar, this.node); } diff --git a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx index d4abffbb5..c1a656206 100644 --- a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx +++ b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx @@ -71,7 +71,7 @@ export class ArduinoToolbarComponent extends React.Component< ); }; - render(): React.ReactNode { + override render(): React.ReactNode { const tooltip = (
{this.state.tooltip} diff --git a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx index bccc4cef6..4ee84e82a 100644 --- a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx +++ b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx @@ -9,7 +9,7 @@ export class ArduinoSelect extends Select { super(props); } - render(): React.ReactNode { + override render(): React.ReactNode { const controlHeight = 27; // from `monitor.css` -> `.serial-monitor-container .head` (`height: 27px;`) const styles: Styles = { control: (styles) => ({ diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx index cc10e34b1..9661c02fe 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-composite-widget.tsx @@ -43,7 +43,7 @@ export class CloudSketchbookCompositeWidget extends BaseWidget { return this.cloudSketchbookTreeWidget; } - protected onAfterAttach(message: Message): void { + protected override onAfterAttach(message: Message): void { super.onAfterAttach(message); Widget.attach(this.cloudSketchbookTreeWidget, this.compositeNode); ReactDOM.render( @@ -58,7 +58,7 @@ export class CloudSketchbookCompositeWidget extends BaseWidget { ); } - protected onResize(message: Widget.ResizeMessage): void { + protected override onResize(message: Widget.ResizeMessage): void { super.onResize(message); MessageLoop.sendMessage( this.cloudSketchbookTreeWidget, diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts index 163a45131..85e703554 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts @@ -23,7 +23,7 @@ import { } from '@theia/core/lib/browser/preferences/preference-service'; import { ArduinoMenus, PlaceholderMenuNode } from '../../menu/arduino-menus'; import { SketchbookCommands } from '../sketchbook/sketchbook-commands'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; import { Contribution } from '../../contributions/contribution'; import { ArduinoPreferences } from '../../arduino-preferences'; import { MainMenuManager } from '../../../common/main-menu-manager'; @@ -149,7 +149,7 @@ export class CloudSketchbookContribution extends Contribution { protected readonly toDisposeBeforeNewContextMenu = new DisposableCollection(); - registerMenus(menus: MenuModelRegistry): void { + override registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, { commandId: CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK.id, label: CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK.label, @@ -157,7 +157,7 @@ export class CloudSketchbookContribution extends Contribution { }); } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { registry.registerCommand(CloudSketchbookCommands.TOGGLE_CLOUD_SKETCHBOOK, { execute: () => { this.preferenceService.set( @@ -279,7 +279,8 @@ export class CloudSketchbookContribution extends Contribution { // disable the "open sketch" command for the current sketch and for those not in sync if ( !CloudSketchbookTree.CloudSketchTreeNode.isSynced(arg.node) || - (currentSketch && currentSketch.uri === arg.node.uri.toString()) + (CurrentSketch.isValid(currentSketch) && + currentSketch.uri === arg.node.uri.toString()) ) { const placeholder = new PlaceholderMenuNode( SKETCHBOOKSYNC__CONTEXT__MAIN_GROUP, diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts index 708e2ec1b..d76c7497f 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-model.ts @@ -53,7 +53,7 @@ export function sketchesToFileStats(sketches: Create.Sketch[]): FileStat[] { @injectable() export class CloudSketchbookTreeModel extends SketchbookTreeModel { @inject(FileService) - protected readonly fileService: FileService; + protected override readonly fileService: FileService; @inject(AuthenticationClientService) protected readonly authenticationService: AuthenticationClientService; @@ -65,7 +65,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { protected readonly cloudSketchbookTree: CloudSketchbookTree; @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + protected override readonly arduinoPreferences: ArduinoPreferences; @inject(LocalCacheFsProvider) protected readonly localCacheFsProvider: LocalCacheFsProvider; @@ -74,14 +74,14 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { protected readonly sketchCache: SketchCache; @postConstruct() - protected init(): void { + protected override init(): void { super.init(); this.toDispose.push( this.authenticationService.onSessionDidChange(() => this.updateRoot()) ); } - async createRoot(): Promise { + override async createRoot(): Promise { const { session } = this.authenticationService; if (!session) { this.tree.root = undefined; @@ -108,7 +108,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { return this.tree as CloudSketchbookTree; } - protected recursivelyFindSketchRoot(node: TreeNode): any { + protected override recursivelyFindSketchRoot(node: TreeNode): any { if (node && CloudSketchbookTree.CloudSketchDirNode.is(node)) { return node; } @@ -121,7 +121,7 @@ export class CloudSketchbookTreeModel extends SketchbookTreeModel { return false; } - async revealFile(uri: URI): Promise { + override async revealFile(uri: URI): Promise { // we use remote uris as keys for the tree // convert local URIs const remoteuri = this.localCacheFsProvider.from(uri); diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx index 523700aed..043dfea86 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree-widget.tsx @@ -28,18 +28,18 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { protected readonly cloudSketchbookTree: CloudSketchbookTree; @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { await super.init(); this.addClass('tree-container'); // Adds `height: 100%` to the tree. Otherwise you cannot see it. } - protected renderTree(model: TreeModel): React.ReactNode { + protected override renderTree(model: TreeModel): React.ReactNode { if (this.shouldShowWelcomeView()) return this.renderViewWelcome(); if (this.shouldShowEmptyView()) return this.renderEmptyView(); return super.renderTree(model); } - protected renderEmptyView() { + protected renderEmptyView(): React.ReactNode { return (
@@ -71,7 +71,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { ); } - protected shouldShowWelcomeView(): boolean { + protected override shouldShowWelcomeView(): boolean { if (!this.model || this.model instanceof CloudSketchbookTreeModel) { return !this.authenticationService.session; } @@ -83,7 +83,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { return CompositeTreeNode.is(node) && node.children.length === 0; } - protected createNodeClassNames(node: any, props: NodeProps): string[] { + protected override createNodeClassNames(node: any, props: NodeProps): string[] { const classNames = super.createNodeClassNames(node, props); if ( @@ -97,7 +97,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { return classNames; } - protected renderInlineCommands(node: any): React.ReactNode { + protected override renderInlineCommands(node: any): React.ReactNode { if (CloudSketchbookTree.CloudSketchDirNode.is(node) && node.commands) { return Array.from(new Set(node.commands)).map((command) => this.renderInlineCommand(command.id, node, { @@ -108,7 +108,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { return undefined; } - protected renderViewWelcome(): React.ReactNode { + protected override renderViewWelcome(): React.ReactNode { return (
@@ -151,7 +151,7 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget { ); } - protected handleDblClickEvent( + protected override handleDblClickEvent( node: TreeNode, event: React.MouseEvent ): void { diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts index 4f4986bc2..7204df632 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts @@ -47,7 +47,7 @@ type FilesToSync = { @injectable() export class CloudSketchbookTree extends SketchbookTree { @inject(FileService) - protected readonly fileService: FileService; + protected override readonly fileService: FileService; @inject(LocalCacheFsProvider) protected readonly localCacheFsProvider: LocalCacheFsProvider; @@ -56,7 +56,7 @@ export class CloudSketchbookTree extends SketchbookTree { protected readonly sketchCache: SketchCache; @inject(ArduinoPreferences) - protected readonly arduinoPreferences: ArduinoPreferences; + protected override readonly arduinoPreferences: ArduinoPreferences; @inject(PreferenceService) protected readonly preferenceService: PreferenceService; @@ -315,7 +315,7 @@ export class CloudSketchbookTree extends SketchbookTree { return { filesToWrite, filesToDelete }; } - async refresh( + override async refresh( node?: CompositeTreeNode ): Promise { if (node) { @@ -375,7 +375,7 @@ export class CloudSketchbookTree extends SketchbookTree { ); } - async resolveChildren(parent: CompositeTreeNode): Promise { + override async resolveChildren(parent: CompositeTreeNode): Promise { return (await super.resolveChildren(parent)).sort((a, b) => { if ( WorkspaceNode.is(parent) && @@ -403,12 +403,12 @@ export class CloudSketchbookTree extends SketchbookTree { } /** - * Retrieve fileStats for the given node, merging the local and remote childrens + * Retrieve fileStats for the given node, merging the local and remote children * Local children take precedence over remote ones * @param node * @returns */ - protected async resolveFileStat( + protected override async resolveFileStat( node: FileStatNode ): Promise { if ( @@ -470,7 +470,7 @@ export class CloudSketchbookTree extends SketchbookTree { } } - protected toNode( + protected override toNode( fileStat: any, parent: CompositeTreeNode ): FileNode | DirNode { @@ -529,7 +529,7 @@ export class CloudSketchbookTree extends SketchbookTree { * @param node * @returns */ - protected async augmentSketchNode(node: DirNode): Promise { + protected override async augmentSketchNode(node: DirNode): Promise { const sketch = this.sketchCache.getSketch( node.fileStat.resource.path.toString() ); @@ -582,7 +582,7 @@ export class CloudSketchbookTree extends SketchbookTree { return node; } - protected async decorateNode( + protected override async decorateNode( node: TreeNode, showAllFiles: boolean ): Promise { @@ -592,7 +592,7 @@ export class CloudSketchbookTree extends SketchbookTree { return node; } - protected async isSketchNode(node: DirNode): Promise { + protected override async isSketchNode(node: DirNode): Promise { if (DirNode.is(node)) { const sketch = this.sketchCache.getSketch( node.fileStat.resource.path.toString() diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts index 7cf71bfe9..22239a227 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-widget.ts @@ -12,11 +12,11 @@ export class CloudSketchbookWidget extends SketchbookWidget { protected readonly arduinoPreferences: ArduinoPreferences; @postConstruct() - protected init(): void { + protected override init(): void { super.init(); } - getTreeWidget(): any { + override getTreeWidget(): any { const widget: any = this.sketchbookTreesContainer.selectedWidgets().next(); if (widget && typeof widget.getTreeWidget !== 'undefined') { @@ -36,7 +36,7 @@ export class CloudSketchbookWidget extends SketchbookWidget { this.setDocumentMode(); } - setDocumentMode() { + setDocumentMode(): void { if (this.arduinoPreferences['arduino.cloud.enabled']) { this.sketchbookTreesContainer.mode = 'multiple-document'; } else { @@ -44,7 +44,7 @@ export class CloudSketchbookWidget extends SketchbookWidget { } } - protected onAfterAttach(msg: any) { + protected override onAfterAttach(msg: any): void { this.sketchbookTreesContainer.addWidget(this.widget); this.setDocumentMode(); this.arduinoPreferences.onPreferenceChanged((event) => { diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx index 1157492ef..0db8dce92 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-user-status.tsx @@ -24,7 +24,7 @@ export class UserStatus extends React.Component< }; } - componentDidMount(): void { + override componentDidMount(): void { const statusListener = () => this.setState({ status: this.status }); window.addEventListener('online', statusListener); window.addEventListener('offline', statusListener); @@ -41,11 +41,11 @@ export class UserStatus extends React.Component< ]); } - componentWillUnmount(): void { + override componentWillUnmount(): void { this.toDispose.dispose(); } - render(): React.ReactNode { + override render(): React.ReactNode { if (!this.props.authenticationService.session) { return null; } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx index a0ebaf44d..39c0a2ce0 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx @@ -43,7 +43,7 @@ export class ComponentListItem< this.setState({ selectedVersion: version }); } - render(): React.ReactNode { + override render(): React.ReactNode { const { item, itemRenderer } = this.props; return itemRenderer.renderItem( Object.assign(this.state, { item }), diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx index 9b212cc2b..42dce70b8 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx @@ -9,7 +9,7 @@ export class ComponentList extends React.Component< > { protected container?: HTMLElement; - render(): React.ReactNode { + override render(): React.ReactNode { return (
{this.props.items.map((item) => this.createItem(item))} @@ -17,7 +17,7 @@ export class ComponentList extends React.Component< ); } - componentDidMount(): void { + override componentDidMount(): void { if (this.container && this.props.resolveContainer) { this.props.resolveContainer(this.container); } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx index ee4882ba7..0fad3ac61 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx @@ -28,19 +28,19 @@ export class FilterableListContainer< }; } - componentDidMount(): void { + override componentDidMount(): void { this.search = debounce(this.search, 500); this.handleFilterTextChange(''); this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this)); } - componentDidUpdate(): void { + override componentDidUpdate(): void { // See: arduino/arduino-pro-ide#101 // Resets the top of the perfect scroll-bar's thumb. this.props.container.updateScrollBar(); } - render(): React.ReactNode { + override render(): React.ReactNode { return (
{this.renderSearchFilter()} diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts index 52e95a116..ed9827919 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts @@ -11,7 +11,7 @@ export abstract class ListWidgetFrontendContribution { async initializeLayout(): Promise {} - registerMenus(): void { + override registerMenus(): void { // NOOP } } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx index c19440d27..f28db5d5b 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx @@ -21,7 +21,7 @@ import { NotificationCenter } from '../../notification-center'; @injectable() export abstract class ListWidget< T extends ArduinoComponent -> extends ReactWidget { + > extends ReactWidget { @inject(MessageService) protected readonly messageService: MessageService; @@ -42,6 +42,11 @@ export abstract class ListWidget< protected readonly filterTextChangeEmitter = new Emitter< string | undefined >(); + /** + * Instead of running an `update` from the `postConstruct` `init` method, + * we use this variable to track first activate, then run. + */ + protected firstActivate = true; constructor(protected options: ListWidget.Options) { super(); @@ -61,7 +66,6 @@ export abstract class ListWidget< @postConstruct() protected init(): void { - this.update(); this.toDispose.pushAll([ this.notificationCenter.onIndexUpdated(() => this.refresh(undefined)), this.notificationCenter.onDaemonStarted(() => this.refresh(undefined)), @@ -69,21 +73,34 @@ export abstract class ListWidget< ]); } - protected getScrollContainer(): MaybePromise { + protected override getScrollContainer(): MaybePromise { return this.deferredContainer.promise; } - protected onActivateRequest(message: Message): void { + protected override onAfterShow(message: Message): void { + this.maybeUpdateOnFirstRender(); + super.onAfterShow(message); + } + + private maybeUpdateOnFirstRender() { + if (this.firstActivate) { + this.firstActivate = false; + this.update(); + } + } + + protected override onActivateRequest(message: Message): void { + this.maybeUpdateOnFirstRender(); super.onActivateRequest(message); (this.focusNode || this.node).focus(); } - protected onUpdateRequest(message: Message): void { + protected override onUpdateRequest(message: Message): void { super.onUpdateRequest(message); this.render(); } - protected onResize(message: Widget.ResizeMessage): void { + protected override onResize(message: Widget.ResizeMessage): void { super.onResize(message); this.updateScrollBar(); } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx b/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx index 81aeed196..cc9630989 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/search-bar.tsx @@ -7,7 +7,7 @@ export class SearchBar extends React.Component { this.handleFilterTextChange = this.handleFilterTextChange.bind(this); } - render(): React.ReactNode { + override render(): React.ReactNode { return ( { + override *getNodesByUri(uri: URI): IterableIterator { const workspace = this.root; if (WorkspaceNode.is(workspace)) { for (const root of workspace.children) { @@ -183,7 +183,7 @@ export class SketchbookTreeModel extends FileTreeModel { /** * Move the given source file or directory to the given target directory. */ - async move(source: TreeNode, target: TreeNode): Promise { + override async move(source: TreeNode, target: TreeNode): Promise { if (source.parent && WorkspaceRootNode.is(source)) { // do not support moving a root folder return undefined; @@ -250,7 +250,7 @@ export class SketchbookTreeModel extends FileTreeModel { // selectNode gets called when the user single-clicks on an item // when this happens, we want to open the file if it belongs to the currently open sketch - async selectNode(node: Readonly): Promise { + override async selectNode(node: Readonly): Promise { super.selectNode(node); if (FileNode.is(node) && (await this.isFileInsideCurrentSketch(node))) { this.open(node.uri); @@ -264,7 +264,7 @@ export class SketchbookTreeModel extends FileTreeModel { }); } - protected async doOpenNode(node: TreeNode): Promise { + protected override async doOpenNode(node: TreeNode): Promise { // if it's a sketch dir, or a file from another sketch, open in new window if (!(await this.isFileInsideCurrentSketch(node))) { const sketchRoot = this.recursivelyFindSketchRoot(node); @@ -294,7 +294,10 @@ export class SketchbookTreeModel extends FileTreeModel { // check if the node is a file that belongs to another sketch const sketch = await this.sketchServiceClient.currentSketch(); - if (sketch && node.uri.toString().indexOf(sketch.uri) !== 0) { + if ( + CurrentSketch.isValid(sketch) && + node.uri.toString().indexOf(sketch.uri) !== 0 + ) { return false; } return true; diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx index 4b1535216..240156472 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree-widget.tsx @@ -14,7 +14,10 @@ import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-render import { SketchbookTree } from './sketchbook-tree'; import { SketchbookTreeModel } from './sketchbook-tree-model'; import { ArduinoPreferences } from '../../arduino-preferences'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { SelectableTreeNode } from '@theia/core/lib/browser/tree/tree-selection'; import { Sketch } from '../../contributions/contribution'; import { nls } from '@theia/core/lib/common'; @@ -33,10 +36,10 @@ export class SketchbookTreeWidget extends FileTreeWidget { protected currentSketchUri = ''; constructor( - @inject(TreeProps) readonly props: TreeProps, - @inject(SketchbookTreeModel) readonly model: SketchbookTreeModel, + @inject(TreeProps) override readonly props: TreeProps, + @inject(SketchbookTreeModel) override readonly model: SketchbookTreeModel, @inject(ContextMenuRenderer) - readonly contextMenuRenderer: ContextMenuRenderer, + override readonly contextMenuRenderer: ContextMenuRenderer, @inject(EditorManager) readonly editorManager: EditorManager ) { super(props, model, contextMenuRenderer); @@ -50,14 +53,14 @@ export class SketchbookTreeWidget extends FileTreeWidget { } @postConstruct() - protected async init(): Promise { + protected override async init(): Promise { super.init(); // cache the current open sketch uri const currentSketch = await this.sketchServiceClient.currentSketch(); - this.currentSketchUri = (currentSketch && currentSketch.uri) || ''; + this.currentSketchUri = (CurrentSketch.isValid(currentSketch) && currentSketch.uri) || ''; } - protected createNodeClassNames(node: TreeNode, props: NodeProps): string[] { + protected override createNodeClassNames(node: TreeNode, props: NodeProps): string[] { const classNames = super.createNodeClassNames(node, props); if ( @@ -70,7 +73,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { return classNames; } - protected renderIcon(node: TreeNode, props: NodeProps): React.ReactNode { + protected override renderIcon(node: TreeNode, props: NodeProps): React.ReactNode { if (SketchbookTree.SketchDirNode.is(node) || Sketch.isSketchFile(node.id)) { return
; } @@ -81,7 +84,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { return undefined; } - protected renderTailDecorations( + protected override renderTailDecorations( node: TreeNode, props: NodeProps ): React.ReactNode { @@ -99,7 +102,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { this.update(); } - protected createNodeAttributes( + protected override createNodeAttributes( node: TreeNode, props: NodeProps ): React.Attributes & React.HTMLAttributes { @@ -160,7 +163,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { return undefined; } - protected handleClickEvent( + protected override handleClickEvent( node: TreeNode | undefined, event: React.MouseEvent ): void { @@ -186,7 +189,7 @@ export class SketchbookTreeWidget extends FileTreeWidget { } } - protected doToggle(event: React.MouseEvent): void { + protected override doToggle(event: React.MouseEvent): void { const nodeId = event.currentTarget.getAttribute('data-node-id'); if (nodeId) { const node = this.model.getNode(nodeId); diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts index aac987f57..6726f12b6 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-tree.ts @@ -18,7 +18,7 @@ export class SketchbookTree extends FileNavigatorTree { @inject(ArduinoPreferences) protected readonly arduinoPreferences: ArduinoPreferences; - async resolveChildren(parent: CompositeTreeNode): Promise { + override async resolveChildren(parent: CompositeTreeNode): Promise { const showAllFiles = this.arduinoPreferences['arduino.sketchbook.showAllFiles']; diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts index 6c68ef55c..16b66a26a 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts @@ -23,7 +23,10 @@ import { Disposable, DisposableCollection, } from '@theia/core/lib/common/disposable'; -import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; +import { + CurrentSketch, + SketchesServiceClientImpl, +} from '../../../common/protocol/sketches-service-client-impl'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { URI } from '../../contributions/contribution'; @@ -95,7 +98,7 @@ export class SketchbookWidgetContribution return this.openView() as Promise; } - registerCommands(registry: CommandRegistry): void { + override registerCommands(registry: CommandRegistry): void { super.registerCommands(registry); registry.registerCommand(SketchbookCommands.OPEN_NEW_WINDOW, { @@ -142,7 +145,10 @@ export class SketchbookWidgetContribution // disable the "open sketch" command for the current sketch. // otherwise make the command clickable const currentSketch = await this.sketchServiceClient.currentSketch(); - if (currentSketch && currentSketch.uri === arg.node.uri.toString()) { + if ( + CurrentSketch.isValid(currentSketch) && + currentSketch.uri === arg.node.uri.toString() + ) { const placeholder = new PlaceholderMenuNode( SKETCHBOOK__CONTEXT__MAIN_GROUP, SketchbookCommands.OPEN_NEW_WINDOW.label! @@ -186,7 +192,7 @@ export class SketchbookWidgetContribution }); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); // unregister main menu action diff --git a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx index ce20b43b3..f0a427de7 100644 --- a/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx @@ -33,7 +33,7 @@ export class SketchbookWidget extends BaseWidget { this.sketchbookTreesContainer.addWidget(this.localSketchbookTreeWidget); } - protected onAfterAttach(message: Message): void { + protected override onAfterAttach(message: Message): void { super.onAfterAttach(message); Widget.attach(this.sketchbookTreesContainer, this.node); this.toDisposeOnDetach.push( @@ -45,7 +45,7 @@ export class SketchbookWidget extends BaseWidget { return this.localSketchbookTreeWidget; } - protected onActivateRequest(message: Message): void { + protected override onActivateRequest(message: Message): void { super.onActivateRequest(message); // TODO: focus the active sketchbook @@ -56,7 +56,7 @@ export class SketchbookWidget extends BaseWidget { this.node.focus(); } - protected onResize(message: Widget.ResizeMessage): void { + protected override onResize(message: Widget.ResizeMessage): void { super.onResize(message); MessageLoop.sendMessage( this.sketchbookTreesContainer, @@ -67,7 +67,7 @@ export class SketchbookWidget extends BaseWidget { } } - protected onAfterShow(msg: Message): void { + protected override onAfterShow(msg: Message): void { super.onAfterShow(msg); this.onResize(Widget.ResizeMessage.UnknownSize); } diff --git a/arduino-ide-extension/src/common/decorators.ts b/arduino-ide-extension/src/common/decorators.ts new file mode 100644 index 000000000..f02fb83d0 --- /dev/null +++ b/arduino-ide-extension/src/common/decorators.ts @@ -0,0 +1,64 @@ +import type { CancellationToken } from '@theia/core/lib/common/cancellation'; +import { default as stringifySafe } from 'fast-safe-stringify'; + +export interface DurationOptions { + /** + * If not specified, falls back to the `String()` value of the `PropertyKey`. + */ + name?: string; + + /** + * If the duration exceeds this timeout (in millis), then the duration will be logged as an error. + */ + timeout?: number; +} + +export function duration(options?: DurationOptions) { + return ( + _target: unknown, + key: PropertyKey, + descriptor: PropertyDescriptor + ): PropertyDescriptor => { + const original = descriptor.value; + descriptor.value = async function (...args: unknown[]) { + const input = args + .filter((arg) => !Boolean(isCancellationToken(arg))) + .map(stringify) + .join(','); + const start = performance.now(); + const result = await original.apply(this, args); + const end = performance.now(); + const duration = end - start; + const slow = duration > (options?.timeout ?? 100); + const message = `---- ${slow ? '!!!SLOW!!! ' : ''}DURATION: ${ + options?.name ?? String(key) + } took ${duration.toFixed(3)} ms. Args: [${input}] ----`; + if (slow) { + console.error(message); + } else { + console.info(message); + } + return result; + }; + return descriptor; + }; +} + +function stringify(arg: unknown): string { + try { + return JSON.stringify(arg); + } catch { + return stringifySafe(arg); + } +} + +// The cancellation token is implicitly the last arg of the JSON-RPC invocation. We want to filter it out from the logs. +// See: https://github.com/eclipse-theia/theia/issues/10129 +function isCancellationToken(arg: unknown): arg is CancellationToken { + return ( + typeof arg === 'object' && + arg !== null && + 'onCancellationRequested' in arg && + 'isCancellationRequested' in arg + ); +} diff --git a/arduino-ide-extension/src/common/protocol/arduino-daemon.ts b/arduino-ide-extension/src/common/protocol/arduino-daemon.ts index 783590048..696629923 100644 --- a/arduino-ide-extension/src/common/protocol/arduino-daemon.ts +++ b/arduino-ide-extension/src/common/protocol/arduino-daemon.ts @@ -1,6 +1,15 @@ export const ArduinoDaemonPath = '/services/arduino-daemon'; export const ArduinoDaemon = Symbol('ArduinoDaemon'); export interface ArduinoDaemon { - isRunning(): Promise; + /** + * Returns with a promise that resolves with the port + * of the CLI daemon when it's up and running. + */ getPort(): Promise; + /** + * Unlike `getPort` this method returns with a promise + * that resolves to `undefined` when the daemon is not running. + * Otherwise resolves to the CLI daemon port. + */ + tryGetPort(): Promise; } diff --git a/arduino-ide-extension/src/common/protocol/config-service.ts b/arduino-ide-extension/src/common/protocol/config-service.ts index b1c6285a1..adc5d9aa0 100644 --- a/arduino-ide-extension/src/common/protocol/config-service.ts +++ b/arduino-ide-extension/src/common/protocol/config-service.ts @@ -9,8 +9,6 @@ export interface ConfigService { getCliConfigFileUri(): Promise; getConfiguration(): Promise; setConfiguration(config: Config): Promise; - isInDataDir(uri: string): Promise; - isInSketchDir(uri: string): Promise; } export interface Daemon { @@ -115,10 +113,8 @@ export interface Config { readonly locale: string; readonly sketchDirUri: string; readonly dataDirUri: string; - readonly downloadsDirUri: string; readonly additionalUrls: AdditionalUrls; readonly network: Network; - readonly daemon: Daemon; } export namespace Config { export function sameAs(left: Config, right: Config): boolean { @@ -135,7 +131,6 @@ export namespace Config { return ( left.locale === right.locale && left.dataDirUri === right.dataDirUri && - left.downloadsDirUri === right.downloadsDirUri && left.sketchDirUri === right.sketchDirUri && Network.sameAs(left.network, right.network) ); diff --git a/arduino-ide-extension/src/common/protocol/notification-service.ts b/arduino-ide-extension/src/common/protocol/notification-service.ts index 59cef1886..3e33f727e 100644 --- a/arduino-ide-extension/src/common/protocol/notification-service.ts +++ b/arduino-ide-extension/src/common/protocol/notification-service.ts @@ -9,7 +9,7 @@ import { export interface NotificationServiceClient { notifyIndexUpdated(): void; - notifyDaemonStarted(): void; + notifyDaemonStarted(port: string): void; notifyDaemonStopped(): void; notifyConfigChanged(event: { config: Config | undefined }): void; notifyPlatformInstalled(event: { item: BoardsPackage }): void; diff --git a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts index 970032a1c..1e50729d6 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts @@ -10,16 +10,24 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { Sketch, SketchesService } from '../../common/protocol'; import { ConfigService } from './config-service'; -import { SketchContainer } from './sketches-service'; +import { SketchContainer, SketchRef } from './sketches-service'; import { ARDUINO_CLOUD_FOLDER, REMOTE_SKETCHBOOK_FOLDER, } from '../../browser/utils/constants'; import * as monaco from '@theia/monaco-editor-core'; +import { Deferred } from '@theia/core/lib/common/promise-util'; const READ_ONLY_FILES = ['sketch.json']; const READ_ONLY_FILES_REMOTE = ['thingProperties.h', 'thingsProperties.h']; +export type CurrentSketch = Sketch | 'invalid'; +export namespace CurrentSketch { + export function isValid(arg: CurrentSketch | undefined): arg is Sketch { + return !!arg && arg !== 'invalid'; + } +} + @injectable() export class SketchesServiceClientImpl implements FrontendApplicationContribution @@ -40,13 +48,16 @@ export class SketchesServiceClientImpl protected readonly configService: ConfigService; protected toDispose = new DisposableCollection(); - protected sketches = new Map(); + protected sketches = new Map(); + // TODO: rename this + event to the `onBlabla` pattern protected sketchbookDidChangeEmitter = new Emitter<{ - created: Sketch[]; - removed: Sketch[]; + created: SketchRef[]; + removed: SketchRef[]; }>(); readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event; + private _currentSketch = new Deferred(); + onStart(): void { this.configService.getConfiguration().then(({ sketchDirUri }) => { this.sketchService @@ -99,13 +110,16 @@ export class SketchesServiceClientImpl ); }); }); + this.loadCurrentSketch().then((currentSketch) => + this._currentSketch.resolve(currentSketch) + ); } onStop(): void { this.toDispose.dispose(); } - async currentSketch(): Promise { + private async loadCurrentSketch(): Promise { const sketches = ( await Promise.all( this.workspaceService @@ -116,7 +130,7 @@ export class SketchesServiceClientImpl ) ).filter(notEmpty); if (!sketches.length) { - return undefined; + return 'invalid'; } if (sketches.length > 1) { console.log( @@ -128,16 +142,14 @@ export class SketchesServiceClientImpl return sketches[0]; } + async currentSketch(): Promise { + return this._currentSketch.promise; + } + async currentSketchFile(): Promise { - const sketch = await this.currentSketch(); - if (sketch) { - const uri = sketch.mainFileUri; - const exists = await this.fileService.exists(new URI(uri)); - if (!exists) { - this.messageService.warn(`Could not find sketch file: ${uri}`); - return undefined; - } - return uri; + const currentSketch = await this.currentSketch(); + if (CurrentSketch.isValid(currentSketch)) { + return currentSketch.mainFileUri; } return undefined; } @@ -145,10 +157,10 @@ export class SketchesServiceClientImpl private fireSoonHandle?: number; private bufferedSketchbookEvents: { type: 'created' | 'removed'; - sketch: Sketch; + sketch: SketchRef; }[] = []; - private fireSoon(sketch: Sketch, type: 'created' | 'removed'): void { + private fireSoon(sketch: SketchRef, type: 'created' | 'removed'): void { this.bufferedSketchbookEvents.push({ type, sketch }); if (typeof this.fireSoonHandle === 'number') { @@ -156,7 +168,7 @@ export class SketchesServiceClientImpl } this.fireSoonHandle = window.setTimeout(() => { - const event: { created: Sketch[]; removed: Sketch[] } = { + const event: { created: SketchRef[]; removed: SketchRef[] } = { created: [], removed: [], }; diff --git a/arduino-ide-extension/src/common/protocol/sketches-service.ts b/arduino-ide-extension/src/common/protocol/sketches-service.ts index 06f979910..3b85af8cc 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service.ts @@ -81,9 +81,20 @@ export interface SketchesService { getIdeTempFolderUri(sketch: Sketch): Promise; } -export interface Sketch { +export interface SketchRef { readonly name: string; readonly uri: string; // `LocationPath` +} +export namespace SketchRef { + export function fromUri(uriLike: string | URI): SketchRef { + const uri = typeof uriLike === 'string' ? new URI(uriLike) : uriLike; + return { + name: uri.path.base, + uri: typeof uriLike === 'string' ? uriLike : uriLike.toString(), + }; + } +} +export interface Sketch extends SketchRef { readonly mainFileUri: string; // `MainFile` readonly otherSketchFileUris: string[]; // `OtherSketchFiles` readonly additionalFileUris: string[]; // `AdditionalFiles` @@ -134,9 +145,16 @@ export namespace Sketch { export interface SketchContainer { readonly label: string; readonly children: SketchContainer[]; - readonly sketches: Sketch[]; + readonly sketches: SketchRef[]; } export namespace SketchContainer { + export function create(label: string): SketchContainer { + return { + label, + children: [], + sketches: [], + }; + } export function is(arg: any): arg is SketchContainer { return ( !!arg && @@ -174,8 +192,8 @@ export namespace SketchContainer { return container; } - export function toArray(container: SketchContainer): Sketch[] { - const visit = (parent: SketchContainer, toPushSketch: Sketch[]) => { + export function toArray(container: SketchContainer): SketchRef[] { + const visit = (parent: SketchContainer, toPushSketch: SketchRef[]) => { toPushSketch.push(...parent.sketches); parent.children.map((child) => visit(child, toPushSketch)); }; diff --git a/arduino-ide-extension/src/electron-browser/electron-window-service.ts b/arduino-ide-extension/src/electron-browser/electron-window-service.ts index 72540e680..1407ac425 100644 --- a/arduino-ide-extension/src/electron-browser/electron-window-service.ts +++ b/arduino-ide-extension/src/electron-browser/electron-window-service.ts @@ -21,7 +21,7 @@ export class ElectronWindowService extends TheiaElectronWindowService { protected readonly appStateService: FrontendApplicationStateService; @postConstruct() - protected init(): void { + protected override init(): void { this.appStateService .reachedAnyState('initialized_layout') .then(() => this.splashService.requestClose()); diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts index 84f2fe715..f18b07df8 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts @@ -1,4 +1,4 @@ -import { injectable } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import * as remote from '@theia/core/electron-shared/@electron/remote'; import { isOSX } from '@theia/core/lib/common/os'; import { @@ -14,10 +14,27 @@ import { ArduinoMenus, PlaceholderMenuNode, } from '../../../browser/menu/arduino-menus'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @injectable() export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { - createElectronMenuBar(): Electron.Menu { + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + + private appReady = false; + private updateWhenReady = false; + + override postConstruct(): void { + super.postConstruct(); + this.appStateService.reachedState('ready').then(() => { + this.appReady = true; + if (this.updateWhenReady) { + this.setMenuBar(); + } + }); + } + + override createElectronMenuBar(): Electron.Menu { this._toggledCommands.clear(); // https://github.com/eclipse-theia/theia/issues/8977 const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR); const template = this.fillMenuTemplate([], menuModel); @@ -29,7 +46,14 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { return menu; } - async setMenuBar(): Promise { + override async setMenuBar(): Promise { + // Avoid updating menu items when the app is not ready. + // Getting the current electron window is not free and synchronous. + // Here, we defer all menu update requests, and fire one when the app is ready. + if (!this.appReady) { + this.updateWhenReady = true; + return; + } await this.preferencesService.ready; const createdMenuBar = this.createElectronMenuBar(); if (isOSX) { @@ -39,7 +63,10 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { } } - createElectronContextMenu(menuPath: MenuPath, args?: any[]): Electron.Menu { + override createElectronContextMenu( + menuPath: MenuPath, + args?: any[] + ): Electron.Menu { const menuModel = this.menuProvider.getMenu(menuPath); const template = this.fillMenuTemplate([], menuModel, args, { showDisabled: false, @@ -64,7 +91,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { return template; } - protected createOSXMenu(): Electron.MenuItemConstructorOptions { + protected override createOSXMenu(): Electron.MenuItemConstructorOptions { const { submenu } = super.createOSXMenu(); const label = 'Arduino IDE'; if (!!submenu && Array.isArray(submenu)) { @@ -96,7 +123,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory { return { label, submenu }; } - protected handleElectronDefault( + protected override handleElectronDefault( menuNode: CompositeMenuNode, args: any[] = [], options?: ElectronMenuOptions diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts index 7e29b53b4..9327637d7 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-contribution.ts @@ -1,4 +1,4 @@ -import { injectable } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { CommandRegistry } from '@theia/core/lib/common/command'; import { MenuModelRegistry } from '@theia/core/lib/common/menu'; import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; @@ -7,35 +7,140 @@ import { ElectronCommands, } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'; import { MainMenuManager } from '../../../common/main-menu-manager'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; +import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; +import { ZoomLevel } from '@theia/core/lib/electron-browser/window/electron-window-preferences'; +import { PreferenceScope } from '@theia/core/lib/browser/preferences/preference-scope'; +import { + getCurrentWindow, + getCurrentWebContents, +} from '@theia/core/electron-shared/@electron/remote'; @injectable() export class ElectronMenuContribution extends TheiaElectronMenuContribution implements MainMenuManager { - protected hideTopPanel(): void { + @inject(FrontendApplicationStateService) + private readonly appStateService: FrontendApplicationStateService; + + // private appReady = false; + // private updateWhenReady = false; + + override onStart(app: FrontendApplication): void { + super.onStart(app); + this.appStateService.reachedState('ready').then(() => { + // this.appReady = true; + // if (this.updateWhenReady) { + // this.update(); + // } + }); + } + + protected override hideTopPanel(): void { // NOOP // We reuse the `div` for the Arduino toolbar. } update(): void { + // if (this.appReady) { (this as any).setMenu(); + // } else { + // this.updateWhenReady = true; + // } } - registerCommands(registry: CommandRegistry): void { - super.registerCommands(registry); + override registerCommands(registry: CommandRegistry): void { + this.theiaRegisterCommands(registry); registry.unregisterCommand(ElectronCommands.CLOSE_WINDOW); } - registerMenus(registry: MenuModelRegistry): void { + override registerMenus(registry: MenuModelRegistry): void { super.registerMenus(registry); registry.unregisterMenuAction(ElectronCommands.CLOSE_WINDOW); } - registerKeybindings(registry: KeybindingRegistry): void { + override registerKeybindings(registry: KeybindingRegistry): void { super.registerKeybindings(registry); registry.unregisterKeybinding(ElectronCommands.CLOSE_WINDOW.id); registry.unregisterKeybinding(ElectronCommands.ZOOM_IN.id); registry.unregisterKeybinding(ElectronCommands.ZOOM_OUT.id); } + + // Copied from Theia: https://github.com/eclipse-theia/theia/blob/9ec8835cf35d5a46101a62ae93285aeb37a2f382/packages/core/src/electron-browser/menu/electron-menu-contribution.ts#L260-L314 + // Unlike the Theia implementation, this does not require synchronously the browser window, but use a function only when the command handler executes. + private theiaRegisterCommands(registry: CommandRegistry): void { + const currentWindow = () => getCurrentWindow(); + + registry.registerCommand(ElectronCommands.TOGGLE_DEVELOPER_TOOLS, { + execute: () => { + const webContent = getCurrentWebContents(); + if (!webContent.isDevToolsOpened()) { + webContent.openDevTools(); + } else { + webContent.closeDevTools(); + } + }, + }); + + registry.registerCommand(ElectronCommands.RELOAD, { + execute: () => this.windowService.reload(), + }); + registry.registerCommand(ElectronCommands.CLOSE_WINDOW, { + execute: () => currentWindow().close(), + }); + + registry.registerCommand(ElectronCommands.ZOOM_IN, { + execute: () => { + const webContents = currentWindow().webContents; + // When starting at a level that is not a multiple of 0.5, increment by at most 0.5 to reach the next highest multiple of 0.5. + let zoomLevel = + Math.floor(webContents.zoomLevel / ZoomLevel.VARIATION) * + ZoomLevel.VARIATION + + ZoomLevel.VARIATION; + if (zoomLevel > ZoomLevel.MAX) { + zoomLevel = ZoomLevel.MAX; + return; + } + this.preferenceService.set( + 'window.zoomLevel', + zoomLevel, + PreferenceScope.User + ); + }, + }); + registry.registerCommand(ElectronCommands.ZOOM_OUT, { + execute: () => { + const webContents = currentWindow().webContents; + // When starting at a level that is not a multiple of 0.5, decrement by at most 0.5 to reach the next lowest multiple of 0.5. + let zoomLevel = + Math.ceil(webContents.zoomLevel / ZoomLevel.VARIATION) * + ZoomLevel.VARIATION - + ZoomLevel.VARIATION; + if (zoomLevel < ZoomLevel.MIN) { + zoomLevel = ZoomLevel.MIN; + return; + } + this.preferenceService.set( + 'window.zoomLevel', + zoomLevel, + PreferenceScope.User + ); + }, + }); + registry.registerCommand(ElectronCommands.RESET_ZOOM, { + execute: () => + this.preferenceService.set( + 'window.zoomLevel', + ZoomLevel.DEFAULT, + PreferenceScope.User + ), + }); + registry.registerCommand(ElectronCommands.TOGGLE_FULL_SCREEN, { + isEnabled: () => currentWindow().isFullScreenable(), + isVisible: () => currentWindow().isFullScreenable(), + execute: () => + currentWindow().setFullScreen(!currentWindow().isFullScreen()), + }); + } } diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts index 1e3b86e60..49bba615e 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts @@ -16,7 +16,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ElectronMenuContribution).toSelf().inSingletonScope(); bind(MainMenuManager).toService(ElectronMenuContribution); rebind(TheiaElectronMenuContribution).to(ElectronMenuContribution); - bind(ElectronMainMenuFactory).toSelf().inRequestScope(); + bind(ElectronMainMenuFactory).toSelf().inSingletonScope(); rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory); bind(ElectronWindowService).toSelf().inSingletonScope(); rebind(WindowService).toService(ElectronWindowService); diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 360f70eed..e91453312 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -1,5 +1,13 @@ import { inject, injectable } from '@theia/core/shared/inversify'; -import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, screen, Event as ElectronEvent } from '@theia/core/electron-shared/electron'; +import { + app, + BrowserWindow, + BrowserWindowConstructorOptions, + contentTracing, + ipcMain, + screen, + Event as ElectronEvent, +} from '@theia/core/electron-shared/electron'; import { fork } from 'child_process'; import { AddressInfo } from 'net'; import { join, dirname } from 'path'; @@ -34,6 +42,28 @@ interface WorkspaceOptions { const WORKSPACES = 'workspaces'; +/** + * Purely a dev thing. If you start the app with the `--nosplash` argument, + * then you won't have the splash screen (which is always on top :confused:) and can debug the app at startup. + * Note: if you start the app from VS Code with the `App (Electron)` config, the splash screen will be disabled. + */ +const APP_STARTED_WITH_NOSPLASH = + typeof process !== 'undefined' && process.argv.indexOf('--nosplash') !== -1; + +/** + * If the app is started with `--open-devtools` argument, the `Dev Tools` will be opened. + */ +const APP_STARTED_WITH_DEV_TOOLS = + typeof process !== 'undefined' && + process.argv.indexOf('--open-devtools') !== -1; + +/** + * If the app is started with `--content-trace` argument, the `Dev Tools` will be opened and content tracing will start. + */ +const APP_STARTED_WITH_CONTENT_TRACE = + typeof process !== 'undefined' && + process.argv.indexOf('--content-trace') !== -1; + @injectable() export class ElectronMainApplication extends TheiaElectronMainApplication { protected startup = false; @@ -42,13 +72,74 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { @inject(SplashServiceImpl) protected readonly splashService: SplashServiceImpl; - async start(config: FrontendApplicationConfig): Promise { + override async start(config: FrontendApplicationConfig): Promise { // Explicitly set the app name to have better menu items on macOS. ("About", "Hide", and "Quit") // See: https://github.com/electron-userland/electron-builder/issues/2468 // Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701 app.on('ready', () => app.setName(config.applicationName)); this.attachFileAssociations(); - return super.start(config); + this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native'; + this._config = config; + this.hookApplicationEvents(); + const [port] = await Promise.all([this.startBackend(), app.whenReady()]); + this.startContentTracing(); + this._backendPort.resolve(port); + await Promise.all([ + this.attachElectronSecurityToken(port), + this.startContributions(), + ]); + return this.launch({ + secondInstance: false, + argv: this.processArgv.getProcessArgvWithoutBin(process.argv), + cwd: process.cwd(), + }); + } + + private startContentTracing(): void { + if (!APP_STARTED_WITH_CONTENT_TRACE) { + return; + } + if (!app.isReady()) { + throw new Error( + 'Cannot start content tracing when the electron app is not ready.' + ); + } + const defaultTraceCategories: Readonly> = [ + '-*', + 'devtools.timeline', + 'disabled-by-default-devtools.timeline', + 'disabled-by-default-devtools.timeline.frame', + 'toplevel', + 'blink.console', + 'disabled-by-default-devtools.timeline.stack', + 'disabled-by-default-v8.cpu_profile', + 'disabled-by-default-v8.cpu_profiler', + 'disabled-by-default-v8.cpu_profiler.hires', + ]; + const traceOptions = { + categoryFilter: defaultTraceCategories.join(','), + traceOptions: 'record-until-full', + options: 'sampling-frequency=10000', + }; + (async () => { + const appPath = app.getAppPath(); + let traceFile: string | undefined; + if (appPath) { + const tracesPath = join(appPath, 'traces'); + await fs.promises.mkdir(tracesPath, { recursive: true }); + traceFile = join(tracesPath, `trace-${new Date().toISOString()}.trace`); + } + console.log('>>> Content tracing has started...'); + await contentTracing.startRecording(traceOptions); + await new Promise((resolve) => setTimeout(resolve, 10_000)); + contentTracing + .stopRecording(traceFile) + .then((out) => + console.log( + `<<< Content tracing has finished. The trace data was written to: ${out}.` + ) + ); + })(); } attachFileAssociations() { @@ -71,7 +162,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { return typeof uri === 'string' && await fs.pathExists(uri); } - protected async launch(params: ElectronMainExecutionParams): Promise { + protected override async launch(params: ElectronMainExecutionParams): Promise { try { // When running on MacOS, we either have to wait until // 1. The `open-file` command has been received by the app, rejecting the promise @@ -143,18 +234,18 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { return electronWindow; } - protected avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions { + protected override avoidOverlap(options: TheiaBrowserWindowOptions): TheiaBrowserWindowOptions { if (this.startup) { return options; } return super.avoidOverlap(options); } - protected getTitleBarStyle(): 'native' | 'custom' { + protected override getTitleBarStyle(config: FrontendApplicationConfig): 'native' | 'custom' { return 'native'; } - protected hookApplicationEvents(): void { + protected override hookApplicationEvents(): void { app.on('will-quit', this.onWillQuit.bind(this)); app.on('second-instance', this.onSecondInstance.bind(this)); app.on('window-all-closed', this.onWindowAllClosed.bind(this)); @@ -164,7 +255,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { }); } - protected async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise { + protected override async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise { if (!os.isOSX && await this.launchFromArgs({ cwd, argv, secondInstance: true })) { // Application has received a file in its arguments return; @@ -177,13 +268,13 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { * * @param options */ - async createWindow( + override async createWindow( asyncOptions: MaybePromise = this.getDefaultTheiaWindowOptions() ): Promise { let options = await asyncOptions; options = this.avoidOverlap(options); let electronWindow: BrowserWindow | undefined; - if (this.windows.size) { + if (this.windows.size || APP_STARTED_WITH_NOSPLASH) { electronWindow = await this.doCreateWindow(options); } else { const { bounds } = screen.getDisplayNearestPoint( @@ -235,10 +326,22 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { options: TheiaBrowserWindowOptions ): Promise { const electronWindow = await super.createWindow(options); + if (APP_STARTED_WITH_DEV_TOOLS) { + electronWindow.webContents.openDevTools(); + } this.attachListenersToWindow(electronWindow); return electronWindow; } + protected override getDefaultOptions(): TheiaBrowserWindowOptions { + const options = super.getDefaultOptions(); + if (!options.webPreferences) { + options.webPreferences = {}; + } + options.webPreferences.v8CacheOptions = 'bypassHeatCheck'; // TODO: verify this. VS Code use this V8 option. + return options; + } + private attachListenersToWindow(electronWindow: BrowserWindow) { electronWindow.webContents.on( 'new-window', @@ -270,7 +373,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { this.attachClosedWorkspace(electronWindow); } - protected async startBackend(): Promise { + protected override async startBackend(): Promise { // Check if we should run everything as one process. const noBackendFork = process.argv.indexOf('--no-cluster') !== -1; // We cannot use the `process.cwd()` as the application project path (the location of the `package.json` in other words) @@ -359,7 +462,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { }); } - protected onWillQuit(event: Electron.Event): void { + protected override onWillQuit(event: Electron.Event): void { // Only add workspaces which were closed within the last second (1000 milliseconds) const threshold = Date.now() - 1000; const visited = new Set(); diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts index bc5a2e91b..2248ac543 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts @@ -6,9 +6,9 @@ import { NewWindowOptions } from '@theia/core/lib/common/window'; @injectable() export class ElectronMainWindowServiceImpl extends TheiaElectronMainWindowService { @inject(ElectronMainApplication) - protected readonly app: ElectronMainApplication; + protected override readonly app: ElectronMainApplication; - openNewWindow(url: string, { external }: NewWindowOptions): undefined { + override openNewWindow(url: string, { external }: NewWindowOptions): undefined { if (!external) { const sanitizedUrl = this.sanitize(url); const existing = this.app.browserWindows.find( diff --git a/arduino-ide-extension/src/node/arduino-daemon-impl.ts b/arduino-ide-extension/src/node/arduino-daemon-impl.ts index 7744cd6ea..e53d6afe3 100644 --- a/arduino-ide-extension/src/node/arduino-daemon-impl.ts +++ b/arduino-ide-extension/src/node/arduino-daemon-impl.ts @@ -13,7 +13,6 @@ import { environment } from '@theia/application-package/lib/environment'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { ArduinoDaemon, NotificationServiceServer } from '../common/protocol'; -import { DaemonLog } from './daemon-log'; import { CLI_CONFIG } from './cli-config'; import { getExecPath, spawnCommand } from './exec-util'; @@ -32,28 +31,30 @@ export class ArduinoDaemonImpl protected readonly notificationService: NotificationServiceServer; protected readonly toDispose = new DisposableCollection(); - protected readonly onDaemonStartedEmitter = new Emitter(); + protected readonly onDaemonStartedEmitter = new Emitter(); protected readonly onDaemonStoppedEmitter = new Emitter(); protected _running = false; - protected _ready = new Deferred(); + protected _port = new Deferred(); protected _execPath: string | undefined; - protected _port: string; // Backend application lifecycle. onStart(): void { - this.startDaemon(); + this.startDaemon(); // no await } // Daemon API - async isRunning(): Promise { - return Promise.resolve(this._running); + async getPort(): Promise { + return this._port.promise; } - async getPort(): Promise { - return Promise.resolve(this._port); + async tryGetPort(): Promise { + if (this._running) { + return this._port.promise; + } + return undefined; } async startDaemon(): Promise { @@ -62,7 +63,6 @@ export class ArduinoDaemonImpl const cliPath = await this.getExecPath(); this.onData(`Starting daemon from ${cliPath}...`); const { daemon, port } = await this.spawnDaemonProcess(); - this._port = port; // Watchdog process for terminating the daemon process when the backend app terminates. spawn( process.execPath, @@ -83,7 +83,7 @@ export class ArduinoDaemonImpl Disposable.create(() => daemon.kill()), Disposable.create(() => this.fireDaemonStopped()), ]); - this.fireDaemonStarted(); + this.fireDaemonStarted(port); this.onData('Daemon is running.'); } catch (err) { this.onData('Failed to start the daemon.'); @@ -103,7 +103,7 @@ export class ArduinoDaemonImpl this.toDispose.dispose(); } - get onDaemonStarted(): Event { + get onDaemonStarted(): Event { return this.onDaemonStartedEmitter.event; } @@ -111,10 +111,6 @@ export class ArduinoDaemonImpl return this.onDaemonStoppedEmitter.event; } - get ready(): Promise { - return this._ready.promise; - } - async getExecPath(): Promise { if (this._execPath) { return this._execPath; @@ -240,11 +236,11 @@ export class ArduinoDaemonImpl return ready.promise; } - protected fireDaemonStarted(): void { + protected fireDaemonStarted(port: string): void { this._running = true; - this._ready.resolve(); - this.onDaemonStartedEmitter.fire(); - this.notificationService.notifyDaemonStarted(); + this._port.resolve(port); + this.onDaemonStartedEmitter.fire(port); + this.notificationService.notifyDaemonStarted(port); } protected fireDaemonStopped(): void { @@ -252,14 +248,14 @@ export class ArduinoDaemonImpl return; } this._running = false; - this._ready.reject(); // Reject all pending. - this._ready = new Deferred(); + this._port.reject(); // Reject all pending. + this._port = new Deferred(); this.onDaemonStoppedEmitter.fire(); this.notificationService.notifyDaemonStopped(); } protected onData(message: string): void { - DaemonLog.log(this.logger, message); + this.logger.info(message); } protected onError(error: any): void { diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 826c41e60..6b39f8fb0 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -58,7 +58,7 @@ import { FileSystemExt, FileSystemExtPath, } from '../common/protocol/filesystem-ext'; -import { ExamplesServiceImpl } from './examples-service-impl'; +import { BuiltInExamplesServiceImpl, ExamplesServiceImpl } from './examples-service-impl'; import { ExamplesService, ExamplesServicePath, @@ -80,8 +80,6 @@ import { } from '../common/protocol'; import { BackendApplication } from './theia/core/backend-application'; import { BoardDiscovery } from './board-discovery'; -import { DefaultGitInit } from './theia/git/git-init'; -import { GitInit } from '@theia/git/lib/node/init/git-init'; import { AuthenticationServiceImpl } from './auth/authentication-service-impl'; import { AuthenticationService, @@ -126,6 +124,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { ) .inSingletonScope(); + // Built-in examples are not board specific, so it is possible to have one shared instance. + bind(BuiltInExamplesServiceImpl).toSelf().inSingletonScope(); + // Examples service. One per backend, each connected FE gets a proxy. bind(ConnectionContainerModule).toConstantValue( ConnectionContainerModule.create(({ bind, bindBackendService }) => { @@ -316,9 +317,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope() .whenTargetNamed(SerialServiceName); - bind(DefaultGitInit).toSelf(); - rebind(GitInit).toService(DefaultGitInit); - // Remote sketchbook bindings bind(AuthenticationServiceImpl).toSelf().inSingletonScope(); bind(AuthenticationService).toService(AuthenticationServiceImpl); diff --git a/arduino-ide-extension/src/node/board-discovery.ts b/arduino-ide-extension/src/node/board-discovery.ts index 3354d53a4..dcaa43d92 100644 --- a/arduino-ide-extension/src/node/board-discovery.ts +++ b/arduino-ide-extension/src/node/board-discovery.ts @@ -55,9 +55,7 @@ export class BoardDiscovery extends CoreClientAware { @postConstruct() protected async init(): Promise { - await this.coreClientProvider.initialized; - const coreClient = await this.coreClient(); - this.startBoardListWatch(coreClient); + this.coreClient().then((client) => this.startBoardListWatch(client)); } stopBoardListWatch(coreClient: CoreClientProvider.Client): Promise { diff --git a/arduino-ide-extension/src/node/cli-config.ts b/arduino-ide-extension/src/node/cli-config.ts index 1b19557af..16bac01a3 100644 --- a/arduino-ide-extension/src/node/cli-config.ts +++ b/arduino-ide-extension/src/node/cli-config.ts @@ -1,42 +1,31 @@ import { RecursivePartial } from '@theia/core/lib/common/types'; -import { Daemon } from '../common/protocol/config-service'; +import { AdditionalUrls } from '../common/protocol'; export const CLI_CONFIG = 'arduino-cli.yaml'; export interface BoardManager { - readonly additional_urls: Array; + readonly additional_urls: AdditionalUrls; } export namespace BoardManager { export function sameAs( left: RecursivePartial | undefined, right: RecursivePartial | undefined ): boolean { - const leftOrDefault = left || {}; - const rightOrDefault = right || {}; - const leftUrls = Array.from(new Set(leftOrDefault.additional_urls || [])); - const rightUrls = Array.from(new Set(rightOrDefault.additional_urls || [])); - if (leftUrls.length !== rightUrls.length) { - return false; - } - return leftUrls.every((url) => rightUrls.indexOf(url) !== -1); + const leftUrls = left?.additional_urls ?? []; + const rightUrls = right?.additional_urls ?? []; + return AdditionalUrls.sameAs(leftUrls, rightUrls); } } export interface Directories { readonly data: string; - readonly downloads: string; readonly user: string; } export namespace Directories { export function is( directories: RecursivePartial | undefined ): directories is Directories { - return ( - !!directories && - !!directories.data && - !!directories.downloads && - !!directories.user - ); + return !!directories && !!directories.data && !!directories.user; } export function sameAs( left: RecursivePartial | undefined, @@ -48,11 +37,7 @@ export namespace Directories { if (right === undefined) { return left === undefined; } - return ( - left.data === right.data && - left.downloads === right.downloads && - left.user === right.user - ); + return left.data === right.data && left.user === right.user; } } @@ -111,15 +96,12 @@ export interface CliConfig { // Bare minimum required CLI config. export interface DefaultCliConfig extends CliConfig { directories: Directories; - daemon: Daemon; } export namespace DefaultCliConfig { export function is( config: RecursivePartial | undefined ): config is DefaultCliConfig { - return ( - !!config && Directories.is(config.directories) && Daemon.is(config.daemon) - ); + return !!config && Directories.is(config.directories); } export function sameAs( left: DefaultCliConfig, @@ -127,7 +109,6 @@ export namespace DefaultCliConfig { ): boolean { return ( Directories.sameAs(left.directories, right.directories) && - Daemon.sameAs(left.daemon, right.daemon) && BoardManager.sameAs(left.board_manager, right.board_manager) && Logging.sameAs(left.logging, right.logging) ); diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts index 94ceab14f..a15ce4719 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.d.ts @@ -62,6 +62,12 @@ export class InitRequest extends jspb.Message { getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): InitRequest; + getProfile(): string; + setProfile(value: string): InitRequest; + + getSketchPath(): string; + setSketchPath(value: string): InitRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): InitRequest.AsObject; @@ -76,6 +82,8 @@ export class InitRequest extends jspb.Message { export namespace InitRequest { export type AsObject = { instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, + profile: string, + sketchPath: string, } } @@ -93,6 +101,12 @@ export class InitResponse extends jspb.Message { setError(value?: google_rpc_status_pb.Status): InitResponse; + hasProfile(): boolean; + clearProfile(): void; + getProfile(): cc_arduino_cli_commands_v1_common_pb.Profile | undefined; + setProfile(value?: cc_arduino_cli_commands_v1_common_pb.Profile): InitResponse; + + getMessageCase(): InitResponse.MessageCase; serializeBinary(): Uint8Array; @@ -109,6 +123,7 @@ export namespace InitResponse { export type AsObject = { initProgress?: InitResponse.Progress.AsObject, error?: google_rpc_status_pb.Status.AsObject, + profile?: cc_arduino_cli_commands_v1_common_pb.Profile.AsObject, } @@ -151,6 +166,8 @@ export namespace InitResponse { ERROR = 2, + PROFILE = 3, + } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js index 1937aa229..820634067 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_pb.js @@ -866,7 +866,9 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.toObject = function(opt_i */ proto.cc.arduino.cli.commands.v1.InitRequest.toObject = function(includeInstance, msg) { var f, obj = { - instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f) + instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), + profile: jspb.Message.getFieldWithDefault(msg, 2, ""), + sketchPath: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -908,6 +910,14 @@ proto.cc.arduino.cli.commands.v1.InitRequest.deserializeBinaryFromReader = funct reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader); msg.setInstance(value); break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setProfile(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSketchPath(value); + break; default: reader.skipField(); break; @@ -945,6 +955,20 @@ proto.cc.arduino.cli.commands.v1.InitRequest.serializeBinaryToWriter = function( cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter ); } + f = message.getProfile(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSketchPath(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } }; @@ -985,6 +1009,42 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function() }; +/** + * optional string profile = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getProfile = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setProfile = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string sketch_path = 3; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.getSketchPath = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.InitRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.InitRequest.prototype.setSketchPath = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + /** * Oneof group definitions for this message. Each group defines the field @@ -994,7 +1054,7 @@ proto.cc.arduino.cli.commands.v1.InitRequest.prototype.hasInstance = function() * @private {!Array>} * @const */ -proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]]; +proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2,3]]; /** * @enum {number} @@ -1002,7 +1062,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_ = [[1,2]]; proto.cc.arduino.cli.commands.v1.InitResponse.MessageCase = { MESSAGE_NOT_SET: 0, INIT_PROGRESS: 1, - ERROR: 2 + ERROR: 2, + PROFILE: 3 }; /** @@ -1044,7 +1105,8 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.toObject = function(opt_ proto.cc.arduino.cli.commands.v1.InitResponse.toObject = function(includeInstance, msg) { var f, obj = { initProgress: (f = msg.getInitProgress()) && proto.cc.arduino.cli.commands.v1.InitResponse.Progress.toObject(includeInstance, f), - error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f) + error: (f = msg.getError()) && google_rpc_status_pb.Status.toObject(includeInstance, f), + profile: (f = msg.getProfile()) && cc_arduino_cli_commands_v1_common_pb.Profile.toObject(includeInstance, f) }; if (includeInstance) { @@ -1091,6 +1153,11 @@ proto.cc.arduino.cli.commands.v1.InitResponse.deserializeBinaryFromReader = func reader.readMessage(value,google_rpc_status_pb.Status.deserializeBinaryFromReader); msg.setError(value); break; + case 3: + var value = new cc_arduino_cli_commands_v1_common_pb.Profile; + reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Profile.deserializeBinaryFromReader); + msg.setProfile(value); + break; default: reader.skipField(); break; @@ -1136,6 +1203,14 @@ proto.cc.arduino.cli.commands.v1.InitResponse.serializeBinaryToWriter = function google_rpc_status_pb.Status.serializeBinaryToWriter ); } + f = message.getProfile(); + if (f != null) { + writer.writeMessage( + 3, + f, + cc_arduino_cli_commands_v1_common_pb.Profile.serializeBinaryToWriter + ); + } }; @@ -1415,6 +1490,43 @@ proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasError = function() { }; +/** + * optional Profile profile = 3; + * @return {?proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.getProfile = function() { + return /** @type{?proto.cc.arduino.cli.commands.v1.Profile} */ ( + jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Profile, 3)); +}; + + +/** + * @param {?proto.cc.arduino.cli.commands.v1.Profile|undefined} value + * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this +*/ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.setProfile = function(value) { + return jspb.Message.setOneofWrapperField(this, 3, proto.cc.arduino.cli.commands.v1.InitResponse.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.cc.arduino.cli.commands.v1.InitResponse} returns this + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.clearProfile = function() { + return this.setProfile(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.InitResponse.prototype.hasProfile = function() { + return jspb.Message.getField(this, 3) != null; +}; + + diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts index 571b58afe..2d273542c 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts @@ -234,3 +234,28 @@ export namespace Board { fqbn: string, } } + +export class Profile extends jspb.Message { + getName(): string; + setName(value: string): Profile; + + getFqbn(): string; + setFqbn(value: string): Profile; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): Profile.AsObject; + static toObject(includeInstance: boolean, msg: Profile): Profile.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: Profile, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): Profile; + static deserializeBinaryFromReader(message: Profile, reader: jspb.BinaryReader): Profile; +} + +export namespace Profile { + export type AsObject = { + name: string, + fqbn: string, + } +} diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js index efd86871e..67098a306 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js @@ -20,6 +20,7 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress', null, glo goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Instance', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.PlatformReference', null, global); +goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Profile', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Programmer', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.TaskProgress', null, global); /** @@ -169,6 +170,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.cc.arduino.cli.commands.v1.Board.displayName = 'proto.cc.arduino.cli.commands.v1.Board'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.commands.v1.Profile = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.commands.v1.Profile, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.cc.arduino.cli.commands.v1.Profile.displayName = 'proto.cc.arduino.cli.commands.v1.Profile'; +} @@ -1709,4 +1731,164 @@ proto.cc.arduino.cli.commands.v1.Board.prototype.setFqbn = function(value) { }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.commands.v1.Profile.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.Profile.toObject = function(includeInstance, msg) { + var f, obj = { + name: jspb.Message.getFieldWithDefault(msg, 1, ""), + fqbn: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.Profile.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.commands.v1.Profile; + return proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.commands.v1.Profile} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.commands.v1.Profile} + */ +proto.cc.arduino.cli.commands.v1.Profile.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setName(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setFqbn(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.commands.v1.Profile} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.commands.v1.Profile.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getName(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getFqbn(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional string name = 1; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.getName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.setName = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string fqbn = 2; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.getFqbn = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.Profile} returns this + */ +proto.cc.arduino.cli.commands.v1.Profile.prototype.setFqbn = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + goog.object.extend(exports, proto.cc.arduino.cli.commands.v1); diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts index 7613a69fd..c0ba371ca 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts @@ -86,6 +86,15 @@ export class CompileRequest extends jspb.Message { setLibraryList(value: Array): CompileRequest; addLibrary(value: string, index?: number): string; + getKeysKeychain(): string; + setKeysKeychain(value: string): CompileRequest; + + getSignKey(): string; + setSignKey(value: string): CompileRequest; + + getEncryptKey(): string; + setEncryptKey(value: string): CompileRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CompileRequest.AsObject; @@ -121,6 +130,9 @@ export namespace CompileRequest { sourceOverrideMap: Array<[string, string]>, exportBinaries?: google_protobuf_wrappers_pb.BoolValue.AsObject, libraryList: Array, + keysKeychain: string, + signKey: string, + encryptKey: string, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js index 1d2bd8977..168deb3c6 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js @@ -146,7 +146,10 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.toObject = function(includeInsta createCompilationDatabaseOnly: jspb.Message.getBooleanFieldWithDefault(msg, 21, false), sourceOverrideMap: (f = msg.getSourceOverrideMap()) ? f.toObject(includeInstance, undefined) : [], exportBinaries: (f = msg.getExportBinaries()) && google_protobuf_wrappers_pb.BoolValue.toObject(includeInstance, f), - libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f + libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f, + keysKeychain: jspb.Message.getFieldWithDefault(msg, 25, ""), + signKey: jspb.Message.getFieldWithDefault(msg, 26, ""), + encryptKey: jspb.Message.getFieldWithDefault(msg, 27, "") }; if (includeInstance) { @@ -271,6 +274,18 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.deserializeBinaryFromReader = fu var value = /** @type {string} */ (reader.readString()); msg.addLibrary(value); break; + case 25: + var value = /** @type {string} */ (reader.readString()); + msg.setKeysKeychain(value); + break; + case 26: + var value = /** @type {string} */ (reader.readString()); + msg.setSignKey(value); + break; + case 27: + var value = /** @type {string} */ (reader.readString()); + msg.setEncryptKey(value); + break; default: reader.skipField(); break; @@ -446,6 +461,27 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.serializeBinaryToWriter = functi f ); } + f = message.getKeysKeychain(); + if (f.length > 0) { + writer.writeString( + 25, + f + ); + } + f = message.getSignKey(); + if (f.length > 0) { + writer.writeString( + 26, + f + ); + } + f = message.getEncryptKey(); + if (f.length > 0) { + writer.writeString( + 27, + f + ); + } }; @@ -926,6 +962,60 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.clearLibraryList = fun }; +/** + * optional string keys_keychain = 25; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getKeysKeychain = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 25, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setKeysKeychain = function(value) { + return jspb.Message.setProto3StringField(this, 25, value); +}; + + +/** + * optional string sign_key = 26; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getSignKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 26, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setSignKey = function(value) { + return jspb.Message.setProto3StringField(this, 26, value); +}; + + +/** + * optional string encrypt_key = 27; + * @return {string} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getEncryptKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 27, "")); +}; + + +/** + * @param {string} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setEncryptKey = function(value) { + return jspb.Message.setProto3StringField(this, 27, value); +}; + + /** * List of repeated fields within this message type. diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts index 1c1ca38ca..4427473a6 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.d.ts @@ -371,9 +371,6 @@ export class SupportedUserFieldsRequest extends jspb.Message { getProtocol(): string; setProtocol(value: string): SupportedUserFieldsRequest; - getAddress(): string; - setAddress(value: string): SupportedUserFieldsRequest; - serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): SupportedUserFieldsRequest.AsObject; @@ -390,7 +387,6 @@ export namespace SupportedUserFieldsRequest { instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, fqbn: string, protocol: string, - address: string, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js index 87914c167..8841575ad 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/upload_pb.js @@ -2718,8 +2718,7 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.toObject = function( var f, obj = { instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), fqbn: jspb.Message.getFieldWithDefault(msg, 2, ""), - protocol: jspb.Message.getFieldWithDefault(msg, 3, ""), - address: jspb.Message.getFieldWithDefault(msg, 4, "") + protocol: jspb.Message.getFieldWithDefault(msg, 3, "") }; if (includeInstance) { @@ -2769,10 +2768,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.deserializeBinaryFro var value = /** @type {string} */ (reader.readString()); msg.setProtocol(value); break; - case 4: - var value = /** @type {string} */ (reader.readString()); - msg.setAddress(value); - break; default: reader.skipField(); break; @@ -2824,13 +2819,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.serializeBinaryToWri f ); } - f = message.getAddress(); - if (f.length > 0) { - writer.writeString( - 4, - f - ); - } }; @@ -2907,24 +2895,6 @@ proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setProtoco }; -/** - * optional string address = 4; - * @return {string} - */ -proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.getAddress = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); -}; - - -/** - * @param {string} value - * @return {!proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest} returns this - */ -proto.cc.arduino.cli.commands.v1.SupportedUserFieldsRequest.prototype.setAddress = function(value) { - return jspb.Message.setProto3StringField(this, 4, value); -}; - - diff --git a/arduino-ide-extension/src/node/config-service-impl.ts b/arduino-ide-extension/src/node/config-service-impl.ts index 3e626f4c1..420d4185f 100644 --- a/arduino-ide-extension/src/node/config-service-impl.ts +++ b/arduino-ide-extension/src/node/config-service-impl.ts @@ -1,8 +1,6 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as temp from 'temp'; +import { promises as fs } from 'fs'; +import { dirname } from 'path'; import * as yaml from 'js-yaml'; -import { promisify } from 'util'; import * as grpc from '@grpc/grpc-js'; import { injectable, inject, named } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; @@ -28,9 +26,9 @@ import { DefaultCliConfig, CLI_CONFIG } from './cli-config'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { deepClone } from '@theia/core'; +import { duration } from '../common/decorators'; const deepmerge = require('deepmerge'); -const track = temp.track(); @injectable() export class ConfigServiceImpl @@ -54,18 +52,19 @@ export class ConfigServiceImpl protected ready = new Deferred(); protected readonly configChangeEmitter = new Emitter(); - async onStart(): Promise { - await this.ensureCliConfigExists(); - this.cliConfig = await this.loadCliConfig(); - if (this.cliConfig) { - const config = await this.mapCliConfigToAppConfig(this.cliConfig); - if (config) { - this.config = config; - this.ready.resolve(); - return; + onStart(): void { + this.loadCliConfig().then(async (cliConfig) => { + this.cliConfig = cliConfig; + if (this.cliConfig) { + const config = await this.mapCliConfigToAppConfig(this.cliConfig); + if (config) { + this.config = config; + this.ready.resolve(); + return; + } } - } - this.fireInvalidConfig(); + this.fireInvalidConfig(); + }); } async getCliConfigFileUri(): Promise { @@ -75,8 +74,7 @@ export class ConfigServiceImpl async getConfiguration(): Promise { await this.ready.promise; - await this.daemon.ready; - return { ...this.config, daemon: { port: await this.daemon.getPort() } }; + return { ...this.config }; } // Used by frontend to update the config. @@ -91,17 +89,10 @@ export class ConfigServiceImpl if (!copyDefaultCliConfig) { copyDefaultCliConfig = await this.getFallbackCliConfig(); } - const { - additionalUrls, - dataDirUri, - downloadsDirUri, - sketchDirUri, - network, - locale, - } = config; + const { additionalUrls, dataDirUri, sketchDirUri, network, locale } = + config; copyDefaultCliConfig.directories = { data: FileUri.fsPath(dataDirUri), - downloads: FileUri.fsPath(downloadsDirUri), user: FileUri.fsPath(sketchDirUri), }; copyDefaultCliConfig.board_manager = { @@ -135,76 +126,44 @@ export class ConfigServiceImpl return this.daemon.getVersion(); } - async isInDataDir(uri: string): Promise { - return this.getConfiguration().then(({ dataDirUri }) => - new URI(dataDirUri).isEqualOrParent(new URI(uri)) - ); - } - - async isInSketchDir(uri: string): Promise { - return this.getConfiguration().then(({ sketchDirUri }) => - new URI(sketchDirUri).isEqualOrParent(new URI(uri)) - ); - } - - protected async loadCliConfig(): Promise { + @duration() + protected async loadCliConfig( + initializeIfAbsent = true + ): Promise { const cliConfigFileUri = await this.getCliConfigFileUri(); const cliConfigPath = FileUri.fsPath(cliConfigFileUri); try { - const content = await promisify(fs.readFile)(cliConfigPath, { + const content = await fs.readFile(cliConfigPath, { encoding: 'utf8', }); - const model = yaml.safeLoad(content) || {}; - // The CLI can run with partial (missing `port`, `directories`), the app cannot, we merge the default with the user's config. + const model = (yaml.safeLoad(content) || {}) as DefaultCliConfig; + if (model.directories.data && model.directories.user) { + return model; + } + // The CLI can run with partial (missing `port`, `directories`), the IDE2 cannot. + // We merge the default CLI config with the partial user's config. const fallbackModel = await this.getFallbackCliConfig(); return deepmerge(fallbackModel, model) as DefaultCliConfig; } catch (error) { - this.logger.error( - `Error occurred when loading CLI config from ${cliConfigPath}.`, - error - ); + if ('code' in error && error.code === 'ENOENT') { + if (initializeIfAbsent) { + await this.initCliConfigTo(dirname(cliConfigPath)); + return this.loadCliConfig(false); + } + } + throw error; } - return undefined; } protected async getFallbackCliConfig(): Promise { const cliPath = await this.daemon.getExecPath(); - const throwawayDirPath = await new Promise((resolve, reject) => { - track.mkdir({}, (err, dirPath) => { - if (err) { - reject(err); - return; - } - resolve(dirPath); - }); - }); - await spawnCommand(`"${cliPath}"`, [ + const rawJson = await spawnCommand(`"${cliPath}"`, [ 'config', - 'init', - '--dest-dir', - `"${throwawayDirPath}"`, + 'dump', + 'format', + '--json', ]); - const rawYaml = await promisify(fs.readFile)( - path.join(throwawayDirPath, CLI_CONFIG), - { encoding: 'utf-8' } - ); - const model = yaml.safeLoad(rawYaml.trim()); - return model as DefaultCliConfig; - } - - protected async ensureCliConfigExists(): Promise { - const cliConfigFileUri = await this.getCliConfigFileUri(); - const cliConfigPath = FileUri.fsPath(cliConfigFileUri); - let exists = await promisify(fs.exists)(cliConfigPath); - if (!exists) { - await this.initCliConfigTo(path.dirname(cliConfigPath)); - exists = await promisify(fs.exists)(cliConfigPath); - if (!exists) { - throw new Error( - `Could not initialize the default CLI configuration file at ${cliConfigPath}.` - ); - } - } + return JSON.parse(rawJson); } protected async initCliConfigTo(fsPathToDir: string): Promise { @@ -220,8 +179,8 @@ export class ConfigServiceImpl protected async mapCliConfigToAppConfig( cliConfig: DefaultCliConfig ): Promise { - const { directories, locale = 'en', daemon } = cliConfig; - const { data, user, downloads } = directories; + const { directories, locale = 'en' } = cliConfig; + const { user, data } = directories; const additionalUrls: Array = []; if (cliConfig.board_manager && cliConfig.board_manager.additional_urls) { additionalUrls.push( @@ -232,11 +191,9 @@ export class ConfigServiceImpl return { dataDirUri: FileUri.create(data).toString(), sketchDirUri: FileUri.create(user).toString(), - downloadsDirUri: FileUri.create(downloads).toString(), additionalUrls, network, locale, - daemon, }; } diff --git a/arduino-ide-extension/src/node/core-client-provider.ts b/arduino-ide-extension/src/node/core-client-provider.ts index 18b78e626..c585afeda 100644 --- a/arduino-ide-extension/src/node/core-client-provider.ts +++ b/arduino-ide-extension/src/node/core-client-provider.ts @@ -1,5 +1,9 @@ import * as grpc from '@grpc/grpc-js'; -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { Event, Emitter } from '@theia/core/lib/common/event'; import { GrpcClientProvider } from './grpc-client-provider'; import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; @@ -16,7 +20,8 @@ import { } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb'; import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; import { NotificationServiceServer } from '../common/protocol'; -import { Deferred } from '@theia/core/lib/common/promise-util'; +import { Deferred, retry } from '@theia/core/lib/common/promise-util'; +import { Status as RpcStatus } from './cli-protocol/google/rpc/status_pb'; @injectable() export class CoreClientProvider extends GrpcClientProvider { @@ -48,9 +53,7 @@ export class CoreClientProvider extends GrpcClientProvider(); } - protected async reconcileClient(): Promise { - const port = await this.daemon.getPort(); - + protected override async reconcileClient(port: string): Promise { if (port && port === this._port) { // No need to create a new gRPC client, but we have to update the indexes. if (this._client && !(this._client instanceof Error)) { @@ -58,29 +61,47 @@ export class CoreClientProvider extends GrpcClientProvider { - this.daemon.ready.then(async () => { + protected override init(): void { + this.daemon.getPort().then(async (port) => { // First create the client and the instance synchronously // and notify client is ready. // TODO: Creation failure should probably be handled here - await this.reconcileClient().then(() => { - this._created.resolve(); - }); + await this.reconcileClient(port); // create instance + this._created.resolve(); - // If client has been created correctly update indexes and initialize - // its instance by loading platforms and cores. + // Normal startup workflow: + // 1. create instance, + // 2. init instance, + // 3. update indexes asynchronously. + + // First startup workflow: + // 1. create instance, + // 2. update indexes and wait, + // 3. init instance. if (this._client && !(this._client instanceof Error)) { - await this.updateIndexes(this._client) - .then(this.initInstance) - .then(() => { + try { + await this.initInstance(this._client); // init instance + this._initialized.resolve(); + this.updateIndex(this._client); // Update the indexes asynchronously + } catch (error: unknown) { + if ( + this.isPackageIndexMissingError(error) || + this.isDiscoveryNotFoundError(error) + ) { + // If it's a first start, IDE2 must run index update before the init request. + await this.updateIndexes(this._client); + await this.initInstance(this._client); this._initialized.resolve(); - }); + } else { + throw error; + } + } } }); @@ -93,6 +114,41 @@ export class CoreClientProvider extends GrpcClientProvider + message.includes('loading json index file'); + // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247 + return this.isRpcStatusError(error, assert); + } + + private isDiscoveryNotFoundError(error: unknown): boolean { + const assert = (message: string) => + message.includes('discovery') && + (message.includes('not found') || message.includes('not installed')); + // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740 + // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744 + return this.isRpcStatusError(error, assert); + } + + private isCancelError(error: unknown): boolean { + return ( + error instanceof Error && + error.message.toLocaleLowerCase().includes('cancelled on client') + ); + } + + // Final error codes are not yet defined by the CLI. Hence, we do string matching in the message RPC status. + private isRpcStatusError( + error: unknown, + assert: (message: string) => boolean + ) { + if (error instanceof RpcStatus) { + const { message } = RpcStatus.toObject(false, error); + return assert(message.toLocaleLowerCase()); + } + return false; + } + protected async createClient( port: string | number ): Promise { @@ -134,8 +190,9 @@ export class CoreClientProvider extends GrpcClientProvider { const initReq = new InitRequest(); initReq.setInstance(instance); - await new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const stream = client.init(initReq); + const errorStatus: RpcStatus[] = []; stream.on('data', (res: InitResponse) => { const progress = res.getInitProgress(); if (progress) { @@ -151,55 +208,39 @@ export class CoreClientProvider extends GrpcClientProvider reject(err)); - stream.on('end', resolve); + stream.on('error', (error) => { + // On any error during the init request, the request is canceled. + // On cancel, the IDE2 ignores the cancel error and rejects with the original one. + reject( + this.isCancelError(error) && errorStatus.length + ? errorStatus[0] + : error + ); + }); + stream.on('end', () => + errorStatus.length ? reject(errorStatus) : resolve() + ); }); } - protected async updateIndexes({ - client, - instance, - }: CoreClientProvider.Client): Promise { - // in a separate promise, try and update the index - let indexUpdateSucceeded = true; - for (let i = 0; i < 10; i++) { - try { - await this.updateIndex({ client, instance }); - indexUpdateSucceeded = true; - break; - } catch (e) { - console.error(`Error while updating index in attempt ${i}.`, e); - } - } - if (!indexUpdateSucceeded) { - console.error('Could not update the index. Please restart to try again.'); - } - - let libIndexUpdateSucceeded = true; - for (let i = 0; i < 10; i++) { - try { - await this.updateLibraryIndex({ client, instance }); - libIndexUpdateSucceeded = true; - break; - } catch (e) { - console.error(`Error while updating library index in attempt ${i}.`, e); - } - } - if (!libIndexUpdateSucceeded) { - console.error( - 'Could not update the library index. Please restart to try again.' - ); - } - - if (indexUpdateSucceeded && libIndexUpdateSucceeded) { - this.notificationService.notifyIndexUpdated(); - } - return { client, instance }; + protected async updateIndexes( + client: CoreClientProvider.Client + ): Promise { + await Promise.all([ + retry(() => this.updateIndex(client), 50, 3), + retry(() => this.updateLibraryIndex(client), 50, 3), + ]); + this.notificationService.notifyIndexUpdated(); + return client; } protected async updateLibraryIndex({ @@ -231,7 +272,9 @@ export class CoreClientProvider extends GrpcClientProvider((resolve, reject) => { - resp.on('error', reject); + resp.on('error', (error) => { + reject(error); + }); resp.on('end', resolve); }); } diff --git a/arduino-ide-extension/src/node/daemon-log.ts b/arduino-ide-extension/src/node/daemon-log.ts deleted file mode 100644 index f87a61425..000000000 --- a/arduino-ide-extension/src/node/daemon-log.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { ILogger, LogLevel } from '@theia/core/lib/common/logger'; - -export interface DaemonLog { - readonly time: string; - readonly level: DaemonLog.Level; - readonly msg: string; -} - -export namespace DaemonLog { - export interface Url { - readonly Scheme: string; - readonly Host: string; - readonly Path: string; - } - - export namespace Url { - export function is(arg: any | undefined): arg is Url { - return ( - !!arg && - typeof arg.Scheme === 'string' && - typeof arg.Host === 'string' && - typeof arg.Path === 'string' - ); - } - - export function toString(url: Url): string { - const { Scheme, Host, Path } = url; - return `${Scheme}://${Host}${Path}`; - } - } - - export interface System { - readonly os: string; - // readonly Resource: Resource; - } - - export namespace System { - export function toString(system: System): string { - return `OS: ${system.os}`; - } - } - - export interface Tool { - readonly version: string; - readonly systems: System[]; - } - - export namespace Tool { - export function is(arg: any | undefined): arg is Tool { - return !!arg && typeof arg.version === 'string' && 'systems' in arg; - } - - export function toString(tool: Tool): string { - const { version, systems } = tool; - return `Version: ${version}${ - !!systems - ? ` Systems: [${tool.systems.map(System.toString).join(', ')}]` - : '' - }`; - } - } - - export type Level = 'trace' | 'debug' | 'info' | 'warning' | 'error'; - - export function is(arg: any | undefined): arg is DaemonLog { - return ( - !!arg && - typeof arg.time === 'string' && - typeof arg.level === 'string' && - typeof arg.msg === 'string' - ); - } - - export function toLogLevel(log: DaemonLog): LogLevel { - const { level } = log; - switch (level) { - case 'trace': - return LogLevel.TRACE; - case 'debug': - return LogLevel.DEBUG; - case 'info': - return LogLevel.INFO; - case 'warning': - return LogLevel.WARN; - case 'error': - return LogLevel.ERROR; - default: - return LogLevel.INFO; - } - } - - export function log(logger: ILogger, logMessages: string): void { - const parsed = parse(logMessages); - for (const log of parsed) { - const logLevel = toLogLevel(log); - const message = toMessage(log, { omitLogLevel: true }); - logger.log(logLevel, message); - } - } - - function parse(toLog: string): DaemonLog[] { - const messages = toLog.trim().split('\n'); - const result: DaemonLog[] = []; - for (let i = 0; i < messages.length; i++) { - try { - const maybeDaemonLog = JSON.parse(messages[i]); - if (DaemonLog.is(maybeDaemonLog)) { - result.push(maybeDaemonLog); - continue; - } - } catch { - /* NOOP */ - } - result.push({ - time: new Date().toString(), - level: 'info', - msg: messages[i], - }); - } - return result; - } - - export function toPrettyString(logMessages: string): string { - const parsed = parse(logMessages); - return parsed.map((log) => toMessage(log)).join('\n') + '\n'; - } - - function toMessage( - log: DaemonLog, - options: { omitLogLevel: boolean } = { omitLogLevel: false } - ): string { - const details = Object.keys(log) - .filter((key) => key !== 'msg' && key !== 'level' && key !== 'time') - .map((key) => toDetails(log, key)) - .join(', '); - const logLevel = options.omitLogLevel - ? '' - : `[${log.level.toUpperCase()}] `; - return `${logLevel}${log.msg}${!!details ? ` [${details}]` : ''}`; - } - - function toDetails(log: DaemonLog, key: string): string { - let value = (log as any)[key]; - if (DaemonLog.Url.is(value)) { - value = DaemonLog.Url.toString(value); - } else if (DaemonLog.Tool.is(value)) { - value = DaemonLog.Tool.toString(value); - } else if (typeof value === 'object') { - value = JSON.stringify(value).replace(/\"([^(\")"]+)\":/g, '$1:'); // Remove the quotes from the property keys. - } - return `${key.toLowerCase()}: ${value}`; - } -} diff --git a/arduino-ide-extension/src/node/examples-service-impl.ts b/arduino-ide-extension/src/node/examples-service-impl.ts index b3730ce53..7b1f0f1b0 100644 --- a/arduino-ide-extension/src/node/examples-service-impl.ts +++ b/arduino-ide-extension/src/node/examples-service-impl.ts @@ -1,10 +1,18 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { join, basename } from 'path'; import * as fs from 'fs'; import { promisify } from 'util'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { notEmpty } from '@theia/core/lib/common/objects'; -import { Sketch, SketchContainer } from '../common/protocol/sketches-service'; +import { + Sketch, + SketchRef, + SketchContainer, +} from '../common/protocol/sketches-service'; import { SketchesServiceImpl } from './sketches-service-impl'; import { ExamplesService } from '../common/protocol/examples-service'; import { @@ -13,6 +21,71 @@ import { LibraryService, } from '../common/protocol'; import { ConfigServiceImpl } from './config-service-impl'; +import { duration } from '../common/decorators'; +import { URI } from '@theia/core/lib/common/uri'; +import { Path } from '@theia/core/lib/common/path'; + +interface BuiltInSketchRef { + readonly name: string; + readonly relativePath: string; +} +namespace BuiltInSketchRef { + export function toSketchRef( + { name, relativePath }: BuiltInSketchRef, + root: URI + ): SketchRef { + return { + name, + uri: root.resolve(relativePath).toString(), + }; + } +} +interface BuiltInSketchContainer { + readonly label: string; + readonly children: BuiltInSketchContainer[]; + readonly sketches: BuiltInSketchRef[]; +} +namespace BuiltInSketchContainer { + export function toSketchContainer( + source: BuiltInSketchContainer, + root: URI + ): SketchContainer { + return { + label: source.label, + children: source.children.map((child) => toSketchContainer(child, root)), + sketches: source.sketches.map((child) => + BuiltInSketchRef.toSketchRef(child, root) + ), + }; + } +} + +@injectable() +export class BuiltInExamplesServiceImpl { + protected _builtIns: SketchContainer[] | undefined; + + @postConstruct() + protected init(): void { + this.builtIns(); + } + + async builtIns(): Promise { + if (this._builtIns) { + return this._builtIns; + } + const examplesRootPath = join(__dirname, '..', '..', 'Examples'); + const examplesRootUri = FileUri.create(examplesRootPath); + const rawJson = await fs.promises.readFile( + join(examplesRootPath, 'examples.json'), + { encoding: 'utf8' } + ); + const examples: BuiltInSketchContainer[] = JSON.parse(rawJson); + this._builtIns = examples.map((container) => + BuiltInSketchContainer.toSketchContainer(container, examplesRootUri) + ); + return this._builtIns; + } +} @injectable() export class ExamplesServiceImpl implements ExamplesService { @@ -25,25 +98,11 @@ export class ExamplesServiceImpl implements ExamplesService { @inject(ConfigServiceImpl) protected readonly configService: ConfigServiceImpl; - protected _all: SketchContainer[] | undefined; + @inject(BuiltInExamplesServiceImpl) + private readonly builtInExamplesService: BuiltInExamplesServiceImpl; - @postConstruct() - protected init(): void { - this.builtIns(); - } - - async builtIns(): Promise { - if (this._all) { - return this._all; - } - const exampleRootPath = join(__dirname, '..', '..', 'Examples'); - const exampleNames = await promisify(fs.readdir)(exampleRootPath); - this._all = await Promise.all( - exampleNames - .map((name) => join(exampleRootPath, name)) - .map((path) => this.load(path)) - ); - return this._all; + builtIns(): Promise { + return this.builtInExamplesService.builtIns(); } // TODO: decide whether it makes sense to cache them. Keys should be: `fqbn` + version of containing core/library. @@ -51,6 +110,28 @@ export class ExamplesServiceImpl implements ExamplesService { user: SketchContainer[]; current: SketchContainer[]; any: SketchContainer[]; + }> { + const [/*old,*/ _new] = await Promise.all([ + // this.installedOld({ fqbn }), + this.installedNew({ fqbn }), + ]); + // Compare new and old + // if (eq(old, _new)) { + // console.log('---- happiness. the packages are the same'); + // } else { + // console.error('---- yayy :( the packages are not the same'); + // const diffObj = diff(old, _new); + // console.error(JSON.stringify(diffObj)); + // } + + return _new; + } + + @duration() + async installedOld({ fqbn }: { fqbn?: string }): Promise<{ + user: SketchContainer[]; + current: SketchContainer[]; + any: SketchContainer[]; }> { const user: SketchContainer[] = []; const current: SketchContainer[] = []; @@ -59,7 +140,7 @@ export class ExamplesServiceImpl implements ExamplesService { fqbn, }); for (const pkg of packages) { - const container = await this.tryGroupExamples(pkg); + const container = await this.tryGroupExamplesOld(pkg); const { location } = pkg; if (location === LibraryLocation.USER) { user.push(container); @@ -72,6 +153,9 @@ export class ExamplesServiceImpl implements ExamplesService { any.push(container); } } + // user.sort((left, right) => left.label.localeCompare(right.label)); + // current.sort((left, right) => left.label.localeCompare(right.label)); + // any.sort((left, right) => left.label.localeCompare(right.label)); return { user, current, any }; } @@ -80,7 +164,7 @@ export class ExamplesServiceImpl implements ExamplesService { * folder hierarchy. This method tries to workaround it by falling back to the `installDirUri` and manually creating the * location of the examples. Otherwise it creates the example container from the direct examples FS paths. */ - protected async tryGroupExamples({ + protected async tryGroupExamplesOld({ label, exampleUris, installDirUri, @@ -102,7 +186,7 @@ export class ExamplesServiceImpl implements ExamplesService { if (isDir) { const fileNames = await promisify(fs.readdir)(examplesPath); const children: SketchContainer[] = []; - const sketches: Sketch[] = []; + const sketches: SketchRef[] = []; for (const fileName of fileNames) { const subPath = join(examplesPath, fileName); const subIsDir = (await promisify(fs.lstat)(subPath)).isDirectory(); @@ -112,9 +196,15 @@ export class ExamplesServiceImpl implements ExamplesService { const container = await this.load(subPath); if (container.children.length || container.sketches.length) { children.push(container); + children.sort((left, right) => + left.label.localeCompare(right.label) + ); } } else { - sketches.push(sketch); + sketches.push({ name: sketch.name, uri: sketch.uri }); + sketches.sort((left, right) => + left.name.localeCompare(right.name) + ); } } } @@ -136,6 +226,140 @@ export class ExamplesServiceImpl implements ExamplesService { }; } + @duration() + async installedNew({ fqbn }: { fqbn?: string }): Promise<{ + user: SketchContainer[]; + current: SketchContainer[]; + any: SketchContainer[]; + }> { + const user: SketchContainer[] = []; + const current: SketchContainer[] = []; + const any: SketchContainer[] = []; + const packages: LibraryPackage[] = await this.libraryService.list({ + fqbn, + }); + for (const pkg of packages) { + const container = await this.tryGroupExamplesNew(pkg); + const { location } = pkg; + if (location === LibraryLocation.USER) { + user.push(container); + } else if ( + location === LibraryLocation.PLATFORM_BUILTIN || + LibraryLocation.REFERENCED_PLATFORM_BUILTIN + ) { + current.push(container); + } else { + any.push(container); + } + } + // user.sort((left, right) => left.label.localeCompare(right.label)); + // current.sort((left, right) => left.label.localeCompare(right.label)); + // any.sort((left, right) => left.label.localeCompare(right.label)); + return { user, current, any }; + } + + /** + * The CLI provides direct FS paths to the examples so that menus and menu groups cannot be built for the UI by traversing the + * folder hierarchy. This method tries to workaround it by falling back to the `installDirUri` and manually creating the + * location of the examples. Otherwise it creates the example container from the direct examples FS paths. + */ + protected async tryGroupExamplesNew({ + label, + exampleUris, + installDirUri, + }: LibraryPackage): Promise { + const container = SketchContainer.create(label); + if (!installDirUri || !exampleUris.length) { + return container; + } + // Args example: + // exampleUris + // 0:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/MQTT' + // 1:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Master' + // 2:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Slave' + // installDirUri + // 'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1' + // Expected menu structure: + // ATOM_DTU_CAT1 > Modbus > ModBus-RTU > Master + // | > Slave + // > MQTT + const logInfo = (ref: SketchRef) => + `Example URI: ${ref.uri}, install location URI: ${installDirUri}.`; + for (const ref of exampleUris.map(SketchRef.fromUri)) { + const path = new URI(installDirUri).relative(new URI(ref.uri)); + if (!path) { + console.warn( + `Could not resolve the sketch location from its install location. Skipping. ${logInfo( + ref + )}` + ); + continue; + } + if (path.isAbsolute) { + console.warn( + `Expected a relative path between the sketch and the install locations. Skipping. Path was: ${path}. ${logInfo( + ref + )}` + ); + continue; + } + const pathSegments = path.toString().split(Path.separator); + if (pathSegments.length < 2) { + console.warn( + `Expected at least two segments long relative path. Skipping. Path segments were: ${pathSegments}. ${logInfo( + ref + )}` + ); + continue; + } + // the relative must start start with `example` or `Examples` or `EXAMPLE`, .etc. It's open source. + if (!/^examples?$/gi.test(pathSegments[0])) { + console.warn( + `First segment must start with "examples-like". More formally: \`/^examples?$/gi\`. Path segments were: ${pathSegments}. ${logInfo( + ref + )}` + ); + } + const getOrCreateChildContainer = ( + label: string, + parent: SketchContainer + ) => { + let child = parent.children.find( + ({ label: childLabel }) => childLabel === label + ); + if (!child) { + child = SketchContainer.create(label); + parent.children.push(child); + //TODO: remove or move sort + parent.children.sort((left, right) => + left.label.localeCompare(right.label) + ); + } + return child; + }; + const refContainer = pathSegments.reduce( + (container, segment, index, segments) => { + if (index === 0) { + // skip the first "example-like" segment + return container; + } + if (index === segments.length - 1) { + // if last segment, it's the example sketch itself, do not create container for it. + return container; + } + return getOrCreateChildContainer(segment, container); + }, + container + ); + refContainer.sketches.push(ref); + //TODO: remove or move sort + refContainer.sketches.sort((left, right) => + left.name.localeCompare(right.name) + ); + } + return container; + } + // Built-ins are included inside the IDE. protected async load(path: string): Promise { if (!(await promisify(fs.exists)(path))) { @@ -146,17 +370,19 @@ export class ExamplesServiceImpl implements ExamplesService { throw new Error(`${path} is not a directory.`); } const names = await promisify(fs.readdir)(path); - const sketches: Sketch[] = []; + const sketches: SketchRef[] = []; const children: SketchContainer[] = []; for (const p of names.map((name) => join(path, name))) { const stat = await promisify(fs.stat)(p); if (stat.isDirectory()) { const sketch = await this.tryLoadSketch(p); if (sketch) { - sketches.push(sketch); + sketches.push({ name: sketch.name, uri: sketch.uri }); + sketches.sort((left, right) => left.name.localeCompare(right.name)); } else { const child = await this.load(p); children.push(child); + children.sort((left, right) => left.label.localeCompare(right.label)); } } } diff --git a/arduino-ide-extension/src/node/grpc-client-provider.ts b/arduino-ide-extension/src/node/grpc-client-provider.ts index af1fcede6..10d7b3d70 100644 --- a/arduino-ide-extension/src/node/grpc-client-provider.ts +++ b/arduino-ide-extension/src/node/grpc-client-provider.ts @@ -1,4 +1,8 @@ -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import { ILogger } from '@theia/core/lib/common/logger'; import { MaybePromise } from '@theia/core/lib/common/types'; import { ConfigServiceImpl } from './config-service-impl'; @@ -15,16 +19,18 @@ export abstract class GrpcClientProvider { @inject(ConfigServiceImpl) protected readonly configService: ConfigServiceImpl; - protected _port: string | number | undefined; + protected _port: string | undefined; protected _client: C | Error | undefined; @postConstruct() protected init(): void { - const updateClient = () => { - this.reconcileClient(); - }; - this.configService.onConfigChange(updateClient); - this.daemon.ready.then(updateClient); + this.configService.onConfigChange(() => { + // Only reconcile the gRPC client if the port is known. Hence the CLI daemon is running. + if (this._port) { + this.reconcileClient(this._port); + } + }); + this.daemon.getPort().then((port) => this.reconcileClient(port)); this.daemon.onDaemonStopped(() => { if (this._client && !(this._client instanceof Error)) { this.close(this._client); @@ -36,32 +42,25 @@ export abstract class GrpcClientProvider { async client(): Promise { try { - await this.daemon.ready; + await this.daemon.getPort(); return this._client; } catch (error) { return error; } } - protected async reconcileClient(): Promise { - const port = await this.daemon.getPort(); - - if (this._port === port) { - return; // Nothing to do. - } + protected async reconcileClient(port: string): Promise { this._port = port; if (this._client && !(this._client instanceof Error)) { this.close(this._client); this._client = undefined; } - if (this._port) { - try { - const client = await this.createClient(this._port); - this._client = client; - } catch (error) { - this.logger.error('Could not create client for gRPC.', error); - this._client = error; - } + try { + const client = await this.createClient(this._port); + this._client = client; + } catch (error) { + this.logger.error('Could not create client for gRPC.', error); + this._client = error; } } diff --git a/arduino-ide-extension/src/node/notification-service-server.ts b/arduino-ide-extension/src/node/notification-service-server.ts index 7ce211eca..851452d38 100644 --- a/arduino-ide-extension/src/node/notification-service-server.ts +++ b/arduino-ide-extension/src/node/notification-service-server.ts @@ -19,8 +19,8 @@ export class NotificationServiceServerImpl this.clients.forEach((client) => client.notifyIndexUpdated()); } - notifyDaemonStarted(): void { - this.clients.forEach((client) => client.notifyDaemonStarted()); + notifyDaemonStarted(port: string): void { + this.clients.forEach((client) => client.notifyDaemonStarted(port)); } notifyDaemonStopped(): void { diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts index b197f2125..e8ac51616 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -3,13 +3,14 @@ import * as minimatch from 'minimatch'; import * as fs from 'fs'; import * as os from 'os'; import * as temp from 'temp'; +import * as tempDir from 'temp-dir'; import * as path from 'path'; import * as crypto from 'crypto'; import { ncp } from 'ncp'; import { promisify } from 'util'; import URI from '@theia/core/lib/common/uri'; import { FileUri } from '@theia/core/lib/node'; -import { isWindows } from '@theia/core/lib/common/os'; +import { isWindows, isOSX } from '@theia/core/lib/common/os'; import { ConfigService } from '../common/protocol/config-service'; import { SketchesService, @@ -24,16 +25,28 @@ import { ArchiveSketchRequest, LoadSketchRequest, } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb'; +import { duration } from '../common/decorators'; +import * as glob from 'glob'; +import { Deferred } from '@theia/core/lib/common/promise-util'; const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/; const prefix = '.arduinoIDE-unsaved'; @injectable() -export class SketchesServiceImpl extends CoreClientAware - implements SketchesService { +export class SketchesServiceImpl + extends CoreClientAware + implements SketchesService +{ private sketchSuffixIndex = 1; private lastSketchBaseName: string; + // If on macOS, the `temp-dir` lib will make sure there is resolved realpath. + // If on Windows, the `C:\Users\KITTAA~1\AppData\Local\Temp` path will be resolved and normalized to `C:\Users\kittaakos\AppData\Local\Temp`. + // Note: VS Code URI normalizes the drive letter. `C:` will be converted into `c:`. + // https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 + private tempDirRealpath = isOSX + ? tempDir + : maybeNormalizeDrive(fs.realpathSync.native(tempDir)); @inject(ConfigService) protected readonly configService: ConfigService; @@ -43,13 +56,146 @@ export class SketchesServiceImpl extends CoreClientAware @inject(EnvVariablesServer) protected readonly envVariableServer: EnvVariablesServer; + async getSketches({ uri, exclude, }: { uri?: string; exclude?: string[]; - }): Promise { + }): Promise { + const [/*old,*/ _new] = await Promise.all([ + // this.getSketchesOld({ uri, exclude }), + this.getSketchesNew({ uri, exclude }), + ]); + return _new; + } + + @duration() + async getSketchesNew({ + uri, + exclude, + }: { + uri?: string; + exclude?: string[]; + }): Promise { + const root = await this.root(uri); + const pathToAllSketchFiles = await new Promise( + (resolve, reject) => { + glob( + '/!(libraries|hardware)/**/*.{ino,pde}', + { root }, + (error, results) => { + if (error) { + reject(error); + } else { + resolve(results); + } + } + ); + } + ); + // Sort by path length to filter out nested sketches, such as the `Nested_folder` inside the `Folder` sketch. + // + // `directories#user` + // | + // +--Folder + // | + // +--Folder.ino + // | + // +--Nested_folder + // | + // +--Nested_folder.ino + pathToAllSketchFiles.sort((left, right) => left.length - right.length); + const container = SketchContainer.create( + uri ? path.basename(root) : 'Sketchbook' + ); + const getOrCreateChildContainer = ( + parent: SketchContainer, + segments: string[] + ) => { + if (segments.length === 1) { + throw new Error( + `Expected at least two segments relative path: ['ExampleSketchName', 'ExampleSketchName.{ino,pde}]. Was: ${segments}` + ); + } + if (segments.length === 2) { + return parent; + } + const label = segments[0]; + const existingSketch = parent.sketches.find( + (sketch) => sketch.name === label + ); + if (existingSketch) { + // If the container has a sketch with the same label, it cannot have a child container. + // See above example about how to ignore nested sketches. + return undefined; + } + let child = parent.children.find((child) => child.label === label); + if (!child) { + child = SketchContainer.create(label); + parent.children.push(child); + } + return child; + }; + for (const pathToSketchFile of pathToAllSketchFiles) { + const relative = path.relative(root, pathToSketchFile); + if (!relative) { + console.warn( + `Could not determine relative sketch path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Relative path was: ${relative}` + ); + continue; + } + const segments = relative.split(path.sep); + if (segments.length < 2) { + // folder name, and sketch name. + console.warn( + `Expected at least one segment relative path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Segments were: ${segments}.` + ); + continue; + } + // the folder name and the sketch name must match. For example, `Foo/foo.ino` is invalid. + // drop the folder name from the sketch name, if `.ino` or `.pde` remains, it's valid + const sketchName = segments[segments.length - 2]; + const sketchFilename = segments[segments.length - 1]; + const sketchFileExtension = segments[segments.length - 1].replace( + new RegExp(sketchName), + '' + ); + if (sketchFileExtension !== '.ino' && sketchFileExtension !== '.pde') { + console.warn( + `Mismatching sketch file <${sketchFilename}> and sketch folder name <${sketchName}>. Skipping` + ); + continue; + } + const child = getOrCreateChildContainer(container, segments); + if (child) { + child.sketches.push({ + name: sketchName, + uri: FileUri.create(pathToSketchFile).toString(), + }); + } + } + return container; + } + + private async root(uri?: string | undefined): Promise { + return FileUri.fsPath(uri ?? (await this.sketchbookUri())); + } + + private async sketchbookUri(): Promise { + const { sketchDirUri } = await this.configService.getConfiguration(); + return sketchDirUri; + } + + @duration() + async getSketchesOld({ + uri, + exclude, + }: { + uri?: string; + exclude?: string[]; + }): Promise { const start = Date.now(); let sketchbookPath: undefined | string; if (!uri) { @@ -137,22 +283,39 @@ export class SketchesServiceImpl extends CoreClientAware } async loadSketch(uri: string): Promise { - await this.coreClientProvider.initialized; const { client, instance } = await this.coreClient(); const req = new LoadSketchRequest(); - req.setSketchPath(FileUri.fsPath(uri)); + const requestSketchPath = FileUri.fsPath(uri); + req.setSketchPath(requestSketchPath); req.setInstance(instance); + const stat = new Deferred(); + fs.lstat(requestSketchPath, (err, result) => + err ? stat.resolve(err) : stat.resolve(result) + ); const sketch = await new Promise((resolve, reject) => { client.loadSketch(req, async (err, resp) => { if (err) { reject(err); return; } - const sketchFolderPath = resp.getLocationPath(); - const { mtimeMs } = await promisify(fs.lstat)(sketchFolderPath); + const responseSketchPath = maybeNormalizeDrive(resp.getLocationPath()); + if (requestSketchPath !== responseSketchPath) { + console.warn( + `Warning! The request sketch path was different than the response sketch path from the CLI. This could be a potential bug. Request: <${requestSketchPath}>, response: <${responseSketchPath}>.` + ); + } + const resolvedStat = await stat.promise; + if (resolvedStat instanceof Error) { + console.error( + `The CLI could load the sketch from ${requestSketchPath}, but stating the folder has failed.` + ); + reject(resolvedStat); + return; + } + const { mtimeMs } = resolvedStat; resolve({ - name: path.basename(sketchFolderPath), - uri: FileUri.create(sketchFolderPath).toString(), + name: path.basename(responseSketchPath), + uri: FileUri.create(responseSketchPath).toString(), mainFileUri: FileUri.create(resp.getMainFile()).toString(), otherSketchFileUris: resp .getOtherSketchFilesList() @@ -292,12 +455,18 @@ export class SketchesServiceImpl extends CoreClientAware ]; const today = new Date(); const parentPath = await new Promise((resolve, reject) => { - temp.mkdir({ prefix }, (err, dirPath) => { - if (err) { - reject(err); + temp.mkdir({ prefix }, (createError, dirPath) => { + if (createError) { + reject(createError); return; } - resolve(dirPath); + fs.realpath.native(dirPath, (resolveError, resolvedDirPath) => { + if (resolveError) { + reject(resolveError); + return; + } + resolve(resolvedDirPath); + }); }); }); const sketchBaseName = `sketch_${ @@ -396,19 +565,20 @@ void loop() { } async isTemp(sketch: Sketch): Promise { - let sketchPath = FileUri.fsPath(sketch.uri); - let temp = await promisify(fs.realpath)(os.tmpdir()); - // Note: VS Code URI normalizes the drive letter. `C:` will be converted into `c:`. - // https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 - if (isWindows) { - if (WIN32_DRIVE_REGEXP.exec(sketchPath)) { - sketchPath = firstToLowerCase(sketchPath); - } - if (WIN32_DRIVE_REGEXP.exec(temp)) { - temp = firstToLowerCase(temp); - } - } - return sketchPath.indexOf(prefix) !== -1 && sketchPath.startsWith(temp); + // Consider the following paths: + // macOS: + // - Temp folder: /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T + // - Sketch folder: /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925 + // Windows: + // - Temp folder: C:\Users\KITTAA~1\AppData\Local\Temp + // - Sketch folder: c:\Users\kittaakos\AppData\Local\Temp\.arduinoIDE-unsaved2022431-21824-116kfaz.9ljl\sketch_may31a + // Both sketches are valid and temp, but this function will give a false-negative result if we use the default `os.tmpdir()` logic. + const sketchPath = maybeNormalizeDrive(FileUri.fsPath(sketch.uri)); + const tempPath = this.tempDirRealpath; // https://github.com/sindresorhus/temp-dir + const result = + sketchPath.indexOf(prefix) !== -1 && sketchPath.startsWith(tempPath); + console.log('isTemp?', result, sketch.uri); + return result; } async copy( @@ -518,12 +688,22 @@ interface SketchContainerWithDetails extends SketchContainer { readonly sketches: SketchWithDetails[]; } +/** + * If on Windows, will change the input `C:\\path\\to\\somewhere` to `c:\\path\\to\\somewhere`. + */ +function maybeNormalizeDrive(input: string): string { + if (isWindows && WIN32_DRIVE_REGEXP.test(input)) { + return firstToLowerCase(input); + } + return input; +} + /* * When a new sketch is created, add a suffix to distinguish it * from other new sketches I created today. * If 'sketch_jul8a' is already used, go with 'sketch_jul8b'. * If 'sketch_jul8b' already used, go with 'sketch_jul8c'. - * When it reacheas 'sketch_jul8z', go with 'sketch_jul8aa', + * When it reach 'sketch_jul8z', go with 'sketch_jul8aa', * and so on. */ function sketchIndexToLetters(num: number): string { diff --git a/arduino-ide-extension/src/node/theia/core/backend-application.ts b/arduino-ide-extension/src/node/theia/core/backend-application.ts index 6346d5e3c..1b7d6f927 100644 --- a/arduino-ide-extension/src/node/theia/core/backend-application.ts +++ b/arduino-ide-extension/src/node/theia/core/backend-application.ts @@ -11,9 +11,9 @@ export class BackendApplication extends TheiaBackendApplication { constructor( @inject(ContributionProvider) @named(BackendApplicationContribution) - protected readonly contributionsProvider: ContributionProvider, + protected override readonly contributionsProvider: ContributionProvider, @inject(BackendApplicationCliContribution) - protected readonly cliParams: BackendApplicationCliContribution + protected override readonly cliParams: BackendApplicationCliContribution ) { super(contributionsProvider, cliParams); // Workaround for Electron not installing a handler to ignore SIGPIPE diff --git a/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts b/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts index 819ce028b..7380cb71e 100644 --- a/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts +++ b/arduino-ide-extension/src/node/theia/env-variables/env-variables-server.ts @@ -7,7 +7,7 @@ import { EnvVariablesServerImpl as TheiaEnvVariablesServerImpl } from '@theia/co @injectable() export class EnvVariablesServer extends TheiaEnvVariablesServerImpl { - protected readonly configDirUri = Promise.resolve( + protected override readonly configDirUri = Promise.resolve( FileUri.create( join(homedir(), BackendApplicationConfigProvider.get().configDirName) ).toString() diff --git a/arduino-ide-extension/src/node/theia/git/git-init.ts b/arduino-ide-extension/src/node/theia/git/git-init.ts deleted file mode 100644 index 56bcc02f1..000000000 --- a/arduino-ide-extension/src/node/theia/git/git-init.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import findGit from 'find-git-exec'; -import { dirname } from 'path'; -import { pathExists } from 'fs-extra'; -import { GitInit } from '@theia/git/lib/node/init/git-init'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; - -@injectable() -export class DefaultGitInit implements GitInit { - protected readonly toDispose = new DisposableCollection(); - - async init(): Promise { - const { env } = process; - try { - const { execPath, path, version } = await findGit(); - if (!!execPath && !!path && !!version) { - const dir = dirname(dirname(path)); - const [execPathOk, pathOk, dirOk] = await Promise.all([ - pathExists(execPath), - pathExists(path), - pathExists(dir), - ]); - if (execPathOk && pathOk && dirOk) { - if ( - typeof env.LOCAL_GIT_DIRECTORY !== 'undefined' && - env.LOCAL_GIT_DIRECTORY !== dir - ) { - console.error( - `Misconfigured env.LOCAL_GIT_DIRECTORY: ${env.LOCAL_GIT_DIRECTORY}. dir was: ${dir}` - ); - return; - } - if ( - typeof env.GIT_EXEC_PATH !== 'undefined' && - env.GIT_EXEC_PATH !== execPath - ) { - console.error( - `Misconfigured env.GIT_EXEC_PATH: ${env.GIT_EXEC_PATH}. execPath was: ${execPath}` - ); - return; - } - process.env.LOCAL_GIT_DIRECTORY = dir; - process.env.GIT_EXEC_PATH = execPath; - console.info(`Using Git [${version}] from the PATH. (${path})`); - return; - } - } - } catch (err) { - console.error(err); - } - } - - dispose(): void { - this.toDispose.dispose(); - } -} diff --git a/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts b/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts index fd322ca4c..3481f7de1 100644 --- a/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts +++ b/arduino-ide-extension/src/node/theia/workspace/default-workspace-server.ts @@ -1,7 +1,10 @@ +import { promises as fs, constants } from 'fs'; import { injectable, inject } from '@theia/core/shared/inversify'; import { ILogger } from '@theia/core/lib/common/logger'; import { DefaultWorkspaceServer as TheiaDefaultWorkspaceServer } from '@theia/workspace/lib/node/default-workspace-server'; import { ConfigService } from '../../../common/protocol/config-service'; +import { SketchesService } from '../../../common/protocol'; +import { FileUri } from '@theia/core/lib/node'; @injectable() export class DefaultWorkspaceServer extends TheiaDefaultWorkspaceServer { @@ -11,13 +14,49 @@ export class DefaultWorkspaceServer extends TheiaDefaultWorkspaceServer { @inject(ILogger) protected readonly logger: ILogger; - protected async getWorkspaceURIFromCli(): Promise { + @inject(SketchesService) + private readonly sketchesService: SketchesService; + + override async onStart(): Promise { + // NOOP + // No need to remove untitled workspaces. IDE2 does not use workspaces. + } + + override async getMostRecentlyUsedWorkspace(): Promise { + const uri = await super.getMostRecentlyUsedWorkspace(); + if (!uri) { + const { uri } = await this.sketchesService.createNewSketch(); + return uri; + } + return uri; + } + + /** + * This is the async re-implementation of the default Theia behavior. + */ + override async getRecentWorkspaces(): Promise { + const listUri: string[] = []; + const data = await this.readRecentWorkspacePathsFromUserHome(); + if (data && data.recentRoots) { + await Promise.all( + data.recentRoots + .filter((element) => Boolean(element)) + .map(async (element) => { + if (await this.exists(element)) { + listUri.push(element); + } + }) + ); + } + return listUri; + } + + private async exists(uri: string): Promise { try { - const config = await this.configService.getConfiguration(); - return config.sketchDirUri; - } catch (err) { - this.logger.error(`Failed to determine the sketch directory: ${err}`); - return super.getWorkspaceURIFromCli(); + await fs.access(FileUri.fsPath(uri), constants.R_OK | constants.W_OK); + return true; + } catch { + return false; } } } diff --git a/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts b/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts index 0fd6cf4d9..b0f44b701 100644 --- a/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts +++ b/arduino-ide-extension/src/test/node/arduino-daemon-impl.test.ts @@ -16,15 +16,15 @@ class SilentArduinoDaemonImpl extends ArduinoDaemonImpl { super(); } - onData(data: string): void { + override onData(data: string): void { // NOOP } - async spawnDaemonProcess(): Promise<{ daemon: ChildProcess; port: string }> { + override async spawnDaemonProcess(): Promise<{ daemon: ChildProcess; port: string }> { return super.spawnDaemonProcess(); } - protected async getSpawnArgs(): Promise { + protected override async getSpawnArgs(): Promise { const cliConfigPath = await this.initCliConfig(); return [ 'daemon', diff --git a/arduino-ide-extension/src/test/node/cli-config.test.ts b/arduino-ide-extension/src/test/node/cli-config.test.ts index d21f56879..3d09ef2ee 100644 --- a/arduino-ide-extension/src/test/node/cli-config.test.ts +++ b/arduino-ide-extension/src/test/node/cli-config.test.ts @@ -19,7 +19,6 @@ describe('cli-config', () => { [ () => { const conf = defaultConfig(); - (conf.daemon as any).port = String(conf.daemon.port); return conf; }, defaultConfig, @@ -41,12 +40,8 @@ describe('cli-config', () => { board_manager: { additional_urls: [], }, - daemon: { - port: 5000, - }, directories: { data: 'data', - downloads: 'downloads', user: 'user', }, }; diff --git a/arduino-ide-extension/tsconfig.json b/arduino-ide-extension/tsconfig.json index e35c848fa..24604fe4c 100644 --- a/arduino-ide-extension/tsconfig.json +++ b/arduino-ide-extension/tsconfig.json @@ -6,6 +6,7 @@ "noEmitOnError": true, "noImplicitThis": true, "noUnusedLocals": true, + "noImplicitOverride": true, "strictNullChecks": true, "experimentalDecorators": true, "downlevelIteration": true, diff --git a/electron/build/template-package.json b/electron/build/template-package.json index 3100d1e89..caa8087cd 100644 --- a/electron/build/template-package.json +++ b/electron/build/template-package.json @@ -3,9 +3,7 @@ "author": "Arduino SA", "resolutions": { "**/fs-extra": "^4.0.3", - "electron-builder": "23.0.2", - "find-git-exec": "0.0.4", - "dugite-extra": "0.1.15" + "electron-builder": "23.0.2" }, "dependencies": { "node-log-rotate": "^0.1.5" diff --git a/i18n/en.json b/i18n/en.json index ccda42fcf..7d0a30505 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -344,8 +344,7 @@ "fileNewName": "Name for new file", "invalidExtension": ".{0} is not a valid extension", "invalidFilename": "Invalid filename.", - "newFileName": "New name for file", - "sketchDirectoryError": "There was an error creating the sketch directory. See the log for more details. The application will probably not work as expected." + "newFileName": "New name for file" } } } diff --git a/package.json b/package.json index 5ad471310..8f974b80d 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,6 @@ "jsdom": "^11.5.1" }, "resolutions": { - "find-git-exec": "0.0.4", - "dugite-extra": "0.1.15", "@types/react": "16.14.25" }, "scripts": { diff --git a/yarn.lock b/yarn.lock index 1ca796316..610fc73fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1105,7 +1105,7 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@grpc/grpc-js@^1.3.7": +"@grpc/grpc-js@^1.6.7": version "1.6.7" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.7.tgz#4c4fa998ff719fe859ac19fe977fdef097bb99aa" integrity sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw== @@ -1870,7 +1870,7 @@ dependencies: cross-spawn "^7.0.1" -"@mapbox/node-pre-gyp@^1.0.4", "@mapbox/node-pre-gyp@^1.0.5": +"@mapbox/node-pre-gyp@^1.0.5": version "1.0.9" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc" integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw== @@ -2638,30 +2638,6 @@ uuid "^8.0.0" vscode-languageserver-textdocument "^1.0.1" -"@theia/git@1.25.0": - version "1.25.0" - resolved "https://registry.yarnpkg.com/@theia/git/-/git-1.25.0.tgz#f1c028d7432ebca8622e296aa1139bcb7fe9fd4d" - integrity sha512-UB/m2vt+WEJZS81PZ2aOXbIDSVNtith/V3o8H41XcxZ25dYwbpC0F6Ln4br15vGOASGBKFPzAwBeNXDyEAOZkw== - dependencies: - "@theia/core" "1.25.0" - "@theia/editor" "1.25.0" - "@theia/filesystem" "1.25.0" - "@theia/monaco-editor-core" "1.65.2" - "@theia/navigator" "1.25.0" - "@theia/scm" "1.25.0" - "@theia/scm-extra" "1.25.0" - "@theia/workspace" "1.25.0" - "@types/diff" "^3.2.2" - "@types/p-queue" "^2.3.1" - diff "^3.4.0" - dugite-extra "0.1.15" - find-git-exec "^0.0.4" - find-git-repositories "^0.1.1" - moment "2.29.2" - octicons "^7.1.0" - p-queue "^2.4.2" - ts-md5 "^1.2.2" - "@theia/keymaps@1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@theia/keymaps/-/keymaps-1.25.0.tgz#0adfb6f088a532aa3c2f05448ac5697e6a16f5ad" @@ -2860,17 +2836,6 @@ node-pty "0.11.0-beta17" string-argv "^0.1.1" -"@theia/scm-extra@1.25.0": - version "1.25.0" - resolved "https://registry.yarnpkg.com/@theia/scm-extra/-/scm-extra-1.25.0.tgz#0ab22c77ef5918e35c44c3750fb46202fad26cbd" - integrity sha512-n4slV6Reb/zjtf9gZCs0l9pFmixi5gY+ut0Du1ZN4sqGJvSF5bfEx6567CbqX1zitsMTr2/dSLReS/Rqsu8flg== - dependencies: - "@theia/core" "1.25.0" - "@theia/editor" "1.25.0" - "@theia/filesystem" "1.25.0" - "@theia/navigator" "1.25.0" - "@theia/scm" "1.25.0" - "@theia/scm@1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@theia/scm/-/scm-1.25.0.tgz#e9765fc09979508b77bcfecd28dda8d8c4e6c8b0" @@ -3016,14 +2981,6 @@ dependencies: "@types/node" "*" -"@types/bytebuffer@^5.0.40": - version "5.0.43" - resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.43.tgz#b5259fca1412106bcee0cabfbf7c104846d06738" - integrity sha512-vQnTYvy4LpSojHjKdmg4nXFI1BAiYPvZ/k3ouczZAQnbDprk1xqxJiFmFHyy8y6MuUq3slz5erNMtn6n87uVKw== - dependencies: - "@types/long" "*" - "@types/node" "*" - "@types/cacheable-request@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" @@ -3161,7 +3118,7 @@ dependencies: "@types/node" "*" -"@types/glob@*", "@types/glob@^7.1.1": +"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== @@ -3169,14 +3126,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/glob@^5.0.35": - version "5.0.37" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.37.tgz#d0982abc88f9aebbd62099d3d70440cbcea692de" - integrity sha512-ATA/xrS7CZ3A2WCPVY4eKdNpybq56zqlTirnHhhyOztZM/lPxJzusOBI3BsaXbu6FrUluqzvMlI4sZ6BDYMlMg== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - "@types/google-protobuf@^3.7.2": version "3.15.6" resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.6.tgz#674a69493ef2c849b95eafe69167ea59079eb504" @@ -3264,7 +3213,7 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== -"@types/long@*", "@types/long@^4.0.1": +"@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== @@ -3351,11 +3300,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef" integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA== -"@types/node@^10.14.22": - version "10.17.60" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" - integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== - "@types/node@^14.6.2": version "14.18.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.18.tgz#5c9503030df484ccffcbb935ea9a9e1d6fad1a20" @@ -3591,7 +3535,7 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.5.tgz#b1d2f772142a301538fae9bdf9cf15b9f2573a29" integrity sha512-hKB88y3YHL8oPOs/CNlaXtjWn93+Bs48sDQR37ZUqG2tLeCS7EA1cmnkKsuQsub9OKEB/y/Rw9zqJqqNSbqVlQ== -"@types/which@^1.3.1", "@types/which@^1.3.2": +"@types/which@^1.3.1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf" integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA== @@ -4470,14 +4414,6 @@ asap@^2.0.0: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== -ascli@~1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc" - integrity sha512-JGQaNxpaCJz9Bd1JvVaFIHuWn9S+l3xhN17R0V/vmUDiGE0QngNMXhjlqpwqV+91plWz9Fg+Lt28Lj7p5vjs8A== - dependencies: - colour "~0.7.1" - optjs "~3.2.2" - asn1@~0.2.3: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" @@ -4961,13 +4897,6 @@ byte-size@^5.0.1: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191" integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw== -bytebuffer@~5: - version "5.0.1" - resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" - integrity sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ== - dependencies: - long "~3" - bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -5169,7 +5098,7 @@ camelcase-keys@^6.2.2: map-obj "^4.0.0" quick-lru "^4.0.1" -camelcase@^2.0.0, camelcase@^2.0.1: +camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw== @@ -5269,13 +5198,6 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== -checksum@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/checksum/-/checksum-0.1.1.tgz#dc6527d4c90be8560dbd1ed4cecf3297d528e9e9" - integrity sha512-xWkkJpoWQ6CptWw2GvtoQbScL3xtvGjoqvHpALE7B0tSHxSw0ex0tlsKOKkbETaOYGBhMliAyscestDyAZIN9g== - dependencies: - optimist "~0.3.5" - chokidar@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" @@ -5368,15 +5290,6 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cliui@^3.0.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - cliui@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" @@ -5515,11 +5428,6 @@ colorette@^2.0.16: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== -colour@~0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" - integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= - columnify@^1.5.4: version "1.6.0" resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" @@ -5940,11 +5848,6 @@ crypto-js@^4.1.1: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== -css-element-queries@^1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/css-element-queries/-/css-element-queries-1.2.3.tgz#e14940b1fcd4bf0da60ea4145d05742d7172e516" - integrity sha512-QK9uovYmKTsV2GXWQiMOByVNrLn2qz6m3P7vWpOR4IdD6I3iXoDw5qtgJEN3Xq7gIbdHVKvzHjdAtcl+4Arc4Q== - css-loader@^6.2.0: version "6.7.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" @@ -6191,6 +6094,13 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" +deep-equals@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/deep-equals/-/deep-equals-0.0.2.tgz#21c164c55d1a50802d15d94f801d32585a99f0ce" + integrity sha1-IcFkxV0aUIAtFdlPgB0yWFqZ8M4= + dependencies: + ramda "^0.23.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -6201,6 +6111,18 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deep-object-diff@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.7.tgz#348b3246f426427dd633eaa50e1ed1fc2eafc7e4" + integrity sha512-QkgBca0mL08P6HiOjoqvmm6xOAl2W6CT2+34Ljhg0OeFan8cwlcdq8jrLKsBBuUFAZLsN5b6y491KdKEoSo9lg== + +deep-sort-object@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/deep-sort-object/-/deep-sort-object-1.0.2.tgz#3892dcef5dfd0efc2d6fa96beabccb33cd8ef91f" + integrity sha1-OJLc7139Dvwtb6lr6rzLM82O+R8= + dependencies: + is-plain-object "^2.0.1" + deepmerge@*, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -6453,28 +6375,6 @@ drivelist@^9.0.2: nan "^2.14.0" prebuild-install "^5.2.4" -dugite-extra@0.1.15: - version "0.1.15" - resolved "https://registry.yarnpkg.com/dugite-extra/-/dugite-extra-0.1.15.tgz#322406b628ea5515c5c6fcd65e4d040543d6268a" - integrity sha512-beLmQcIXLA8aXqWQZF/ooECoZvYKpBywIFwgqAoYnV04NdWUXDtZ6mMcjQf5eAz5PjXGXAYSuQ31zkPL8J85+A== - dependencies: - byline "^5.0.0" - dugite-no-gpl "1.69.0" - find-git-exec "^0.0.4" - upath "^2.0.1" - -dugite-no-gpl@1.69.0: - version "1.69.0" - resolved "https://registry.yarnpkg.com/dugite-no-gpl/-/dugite-no-gpl-1.69.0.tgz#bc9007cf5a595180f563ccc0e4f2cc80ebbaa52e" - integrity sha512-9NzPMyWW1uWEm+rEGivfQ0+zZ9soXrtk/zb6FIVpPa5CLoUdhMkLY4jHc0DDyayarxivJgrI/rHDdTUej4Zhrw== - dependencies: - checksum "^0.1.1" - mkdirp "^0.5.1" - progress "^2.0.0" - request "^2.86.0" - rimraf "^2.5.4" - tar "^4.0.2" - duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -7247,7 +7147,7 @@ fast-plist@^0.1.2: resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= -fast-safe-stringify@^2.0.7: +fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -7392,22 +7292,6 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-git-exec@0.0.4, find-git-exec@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/find-git-exec/-/find-git-exec-0.0.4.tgz#f1d0d35f93ad99bc81aacd357388d00ae902bc92" - integrity sha512-klzQwno+dpdeahtHhvZZ5Yn6K+zme1Aj+YJ4ZD+DywSLrQoyCywTrsubUZa1hHRehmfwBThoeKjS7fsaxhpfNA== - dependencies: - "@types/node" "^10.14.22" - "@types/which" "^1.3.2" - which "^2.0.1" - -find-git-repositories@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/find-git-repositories/-/find-git-repositories-0.1.3.tgz#4e83e085b20cb3e393c1e091adc3a3eec50b6dda" - integrity sha512-6q8ZIQ7loe0eWbz1O79J0gQ2wVpQ1ajsjV64HC2iJ7gOOqlEuDlG/T0xYr5gDYBFSHlS8dah1KGbndiWWdJ0PA== - dependencies: - nan "^2.14.0" - find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -7939,7 +7823,7 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -8046,7 +7930,7 @@ google-protobuf@3.12.4: resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.12.4.tgz#fd89b7e5052cdb35a80f9b455612851d542a5c9f" integrity sha512-ItTn8YepDQMHEMHloUPH+FDaTPiHTnbsMvP50aXfbI65IK3AA5+wXlHSygJH8xz+h1g4gu7V+CK5X1/SaGITsA== -google-protobuf@^3.11.4: +google-protobuf@^3.20.1: version "3.20.1" resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.20.1.tgz#1b255c2b59bcda7c399df46c65206aa3c7a0ce8b" integrity sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw== @@ -8125,18 +8009,6 @@ grpc-tools@^1.9.0: dependencies: "@mapbox/node-pre-gyp" "^1.0.5" -grpc@^1.24.11: - version "1.24.11" - resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.24.11.tgz#7039da9f6f22ce35168535a6d5dda618398a5966" - integrity sha512-8/AQdFCzCeCDWW3SoaMNp6ccbRvTQEH1O1u1uFtt29eWsg5gSZCJ3m6fbkduEIh3smY7WAPP+LgVJ5n3nZRxcA== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.4" - "@types/bytebuffer" "^5.0.40" - lodash.camelcase "^4.3.0" - lodash.clone "^4.5.0" - nan "^2.13.2" - protobufjs "^5.0.3" - grpc_tools_node_protoc_ts@^4.1.0: version "4.1.5" resolved "https://registry.yarnpkg.com/grpc_tools_node_protoc_ts/-/grpc_tools_node_protoc_ts-4.1.5.tgz#ad540a51867ff407196538d2d6370b27d6d3cfc8" @@ -8763,11 +8635,6 @@ inversify@^5.1.1: resolved "https://registry.yarnpkg.com/inversify/-/inversify-5.1.1.tgz#6fbd668c591337404e005a1946bfe0d802c08730" integrity sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ== -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - invert-kv@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" @@ -9079,7 +8946,7 @@ is-plain-obj@^4.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.0.0.tgz#06c0999fd7574edf5a906ba5644ad0feb3a84d22" integrity sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -9551,13 +9418,6 @@ lazy-val@^1.0.5: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - lcid@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" @@ -9772,11 +9632,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.clone@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" - integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= - lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -9916,11 +9771,6 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@~3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" - integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= - loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -10786,11 +10636,6 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@2.29.2: - version "2.29.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" - integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== - moment@^2.18.1, moment@^2.24.0: version "2.29.3" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" @@ -10894,7 +10739,7 @@ mz@^2.5.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.13.2, nan@^2.14.0: +nan@^2.14.0: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== @@ -11420,13 +11265,6 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -octicons@^7.1.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/octicons/-/octicons-7.4.0.tgz#0be0082ed75b81e680800ef978bf47078b670091" - integrity sha512-j53BDX+FpJ4DQwENARbk9hHkwG/Oaq5NPUMNzYdGxRA/R5M6BbPVQEakUVMNKLzvzPue/gEEUTtSj6utFse5QQ== - dependencies: - object-assign "^4.1.1" - octokit-pagination-methods@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" @@ -11483,13 +11321,6 @@ open@^8.0.6: is-docker "^2.1.1" is-wsl "^2.2.0" -optimist@~0.3.5: - version "0.3.7" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9" - integrity sha1-yQlBrVnkJzMokjB00s8ufLxuwNk= - dependencies: - wordwrap "~0.0.2" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -11514,11 +11345,6 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -optjs@~3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee" - integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4= - ora@^5.1.0: version "5.4.1" resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" @@ -11539,13 +11365,6 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - os-locale@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" @@ -12230,16 +12049,6 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -protobufjs@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.3.tgz#e4dfe9fb67c90b2630d15868249bcc4961467a17" - integrity sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA== - dependencies: - ascli "~1" - bytebuffer "~5" - glob "^7.0.5" - yargs "^3.10.0" - protobufjs@^6.10.0: version "6.11.2" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" @@ -12459,6 +12268,11 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +ramda@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.23.0.tgz#ccd13fff73497a93974e3e86327bfd87bd6e8e2b" + integrity sha1-zNE//3NJepOXTj6GMnv9h71ujis= + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -12975,7 +12789,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.82.0, request@^2.86.0, request@^2.87.0, request@^2.88.0: +request@^2.82.0, request@^2.87.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -14274,7 +14088,7 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.0.0, tar@^4.0.2, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: +tar@^4.0.0, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== @@ -14304,6 +14118,11 @@ temp-dir@^1.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + temp-write@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492" @@ -14947,11 +14766,6 @@ upath@^1.1.2, upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -upath@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" - integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -15375,11 +15189,6 @@ winchan@^0.2.2: resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.2.tgz#6766917b88e5e1cb75f455ffc7cc13f51e5c834e" integrity sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ== -window-size@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= - windows-release@^3.1.0: version "3.3.3" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" @@ -15397,11 +15206,6 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - worker-loader@^3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37" @@ -15563,7 +15367,7 @@ xterm@^4.16.0: resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1" integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ== -y18n@^3.2.0, y18n@^3.2.1: +y18n@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== @@ -15742,19 +15546,6 @@ yargs@^17.0.1: y18n "^5.0.5" yargs-parser "^21.0.0" -yargs@^3.10.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" - integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= - dependencies: - camelcase "^2.0.1" - cliui "^3.0.3" - decamelize "^1.1.1" - os-locale "^1.4.0" - string-width "^1.0.1" - window-size "^0.1.4" - y18n "^3.2.0" - yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"