mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-09-28 14:18:32 +00:00
Compare commits
46 Commits
2.0.0-rc9.
...
2.0.0-rc9.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4907ef2a47 | ||
![]() |
9ae3402631 | ||
![]() |
d0dfc656e6 | ||
![]() |
df3a34eec6 | ||
![]() |
20cc34ca9d | ||
![]() |
1b7f86b231 | ||
![]() |
0d545bea0e | ||
![]() |
204d71b2dd | ||
![]() |
5cb9166c83 | ||
![]() |
7828cc11ac | ||
![]() |
34a7fdb733 | ||
![]() |
7c361cf2d1 | ||
![]() |
8beade0867 | ||
![]() |
3afc2d7e4b | ||
![]() |
d40401437a | ||
![]() |
10ac7fd50a | ||
![]() |
07962e81d4 | ||
![]() |
785775327b | ||
![]() |
80dfa5b7dd | ||
![]() |
40425d49e0 | ||
![]() |
0c87fa9877 | ||
![]() |
5b79320302 | ||
![]() |
1da2dfc349 | ||
![]() |
d7bbfc515d | ||
![]() |
0c22884729 | ||
![]() |
fc9107c084 | ||
![]() |
474d5e5975 | ||
![]() |
f7f644cf36 | ||
![]() |
b5f9aa0f15 | ||
![]() |
cc5cf3b165 | ||
![]() |
125bd64c91 | ||
![]() |
ca47e8a09a | ||
![]() |
52804a5b52 | ||
![]() |
3ec62642dd | ||
![]() |
1281ad1932 | ||
![]() |
de32bddc20 | ||
![]() |
79ea0fa9a6 | ||
![]() |
683219dc1c | ||
![]() |
d674ab9b73 | ||
![]() |
5be1f9d7fe | ||
![]() |
9e2b73a045 | ||
![]() |
75e00c2bae | ||
![]() |
989300f25d | ||
![]() |
5226636fed | ||
![]() |
8b3f3c69fc | ||
![]() |
a39ab47e70 |
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -28,6 +28,8 @@ on:
|
||||
- cron: '0 3 * * *' # run every day at 3AM (https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule)
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: "1.17"
|
||||
JOB_TRANSFER_ARTIFACT: build-artifacts
|
||||
CHANGELOG_ARTIFACTS: changelog
|
||||
|
||||
@@ -66,6 +68,17 @@ jobs:
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Taskfile
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Package
|
||||
shell: bash
|
||||
env:
|
||||
|
15
.github/workflows/check-i18n-task.yml
vendored
15
.github/workflows/check-i18n-task.yml
vendored
@@ -1,5 +1,9 @@
|
||||
name: Check Internationalization
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: "1.17"
|
||||
|
||||
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
|
||||
on:
|
||||
push:
|
||||
@@ -31,6 +35,17 @@ jobs:
|
||||
node-version: '14.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Taskfile
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
|
15
.github/workflows/i18n-nightly-push.yml
vendored
15
.github/workflows/i18n-nightly-push.yml
vendored
@@ -1,5 +1,9 @@
|
||||
name: i18n-nightly-push
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: "1.17"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run every day at 1AM
|
||||
@@ -18,6 +22,17 @@ jobs:
|
||||
node-version: '14.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
|
15
.github/workflows/i18n-weekly-pull.yml
vendored
15
.github/workflows/i18n-weekly-pull.yml
vendored
@@ -1,5 +1,9 @@
|
||||
name: i18n-weekly-pull
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: "1.17"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run every monday at 2AM
|
||||
@@ -18,6 +22,17 @@ jobs:
|
||||
node-version: '14.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
|
13
.github/workflows/themes-weekly-pull.yml
vendored
13
.github/workflows/themes-weekly-pull.yml
vendored
@@ -7,6 +7,8 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# See vars.GO_VERSION field of https://github.com/arduino/arduino-cli/blob/master/DistTasks.yml
|
||||
GO_VERSION: "1.17"
|
||||
NODE_VERSION: 14.x
|
||||
|
||||
jobs:
|
||||
@@ -22,6 +24,17 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 3.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
|
@@ -62,6 +62,15 @@ The Config Service knows about your system, like for example the default sketch
|
||||
- Some CLI updates can bring changes to the gRPC interfaces, as the API might change. gRPC interfaces can be updated running the command
|
||||
`yarn --cwd arduino-ide-extension generate-protocol`
|
||||
|
||||
### Update **clangd** and **ClangFormat**
|
||||
|
||||
The [**clangd** C++ language server](https://clangd.llvm.org/) and the [**ClangFormat** code formatter](https://clang.llvm.org/docs/ClangFormat.html) tool dependencies are managed in parallel. Updating them to a different version is done by the following procedure:
|
||||
|
||||
1. If the target version is not already [available from the `arduino/clang-static-binaries` repository](https://github.com/arduino/clang-static-binaries/releases), submit [an issue there](https://github.com/arduino/clang-static-binaries/issues) requesting a build and wait for that to be completed.
|
||||
1. Validate the **ClangFormat** configuration for the target version by following the instructions [**here**](https://github.com/arduino/tooling-project-assets/tree/main/other/clang-format-configuration#clangformat-version-updates)
|
||||
1. Submit a pull request in the `arduino/arduino-ide` repository to update the version in the `arduino.clangd.version` key of [`package.json`](package.json).
|
||||
1. Submit a pull request in [the `arduino/tooling-project-assets` repository](https://github.com/arduino/tooling-project-assets) to update the version in the `vars.DEFAULT_CLANG_FORMAT_VERSION` field of [`Taskfile.yml`](https://github.com/arduino/tooling-project-assets/blob/main/Taskfile.yml).
|
||||
|
||||
### Customize Icons
|
||||
ArduinoIde uses a customized version of FontAwesome.
|
||||
In order to update/replace icons follow the following steps:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "arduino-ide-extension",
|
||||
"version": "2.0.0-rc9.2",
|
||||
"version": "2.0.0-rc9.3",
|
||||
"description": "An extension for Theia building the Arduino IDE",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
@@ -160,7 +160,7 @@
|
||||
],
|
||||
"arduino": {
|
||||
"cli": {
|
||||
"version": "0.26.0-rc.1"
|
||||
"version": "0.27.0-rc.1"
|
||||
},
|
||||
"fwuploader": {
|
||||
"version": "2.2.0"
|
||||
|
@@ -6,7 +6,7 @@
|
||||
const semver = require('semver');
|
||||
const moment = require('moment');
|
||||
const downloader = require('./downloader');
|
||||
const { goBuildFromGit } = require('./utils');
|
||||
const { taskBuildFromGit } = require('./utils');
|
||||
|
||||
const version = (() => {
|
||||
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||
@@ -82,6 +82,6 @@
|
||||
shell.exit(1);
|
||||
}
|
||||
} else {
|
||||
goBuildFromGit(version, destinationPath, 'CLI');
|
||||
taskBuildFromGit(version, destinationPath, 'CLI');
|
||||
}
|
||||
})();
|
||||
|
@@ -1,3 +1,14 @@
|
||||
/**
|
||||
* Clones something from GitHub and builds it with [`Task`](https://taskfile.dev/).
|
||||
*
|
||||
* @param version {object} the version object.
|
||||
* @param destinationPath {string} the absolute path of the output binary. For example, `C:\\folder\\arduino-cli.exe` or `/path/to/arduino-language-server`
|
||||
* @param taskName {string} for the CLI logging . Can be `'CLI'` or `'language-server'`, etc.
|
||||
*/
|
||||
exports.taskBuildFromGit = (version, destinationPath, taskName) => {
|
||||
return buildFromGit('task', version, destinationPath, taskName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clones something from GitHub and builds it with `Golang`.
|
||||
*
|
||||
@@ -6,6 +17,13 @@
|
||||
* @param taskName {string} for the CLI logging . Can be `'CLI'` or `'language-server'`, etc.
|
||||
*/
|
||||
exports.goBuildFromGit = (version, destinationPath, taskName) => {
|
||||
return buildFromGit('go', version, destinationPath, taskName);
|
||||
};
|
||||
|
||||
/**
|
||||
* The `command` is either `go` or `task`.
|
||||
*/
|
||||
function buildFromGit(command, version, destinationPath, taskName) {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const temp = require('temp');
|
||||
@@ -62,7 +80,7 @@ exports.goBuildFromGit = (version, destinationPath, taskName) => {
|
||||
}
|
||||
|
||||
shell.echo(`>>> Building the ${taskName}...`);
|
||||
if (shell.exec('go build', { cwd: tempRepoPath }).code !== 0) {
|
||||
if (shell.exec(`${command} build`, { cwd: tempRepoPath }).code !== 0) {
|
||||
shell.exit(1);
|
||||
}
|
||||
shell.echo(`<<< Done ${taskName} build.`);
|
||||
@@ -89,4 +107,4 @@ exports.goBuildFromGit = (version, destinationPath, taskName) => {
|
||||
shell.exit(1);
|
||||
}
|
||||
shell.echo(`>>> Verified ${taskName}.`);
|
||||
};
|
||||
}
|
||||
|
@@ -5,17 +5,14 @@ import {
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { SketchesService } from '../common/protocol';
|
||||
import {
|
||||
MAIN_MENU_BAR,
|
||||
MenuContribution,
|
||||
MenuModelRegistry,
|
||||
} from '@theia/core';
|
||||
import {
|
||||
Dialog,
|
||||
FrontendApplication,
|
||||
FrontendApplicationContribution,
|
||||
OnWillStopAction,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||
@@ -34,14 +31,9 @@ import { EditorCommands, EditorMainMenu } from '@theia/editor/lib/browser';
|
||||
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
||||
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||
import {
|
||||
CurrentSketch,
|
||||
SketchesServiceClientImpl,
|
||||
} from '../common/protocol/sketches-service-client-impl';
|
||||
import { ArduinoPreferences } from './arduino-preferences';
|
||||
import { BoardsServiceProvider } from './boards/boards-service-provider';
|
||||
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||
import { ArduinoMenus } from './menu/arduino-menus';
|
||||
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
|
||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||
@@ -63,18 +55,12 @@ export class ArduinoFrontendContribution
|
||||
@inject(BoardsServiceProvider)
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
|
||||
@inject(SketchesService)
|
||||
private readonly sketchService: SketchesService;
|
||||
|
||||
@inject(CommandRegistry)
|
||||
private readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(ArduinoPreferences)
|
||||
private readonly arduinoPreferences: ArduinoPreferences;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
private readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(FrontendApplicationStateService)
|
||||
private readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
@@ -91,7 +77,7 @@ export class ArduinoFrontendContribution
|
||||
}
|
||||
}
|
||||
|
||||
async onStart(app: FrontendApplication): Promise<void> {
|
||||
onStart(app: FrontendApplication): void {
|
||||
this.arduinoPreferences.onPreferenceChanged((event) => {
|
||||
if (event.newValue !== event.oldValue) {
|
||||
switch (event.preferenceName) {
|
||||
@@ -303,58 +289,4 @@ export class ArduinoFrontendContribution
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: should be handled by `Close` contribution. https://github.com/arduino/arduino-ide/issues/1016
|
||||
onWillStop(): OnWillStopAction {
|
||||
return {
|
||||
reason: 'temp-sketch',
|
||||
action: () => {
|
||||
return this.showTempSketchDialog();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async showTempSketchDialog(): Promise<boolean> {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!CurrentSketch.isValid(sketch)) {
|
||||
return true;
|
||||
}
|
||||
const isTemp = await this.sketchService.isTemp(sketch);
|
||||
if (!isTemp) {
|
||||
return true;
|
||||
}
|
||||
const messageBoxResult = await remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
message: nls.localize(
|
||||
'arduino/sketch/saveTempSketch',
|
||||
'Save your sketch to open it again later.'
|
||||
),
|
||||
title: nls.localize(
|
||||
'theia/core/quitTitle',
|
||||
'Are you sure you want to quit?'
|
||||
),
|
||||
type: 'question',
|
||||
buttons: [
|
||||
Dialog.CANCEL,
|
||||
nls.localizeByDefault('Save As...'),
|
||||
nls.localizeByDefault("Don't Save"),
|
||||
],
|
||||
}
|
||||
);
|
||||
const result = messageBoxResult.response;
|
||||
if (result === 2) {
|
||||
return true;
|
||||
} else if (result === 1) {
|
||||
return !!(await this.commandRegistry.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: false,
|
||||
wipeOriginal: true,
|
||||
}
|
||||
));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -141,8 +141,6 @@ import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handl
|
||||
import { TabBarToolbar } from './theia/core/tab-bar-toolbar';
|
||||
import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
|
||||
import { EditorWidgetFactory } from './theia/editor/editor-widget-factory';
|
||||
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { OutputWidget } from './theia/output/output-widget';
|
||||
import { BurnBootloader } from './contributions/burn-bootloader';
|
||||
import {
|
||||
ExamplesServicePath,
|
||||
@@ -215,7 +213,10 @@ import { SearchInWorkspaceFactory } from './theia/search-in-workspace/search-in-
|
||||
import { SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-result-tree-widget';
|
||||
import { SearchInWorkspaceResultTreeWidget } from './theia/search-in-workspace/search-in-workspace-result-tree-widget';
|
||||
import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider';
|
||||
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import {
|
||||
MonacoEditorFactory,
|
||||
MonacoEditorProvider as TheiaMonacoEditorProvider,
|
||||
} from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import { StorageWrapper } from './storage-wrapper';
|
||||
import { NotificationManager } from './theia/messages/notifications-manager';
|
||||
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||
@@ -314,13 +315,25 @@ import { FirstStartupInstaller } from './contributions/first-startup-installer';
|
||||
import { OpenSketchFiles } from './contributions/open-sketch-files';
|
||||
import { InoLanguage } from './contributions/ino-language';
|
||||
import { SelectedBoard } from './contributions/selected-board';
|
||||
import { CheckForUpdates } from './contributions/check-for-updates';
|
||||
import { CheckForIDEUpdates } from './contributions/check-for-ide-updates';
|
||||
import { OpenBoardsConfig } from './contributions/open-boards-config';
|
||||
import { SketchFilesTracker } from './contributions/sketch-files-tracker';
|
||||
import { MonacoThemeServiceIsReady } from './utils/window';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { StatusBarImpl } from './theia/core/status-bar';
|
||||
import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser';
|
||||
import { EditorMenuContribution } from './theia/editor/editor-file';
|
||||
import { EditorMenuContribution as TheiaEditorMenuContribution } from '@theia/editor/lib/browser/editor-menu';
|
||||
import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget';
|
||||
import { PreferencesEditorWidget } from './theia/preferences/preference-editor-widget';
|
||||
import { PreferencesWidget } from '@theia/preferences/lib/browser/views/preference-widget';
|
||||
import { createPreferencesWidgetContainer } from '@theia/preferences/lib/browser/views/preference-widget-bindings';
|
||||
import {
|
||||
BoardsFilterRenderer,
|
||||
LibraryFilterRenderer,
|
||||
} from './widgets/component-list/filter-renderer';
|
||||
import { CheckForUpdates } from './contributions/check-for-updates';
|
||||
import { OutputEditorFactory } from './theia/output/output-editor-factory';
|
||||
|
||||
const registerArduinoThemes = () => {
|
||||
const themes: MonacoThemeJson[] = [
|
||||
@@ -362,6 +375,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
|
||||
// Renderer for both the library and the core widgets.
|
||||
bind(ListItemRenderer).toSelf().inSingletonScope();
|
||||
bind(LibraryFilterRenderer).toSelf().inSingletonScope();
|
||||
bind(BoardsFilterRenderer).toSelf().inSingletonScope();
|
||||
|
||||
// Library service
|
||||
bind(LibraryService)
|
||||
@@ -453,7 +468,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
|
||||
bind(BoardsConfigDialog).toSelf().inSingletonScope();
|
||||
bind(BoardsConfigDialogProps).toConstantValue({
|
||||
title: nls.localize('arduino/common/selectBoard', 'Select Board'),
|
||||
title: nls.localize(
|
||||
'arduino/board/boardConfigDialogTitle',
|
||||
'Select Other Board and Port'
|
||||
),
|
||||
});
|
||||
|
||||
// Core service
|
||||
@@ -571,8 +589,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
return container.get(TabBarToolbar);
|
||||
}
|
||||
);
|
||||
bind(OutputWidget).toSelf().inSingletonScope();
|
||||
rebind(TheiaOutputWidget).toService(OutputWidget);
|
||||
bind(OutputChannelManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaOutputChannelManager).toService(OutputChannelManager);
|
||||
bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope();
|
||||
@@ -637,6 +653,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(WindowContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaWindowContribution).toService(WindowContribution);
|
||||
|
||||
// To remove `File` > `Close Editor`.
|
||||
bind(EditorMenuContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaEditorMenuContribution).toService(EditorMenuContribution);
|
||||
|
||||
// To disable the highlighting of non-unicode characters in the _Output_ view
|
||||
bind(OutputEditorFactory).toSelf().inSingletonScope();
|
||||
// Rebind to `TheiaOutputEditorFactory` when https://github.com/eclipse-theia/theia/pull/11615 is available.
|
||||
rebind(MonacoEditorFactory).toService(OutputEditorFactory);
|
||||
|
||||
bind(ArduinoDaemon)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
@@ -728,9 +753,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, OpenSketchFiles);
|
||||
Contribution.configure(bind, InoLanguage);
|
||||
Contribution.configure(bind, SelectedBoard);
|
||||
Contribution.configure(bind, CheckForUpdates);
|
||||
Contribution.configure(bind, CheckForIDEUpdates);
|
||||
Contribution.configure(bind, OpenBoardsConfig);
|
||||
Contribution.configure(bind, SketchFilesTracker);
|
||||
Contribution.configure(bind, CheckForUpdates);
|
||||
|
||||
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
||||
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
||||
@@ -836,6 +862,18 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(DockPanelRenderer).toSelf();
|
||||
rebind(TheiaDockPanelRenderer).toService(DockPanelRenderer);
|
||||
|
||||
// Avoid running the "reset scroll" interval tasks until the preference editor opens.
|
||||
rebind(PreferencesWidget)
|
||||
.toDynamicValue(({ container }) => {
|
||||
const child = createPreferencesWidgetContainer(container);
|
||||
child.bind(PreferencesEditorWidget).toSelf().inSingletonScope();
|
||||
child
|
||||
.rebind(TheiaPreferencesEditorWidget)
|
||||
.toService(PreferencesEditorWidget);
|
||||
return child.get(PreferencesWidget);
|
||||
})
|
||||
.inSingletonScope();
|
||||
|
||||
// Preferences
|
||||
bindArduinoPreferences(bind);
|
||||
|
||||
|
@@ -241,6 +241,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
|
||||
),
|
||||
default: false,
|
||||
},
|
||||
'arduino.checkForUpdates': {
|
||||
type: 'boolean',
|
||||
description: nls.localize(
|
||||
'arduino/preferences/checkForUpdate',
|
||||
"Receive notifications of available updates for the IDE, boards, and libraries. Requires an IDE restart after change. It's true by default."
|
||||
),
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -270,6 +278,7 @@ export interface ArduinoConfiguration {
|
||||
'arduino.auth.registerUri': string;
|
||||
'arduino.survey.notification': boolean;
|
||||
'arduino.cli.daemon.debug': boolean;
|
||||
'arduino.checkForUpdates': boolean;
|
||||
}
|
||||
|
||||
export const ArduinoPreferences = Symbol('ArduinoPreferences');
|
||||
|
@@ -12,6 +12,7 @@ import { Installable, ResponseServiceClient } from '../../common/protocol';
|
||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { InstallManually } from '../../common/nls';
|
||||
|
||||
interface AutoInstallPromptAction {
|
||||
// isAcceptance, whether or not the action indicates acceptance of auto-install proposal
|
||||
@@ -231,19 +232,18 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
candidate: BoardsPackage
|
||||
): AutoInstallPromptActions {
|
||||
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
||||
const manualInstall = nls.localize(
|
||||
'arduino/board/installManually',
|
||||
'Install Manually'
|
||||
);
|
||||
|
||||
const actions: AutoInstallPromptActions = [
|
||||
{
|
||||
key: manualInstall,
|
||||
key: InstallManually,
|
||||
handler: () => {
|
||||
this.boardsManagerFrontendContribution
|
||||
.openView({ reveal: true })
|
||||
.then((widget) =>
|
||||
widget.refresh(candidate.name.toLocaleLowerCase())
|
||||
widget.refresh({
|
||||
query: candidate.name.toLocaleLowerCase(),
|
||||
type: 'All',
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@@ -1,4 +1,8 @@
|
||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
injectable,
|
||||
inject,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||
import { DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
|
||||
import { AbstractDialog } from '../theia/dialogs/dialogs';
|
||||
@@ -28,7 +32,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
@inject(BoardsConfigDialogProps)
|
||||
protected override readonly props: BoardsConfigDialogProps
|
||||
) {
|
||||
super(props);
|
||||
super({ ...props, maxWidth: 500 });
|
||||
|
||||
this.contentNode.classList.add('select-board-dialog');
|
||||
this.contentNode.appendChild(this.createDescription());
|
||||
@@ -65,14 +69,6 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
const head = document.createElement('div');
|
||||
head.classList.add('head');
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.textContent = nls.localize(
|
||||
'arduino/board/configDialogTitle',
|
||||
'Select Other Board & Port'
|
||||
);
|
||||
title.classList.add('title');
|
||||
head.appendChild(title);
|
||||
|
||||
const text = document.createElement('div');
|
||||
text.classList.add('text');
|
||||
head.appendChild(text);
|
||||
|
@@ -258,14 +258,14 @@ export class BoardsConfig extends React.Component<
|
||||
|
||||
override render(): React.ReactNode {
|
||||
return (
|
||||
<div className="body">
|
||||
<>
|
||||
{this.renderContainer('boards', this.renderBoards.bind(this))}
|
||||
{this.renderContainer(
|
||||
'ports',
|
||||
this.renderPorts.bind(this),
|
||||
this.renderPortsFooter.bind(this)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -306,7 +306,10 @@ export class BoardsConfig extends React.Component<
|
||||
type="search"
|
||||
value={query}
|
||||
className="theia-input"
|
||||
placeholder="SEARCH BOARD"
|
||||
placeholder={nls.localize(
|
||||
'arduino/board/searchBoard',
|
||||
'Search board'
|
||||
)}
|
||||
onChange={this.updateBoards}
|
||||
ref={this.focusNodeSet}
|
||||
/>
|
||||
@@ -334,27 +337,19 @@ export class BoardsConfig extends React.Component<
|
||||
if (this.state.showAllPorts) {
|
||||
ports = this.state.knownPorts;
|
||||
} else {
|
||||
ports = this.state.knownPorts.filter((port) => {
|
||||
if (port.protocol === 'serial') {
|
||||
return true;
|
||||
}
|
||||
// All other ports with different protocol are
|
||||
// only shown if there is a recognized board
|
||||
// connected
|
||||
for (const board of this.availableBoards) {
|
||||
if (board.port?.address === port.address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
ports = this.state.knownPorts.filter(
|
||||
Port.visiblePorts(this.availableBoards)
|
||||
);
|
||||
}
|
||||
return !ports.length ? (
|
||||
<div className="loading noselect">No ports discovered</div>
|
||||
<div className="loading noselect">
|
||||
{nls.localize('arduino/board/noPortsDiscovered', 'No ports discovered')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="ports list">
|
||||
{ports.map((port) => (
|
||||
<Item<Port>
|
||||
key={`${port.id}`}
|
||||
key={`${Port.keyOf(port)}`}
|
||||
item={port}
|
||||
label={Port.toString(port)}
|
||||
selected={Port.sameAs(this.state.selectedPort, port)}
|
||||
|
@@ -4,22 +4,24 @@ import {
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import {
|
||||
BoardSearch,
|
||||
BoardsPackage,
|
||||
BoardsService,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { BoardsFilterRenderer } from '../widgets/component-list/filter-renderer';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
||||
export class BoardsListWidget extends ListWidget<BoardsPackage, BoardSearch> {
|
||||
static WIDGET_ID = 'boards-list-widget';
|
||||
static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager');
|
||||
|
||||
constructor(
|
||||
@inject(BoardsService) protected service: BoardsService,
|
||||
@inject(ListItemRenderer)
|
||||
protected itemRenderer: ListItemRenderer<BoardsPackage>
|
||||
@inject(BoardsService) service: BoardsService,
|
||||
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<BoardsPackage>,
|
||||
@inject(BoardsFilterRenderer) filterRenderer: BoardsFilterRenderer
|
||||
) {
|
||||
super({
|
||||
id: BoardsListWidget.WIDGET_ID,
|
||||
@@ -30,6 +32,8 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
||||
itemLabel: (item: BoardsPackage) => item.name,
|
||||
itemDeprecated: (item: BoardsPackage) => item.deprecated,
|
||||
itemRenderer,
|
||||
filterRenderer,
|
||||
defaultSearchOptions: { query: '', type: 'All' },
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,7 @@ import {
|
||||
AttachedBoardsChangeEvent,
|
||||
BoardWithPackage,
|
||||
BoardUserField,
|
||||
AvailablePorts,
|
||||
} from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { naturalCompare } from '../../common/utils';
|
||||
@@ -21,6 +22,7 @@ import { StorageWrapper } from '../storage-wrapper';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { Unknown } from '../../common/nls';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
@@ -65,11 +67,16 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
protected _availablePorts: Port[] = [];
|
||||
protected _availableBoards: AvailableBoard[] = [];
|
||||
|
||||
private lastBoardsConfigOnUpload: BoardsConfig.Config | undefined;
|
||||
private lastAvailablePortsOnUpload: Port[] | undefined;
|
||||
private boardConfigToAutoSelect: BoardsConfig.Config | undefined;
|
||||
|
||||
/**
|
||||
* Unlike `onAttachedBoardsChanged` this even fires when the user modifies the selected board in the IDE.\
|
||||
* This even also fires, when the boards package was not available for the currently selected board,
|
||||
* Unlike `onAttachedBoardsChanged` this event fires when the user modifies the selected board in the IDE.\
|
||||
* This event also fires, when the boards package was not available for the currently selected board,
|
||||
* and the user installs the board package. Note: installing a board package will set the `fqbn` of the
|
||||
* currently selected board.\
|
||||
* currently selected board.
|
||||
*
|
||||
* This event is also emitted when the board package for the currently selected board was uninstalled.
|
||||
*/
|
||||
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
|
||||
@@ -91,11 +98,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
);
|
||||
|
||||
this.appStateService.reachedState('ready').then(async () => {
|
||||
const [attachedBoards, availablePorts] = await Promise.all([
|
||||
this.boardsService.getAttachedBoards(),
|
||||
this.boardsService.getAvailablePorts(),
|
||||
const [state] = await Promise.all([
|
||||
this.boardsService.getState(),
|
||||
this.loadState(),
|
||||
]);
|
||||
const { boards: attachedBoards, ports: availablePorts } =
|
||||
AvailablePorts.split(state);
|
||||
this._attachedBoards = attachedBoards;
|
||||
this._availablePorts = availablePorts;
|
||||
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
||||
@@ -111,6 +119,84 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
return this._reconciled.promise;
|
||||
}
|
||||
|
||||
snapshotBoardDiscoveryOnUpload(): void {
|
||||
this.lastBoardsConfigOnUpload = this._boardsConfig;
|
||||
this.lastAvailablePortsOnUpload = this._availablePorts;
|
||||
}
|
||||
|
||||
clearBoardDiscoverySnapshot(): void {
|
||||
this.lastBoardsConfigOnUpload = undefined;
|
||||
this.lastAvailablePortsOnUpload = undefined;
|
||||
}
|
||||
|
||||
private portToAutoSelectCanBeDerived(): boolean {
|
||||
return Boolean(
|
||||
this.lastBoardsConfigOnUpload && this.lastAvailablePortsOnUpload
|
||||
);
|
||||
}
|
||||
|
||||
attemptPostUploadAutoSelect(): void {
|
||||
setTimeout(() => {
|
||||
if (this.portToAutoSelectCanBeDerived()) {
|
||||
this.attemptAutoSelect({
|
||||
ports: this._availablePorts,
|
||||
boards: this._availableBoards,
|
||||
});
|
||||
}
|
||||
}, 2000); // 2 second delay same as IDE 1.8
|
||||
}
|
||||
|
||||
private attemptAutoSelect(
|
||||
newState: AttachedBoardsChangeEvent['newState']
|
||||
): void {
|
||||
this.deriveBoardConfigToAutoSelect(newState);
|
||||
this.tryReconnect();
|
||||
}
|
||||
|
||||
private deriveBoardConfigToAutoSelect(
|
||||
newState: AttachedBoardsChangeEvent['newState']
|
||||
): void {
|
||||
if (!this.portToAutoSelectCanBeDerived()) {
|
||||
this.boardConfigToAutoSelect = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const oldPorts = this.lastAvailablePortsOnUpload!;
|
||||
const { ports: newPorts, boards: newBoards } = newState;
|
||||
|
||||
const appearedPorts =
|
||||
oldPorts.length > 0
|
||||
? newPorts.filter((newPort: Port) =>
|
||||
oldPorts.every((oldPort: Port) => !Port.sameAs(newPort, oldPort))
|
||||
)
|
||||
: newPorts;
|
||||
|
||||
for (const port of appearedPorts) {
|
||||
const boardOnAppearedPort = newBoards.find((board: Board) =>
|
||||
Port.sameAs(board.port, port)
|
||||
);
|
||||
|
||||
const lastBoardsConfigOnUpload = this.lastBoardsConfigOnUpload!;
|
||||
|
||||
if (
|
||||
boardOnAppearedPort &&
|
||||
lastBoardsConfigOnUpload.selectedBoard &&
|
||||
Board.sameAs(
|
||||
boardOnAppearedPort,
|
||||
lastBoardsConfigOnUpload.selectedBoard
|
||||
)
|
||||
) {
|
||||
this.clearBoardDiscoverySnapshot();
|
||||
|
||||
this.boardConfigToAutoSelect = {
|
||||
selectedBoard: boardOnAppearedPort,
|
||||
selectedPort: port,
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected notifyAttachedBoardsChanged(
|
||||
event: AttachedBoardsChangeEvent
|
||||
): void {
|
||||
@@ -119,10 +205,18 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
this.logger.info(AttachedBoardsChangeEvent.toString(event));
|
||||
this.logger.info('------------------------------------------');
|
||||
}
|
||||
|
||||
this._attachedBoards = event.newState.boards;
|
||||
this._availablePorts = event.newState.ports;
|
||||
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
||||
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
||||
this.reconcileAvailableBoards().then(() => {
|
||||
const { uploadInProgress } = event;
|
||||
// avoid attempting "auto-selection" while an
|
||||
// upload is in progress
|
||||
if (!uploadInProgress) {
|
||||
this.attemptAutoSelect(event.newState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected notifyPlatformInstalled(event: { item: BoardsPackage }): void {
|
||||
@@ -238,24 +332,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed.
|
||||
// See documentation on `latestValidBoardsConfig`.
|
||||
for (const board of this.availableBoards.filter(
|
||||
({ state }) => state !== AvailableBoard.State.incomplete
|
||||
)) {
|
||||
if (
|
||||
this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn &&
|
||||
this.latestValidBoardsConfig.selectedBoard.name === board.name &&
|
||||
this.latestValidBoardsConfig.selectedPort.protocol ===
|
||||
board.port?.protocol
|
||||
) {
|
||||
this.boardsConfig = {
|
||||
...this.latestValidBoardsConfig,
|
||||
selectedPort: board.port,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.boardConfigToAutoSelect) return false;
|
||||
|
||||
this.boardsConfig = this.boardConfigToAutoSelect;
|
||||
this.boardConfigToAutoSelect = undefined;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -380,6 +462,16 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
return this._availableBoards;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not use this API, it will be removed. This is a hack to be able to set the missing port `properties` before an upload.
|
||||
*
|
||||
* See: https://github.com/arduino/arduino-ide/pull/1335#issuecomment-1224355236.
|
||||
*/
|
||||
// TODO: remove this API and fix the selected board config store/restore correctly.
|
||||
get availablePorts(): Port[] {
|
||||
return this._availablePorts.slice();
|
||||
}
|
||||
|
||||
async waitUntilAvailable(
|
||||
what: Board & { port: Port },
|
||||
timeout?: number
|
||||
@@ -436,28 +528,19 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const currentAvailableBoards = this._availableBoards;
|
||||
const availableBoards: AvailableBoard[] = [];
|
||||
const attachedBoards = this._attachedBoards.filter(({ port }) => !!port);
|
||||
const availableBoardPorts = availablePorts.filter((port) => {
|
||||
if (port.protocol === 'serial') {
|
||||
// We always show all serial ports, even if there
|
||||
// is no recognized board connected to it
|
||||
return true;
|
||||
}
|
||||
|
||||
// All other ports with different protocol are
|
||||
// only shown if there is a recognized board
|
||||
// connected
|
||||
for (const board of attachedBoards) {
|
||||
if (board.port?.address === port.address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const availableBoardPorts = availablePorts.filter(
|
||||
Port.visiblePorts(attachedBoards)
|
||||
);
|
||||
|
||||
for (const boardPort of availableBoardPorts) {
|
||||
const board = attachedBoards.find(({ port }) =>
|
||||
Port.sameAs(boardPort, port)
|
||||
);
|
||||
// "board" will always be falsey for
|
||||
// port that was originally mapped
|
||||
// to unknown board and then selected
|
||||
// manually by user
|
||||
|
||||
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
|
||||
boardPort
|
||||
);
|
||||
@@ -476,12 +559,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
availableBoard = {
|
||||
...lastSelectedBoard,
|
||||
state: AvailableBoard.State.guessed,
|
||||
selected: BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard),
|
||||
selected:
|
||||
BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard) &&
|
||||
Port.sameAs(boardPort, boardsConfig.selectedPort), // to avoid double selection
|
||||
port: boardPort,
|
||||
};
|
||||
} else {
|
||||
availableBoard = {
|
||||
name: nls.localize('arduino/common/unknown', 'Unknown'),
|
||||
name: Unknown,
|
||||
port: boardPort,
|
||||
state: AvailableBoard.State.incomplete,
|
||||
};
|
||||
@@ -491,7 +576,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
|
||||
if (
|
||||
boardsConfig.selectedBoard &&
|
||||
!availableBoards.some(({ selected }) => selected)
|
||||
availableBoards.every(({ selected }) => !selected)
|
||||
) {
|
||||
// If the selected board has the same port of an unknown board
|
||||
// that is already in availableBoards we might get a duplicate port.
|
||||
|
@@ -138,7 +138,7 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
||||
{boardLabel}
|
||||
</div>
|
||||
<div className="arduino-boards-dropdown-item--port-label noWrapInfo noselect">
|
||||
{port.address}
|
||||
{port.addressLabel}
|
||||
</div>
|
||||
</div>
|
||||
{selected ? <div className="fa fa-check" /> : ''}
|
||||
|
@@ -1,10 +1,16 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { BoardsListWidget } from './boards-list-widget';
|
||||
import { BoardsPackage } from '../../common/protocol/boards-service';
|
||||
import type {
|
||||
BoardSearch,
|
||||
BoardsPackage,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<
|
||||
BoardsPackage,
|
||||
BoardSearch
|
||||
> {
|
||||
constructor() {
|
||||
super({
|
||||
widgetId: BoardsListWidget.WIDGET_ID,
|
||||
|
@@ -288,7 +288,7 @@ PID: ${PID}`;
|
||||
for (let i = 0; i < sortedIDs.length; i++) {
|
||||
const portID = sortedIDs[i];
|
||||
const [port, boards] = ports[portID];
|
||||
let label = `${port.address}`;
|
||||
let label = `${port.addressLabel}`;
|
||||
if (boards.length) {
|
||||
const boardsList = boards.map((board) => board.name).join(', ');
|
||||
label = `${label} (${boardsList})`;
|
||||
@@ -331,7 +331,7 @@ PID: ${PID}`;
|
||||
}
|
||||
};
|
||||
|
||||
const grouped = AvailablePorts.byProtocol(availablePorts);
|
||||
const grouped = AvailablePorts.groupByProtocol(availablePorts);
|
||||
let protocolOrder = 100;
|
||||
// We first show serial and network ports, then all the rest
|
||||
['serial', 'network'].forEach((protocol) => {
|
||||
|
@@ -29,6 +29,7 @@ export class BurnBootloader extends CoreServiceContribution {
|
||||
}
|
||||
|
||||
private async burnBootloader(): Promise<void> {
|
||||
this.clearVisibleNotification();
|
||||
const options = await this.options();
|
||||
try {
|
||||
await this.doWithProgress({
|
||||
|
@@ -0,0 +1,68 @@
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
IDEUpdater,
|
||||
SKIP_IDE_VERSION,
|
||||
} from '../../common/protocol/ide-updater';
|
||||
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
|
||||
import { Contribution } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class CheckForIDEUpdates extends Contribution {
|
||||
@inject(IDEUpdater)
|
||||
private readonly updater: IDEUpdater;
|
||||
|
||||
@inject(IDEUpdaterDialog)
|
||||
private readonly updaterDialog: IDEUpdaterDialog;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
private readonly localStorage: LocalStorageService;
|
||||
|
||||
override onStart(): void {
|
||||
this.preferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
if (newValue !== oldValue) {
|
||||
switch (preferenceName) {
|
||||
case 'arduino.ide.updateChannel':
|
||||
case 'arduino.ide.updateBaseUrl':
|
||||
this.updater.init(
|
||||
this.preferences.get('arduino.ide.updateChannel'),
|
||||
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
const checkForUpdates = this.preferences['arduino.checkForUpdates'];
|
||||
if (!checkForUpdates) {
|
||||
return;
|
||||
}
|
||||
this.updater
|
||||
.init(
|
||||
this.preferences.get('arduino.ide.updateChannel'),
|
||||
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||
)
|
||||
.then(() => this.updater.checkForUpdates(true))
|
||||
.then(async (updateInfo) => {
|
||||
if (!updateInfo) return;
|
||||
const versionToSkip = await this.localStorage.getData<string>(
|
||||
SKIP_IDE_VERSION
|
||||
);
|
||||
if (versionToSkip === updateInfo.version) return;
|
||||
this.updaterDialog.open(updateInfo);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.messageService.error(
|
||||
nls.localize(
|
||||
'arduino/ide-updater/errorCheckingForUpdates',
|
||||
'Error while checking for Arduino IDE updates.\n{0}',
|
||||
e.message
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,64 +1,221 @@
|
||||
import type { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { InstallManually, Later } from '../../common/nls';
|
||||
import {
|
||||
IDEUpdater,
|
||||
SKIP_IDE_VERSION,
|
||||
} from '../../common/protocol/ide-updater';
|
||||
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
|
||||
import { Contribution } from './contribution';
|
||||
ArduinoComponent,
|
||||
BoardsPackage,
|
||||
BoardsService,
|
||||
LibraryPackage,
|
||||
LibraryService,
|
||||
ResponseServiceClient,
|
||||
Searchable,
|
||||
} from '../../common/protocol';
|
||||
import { Installable } from '../../common/protocol/installable';
|
||||
import { ExecuteWithProgress } from '../../common/protocol/progressible';
|
||||
import { BoardsListWidgetFrontendContribution } from '../boards/boards-widget-frontend-contribution';
|
||||
import { LibraryListWidgetFrontendContribution } from '../library/library-widget-frontend-contribution';
|
||||
import { WindowServiceExt } from '../theia/core/window-service-ext';
|
||||
import type { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { Command, CommandRegistry, Contribution } from './contribution';
|
||||
|
||||
const NoUpdates = nls.localize(
|
||||
'arduino/checkForUpdates/noUpdates',
|
||||
'There are no recent updates available.'
|
||||
);
|
||||
const PromptUpdateBoards = nls.localize(
|
||||
'arduino/checkForUpdates/promptUpdateBoards',
|
||||
'Updates are available for some of your boards.'
|
||||
);
|
||||
const PromptUpdateLibraries = nls.localize(
|
||||
'arduino/checkForUpdates/promptUpdateLibraries',
|
||||
'Updates are available for some of your libraries.'
|
||||
);
|
||||
const UpdatingBoards = nls.localize(
|
||||
'arduino/checkForUpdates/updatingBoards',
|
||||
'Updating boards...'
|
||||
);
|
||||
const UpdatingLibraries = nls.localize(
|
||||
'arduino/checkForUpdates/updatingLibraries',
|
||||
'Updating libraries...'
|
||||
);
|
||||
const InstallAll = nls.localize(
|
||||
'arduino/checkForUpdates/installAll',
|
||||
'Install All'
|
||||
);
|
||||
|
||||
interface Task<T extends ArduinoComponent> {
|
||||
readonly run: () => Promise<void>;
|
||||
readonly item: T;
|
||||
}
|
||||
|
||||
const Updatable = { type: 'Updatable' } as const;
|
||||
|
||||
@injectable()
|
||||
export class CheckForUpdates extends Contribution {
|
||||
@inject(IDEUpdater)
|
||||
private readonly updater: IDEUpdater;
|
||||
@inject(WindowServiceExt)
|
||||
private readonly windowService: WindowServiceExt;
|
||||
@inject(ResponseServiceClient)
|
||||
private readonly responseService: ResponseServiceClient;
|
||||
@inject(BoardsService)
|
||||
private readonly boardsService: BoardsService;
|
||||
@inject(LibraryService)
|
||||
private readonly libraryService: LibraryService;
|
||||
@inject(BoardsListWidgetFrontendContribution)
|
||||
private readonly boardsContribution: BoardsListWidgetFrontendContribution;
|
||||
@inject(LibraryListWidgetFrontendContribution)
|
||||
private readonly librariesContribution: LibraryListWidgetFrontendContribution;
|
||||
|
||||
@inject(IDEUpdaterDialog)
|
||||
private readonly updaterDialog: IDEUpdaterDialog;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
private readonly localStorage: LocalStorageService;
|
||||
|
||||
override onStart(): void {
|
||||
this.preferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
if (newValue !== oldValue) {
|
||||
switch (preferenceName) {
|
||||
case 'arduino.ide.updateChannel':
|
||||
case 'arduino.ide.updateBaseUrl':
|
||||
this.updater.init(
|
||||
this.preferences.get('arduino.ide.updateChannel'),
|
||||
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
override registerCommands(register: CommandRegistry): void {
|
||||
register.registerCommand(CheckForUpdates.Commands.CHECK_FOR_UPDATES, {
|
||||
execute: () => this.checkForUpdates(false),
|
||||
});
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this.updater
|
||||
.init(
|
||||
this.preferences.get('arduino.ide.updateChannel'),
|
||||
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||
)
|
||||
.then(() => this.updater.checkForUpdates(true))
|
||||
.then(async (updateInfo) => {
|
||||
if (!updateInfo) return;
|
||||
const versionToSkip = await this.localStorage.getData<string>(
|
||||
SKIP_IDE_VERSION
|
||||
);
|
||||
if (versionToSkip === updateInfo.version) return;
|
||||
this.updaterDialog.open(updateInfo);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.messageService.error(
|
||||
nls.localize(
|
||||
'arduino/ide-updater/errorCheckingForUpdates',
|
||||
'Error while checking for Arduino IDE updates.\n{0}',
|
||||
e.message
|
||||
)
|
||||
);
|
||||
override async onReady(): Promise<void> {
|
||||
const checkForUpdates = this.preferences['arduino.checkForUpdates'];
|
||||
if (checkForUpdates) {
|
||||
this.windowService.isFirstWindow().then((firstWindow) => {
|
||||
if (firstWindow) {
|
||||
this.checkForUpdates();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async checkForUpdates(silent = true) {
|
||||
const [boardsPackages, libraryPackages] = await Promise.all([
|
||||
this.boardsService.search(Updatable),
|
||||
this.libraryService.search(Updatable),
|
||||
]);
|
||||
this.promptUpdateBoards(boardsPackages);
|
||||
this.promptUpdateLibraries(libraryPackages);
|
||||
if (!libraryPackages.length && !boardsPackages.length && !silent) {
|
||||
this.messageService.info(NoUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
private promptUpdateBoards(items: BoardsPackage[]): void {
|
||||
this.prompt({
|
||||
items,
|
||||
installable: this.boardsService,
|
||||
viewContribution: this.boardsContribution,
|
||||
viewSearchOptions: { query: '', ...Updatable },
|
||||
promptMessage: PromptUpdateBoards,
|
||||
updatingMessage: UpdatingBoards,
|
||||
});
|
||||
}
|
||||
|
||||
private promptUpdateLibraries(items: LibraryPackage[]): void {
|
||||
this.prompt({
|
||||
items,
|
||||
installable: this.libraryService,
|
||||
viewContribution: this.librariesContribution,
|
||||
viewSearchOptions: { query: '', topic: 'All', ...Updatable },
|
||||
promptMessage: PromptUpdateLibraries,
|
||||
updatingMessage: UpdatingLibraries,
|
||||
});
|
||||
}
|
||||
|
||||
private prompt<
|
||||
T extends ArduinoComponent,
|
||||
S extends Searchable.Options
|
||||
>(options: {
|
||||
items: T[];
|
||||
installable: Installable<T>;
|
||||
viewContribution: AbstractViewContribution<ListWidget<T, S>>;
|
||||
viewSearchOptions: S;
|
||||
promptMessage: string;
|
||||
updatingMessage: string;
|
||||
}): void {
|
||||
const {
|
||||
items,
|
||||
installable,
|
||||
viewContribution,
|
||||
promptMessage: message,
|
||||
viewSearchOptions,
|
||||
updatingMessage,
|
||||
} = options;
|
||||
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
this.messageService
|
||||
.info(message, Later, InstallManually, InstallAll)
|
||||
.then((answer) => {
|
||||
if (answer === InstallAll) {
|
||||
const tasks = items.map((item) =>
|
||||
this.createInstallTask(item, installable)
|
||||
);
|
||||
this.executeTasks(updatingMessage, tasks);
|
||||
} else if (answer === InstallManually) {
|
||||
viewContribution
|
||||
.openView({ reveal: true })
|
||||
.then((widget) => widget.refresh(viewSearchOptions));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async executeTasks(
|
||||
message: string,
|
||||
tasks: Task<ArduinoComponent>[]
|
||||
): Promise<void> {
|
||||
if (tasks.length) {
|
||||
return ExecuteWithProgress.withProgress(
|
||||
message,
|
||||
this.messageService,
|
||||
async (progress) => {
|
||||
try {
|
||||
const total = tasks.length;
|
||||
let count = 0;
|
||||
for (const { run, item } of tasks) {
|
||||
try {
|
||||
await run(); // runs update sequentially. // TODO: is parallel update desired?
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.messageService.error(
|
||||
`Failed to update ${item.name}. ${err}`
|
||||
);
|
||||
} finally {
|
||||
progress.report({ work: { total, done: ++count } });
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
progress.cancel();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private createInstallTask<T extends ArduinoComponent>(
|
||||
item: T,
|
||||
installable: Installable<T>
|
||||
): Task<T> {
|
||||
const latestVersion = item.availableVersions[0];
|
||||
return {
|
||||
item,
|
||||
run: () =>
|
||||
Installable.installWithProgress({
|
||||
installable,
|
||||
item,
|
||||
version: latestVersion,
|
||||
messageService: this.messageService,
|
||||
responseService: this.responseService,
|
||||
keepOutput: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
export namespace CheckForUpdates {
|
||||
export namespace Commands {
|
||||
export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand(
|
||||
{
|
||||
id: 'arduino-check-for-updates',
|
||||
label: 'Check for Arduino Updates',
|
||||
category: 'Arduino',
|
||||
},
|
||||
'arduino/checkForUpdates/checkForUpdates'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,14 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { toArray } from '@theia/core/shared/@phosphor/algorithm';
|
||||
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import type { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import type {
|
||||
FrontendApplication,
|
||||
OnWillStopAction,
|
||||
} from '@theia/core/lib/browser/frontend-application';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import {
|
||||
SketchContribution,
|
||||
@@ -11,27 +16,48 @@ import {
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
Sketch,
|
||||
URI,
|
||||
} from './contribution';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { Dialog } from '@theia/core/lib/browser/dialogs';
|
||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||
import { SaveAsSketch } from './save-as-sketch';
|
||||
|
||||
/**
|
||||
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
|
||||
*/
|
||||
@injectable()
|
||||
export class Close extends SketchContribution {
|
||||
@inject(EditorManager)
|
||||
protected override readonly editorManager: EditorManager;
|
||||
private shell: ApplicationShell | undefined;
|
||||
|
||||
protected shell: ApplicationShell;
|
||||
|
||||
override onStart(app: FrontendApplication): void {
|
||||
override onStart(app: FrontendApplication): MaybePromise<void> {
|
||||
this.shell = app.shell;
|
||||
}
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Close.Commands.CLOSE, {
|
||||
execute: () => remote.getCurrentWindow().close()
|
||||
execute: () => {
|
||||
// Close current editor if closeable.
|
||||
const { currentEditor } = this.editorManager;
|
||||
if (currentEditor && currentEditor.title.closable) {
|
||||
currentEditor.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shell) {
|
||||
// Close current widget from the main area if possible.
|
||||
const { currentWidget } = this.shell;
|
||||
if (currentWidget) {
|
||||
const currentWidgetInMain = toArray(
|
||||
this.shell.mainPanel.widgets()
|
||||
).find((widget) => widget === currentWidget);
|
||||
if (currentWidgetInMain && currentWidgetInMain.title.closable) {
|
||||
return currentWidgetInMain.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return remote.getCurrentWindow().close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,6 +76,123 @@ export class Close extends SketchContribution {
|
||||
});
|
||||
}
|
||||
|
||||
// `FrontendApplicationContribution#onWillStop`
|
||||
onWillStop(): OnWillStopAction {
|
||||
return {
|
||||
reason: 'save-sketch',
|
||||
action: () => {
|
||||
return this.showSaveSketchDialog();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If returns with `true`, IDE2 will close. Otherwise, it won't.
|
||||
*/
|
||||
private async showSaveSketchDialog(): Promise<boolean> {
|
||||
const sketch = await this.isCurrentSketchTemp();
|
||||
if (!sketch) {
|
||||
// Normal close workflow: if there are dirty editors prompt the user.
|
||||
if (!this.shell) {
|
||||
console.error(
|
||||
`Could not get the application shell. Something went wrong.`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if (this.shell.canSaveAll()) {
|
||||
const prompt = await this.prompt(false);
|
||||
switch (prompt) {
|
||||
case Prompt.DoNotSave:
|
||||
return true;
|
||||
case Prompt.Cancel:
|
||||
return false;
|
||||
case Prompt.Save: {
|
||||
await this.shell.saveAll();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected prompt: ${prompt}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If non of the sketch files were ever touched, do not prompt the save dialog. (#1274)
|
||||
const wereTouched = await Promise.all(
|
||||
Sketch.uris(sketch).map((uri) => this.wasTouched(uri))
|
||||
);
|
||||
if (wereTouched.every((wasTouched) => !Boolean(wasTouched))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const prompt = await this.prompt(true);
|
||||
switch (prompt) {
|
||||
case Prompt.DoNotSave:
|
||||
return true;
|
||||
case Prompt.Cancel:
|
||||
return false;
|
||||
case Prompt.Save: {
|
||||
// If `save as` was canceled by user, the result will be `undefined`, otherwise the new URI.
|
||||
const result = await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: false,
|
||||
wipeOriginal: true,
|
||||
markAsRecentlyOpened: true,
|
||||
}
|
||||
);
|
||||
return !!result;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected prompt: ${prompt}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async prompt(isTemp: boolean): Promise<Prompt> {
|
||||
const { response } = await remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
message: nls.localize(
|
||||
'arduino/sketch/saveSketch',
|
||||
'Save your sketch to open it again later.'
|
||||
),
|
||||
title: nls.localize(
|
||||
'theia/core/quitTitle',
|
||||
'Are you sure you want to quit?'
|
||||
),
|
||||
type: 'question',
|
||||
buttons: [
|
||||
nls.localizeByDefault("Don't Save"),
|
||||
Dialog.CANCEL,
|
||||
nls.localizeByDefault(isTemp ? 'Save As...' : 'Save'),
|
||||
],
|
||||
defaultId: 2, // `Save`/`Save As...` button index is the default.
|
||||
}
|
||||
);
|
||||
switch (response) {
|
||||
case 0:
|
||||
return Prompt.DoNotSave;
|
||||
case 1:
|
||||
return Prompt.Cancel;
|
||||
case 2:
|
||||
return Prompt.Save;
|
||||
default:
|
||||
throw new Error(`Unexpected response: ${response}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async isCurrentSketchTemp(): Promise<false | Sketch> {
|
||||
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||
if (CurrentSketch.isValid(currentSketch)) {
|
||||
const isTemp = await this.sketchService.isTemp(currentSketch);
|
||||
if (isTemp) {
|
||||
return currentSketch;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the file was ever touched/modified. We get this based on the `version` of the monaco model.
|
||||
*/
|
||||
@@ -59,13 +202,23 @@ export class Close extends SketchContribution {
|
||||
const { editor } = editorWidget;
|
||||
if (editor instanceof MonacoEditor) {
|
||||
const versionId = editor.getControl().getModel()?.getVersionId();
|
||||
if (Number.isInteger(versionId) && versionId! > 1) {
|
||||
if (this.isInteger(versionId) && versionId > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private isInteger(arg: unknown): arg is number {
|
||||
return Number.isInteger(arg);
|
||||
}
|
||||
}
|
||||
|
||||
enum Prompt {
|
||||
Save,
|
||||
DoNotSave,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
export namespace Close {
|
||||
|
@@ -4,11 +4,13 @@ import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
Emitter,
|
||||
MaybeArray,
|
||||
MaybePromise,
|
||||
nls,
|
||||
notEmpty,
|
||||
} from '@theia/core';
|
||||
import { ApplicationShell, FrontendApplication } from '@theia/core/lib/browser';
|
||||
import { ITextModel } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
@@ -28,14 +30,15 @@ import * as monaco from '@theia/monaco-editor-core';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
|
||||
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
|
||||
import { OutputUri } from '@theia/output/lib/common/output-uri';
|
||||
import { CoreError } from '../../common/protocol/core-service';
|
||||
import { ErrorRevealStrategy } from '../arduino-preferences';
|
||||
import { InoSelector } from '../ino-selectors';
|
||||
import { fullRange } from '../utils/monaco';
|
||||
import { ArduinoOutputSelector, InoSelector } from '../selectors';
|
||||
import { Contribution } from './contribution';
|
||||
import { CoreErrorHandler } from './core-error-handler';
|
||||
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
|
||||
|
||||
interface ErrorDecoration {
|
||||
interface ErrorDecorationRef {
|
||||
/**
|
||||
* This is the unique ID of the decoration given by `monaco`.
|
||||
*/
|
||||
@@ -45,72 +48,89 @@ interface ErrorDecoration {
|
||||
*/
|
||||
readonly uri: string;
|
||||
}
|
||||
namespace ErrorDecoration {
|
||||
export function rangeOf(
|
||||
{ id, uri }: ErrorDecoration,
|
||||
editorProvider: (uri: string) => Promise<MonacoEditor | undefined>
|
||||
): Promise<monaco.Range | undefined>;
|
||||
export function rangeOf(
|
||||
{ id, uri }: ErrorDecoration,
|
||||
editorProvider: MonacoEditor
|
||||
): monaco.Range | undefined;
|
||||
export function rangeOf(
|
||||
{ id, uri }: ErrorDecoration,
|
||||
editorProvider:
|
||||
| ((uri: string) => Promise<MonacoEditor | undefined>)
|
||||
| MonacoEditor
|
||||
): MaybePromise<monaco.Range | undefined> {
|
||||
if (editorProvider instanceof MonacoEditor) {
|
||||
const control = editorProvider.getControl();
|
||||
const model = control.getModel();
|
||||
if (model) {
|
||||
return control
|
||||
.getDecorationsInRange(fullRange(model))
|
||||
?.find(({ id: candidateId }) => id === candidateId)?.range;
|
||||
}
|
||||
return undefined;
|
||||
export namespace ErrorDecorationRef {
|
||||
export function is(arg: unknown): arg is ErrorDecorationRef {
|
||||
if (typeof arg === 'object') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const object = arg as any;
|
||||
return (
|
||||
'uri' in object &&
|
||||
typeof object['uri'] === 'string' &&
|
||||
'id' in object &&
|
||||
typeof object['id'] === 'string'
|
||||
);
|
||||
}
|
||||
return editorProvider(uri).then((editor) => {
|
||||
if (editor) {
|
||||
return rangeOf({ id, uri }, editor);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// export async function rangeOf(
|
||||
// { id, uri }: ErrorDecoration,
|
||||
// editorProvider:
|
||||
// | ((uri: string) => Promise<MonacoEditor | undefined>)
|
||||
// | MonacoEditor
|
||||
// ): Promise<monaco.Range | undefined> {
|
||||
// const editor =
|
||||
// editorProvider instanceof MonacoEditor
|
||||
// ? editorProvider
|
||||
// : await editorProvider(uri);
|
||||
// if (editor) {
|
||||
// const control = editor.getControl();
|
||||
// const model = control.getModel();
|
||||
// if (model) {
|
||||
// return control
|
||||
// .getDecorationsInRange(fullRange(model))
|
||||
// ?.find(({ id: candidateId }) => id === candidateId)?.range;
|
||||
// }
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
export function sameAs(
|
||||
left: ErrorDecoration,
|
||||
right: ErrorDecoration
|
||||
left: ErrorDecorationRef,
|
||||
right: ErrorDecorationRef
|
||||
): boolean {
|
||||
return left.id === right.id && left.uri === right.uri;
|
||||
}
|
||||
}
|
||||
|
||||
interface ErrorDecoration extends ErrorDecorationRef {
|
||||
/**
|
||||
* The range of the error location the error in the compiler output from the CLI.
|
||||
*/
|
||||
readonly rangesInOutput: monaco.Range[];
|
||||
}
|
||||
namespace ErrorDecoration {
|
||||
export function rangeOf(
|
||||
editorOrModel: MonacoEditor | ITextModel | undefined,
|
||||
decorations: ErrorDecoration
|
||||
): monaco.Range | undefined;
|
||||
export function rangeOf(
|
||||
editorOrModel: MonacoEditor | ITextModel | undefined,
|
||||
decorations: ErrorDecoration[]
|
||||
): (monaco.Range | undefined)[];
|
||||
export function rangeOf(
|
||||
editorOrModel: MonacoEditor | ITextModel | undefined,
|
||||
decorations: ErrorDecoration | ErrorDecoration[]
|
||||
): MaybePromise<MaybeArray<monaco.Range | undefined>> {
|
||||
if (editorOrModel) {
|
||||
const allDecorations = getAllDecorations(editorOrModel);
|
||||
if (allDecorations) {
|
||||
if (Array.isArray(decorations)) {
|
||||
return decorations.map(({ id: decorationId }) =>
|
||||
findRangeOf(decorationId, allDecorations)
|
||||
);
|
||||
} else {
|
||||
return findRangeOf(decorations.id, allDecorations);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.isArray(decorations)
|
||||
? decorations.map(() => undefined)
|
||||
: undefined;
|
||||
}
|
||||
function findRangeOf(
|
||||
decorationId: string,
|
||||
allDecorations: { id: string; range?: monaco.Range }[]
|
||||
): monaco.Range | undefined {
|
||||
return allDecorations.find(
|
||||
({ id: candidateId }) => candidateId === decorationId
|
||||
)?.range;
|
||||
}
|
||||
function getAllDecorations(
|
||||
editorOrModel: MonacoEditor | ITextModel
|
||||
): { id: string; range?: monaco.Range }[] {
|
||||
if (editorOrModel instanceof MonacoEditor) {
|
||||
const model = editorOrModel.getControl().getModel();
|
||||
if (!model) {
|
||||
return [];
|
||||
}
|
||||
return model.getAllDecorations();
|
||||
}
|
||||
return editorOrModel.getAllDecorations();
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class CompilerErrors
|
||||
extends Contribution
|
||||
implements monaco.languages.CodeLensProvider
|
||||
implements monaco.languages.CodeLensProvider, monaco.languages.LinkProvider
|
||||
{
|
||||
@inject(EditorManager)
|
||||
private readonly editorManager: EditorManager;
|
||||
@@ -119,11 +139,14 @@ export class CompilerErrors
|
||||
private readonly p2m: ProtocolToMonacoConverter;
|
||||
|
||||
@inject(MonacoToProtocolConverter)
|
||||
private readonly mp2: MonacoToProtocolConverter;
|
||||
private readonly m2p: MonacoToProtocolConverter;
|
||||
|
||||
@inject(CoreErrorHandler)
|
||||
private readonly coreErrorHandler: CoreErrorHandler;
|
||||
|
||||
private revealStrategy = ErrorRevealStrategy.Default;
|
||||
private experimental = false;
|
||||
|
||||
private readonly errors: ErrorDecoration[] = [];
|
||||
private readonly onDidChangeEmitter = new monaco.Emitter<this>();
|
||||
private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
|
||||
@@ -131,8 +154,8 @@ export class CompilerErrors
|
||||
this.currentErrorDidChangEmitter.event;
|
||||
private readonly toDisposeOnCompilerErrorDidChange =
|
||||
new DisposableCollection();
|
||||
|
||||
private shell: ApplicationShell | undefined;
|
||||
private revealStrategy = ErrorRevealStrategy.Default;
|
||||
private currentError: ErrorDecoration | undefined;
|
||||
private get currentErrorIndex(): number {
|
||||
const current = this.currentError;
|
||||
@@ -140,46 +163,75 @@ export class CompilerErrors
|
||||
return -1;
|
||||
}
|
||||
return this.errors.findIndex((error) =>
|
||||
ErrorDecoration.sameAs(error, current)
|
||||
ErrorDecorationRef.sameAs(error, current)
|
||||
);
|
||||
}
|
||||
|
||||
override onStart(app: FrontendApplication): void {
|
||||
this.shell = app.shell;
|
||||
monaco.languages.registerCodeLensProvider(InoSelector, this);
|
||||
monaco.languages.registerLinkProvider(ArduinoOutputSelector, this);
|
||||
this.coreErrorHandler.onCompilerErrorsDidChange((errors) =>
|
||||
this.filter(errors).then(this.handleCompilerErrorsDidChange.bind(this))
|
||||
this.handleCompilerErrorsDidChange(errors)
|
||||
);
|
||||
this.onCurrentErrorDidChange(async (error) => {
|
||||
const range = await ErrorDecoration.rangeOf(error, (uri) =>
|
||||
this.monacoEditor(uri)
|
||||
);
|
||||
if (!range) {
|
||||
const monacoEditor = await this.monacoEditor(error.uri);
|
||||
const monacoRange = ErrorDecoration.rangeOf(monacoEditor, error);
|
||||
if (!monacoRange) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Could not find range of decoration: ${error.id}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const range = this.m2p.asRange(monacoRange);
|
||||
const editor = await this.revealLocationInEditor({
|
||||
uri: error.uri,
|
||||
range: this.mp2.asRange(range),
|
||||
range,
|
||||
});
|
||||
if (!editor) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Failed to mark error ${error.id} as the current one.`
|
||||
);
|
||||
} else {
|
||||
const monacoEditor = this.monacoEditor(editor);
|
||||
if (monacoEditor) {
|
||||
monacoEditor.cursor = range.start;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override onReady(): MaybePromise<void> {
|
||||
this.preferences.ready.then(() => {
|
||||
this.preferences.onPreferenceChanged(({ preferenceName, newValue }) => {
|
||||
if (preferenceName === 'arduino.compile.revealRange') {
|
||||
this.revealStrategy = ErrorRevealStrategy.is(newValue)
|
||||
? newValue
|
||||
: ErrorRevealStrategy.Default;
|
||||
this.experimental = Boolean(
|
||||
this.preferences['arduino.compile.experimental']
|
||||
);
|
||||
const strategy = this.preferences['arduino.compile.revealRange'];
|
||||
this.revealStrategy = ErrorRevealStrategy.is(strategy)
|
||||
? strategy
|
||||
: ErrorRevealStrategy.Default;
|
||||
this.preferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
if (newValue === oldValue) {
|
||||
return;
|
||||
}
|
||||
switch (preferenceName) {
|
||||
case 'arduino.compile.revealRange': {
|
||||
this.revealStrategy = ErrorRevealStrategy.is(newValue)
|
||||
? newValue
|
||||
: ErrorRevealStrategy.Default;
|
||||
return;
|
||||
}
|
||||
case 'arduino.compile.experimental': {
|
||||
this.experimental = Boolean(newValue);
|
||||
this.onDidChangeEmitter.fire(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -196,9 +248,13 @@ export class CompilerErrors
|
||||
}
|
||||
const nextError =
|
||||
this.errors[index === this.errors.length - 1 ? 0 : index + 1];
|
||||
this.markAsCurrentError(nextError);
|
||||
return this.markAsCurrentError(nextError, {
|
||||
forceReselect: true,
|
||||
reveal: true,
|
||||
});
|
||||
},
|
||||
isEnabled: () => !!this.currentError && this.errors.length > 1,
|
||||
isEnabled: () =>
|
||||
this.experimental && !!this.currentError && this.errors.length > 1,
|
||||
});
|
||||
registry.registerCommand(CompilerErrors.Commands.PREVIOUS_ERROR, {
|
||||
execute: () => {
|
||||
@@ -212,9 +268,24 @@ export class CompilerErrors
|
||||
}
|
||||
const previousError =
|
||||
this.errors[index === 0 ? this.errors.length - 1 : index - 1];
|
||||
this.markAsCurrentError(previousError);
|
||||
return this.markAsCurrentError(previousError, {
|
||||
forceReselect: true,
|
||||
reveal: true,
|
||||
});
|
||||
},
|
||||
isEnabled: () => !!this.currentError && this.errors.length > 1,
|
||||
isEnabled: () =>
|
||||
this.experimental && !!this.currentError && this.errors.length > 1,
|
||||
});
|
||||
registry.registerCommand(CompilerErrors.Commands.MARK_AS_CURRENT, {
|
||||
execute: (arg: unknown) => {
|
||||
if (ErrorDecorationRef.is(arg)) {
|
||||
return this.markAsCurrentError(
|
||||
{ id: arg.id, uri: new URI(arg.uri).toString() }, // Make sure the URI fragments are encoded. On Windows, `C:` is encoded as `C%3A`.
|
||||
{ forceReselect: true, reveal: true }
|
||||
);
|
||||
}
|
||||
},
|
||||
isEnabled: () => !!this.errors.length,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,13 +300,13 @@ export class CompilerErrors
|
||||
): Promise<monaco.languages.CodeLensList> {
|
||||
const lenses: monaco.languages.CodeLens[] = [];
|
||||
if (
|
||||
this.experimental &&
|
||||
this.currentError &&
|
||||
this.currentError.uri === model.uri.toString() &&
|
||||
this.errors.length > 1
|
||||
) {
|
||||
const range = await ErrorDecoration.rangeOf(this.currentError, (uri) =>
|
||||
this.monacoEditor(uri)
|
||||
);
|
||||
const monacoEditor = await this.monacoEditor(model.uri);
|
||||
const range = ErrorDecoration.rangeOf(monacoEditor, this.currentError);
|
||||
if (range) {
|
||||
lenses.push(
|
||||
{
|
||||
@@ -268,14 +339,81 @@ export class CompilerErrors
|
||||
};
|
||||
}
|
||||
|
||||
async provideLinks(
|
||||
model: monaco.editor.ITextModel,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.ILinksList> {
|
||||
const links: monaco.languages.ILink[] = [];
|
||||
if (
|
||||
model.uri.scheme === OutputUri.SCHEME &&
|
||||
model.uri.path === '/Arduino'
|
||||
) {
|
||||
links.push(
|
||||
...this.errors
|
||||
.filter((decoration) => !!decoration.rangesInOutput.length)
|
||||
.map(({ rangesInOutput, id, uri }) =>
|
||||
rangesInOutput.map(
|
||||
(range) =>
|
||||
<monaco.languages.ILink>{
|
||||
range,
|
||||
url: monaco.Uri.parse(`command://`).with({
|
||||
query: JSON.stringify({ id, uri }),
|
||||
path: CompilerErrors.Commands.MARK_AS_CURRENT.id,
|
||||
}),
|
||||
tooltip: nls.localize(
|
||||
'arduino/editor/revealError',
|
||||
'Reveal Error'
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
.reduce((acc, curr) => acc.concat(curr), [])
|
||||
);
|
||||
} else {
|
||||
console.warn('unexpected URI: ' + model.uri.toString());
|
||||
}
|
||||
return { links };
|
||||
}
|
||||
|
||||
async resolveLink(
|
||||
link: monaco.languages.ILink,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.ILink | undefined> {
|
||||
if (!this.experimental) {
|
||||
return undefined;
|
||||
}
|
||||
const { url } = link;
|
||||
if (url) {
|
||||
const candidateUri = new URI(
|
||||
typeof url === 'string' ? url : url.toString()
|
||||
);
|
||||
const candidateId = candidateUri.path.toString();
|
||||
const error = this.errors.find((error) => error.id === candidateId);
|
||||
if (error) {
|
||||
const monacoEditor = await this.monacoEditor(error.uri);
|
||||
const range = ErrorDecoration.rangeOf(monacoEditor, error);
|
||||
if (range) {
|
||||
return {
|
||||
range,
|
||||
url: monaco.Uri.parse(error.uri),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async handleCompilerErrorsDidChange(
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Promise<void> {
|
||||
this.toDisposeOnCompilerErrorDidChange.dispose();
|
||||
const compilerErrorsPerResource = this.groupByResource(
|
||||
await this.filter(errors)
|
||||
const groupedErrors = this.groupBy(
|
||||
errors,
|
||||
(error: CoreError.ErrorLocation) => error.location.uri
|
||||
);
|
||||
const decorations = await this.decorateEditors(compilerErrorsPerResource);
|
||||
const decorations = await this.decorateEditors(groupedErrors);
|
||||
this.errors.push(...decorations.errors);
|
||||
this.toDisposeOnCompilerErrorDidChange.pushAll([
|
||||
Disposable.create(() => (this.errors.length = 0)),
|
||||
@@ -283,17 +421,17 @@ export class CompilerErrors
|
||||
...(await Promise.all([
|
||||
decorations.dispose,
|
||||
this.trackEditors(
|
||||
compilerErrorsPerResource,
|
||||
groupedErrors,
|
||||
(editor) =>
|
||||
editor.editor.onSelectionChanged((selection) =>
|
||||
editor.onSelectionChanged((selection) =>
|
||||
this.handleSelectionChange(editor, selection)
|
||||
),
|
||||
(editor) =>
|
||||
editor.onDidDispose(() =>
|
||||
this.handleEditorDidDispose(editor.editor.uri.toString())
|
||||
editor.onDispose(() =>
|
||||
this.handleEditorDidDispose(editor.uri.toString())
|
||||
),
|
||||
(editor) =>
|
||||
editor.editor.onDocumentContentChanged((event) =>
|
||||
editor.onDocumentContentChanged((event) =>
|
||||
this.handleDocumentContentChange(editor, event)
|
||||
)
|
||||
),
|
||||
@@ -301,24 +439,13 @@ export class CompilerErrors
|
||||
]);
|
||||
const currentError = this.errors[0];
|
||||
if (currentError) {
|
||||
await this.markAsCurrentError(currentError);
|
||||
await this.markAsCurrentError(currentError, {
|
||||
forceReselect: true,
|
||||
reveal: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async filter(
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Promise<CoreError.ErrorLocation[]> {
|
||||
if (!errors.length) {
|
||||
return [];
|
||||
}
|
||||
await this.preferences.ready;
|
||||
if (this.preferences['arduino.compile.experimental']) {
|
||||
return errors;
|
||||
}
|
||||
// Always shows maximum one error; hence the code lens navigation is unavailable.
|
||||
return [errors[0]];
|
||||
}
|
||||
|
||||
private async decorateEditors(
|
||||
errors: Map<string, CoreError.ErrorLocation[]>
|
||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||
@@ -342,11 +469,11 @@ export class CompilerErrors
|
||||
uri: string,
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||
const editor = await this.editorManager.getByUri(new URI(uri));
|
||||
const editor = await this.monacoEditor(uri);
|
||||
if (!editor) {
|
||||
return { dispose: Disposable.NULL, errors: [] };
|
||||
}
|
||||
const oldDecorations = editor.editor.deltaDecorations({
|
||||
const oldDecorations = editor.deltaDecorations({
|
||||
oldDecorations: [],
|
||||
newDecorations: errors.map((error) =>
|
||||
this.compilerErrorDecoration(error.location.range)
|
||||
@@ -355,13 +482,19 @@ export class CompilerErrors
|
||||
return {
|
||||
dispose: Disposable.create(() => {
|
||||
if (editor) {
|
||||
editor.editor.deltaDecorations({
|
||||
editor.deltaDecorations({
|
||||
oldDecorations,
|
||||
newDecorations: [],
|
||||
});
|
||||
}
|
||||
}),
|
||||
errors: oldDecorations.map((id) => ({ id, uri })),
|
||||
errors: oldDecorations.map((id, index) => ({
|
||||
id,
|
||||
uri,
|
||||
rangesInOutput: errors[index].rangesInOutput.map((range) =>
|
||||
this.p2m.asRange(range)
|
||||
),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -371,7 +504,7 @@ export class CompilerErrors
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
className: 'compiler-error',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -379,11 +512,10 @@ export class CompilerErrors
|
||||
/**
|
||||
* Tracks the selection in all editors that have an error. If the editor selection overlaps one of the compiler error's range, mark as current error.
|
||||
*/
|
||||
private handleSelectionChange(editor: EditorWidget, selection: Range): void {
|
||||
const monacoEditor = this.monacoEditor(editor);
|
||||
if (!monacoEditor) {
|
||||
return;
|
||||
}
|
||||
private handleSelectionChange(
|
||||
monacoEditor: MonacoEditor,
|
||||
selection: Range
|
||||
): void {
|
||||
const uri = monacoEditor.uri.toString();
|
||||
const monacoSelection = this.p2m.asRange(selection);
|
||||
console.log(
|
||||
@@ -418,12 +550,13 @@ export class CompilerErrors
|
||||
console.trace('No match');
|
||||
return undefined;
|
||||
};
|
||||
const error = this.errors
|
||||
.filter((error) => error.uri === uri)
|
||||
.map((error) => ({
|
||||
error,
|
||||
range: ErrorDecoration.rangeOf(error, monacoEditor),
|
||||
}))
|
||||
const errorsPerResource = this.errors.filter((error) => error.uri === uri);
|
||||
const rangesPerResource = ErrorDecoration.rangeOf(
|
||||
monacoEditor,
|
||||
errorsPerResource
|
||||
);
|
||||
const error = rangesPerResource
|
||||
.map((range, index) => ({ error: errorsPerResource[index], range }))
|
||||
.map(({ error, range }) => {
|
||||
if (range) {
|
||||
const priority = calculatePriority(range, monacoSelection);
|
||||
@@ -464,66 +597,77 @@ export class CompilerErrors
|
||||
}
|
||||
|
||||
/**
|
||||
* If a document change "destroys" the range of the decoration, the decoration must be removed.
|
||||
* If the text document changes in the line where compiler errors are, the compiler errors will be removed.
|
||||
*/
|
||||
private handleDocumentContentChange(
|
||||
editor: EditorWidget,
|
||||
monacoEditor: MonacoEditor,
|
||||
event: TextDocumentChangeEvent
|
||||
): void {
|
||||
const monacoEditor = this.monacoEditor(editor);
|
||||
if (!monacoEditor) {
|
||||
return;
|
||||
}
|
||||
// A decoration location can be "destroyed", hence should be deleted when:
|
||||
// - deleting range (start != end AND text is empty)
|
||||
// - inserting text into range (start != end AND text is not empty)
|
||||
// Filter unrelated delta changes to spare the CPU.
|
||||
const relevantChanges = event.contentChanges.filter(
|
||||
({ range: { start, end } }) =>
|
||||
start.line !== end.line || start.character !== end.character
|
||||
const errorsPerResource = this.errors.filter(
|
||||
(error) => error.uri === event.document.uri
|
||||
);
|
||||
if (!relevantChanges.length) {
|
||||
return;
|
||||
let editorOrModel: MonacoEditor | ITextModel = monacoEditor;
|
||||
const doc = event.document;
|
||||
if (doc instanceof MonacoEditorModel) {
|
||||
editorOrModel = doc.textEditorModel;
|
||||
}
|
||||
|
||||
const resolvedMarkers = this.errors
|
||||
.filter((error) => error.uri === event.document.uri)
|
||||
.map((error, index) => {
|
||||
const range = ErrorDecoration.rangeOf(error, monacoEditor);
|
||||
if (range) {
|
||||
return { error, range, index };
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(notEmpty);
|
||||
|
||||
const decorationIdsToRemove = relevantChanges
|
||||
const rangesPerResource = ErrorDecoration.rangeOf(
|
||||
editorOrModel,
|
||||
errorsPerResource
|
||||
);
|
||||
const resolvedDecorations = rangesPerResource.map((range, index) => ({
|
||||
error: errorsPerResource[index],
|
||||
range,
|
||||
}));
|
||||
const decoratorsToRemove = event.contentChanges
|
||||
.map(({ range }) => this.p2m.asRange(range))
|
||||
.map((changeRange) =>
|
||||
resolvedMarkers.filter(({ range: decorationRange }) =>
|
||||
changeRange.containsRange(decorationRange)
|
||||
)
|
||||
.map((changedRange) =>
|
||||
resolvedDecorations
|
||||
.filter(({ range: decorationRange }) => {
|
||||
if (!decorationRange) {
|
||||
return false;
|
||||
}
|
||||
const affects =
|
||||
changedRange.startLineNumber <= decorationRange.startLineNumber &&
|
||||
changedRange.endLineNumber >= decorationRange.endLineNumber;
|
||||
console.log(
|
||||
'compiler-errors',
|
||||
`decoration range: ${decorationRange.toString()}, change range: ${changedRange.toString()}, affects: ${affects}`
|
||||
);
|
||||
return affects;
|
||||
})
|
||||
.map(({ error }) => {
|
||||
const index = this.errors.findIndex((candidate) =>
|
||||
ErrorDecorationRef.sameAs(candidate, error)
|
||||
);
|
||||
return index !== -1 ? { error, index } : undefined;
|
||||
})
|
||||
.filter(notEmpty)
|
||||
)
|
||||
.reduce((acc, curr) => acc.concat(curr), [])
|
||||
.map(({ error, index }) => {
|
||||
this.errors.splice(index, 1);
|
||||
return error.id;
|
||||
});
|
||||
if (!decorationIdsToRemove.length) {
|
||||
return;
|
||||
.sort((left, right) => left.index - right.index); // highest index last
|
||||
|
||||
if (decoratorsToRemove.length) {
|
||||
let i = decoratorsToRemove.length;
|
||||
while (i--) {
|
||||
this.errors.splice(decoratorsToRemove[i].index, 1);
|
||||
}
|
||||
monacoEditor.getControl().deltaDecorations(
|
||||
decoratorsToRemove.map(({ error }) => error.id),
|
||||
[]
|
||||
);
|
||||
this.onDidChangeEmitter.fire(this);
|
||||
}
|
||||
monacoEditor.getControl().deltaDecorations(decorationIdsToRemove, []);
|
||||
this.onDidChangeEmitter.fire(this);
|
||||
}
|
||||
|
||||
private async trackEditors(
|
||||
errors: Map<string, CoreError.ErrorLocation[]>,
|
||||
...track: ((editor: EditorWidget) => Disposable)[]
|
||||
...track: ((editor: MonacoEditor) => Disposable)[]
|
||||
): Promise<Disposable> {
|
||||
return new DisposableCollection(
|
||||
...(await Promise.all(
|
||||
Array.from(errors.keys()).map(async (uri) => {
|
||||
const editor = await this.editorManager.getByUri(new URI(uri));
|
||||
const editor = await this.monacoEditor(uri);
|
||||
if (!editor) {
|
||||
return Disposable.NULL;
|
||||
}
|
||||
@@ -533,15 +677,18 @@ export class CompilerErrors
|
||||
);
|
||||
}
|
||||
|
||||
private async markAsCurrentError(error: ErrorDecoration): Promise<void> {
|
||||
private async markAsCurrentError(
|
||||
ref: ErrorDecorationRef,
|
||||
options?: { forceReselect?: boolean; reveal?: boolean }
|
||||
): Promise<void> {
|
||||
const index = this.errors.findIndex((candidate) =>
|
||||
ErrorDecoration.sameAs(candidate, error)
|
||||
ErrorDecorationRef.sameAs(candidate, ref)
|
||||
);
|
||||
if (index < 0) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Failed to mark error ${
|
||||
error.id
|
||||
ref.id
|
||||
} as the current one. Error is unknown. Known errors are: ${this.errors.map(
|
||||
({ id }) => id
|
||||
)}`
|
||||
@@ -550,15 +697,18 @@ export class CompilerErrors
|
||||
}
|
||||
const newError = this.errors[index];
|
||||
if (
|
||||
options?.forceReselect ||
|
||||
!this.currentError ||
|
||||
!ErrorDecoration.sameAs(this.currentError, newError)
|
||||
!ErrorDecorationRef.sameAs(this.currentError, newError)
|
||||
) {
|
||||
this.currentError = this.errors[index];
|
||||
console.log(
|
||||
'compiler-errors',
|
||||
`Current error changed to ${this.currentError.id}`
|
||||
);
|
||||
this.currentErrorDidChangEmitter.fire(this.currentError);
|
||||
if (options?.reveal) {
|
||||
this.currentErrorDidChangEmitter.fire(this.currentError);
|
||||
}
|
||||
this.onDidChangeEmitter.fire(this);
|
||||
}
|
||||
}
|
||||
@@ -593,32 +743,33 @@ export class CompilerErrors
|
||||
}
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`could not found editor widget for URI: ${uri}`
|
||||
`could not find editor widget for URI: ${uri}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private groupByResource(
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Map<string, CoreError.ErrorLocation[]> {
|
||||
return errors.reduce((acc, curr) => {
|
||||
const {
|
||||
location: { uri },
|
||||
} = curr;
|
||||
let errors = acc.get(uri);
|
||||
if (!errors) {
|
||||
errors = [];
|
||||
acc.set(uri, errors);
|
||||
private groupBy<K, V>(
|
||||
elements: V[],
|
||||
extractKey: (element: V) => K
|
||||
): Map<K, V[]> {
|
||||
return elements.reduce((acc, curr) => {
|
||||
const key = extractKey(curr);
|
||||
let values = acc.get(key);
|
||||
if (!values) {
|
||||
values = [];
|
||||
acc.set(key, values);
|
||||
}
|
||||
errors.push(curr);
|
||||
values.push(curr);
|
||||
return acc;
|
||||
}, new Map<string, CoreError.ErrorLocation[]>());
|
||||
}, new Map<K, V[]>());
|
||||
}
|
||||
|
||||
private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;
|
||||
private monacoEditor(uri: string): Promise<MonacoEditor | undefined>;
|
||||
private monacoEditor(
|
||||
uriOrWidget: string | EditorWidget
|
||||
uri: string | monaco.Uri
|
||||
): Promise<MonacoEditor | undefined>;
|
||||
private monacoEditor(
|
||||
uriOrWidget: string | monaco.Uri | EditorWidget
|
||||
): MaybePromise<MonacoEditor | undefined> {
|
||||
if (uriOrWidget instanceof EditorWidget) {
|
||||
const editor = uriOrWidget.editor;
|
||||
@@ -646,5 +797,8 @@ export namespace CompilerErrors {
|
||||
export const PREVIOUS_ERROR: Command = {
|
||||
id: 'arduino-editor-previous-error',
|
||||
};
|
||||
export const MARK_AS_CURRENT: Command = {
|
||||
id: 'arduino-editor-mark-as-current-error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -59,6 +59,8 @@ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||
import { ExecuteWithProgress } from '../../common/protocol/progressible';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { NotificationManager } from '../theia/messages/notifications-manager';
|
||||
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
|
||||
|
||||
export {
|
||||
Command,
|
||||
@@ -186,6 +188,22 @@ export abstract class CoreServiceContribution extends SketchContribution {
|
||||
@inject(ResponseServiceClient)
|
||||
private readonly responseService: ResponseServiceClient;
|
||||
|
||||
@inject(NotificationManager)
|
||||
private readonly notificationManager: NotificationManager;
|
||||
|
||||
/**
|
||||
* This is the internal (Theia) ID of the notification that is currently visible.
|
||||
* It's stored here as a field to be able to close it before executing any new core command (such as verify, upload, etc.)
|
||||
*/
|
||||
private visibleNotificationId: string | undefined;
|
||||
|
||||
protected clearVisibleNotification(): void {
|
||||
if (this.visibleNotificationId) {
|
||||
this.notificationManager.clear(this.visibleNotificationId);
|
||||
this.visibleNotificationId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected handleError(error: unknown): void {
|
||||
this.tryToastErrorMessage(error);
|
||||
}
|
||||
@@ -204,10 +222,17 @@ export abstract class CoreServiceContribution extends SketchContribution {
|
||||
} catch {}
|
||||
}
|
||||
if (message) {
|
||||
if (message.includes('Missing FQBN (Fully Qualified Board Name)')) {
|
||||
message = nls.localize(
|
||||
'arduino/coreContribution/noBoardSelected',
|
||||
'No board selected. Please select your Arduino board from the Tools > Board menu.'
|
||||
);
|
||||
}
|
||||
const copyAction = nls.localize(
|
||||
'arduino/coreContribution/copyError',
|
||||
'Copy error messages'
|
||||
);
|
||||
this.visibleNotificationId = this.notificationId(message, copyAction);
|
||||
this.messageService.error(message, copyAction).then(async (action) => {
|
||||
if (action === copyAction) {
|
||||
const content = await this.outputChannelManager.contentOfChannel(
|
||||
@@ -241,6 +266,14 @@ export abstract class CoreServiceContribution extends SketchContribution {
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private notificationId(message: string, ...actions: string[]): string {
|
||||
return this.notificationManager.getMessageId({
|
||||
text: message,
|
||||
actions,
|
||||
type: MessageType.Error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Contribution {
|
||||
|
@@ -1,6 +1,10 @@
|
||||
import { LocalStorageService } from '@theia/core/lib/browser';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { BoardsService, LibraryService } from '../../common/protocol';
|
||||
import {
|
||||
BoardsService,
|
||||
LibraryLocation,
|
||||
LibraryService,
|
||||
} from '../../common/protocol';
|
||||
import { Contribution } from './contribution';
|
||||
|
||||
@injectable()
|
||||
@@ -57,6 +61,7 @@ export class FirstStartupInstaller extends Contribution {
|
||||
item: builtInLibrary,
|
||||
installDependencies: true,
|
||||
noOverwrite: true, // We don't want to automatically replace custom libraries the user might already have in place
|
||||
installLocation: LibraryLocation.BUILTIN,
|
||||
});
|
||||
} catch (e) {
|
||||
// There's no error code, I need to parse the error message: https://github.com/arduino/arduino-cli/commit/2ea3608453b17b1157f8a1dc892af2e13e40f4f0#diff-1de7569144d4e260f8dde0e0d00a4e2a218c57966d583da1687a70d518986649R95
|
||||
|
@@ -2,8 +2,7 @@ import { MaybePromise } from '@theia/core';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { Formatter } from '../../common/protocol/formatter';
|
||||
import { InoSelector } from '../ino-selectors';
|
||||
import { fullRange } from '../utils/monaco';
|
||||
import { InoSelector } from '../selectors';
|
||||
import { Contribution, URI } from './contribution';
|
||||
|
||||
@injectable()
|
||||
@@ -40,7 +39,7 @@ export class Format
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.TextEdit[]> {
|
||||
const range = fullRange(model);
|
||||
const range = model.getFullModelRange();
|
||||
const text = await this.format(model, range, options);
|
||||
return [{ range, text }];
|
||||
}
|
||||
|
@@ -41,7 +41,9 @@ export class Help extends Contribution {
|
||||
);
|
||||
registry.registerCommand(
|
||||
Help.Commands.ENVIRONMENT,
|
||||
createOpenHandler('https://www.arduino.cc/en/Guide/Environment')
|
||||
createOpenHandler(
|
||||
'https://docs.arduino.cc/software/ide-v2/tutorials/getting-started-ide-v2'
|
||||
)
|
||||
);
|
||||
registry.registerCommand(
|
||||
Help.Commands.TROUBLESHOOTING,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { Later } from '../../common/nls';
|
||||
import { SketchesError } from '../../common/protocol';
|
||||
import {
|
||||
Command,
|
||||
@@ -41,20 +42,18 @@ export class OpenSketchFiles extends SketchContribution {
|
||||
sketch.name
|
||||
);
|
||||
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
||||
this.messageService
|
||||
.info(message, nls.localize('arduino/common/later', 'Later'), yes)
|
||||
.then(async (answer) => {
|
||||
if (answer === yes) {
|
||||
this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true,
|
||||
wipeOriginal: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
this.messageService.info(message, Later, yes).then((answer) => {
|
||||
if (answer === yes) {
|
||||
this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true,
|
||||
wipeOriginal: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (SketchesError.NotFound.is(err)) {
|
||||
|
@@ -57,6 +57,7 @@ export class SaveAsSketch extends SketchContribution {
|
||||
execOnlyIfTemp,
|
||||
openAfterMove,
|
||||
wipeOriginal,
|
||||
markAsRecentlyOpened,
|
||||
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
|
||||
): Promise<boolean> {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
@@ -102,18 +103,22 @@ export class SaveAsSketch extends SketchContribution {
|
||||
});
|
||||
if (workspaceUri) {
|
||||
await this.saveOntoCopiedSketch(sketch.mainFileUri, sketch.uri, workspaceUri);
|
||||
if (markAsRecentlyOpened) {
|
||||
this.sketchService.markAsRecentlyOpened(workspaceUri);
|
||||
}
|
||||
}
|
||||
if (workspaceUri && openAfterMove) {
|
||||
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||
try {
|
||||
await this.fileService.delete(new URI(sketch.uri), {
|
||||
recursive: true,
|
||||
});
|
||||
} catch {
|
||||
/* NOOP: from time to time, it's not possible to wipe the old resource from the temp dir on Windows */
|
||||
}
|
||||
}
|
||||
this.windowService.setSafeToShutDown();
|
||||
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||
// This window will navigate away.
|
||||
// Explicitly stop the contribution to dispose the file watcher before deleting the temp sketch.
|
||||
// Otherwise, users might see irrelevant _Unable to watch for file changes in this large workspace._ notification.
|
||||
// https://github.com/arduino/arduino-ide/issues/39.
|
||||
this.sketchServiceClient.onStop();
|
||||
// TODO: consider implementing the temp sketch deletion the following way:
|
||||
// Open the other sketch with a `delete the temp sketch` startup-task.
|
||||
this.sketchService.notifyDeleteSketch(sketch); // This is a notification and will execute on the backend.
|
||||
}
|
||||
this.workspaceService.open(new URI(workspaceUri), {
|
||||
preserveWindow: true,
|
||||
});
|
||||
@@ -170,12 +175,14 @@ export namespace SaveAsSketch {
|
||||
* Ignored if `openAfterMove` is `false`.
|
||||
*/
|
||||
readonly wipeOriginal?: boolean;
|
||||
readonly markAsRecentlyOpened?: boolean;
|
||||
}
|
||||
export namespace Options {
|
||||
export const DEFAULT: Options = {
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true,
|
||||
wipeOriginal: false,
|
||||
markAsRecentlyOpened: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -30,10 +30,7 @@ export class SketchFilesTracker extends SketchContribution {
|
||||
|
||||
override onReady(): void {
|
||||
this.sketchServiceClient.currentSketch().then(async (sketch) => {
|
||||
if (
|
||||
CurrentSketch.isValid(sketch) &&
|
||||
!(await this.sketchService.isTemp(sketch))
|
||||
) {
|
||||
if (CurrentSketch.isValid(sketch)) {
|
||||
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
|
||||
this.toDisposeOnStop.push(
|
||||
this.fileService.onDidFilesChange(async (event) => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { BoardUserField, CoreService } from '../../common/protocol';
|
||||
import { BoardUserField, CoreService, Port } from '../../common/protocol';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import {
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CoreServiceContribution,
|
||||
} from './contribution';
|
||||
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
|
||||
import { DisposableCollection, nls } from '@theia/core/lib/common';
|
||||
import { deepClone, DisposableCollection, nls } from '@theia/core/lib/common';
|
||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||
import type { VerifySketchParams } from './verify-sketch';
|
||||
|
||||
@@ -61,10 +61,11 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
|
||||
execute: async () => {
|
||||
const key = this.selectedFqbnAddress();
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (this.boardRequiresUserFields && !this.cachedUserFields.has(key)) {
|
||||
if (
|
||||
this.boardRequiresUserFields &&
|
||||
key &&
|
||||
!this.cachedUserFields.has(key)
|
||||
) {
|
||||
// Deep clone the array of board fields to avoid editing the cached ones
|
||||
this.userFieldsDialog.value = (
|
||||
await this.boardsServiceProvider.selectedBoardUserFields()
|
||||
@@ -190,7 +191,9 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
// toggle the toolbar button and menu item state.
|
||||
// uploadInProgress will be set to false whether the upload fails or not
|
||||
this.uploadInProgress = true;
|
||||
this.boardsServiceProvider.snapshotBoardDiscoveryOnUpload();
|
||||
this.onDidChangeEmitter.fire();
|
||||
this.clearVisibleNotification();
|
||||
|
||||
const verifyOptions =
|
||||
await this.commandService.executeCommand<CoreService.Options.Compile>(
|
||||
@@ -242,6 +245,7 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
this.handleError(e);
|
||||
} finally {
|
||||
this.uploadInProgress = false;
|
||||
this.boardsServiceProvider.attemptPostUploadAutoSelect();
|
||||
this.onDidChangeEmitter.fire();
|
||||
}
|
||||
}
|
||||
@@ -263,7 +267,7 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose'),
|
||||
]);
|
||||
const port = boardsConfig.selectedPort;
|
||||
const port = this.maybeUpdatePortProperties(boardsConfig.selectedPort);
|
||||
return {
|
||||
sketch,
|
||||
fqbn,
|
||||
@@ -275,7 +279,29 @@ export class UploadSketch extends CoreServiceContribution {
|
||||
};
|
||||
}
|
||||
|
||||
private userFields() {
|
||||
/**
|
||||
* This is a hack to ensure that the port object has the `properties` when uploading.(https://github.com/arduino/arduino-ide/issues/740)
|
||||
* This method works around a bug when restoring a `port` persisted by an older version of IDE2. See the bug [here](https://github.com/arduino/arduino-ide/pull/1335#issuecomment-1224355236).
|
||||
*
|
||||
* Before the upload, this method checks the available ports and makes sure that the `properties` of an available port, and the port selected by the user have the same `properties`.
|
||||
* This method does not update any state (for example, the `BoardsConfig.Config`) but uses the correct `properties` for the `upload`.
|
||||
*/
|
||||
private maybeUpdatePortProperties(port: Port | undefined): Port | undefined {
|
||||
if (port) {
|
||||
const key = Port.keyOf(port);
|
||||
for (const candidate of this.boardsServiceProvider.availablePorts) {
|
||||
if (key === Port.keyOf(candidate) && candidate.properties) {
|
||||
return {
|
||||
...port,
|
||||
properties: deepClone(candidate.properties),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
private userFields(): BoardUserField[] {
|
||||
return this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
|
||||
}
|
||||
|
||||
|
@@ -108,6 +108,7 @@ export class VerifySketch extends CoreServiceContribution {
|
||||
this.verifyInProgress = true;
|
||||
this.onDidChangeEmitter.fire();
|
||||
}
|
||||
this.clearVisibleNotification();
|
||||
this.coreErrorHandler.reset();
|
||||
|
||||
const options = await this.options(params?.exportBinaries);
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
||||
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
@@ -153,6 +157,7 @@ export class UploadCertificateDialog extends AbstractDialog<void> {
|
||||
'Upload SSL Root Certificates'
|
||||
),
|
||||
});
|
||||
this.node.id = 'certificate-uploader-dialog-container';
|
||||
this.contentNode.classList.add('certificate-uploader-dialog');
|
||||
this.acceptButton = undefined;
|
||||
}
|
||||
|
@@ -101,6 +101,7 @@ export class UploadFirmwareDialog extends AbstractDialog<void> {
|
||||
protected override readonly props: UploadFirmwareDialogProps
|
||||
) {
|
||||
super({ title: UploadFirmware.Commands.OPEN.label || '' });
|
||||
this.node.id = 'firmware-uploader-dialog-container';
|
||||
this.contentNode.classList.add('firmware-uploader-dialog');
|
||||
this.acceptButton = undefined;
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { shell } from 'electron';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
@@ -7,36 +6,32 @@ import ReactMarkdown from 'react-markdown';
|
||||
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
|
||||
import ProgressBar from '../../components/ProgressBar';
|
||||
|
||||
export type IDEUpdaterComponentProps = {
|
||||
updateInfo: UpdateInfo;
|
||||
windowService: WindowService;
|
||||
export interface UpdateProgress {
|
||||
progressInfo?: ProgressInfo | undefined;
|
||||
downloadFinished?: boolean;
|
||||
downloadStarted?: boolean;
|
||||
progress?: ProgressInfo;
|
||||
error?: Error;
|
||||
onDownload: () => void;
|
||||
onClose: () => void;
|
||||
onSkipVersion: () => void;
|
||||
onCloseAndInstall: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDEUpdaterComponentProps {
|
||||
updateInfo: UpdateInfo;
|
||||
updateProgress: UpdateProgress;
|
||||
}
|
||||
|
||||
export const IDEUpdaterComponent = ({
|
||||
updateInfo: { version, releaseNotes },
|
||||
downloadStarted = false,
|
||||
downloadFinished = false,
|
||||
windowService,
|
||||
progress,
|
||||
error,
|
||||
onDownload,
|
||||
onClose,
|
||||
onSkipVersion,
|
||||
onCloseAndInstall,
|
||||
updateInfo,
|
||||
updateProgress: {
|
||||
downloadStarted = false,
|
||||
downloadFinished = false,
|
||||
progressInfo,
|
||||
error,
|
||||
},
|
||||
}: IDEUpdaterComponentProps): React.ReactElement => {
|
||||
const changelogDivRef = React.useRef() as React.MutableRefObject<
|
||||
HTMLDivElement
|
||||
>;
|
||||
const { version, releaseNotes } = updateInfo;
|
||||
const changelogDivRef =
|
||||
React.useRef() as React.MutableRefObject<HTMLDivElement>;
|
||||
React.useEffect(() => {
|
||||
if (!!releaseNotes) {
|
||||
if (!!releaseNotes && changelogDivRef.current) {
|
||||
let changelog: string;
|
||||
if (typeof releaseNotes === 'string') changelog = releaseNotes;
|
||||
else
|
||||
@@ -58,12 +53,7 @@ export const IDEUpdaterComponent = ({
|
||||
changelogDivRef.current
|
||||
);
|
||||
}
|
||||
}, [releaseNotes]);
|
||||
const closeButton = (
|
||||
<button onClick={onClose} type="button" className="theia-button secondary">
|
||||
{nls.localize('arduino/ide-updater/notNowButton', 'Not now')}
|
||||
</button>
|
||||
);
|
||||
}, [updateInfo]);
|
||||
|
||||
const DownloadCompleted: () => React.ReactElement = () => (
|
||||
<div className="ide-updater-dialog--downloaded">
|
||||
@@ -80,19 +70,6 @@ export const IDEUpdaterComponent = ({
|
||||
'Close the software and install the update on your machine.'
|
||||
)}
|
||||
</div>
|
||||
<div className="buttons-container">
|
||||
{closeButton}
|
||||
<button
|
||||
onClick={onCloseAndInstall}
|
||||
type="button"
|
||||
className="theia-button close-and-install"
|
||||
>
|
||||
{nls.localize(
|
||||
'arduino/ide-updater/closeAndInstallButton',
|
||||
'Close and Install'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -104,7 +81,7 @@ export const IDEUpdaterComponent = ({
|
||||
'Downloading the latest version of the Arduino IDE.'
|
||||
)}
|
||||
</div>
|
||||
<ProgressBar percent={progress?.percent} showPercentage />
|
||||
<ProgressBar percent={progressInfo?.percent} showPercentage />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -130,46 +107,14 @@ export const IDEUpdaterComponent = ({
|
||||
)}
|
||||
</div>
|
||||
{releaseNotes && (
|
||||
<div className="dialogRow">
|
||||
<div className="changelog-container" ref={changelogDivRef} />
|
||||
<div className="dialogRow changelog-container">
|
||||
<div className="changelog" ref={changelogDivRef} />
|
||||
</div>
|
||||
)}
|
||||
<div className="buttons-container">
|
||||
<button
|
||||
onClick={onSkipVersion}
|
||||
type="button"
|
||||
className="theia-button secondary skip-version"
|
||||
>
|
||||
{nls.localize(
|
||||
'arduino/ide-updater/skipVersionButton',
|
||||
'Skip Version'
|
||||
)}
|
||||
</button>
|
||||
<div className="push"></div>
|
||||
{closeButton}
|
||||
<button
|
||||
onClick={onDownload}
|
||||
type="button"
|
||||
className="theia-button primary"
|
||||
>
|
||||
{nls.localize('arduino/ide-updater/downloadButton', 'Download')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const onGoToDownloadClick = (
|
||||
event: React.SyntheticEvent<HTMLAnchorElement, Event>
|
||||
) => {
|
||||
const { target } = event.nativeEvent;
|
||||
if (target instanceof HTMLAnchorElement) {
|
||||
event.nativeEvent.preventDefault();
|
||||
windowService.openNewWindow(target.href, { external: true });
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const GoToDownloadPage: () => React.ReactElement = () => (
|
||||
<div className="ide-updater-dialog--go-to-download-page">
|
||||
<div>
|
||||
@@ -178,19 +123,6 @@ export const IDEUpdaterComponent = ({
|
||||
"An update for the Arduino IDE is available, but we're not able to download and install it automatically. Please go to the download page and download the latest version from there."
|
||||
)}
|
||||
</div>
|
||||
<div className="buttons-container">
|
||||
{closeButton}
|
||||
<a
|
||||
className="theia-button primary"
|
||||
href="https://www.arduino.cc/en/software#experimental-software"
|
||||
onClick={onGoToDownloadClick}
|
||||
>
|
||||
{nls.localize(
|
||||
'arduino/ide-updater/goToDownloadButton',
|
||||
'Go To Download'
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@@ -1,113 +1,57 @@
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
||||
import { AbstractDialog } from '../../theia/dialogs/dialogs';
|
||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
||||
import { nls } from '@theia/core';
|
||||
import { IDEUpdaterComponent } from './ide-updater-component';
|
||||
|
||||
import { IDEUpdaterComponent, UpdateProgress } from './ide-updater-component';
|
||||
import {
|
||||
IDEUpdater,
|
||||
IDEUpdaterClient,
|
||||
ProgressInfo,
|
||||
SKIP_IDE_VERSION,
|
||||
UpdateInfo,
|
||||
} from '../../../common/protocol/ide-updater';
|
||||
import { LocalStorageService } from '@theia/core/lib/browser';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
|
||||
const DOWNLOAD_PAGE_URL =
|
||||
'https://www.arduino.cc/en/software#experimental-software';
|
||||
|
||||
@injectable()
|
||||
export class IDEUpdaterDialogWidget extends ReactWidget {
|
||||
protected isOpen = new Object();
|
||||
updateInfo: UpdateInfo;
|
||||
progressInfo: ProgressInfo | undefined;
|
||||
error: Error | undefined;
|
||||
downloadFinished: boolean;
|
||||
downloadStarted: boolean;
|
||||
onClose: () => void;
|
||||
private _updateInfo: UpdateInfo;
|
||||
private _updateProgress: UpdateProgress = {};
|
||||
|
||||
@inject(IDEUpdater)
|
||||
protected readonly updater: IDEUpdater;
|
||||
|
||||
@inject(IDEUpdaterClient)
|
||||
protected readonly updaterClient: IDEUpdaterClient;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
protected readonly localStorageService: LocalStorageService;
|
||||
|
||||
@inject(WindowService)
|
||||
protected windowService: WindowService;
|
||||
|
||||
init(updateInfo: UpdateInfo, onClose: () => void): void {
|
||||
this.updateInfo = updateInfo;
|
||||
this.progressInfo = undefined;
|
||||
this.error = undefined;
|
||||
this.downloadStarted = false;
|
||||
this.downloadFinished = false;
|
||||
this.onClose = onClose;
|
||||
|
||||
this.updaterClient.onError((e) => {
|
||||
this.error = e;
|
||||
this.update();
|
||||
});
|
||||
this.updaterClient.onDownloadProgressChanged((e) => {
|
||||
this.progressInfo = e;
|
||||
this.update();
|
||||
});
|
||||
this.updaterClient.onDownloadFinished((e) => {
|
||||
this.downloadFinished = true;
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
async onSkipVersion(): Promise<void> {
|
||||
this.localStorageService.setData<string>(
|
||||
SKIP_IDE_VERSION,
|
||||
this.updateInfo.version
|
||||
);
|
||||
this.close();
|
||||
}
|
||||
|
||||
override close(): void {
|
||||
super.close();
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
onDispose(): void {
|
||||
if (this.downloadStarted && !this.downloadFinished)
|
||||
this.updater.stopDownload();
|
||||
}
|
||||
|
||||
async onDownload(): Promise<void> {
|
||||
this.progressInfo = undefined;
|
||||
this.downloadStarted = true;
|
||||
this.error = undefined;
|
||||
this.updater.downloadUpdate();
|
||||
setUpdateInfo(updateInfo: UpdateInfo): void {
|
||||
this._updateInfo = updateInfo;
|
||||
this.update();
|
||||
}
|
||||
|
||||
onCloseAndInstall(): void {
|
||||
this.updater.quitAndInstall();
|
||||
mergeUpdateProgress(updateProgress: UpdateProgress): void {
|
||||
this._updateProgress = { ...this._updateProgress, ...updateProgress };
|
||||
this.update();
|
||||
}
|
||||
|
||||
get updateInfo(): UpdateInfo {
|
||||
return this._updateInfo;
|
||||
}
|
||||
|
||||
get updateProgress(): UpdateProgress {
|
||||
return this._updateProgress;
|
||||
}
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return !!this.updateInfo ? (
|
||||
<form>
|
||||
<IDEUpdaterComponent
|
||||
updateInfo={this.updateInfo}
|
||||
windowService={this.windowService}
|
||||
downloadStarted={this.downloadStarted}
|
||||
downloadFinished={this.downloadFinished}
|
||||
progress={this.progressInfo}
|
||||
error={this.error}
|
||||
onClose={this.close.bind(this)}
|
||||
onSkipVersion={this.onSkipVersion.bind(this)}
|
||||
onDownload={this.onDownload.bind(this)}
|
||||
onCloseAndInstall={this.onCloseAndInstall.bind(this)}
|
||||
/>
|
||||
</form>
|
||||
return !!this._updateInfo ? (
|
||||
<IDEUpdaterComponent
|
||||
updateInfo={this._updateInfo}
|
||||
updateProgress={this._updateProgress}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
@@ -118,7 +62,19 @@ export class IDEUpdaterDialogProps extends DialogProps {}
|
||||
@injectable()
|
||||
export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
||||
@inject(IDEUpdaterDialogWidget)
|
||||
protected readonly widget: IDEUpdaterDialogWidget;
|
||||
private readonly widget: IDEUpdaterDialogWidget;
|
||||
|
||||
@inject(IDEUpdater)
|
||||
private readonly updater: IDEUpdater;
|
||||
|
||||
@inject(IDEUpdaterClient)
|
||||
private readonly updaterClient: IDEUpdaterClient;
|
||||
|
||||
@inject(LocalStorageService)
|
||||
private readonly localStorageService: LocalStorageService;
|
||||
|
||||
@inject(WindowService)
|
||||
private readonly windowService: WindowService;
|
||||
|
||||
constructor(
|
||||
@inject(IDEUpdaterDialogProps)
|
||||
@@ -130,10 +86,26 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
||||
'Software Update'
|
||||
),
|
||||
});
|
||||
this.node.id = 'ide-updater-dialog-container';
|
||||
this.contentNode.classList.add('ide-updater-dialog');
|
||||
this.acceptButton = undefined;
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.updaterClient.onUpdaterDidFail((error) => {
|
||||
this.appendErrorButtons();
|
||||
this.widget.mergeUpdateProgress({ error });
|
||||
});
|
||||
this.updaterClient.onDownloadProgressDidChange((progressInfo) => {
|
||||
this.widget.mergeUpdateProgress({ progressInfo });
|
||||
});
|
||||
this.updaterClient.onDownloadDidFinish(() => {
|
||||
this.appendInstallButtons();
|
||||
this.widget.mergeUpdateProgress({ downloadFinished: true });
|
||||
});
|
||||
}
|
||||
|
||||
get value(): UpdateInfo {
|
||||
return this.widget.updateInfo;
|
||||
}
|
||||
@@ -143,24 +115,123 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
||||
Widget.detach(this.widget);
|
||||
}
|
||||
Widget.attach(this.widget, this.contentNode);
|
||||
this.appendInitialButtons();
|
||||
super.onAfterAttach(msg);
|
||||
this.update();
|
||||
}
|
||||
|
||||
private clearButtons(): void {
|
||||
while (this.controlPanel.firstChild) {
|
||||
this.controlPanel.removeChild(this.controlPanel.firstChild);
|
||||
}
|
||||
this.closeButton = undefined;
|
||||
}
|
||||
|
||||
private appendNotNowButton(): void {
|
||||
this.appendCloseButton(
|
||||
nls.localize('arduino/ide-updater/notNowButton', 'Not now')
|
||||
);
|
||||
if (this.closeButton) {
|
||||
this.addCloseAction(this.closeButton, 'click');
|
||||
}
|
||||
}
|
||||
|
||||
private appendInitialButtons(): void {
|
||||
this.clearButtons();
|
||||
|
||||
const skipVersionButton = this.createButton(
|
||||
nls.localize('arduino/ide-updater/skipVersionButton', 'Skip Version')
|
||||
);
|
||||
skipVersionButton.classList.add('secondary');
|
||||
skipVersionButton.classList.add('skip-version-button');
|
||||
this.addAction(skipVersionButton, this.skipVersion.bind(this), 'click');
|
||||
this.controlPanel.appendChild(skipVersionButton);
|
||||
|
||||
this.appendNotNowButton();
|
||||
|
||||
const downloadButton = this.createButton(
|
||||
nls.localize('arduino/ide-updater/downloadButton', 'Download')
|
||||
);
|
||||
this.addAction(downloadButton, this.startDownload.bind(this), 'click');
|
||||
this.controlPanel.appendChild(downloadButton);
|
||||
downloadButton.focus();
|
||||
}
|
||||
|
||||
private appendInstallButtons(): void {
|
||||
this.clearButtons();
|
||||
this.appendNotNowButton();
|
||||
|
||||
const closeAndInstallButton = this.createButton(
|
||||
nls.localize(
|
||||
'arduino/ide-updater/closeAndInstallButton',
|
||||
'Close and Install'
|
||||
)
|
||||
);
|
||||
this.addAction(
|
||||
closeAndInstallButton,
|
||||
this.closeAndInstall.bind(this),
|
||||
'click'
|
||||
);
|
||||
this.controlPanel.appendChild(closeAndInstallButton);
|
||||
closeAndInstallButton.focus();
|
||||
}
|
||||
|
||||
private appendErrorButtons(): void {
|
||||
this.clearButtons();
|
||||
this.appendNotNowButton();
|
||||
|
||||
const goToDownloadPageButton = this.createButton(
|
||||
nls.localize('arduino/ide-updater/goToDownloadButton', 'Go To Download')
|
||||
);
|
||||
this.addAction(
|
||||
goToDownloadPageButton,
|
||||
this.openDownloadPage.bind(this),
|
||||
'click'
|
||||
);
|
||||
this.controlPanel.appendChild(goToDownloadPageButton);
|
||||
goToDownloadPageButton.focus();
|
||||
}
|
||||
|
||||
private openDownloadPage(): void {
|
||||
this.windowService.openNewWindow(DOWNLOAD_PAGE_URL, { external: true });
|
||||
this.close();
|
||||
}
|
||||
|
||||
private skipVersion(): void {
|
||||
this.localStorageService.setData<string>(
|
||||
SKIP_IDE_VERSION,
|
||||
this.widget.updateInfo.version
|
||||
);
|
||||
this.close();
|
||||
}
|
||||
|
||||
private startDownload(): void {
|
||||
this.widget.mergeUpdateProgress({
|
||||
downloadStarted: true,
|
||||
});
|
||||
this.clearButtons();
|
||||
this.updater.downloadUpdate();
|
||||
}
|
||||
|
||||
private closeAndInstall() {
|
||||
this.updater.quitAndInstall();
|
||||
this.close();
|
||||
}
|
||||
|
||||
override async open(
|
||||
data: UpdateInfo | undefined = undefined
|
||||
): Promise<UpdateInfo | undefined> {
|
||||
if (data && data.version) {
|
||||
this.widget.init(data, this.close.bind(this));
|
||||
this.widget.mergeUpdateProgress({
|
||||
progressInfo: undefined,
|
||||
downloadStarted: false,
|
||||
downloadFinished: false,
|
||||
error: undefined,
|
||||
});
|
||||
this.widget.setUpdateInfo(data);
|
||||
return super.open();
|
||||
}
|
||||
}
|
||||
|
||||
protected override onUpdateRequest(msg: Message): void {
|
||||
super.onUpdateRequest(msg);
|
||||
this.widget.update();
|
||||
}
|
||||
|
||||
protected override onActivateRequest(msg: Message): void {
|
||||
super.onActivateRequest(msg);
|
||||
this.widget.activate();
|
||||
@@ -168,6 +239,12 @@ export class IDEUpdaterDialog extends AbstractDialog<UpdateInfo> {
|
||||
|
||||
override close(): void {
|
||||
this.widget.dispose();
|
||||
if (
|
||||
this.widget.updateProgress?.downloadStarted &&
|
||||
!this.widget.updateProgress?.downloadFinished
|
||||
) {
|
||||
this.updater.stopDownload();
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
@@ -23,8 +23,8 @@ import {
|
||||
} from '@theia/core/lib/common/i18n/localization';
|
||||
import SettingsStepInput from './settings-step-input';
|
||||
|
||||
const maxScale = 200;
|
||||
const minScale = -100;
|
||||
const maxScale = 280;
|
||||
const minScale = -60;
|
||||
const scaleStep = 20;
|
||||
|
||||
const maxFontSize = 72;
|
||||
@@ -188,15 +188,17 @@ export class SettingsComponent extends React.Component<
|
||||
/>
|
||||
{nls.localize('arduino/preferences/automatic', 'Automatic')}
|
||||
</label>
|
||||
<SettingsStepInput
|
||||
value={scalePercentage}
|
||||
setSettingsStateValue={this.setInterfaceScale}
|
||||
step={scaleStep}
|
||||
maxValue={maxScale}
|
||||
minValue={minScale}
|
||||
classNames={{ input: 'theia-input small with-margin' }}
|
||||
/>
|
||||
%
|
||||
<div>
|
||||
<SettingsStepInput
|
||||
value={scalePercentage}
|
||||
setSettingsStateValue={this.setInterfaceScale}
|
||||
step={scaleStep}
|
||||
maxValue={maxScale}
|
||||
minValue={minScale}
|
||||
unitOfMeasure="%"
|
||||
classNames={{ input: 'theia-input small with-margin' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-line">
|
||||
<select
|
||||
|
@@ -7,14 +7,22 @@ interface SettingsStepInputProps {
|
||||
step: number;
|
||||
maxValue: number;
|
||||
minValue: number;
|
||||
unitOfMeasure?: string;
|
||||
classNames?: { [key: string]: string };
|
||||
}
|
||||
|
||||
const SettingsStepInput: React.FC<SettingsStepInputProps> = (
|
||||
props: SettingsStepInputProps
|
||||
) => {
|
||||
const { value, setSettingsStateValue, step, maxValue, minValue, classNames } =
|
||||
props;
|
||||
const {
|
||||
value,
|
||||
setSettingsStateValue,
|
||||
step,
|
||||
maxValue,
|
||||
minValue,
|
||||
unitOfMeasure,
|
||||
classNames,
|
||||
} = props;
|
||||
|
||||
const clamp = (value: number, min: number, max: number): number => {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
@@ -86,6 +94,7 @@ const SettingsStepInput: React.FC<SettingsStepInputProps> = (
|
||||
▾
|
||||
</button>
|
||||
</div>
|
||||
{unitOfMeasure && `${unitOfMeasure}`}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -65,7 +65,11 @@ export const UserFieldsComponent = ({
|
||||
type={field.secret ? 'password' : 'text'}
|
||||
value={field.value}
|
||||
className="theia-input"
|
||||
placeholder={'Enter ' + field.label}
|
||||
placeholder={nls.localize(
|
||||
'arduino/userFields/enterField',
|
||||
'Enter {0}',
|
||||
field.label
|
||||
)}
|
||||
onChange={updateUserField(index)}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -5,36 +5,43 @@ import { IDEUpdaterClient } from '../../common/protocol/ide-updater';
|
||||
|
||||
@injectable()
|
||||
export class IDEUpdaterClientImpl implements IDEUpdaterClient {
|
||||
protected readonly onErrorEmitter = new Emitter<Error>();
|
||||
protected readonly onCheckingForUpdateEmitter = new Emitter<void>();
|
||||
protected readonly onUpdateAvailableEmitter = new Emitter<UpdateInfo>();
|
||||
protected readonly onUpdateNotAvailableEmitter = new Emitter<UpdateInfo>();
|
||||
protected readonly onDownloadProgressEmitter = new Emitter<ProgressInfo>();
|
||||
protected readonly onDownloadFinishedEmitter = new Emitter<UpdateInfo>();
|
||||
protected readonly onUpdaterDidFailEmitter = new Emitter<Error>();
|
||||
protected readonly onUpdaterDidCheckForUpdateEmitter = new Emitter<void>();
|
||||
protected readonly onUpdaterDidFindUpdateAvailableEmitter =
|
||||
new Emitter<UpdateInfo>();
|
||||
protected readonly onUpdaterDidNotFindUpdateAvailableEmitter =
|
||||
new Emitter<UpdateInfo>();
|
||||
protected readonly onDownloadProgressDidChangeEmitter =
|
||||
new Emitter<ProgressInfo>();
|
||||
protected readonly onDownloadDidFinishEmitter = new Emitter<UpdateInfo>();
|
||||
|
||||
readonly onError = this.onErrorEmitter.event;
|
||||
readonly onCheckingForUpdate = this.onCheckingForUpdateEmitter.event;
|
||||
readonly onUpdateAvailable = this.onUpdateAvailableEmitter.event;
|
||||
readonly onUpdateNotAvailable = this.onUpdateNotAvailableEmitter.event;
|
||||
readonly onDownloadProgressChanged = this.onDownloadProgressEmitter.event;
|
||||
readonly onDownloadFinished = this.onDownloadFinishedEmitter.event;
|
||||
readonly onUpdaterDidFail = this.onUpdaterDidFailEmitter.event;
|
||||
readonly onUpdaterDidCheckForUpdate =
|
||||
this.onUpdaterDidCheckForUpdateEmitter.event;
|
||||
readonly onUpdaterDidFindUpdateAvailable =
|
||||
this.onUpdaterDidFindUpdateAvailableEmitter.event;
|
||||
readonly onUpdaterDidNotFindUpdateAvailable =
|
||||
this.onUpdaterDidNotFindUpdateAvailableEmitter.event;
|
||||
readonly onDownloadProgressDidChange =
|
||||
this.onDownloadProgressDidChangeEmitter.event;
|
||||
readonly onDownloadDidFinish = this.onDownloadDidFinishEmitter.event;
|
||||
|
||||
notifyError(message: Error): void {
|
||||
this.onErrorEmitter.fire(message);
|
||||
notifyUpdaterFailed(message: Error): void {
|
||||
this.onUpdaterDidFailEmitter.fire(message);
|
||||
}
|
||||
notifyCheckingForUpdate(message: void): void {
|
||||
this.onCheckingForUpdateEmitter.fire(message);
|
||||
notifyCheckedForUpdate(message: void): void {
|
||||
this.onUpdaterDidCheckForUpdateEmitter.fire(message);
|
||||
}
|
||||
notifyUpdateAvailable(message: UpdateInfo): void {
|
||||
this.onUpdateAvailableEmitter.fire(message);
|
||||
notifyUpdateAvailableFound(message: UpdateInfo): void {
|
||||
this.onUpdaterDidFindUpdateAvailableEmitter.fire(message);
|
||||
}
|
||||
notifyUpdateNotAvailable(message: UpdateInfo): void {
|
||||
this.onUpdateNotAvailableEmitter.fire(message);
|
||||
notifyUpdateAvailableNotFound(message: UpdateInfo): void {
|
||||
this.onUpdaterDidNotFindUpdateAvailableEmitter.fire(message);
|
||||
}
|
||||
notifyDownloadProgressChanged(message: ProgressInfo): void {
|
||||
this.onDownloadProgressEmitter.fire(message);
|
||||
this.onDownloadProgressDidChangeEmitter.fire(message);
|
||||
}
|
||||
notifyDownloadFinished(message: UpdateInfo): void {
|
||||
this.onDownloadFinishedEmitter.fire(message);
|
||||
this.onDownloadDidFinishEmitter.fire(message);
|
||||
}
|
||||
}
|
||||
|
@@ -54,8 +54,8 @@ export class IDEUpdaterCommands implements CommandContribution {
|
||||
export namespace IDEUpdaterCommands {
|
||||
export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand(
|
||||
{
|
||||
id: 'arduino-ide-check-for-updates',
|
||||
label: 'Check for Arduino IDE updates',
|
||||
id: 'arduino-check-for-ide-updates',
|
||||
label: 'Check for Arduino IDE Updates',
|
||||
category: 'Arduino',
|
||||
},
|
||||
'arduino/ide-updater/checkForUpdates'
|
||||
|
@@ -1,19 +1,28 @@
|
||||
import { injectable, postConstruct, inject } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
injectable,
|
||||
postConstruct,
|
||||
inject,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
|
||||
import { DialogProps } from '@theia/core/lib/browser/dialogs';
|
||||
import { AbstractDialog } from '../theia/dialogs/dialogs';
|
||||
import {
|
||||
LibraryPackage,
|
||||
LibrarySearch,
|
||||
LibraryService,
|
||||
} from '../../common/protocol/library-service';
|
||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { Installable } from '../../common/protocol';
|
||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
|
||||
|
||||
@injectable()
|
||||
export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
export class LibraryListWidget extends ListWidget<
|
||||
LibraryPackage,
|
||||
LibrarySearch
|
||||
> {
|
||||
static WIDGET_ID = 'library-list-widget';
|
||||
static WIDGET_LABEL = nls.localize(
|
||||
'arduino/library/title',
|
||||
@@ -21,9 +30,9 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
);
|
||||
|
||||
constructor(
|
||||
@inject(LibraryService) protected service: LibraryService,
|
||||
@inject(ListItemRenderer)
|
||||
protected itemRenderer: ListItemRenderer<LibraryPackage>
|
||||
@inject(LibraryService) private service: LibraryService,
|
||||
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<LibraryPackage>,
|
||||
@inject(LibraryFilterRenderer) filterRenderer: LibraryFilterRenderer
|
||||
) {
|
||||
super({
|
||||
id: LibraryListWidget.WIDGET_ID,
|
||||
@@ -34,6 +43,8 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
itemLabel: (item: LibraryPackage) => item.name,
|
||||
itemDeprecated: (item: LibraryPackage) => item.deprecated,
|
||||
itemRenderer,
|
||||
filterRenderer,
|
||||
defaultSearchOptions: { query: '', type: 'All', topic: 'All' },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,7 +52,9 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
protected override init(): void {
|
||||
super.init();
|
||||
this.toDispose.pushAll([
|
||||
this.notificationCenter.onLibraryDidInstall(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onLibraryDidInstall(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
this.notificationCenter.onLibraryDidUninstall(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
|
@@ -145,7 +145,10 @@ export class MonitorManagerProxyClientImpl
|
||||
if (
|
||||
selectedBoard?.fqbn !==
|
||||
this.lastConnectedBoard?.selectedBoard?.fqbn ||
|
||||
selectedPort?.id !== this.lastConnectedBoard?.selectedPort?.id
|
||||
Port.keyOf(selectedPort) !==
|
||||
(this.lastConnectedBoard.selectedPort
|
||||
? Port.keyOf(this.lastConnectedBoard.selectedPort)
|
||||
: undefined)
|
||||
) {
|
||||
this.onMonitorShouldResetEmitter.fire(null);
|
||||
this.lastConnectedBoard = {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { OutputUri } from '@theia/output/lib/common/output-uri';
|
||||
/**
|
||||
* Exclusive "ino" document selector for monaco.
|
||||
*/
|
||||
@@ -11,3 +12,11 @@ function selectorOf(
|
||||
exclusive: true, // <-- this should make sure the custom formatter has higher precedence over the LS formatter.
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for the `monaco` resource in the Arduino _Output_ channel.
|
||||
*/
|
||||
export const ArduinoOutputSelector: monaco.languages.LanguageSelector = {
|
||||
scheme: OutputUri.SCHEME,
|
||||
pattern: '**/Arduino',
|
||||
};
|
@@ -5,6 +5,7 @@ import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { DisposableCollection, nls } from '@theia/core/lib/common';
|
||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||
import { MonitorModel } from '../../monitor-model';
|
||||
import { Unknown } from '../../../common/nls';
|
||||
|
||||
export namespace SerialMonitorSendInput {
|
||||
export interface Props {
|
||||
@@ -86,8 +87,8 @@ export class SerialMonitorSendInput extends React.Component<
|
||||
? Board.toString(board, {
|
||||
useFqbn: false,
|
||||
})
|
||||
: 'unknown',
|
||||
port ? port.address : 'unknown'
|
||||
: Unknown,
|
||||
port ? port.address : Unknown
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -2,9 +2,11 @@ div#select-board-dialog {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
div#select-board-dialog .selectBoardContainer .body {
|
||||
div#select-board-dialog .selectBoardContainer {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.select-board-dialog .head {
|
||||
@@ -19,12 +21,13 @@ div.dialogContent.select-board-dialog > div.head .title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
div#select-board-dialog .selectBoardContainer .body .list .item.selected {
|
||||
|
||||
div#select-board-dialog .selectBoardContainer .list .item.selected {
|
||||
background: var(--theia-secondaryButton-hoverBackground);
|
||||
}
|
||||
|
||||
div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
||||
color: var(--theia-list-activeSelectionIconForeground);
|
||||
div#select-board-dialog .selectBoardContainer .list .item.selected i {
|
||||
color: var(--theia-arduino-branding-primary);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .search,
|
||||
@@ -34,7 +37,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
||||
background: var(--theia-editor-background);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .search input {
|
||||
#select-board-dialog .selectBoardContainer .search input {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
@@ -46,58 +49,63 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
||||
color: var(--theia-input-foreground);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .search input:focus {
|
||||
#select-board-dialog .selectBoardContainer .search input:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .container {
|
||||
#select-board-dialog .selectBoardContainer .container {
|
||||
flex: 1;
|
||||
padding: 0px 10px 0px 0px;
|
||||
min-width: 240px;
|
||||
max-width: 240px;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .left.container .content {
|
||||
#select-board-dialog .selectBoardContainer .container .content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .left.container .content {
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .right.container .content {
|
||||
#select-board-dialog .selectBoardContainer .right.container .content {
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .container .content .title {
|
||||
#select-board-dialog .selectBoardContainer .container .content .title {
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
padding: 0px 0px 10px 0px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .container .content .footer {
|
||||
#select-board-dialog .selectBoardContainer .container .content .footer {
|
||||
padding: 10px 5px 10px 0px;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .container .content .loading {
|
||||
#select-board-dialog .selectBoardContainer .container .content .loading {
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
color: var(--theia-editorWidget-foreground);
|
||||
padding: 10px 5px 10px 10px;
|
||||
text-transform: uppercase;
|
||||
/* The max, min-height comes from `.body .list` 200px + 47px top padding - 2 * 10px top padding */
|
||||
/* The max, min-height comes from `.list` 200px + 47px top padding - 2 * 10px top padding */
|
||||
max-height: 227px;
|
||||
min-height: 227px;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list .item {
|
||||
#select-board-dialog .selectBoardContainer .list .item {
|
||||
padding: 10px 5px 10px 10px;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list .item .selected-icon {
|
||||
#select-board-dialog .selectBoardContainer .list .item .selected-icon {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list .item .details {
|
||||
#select-board-dialog .selectBoardContainer .list .item .details {
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
opacity: var(--theia-mod-disabled-opacity);
|
||||
width: 155px; /* used heuristics for the calculation */
|
||||
@@ -106,43 +114,36 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list .item.missing {
|
||||
#select-board-dialog .selectBoardContainer .list .item.missing {
|
||||
opacity: var(--theia-mod-disabled-opacity);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list .item:hover {
|
||||
#select-board-dialog .selectBoardContainer .list .item:hover {
|
||||
background: var(--theia-secondaryButton-hoverBackground);
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list .label {
|
||||
max-width: 215px;
|
||||
width: 215px;
|
||||
#select-board-dialog .selectBoardContainer .list .label {
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .list {
|
||||
#select-board-dialog .selectBoardContainer .list {
|
||||
max-height: 200px;
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .ports.list {
|
||||
#select-board-dialog .selectBoardContainer .ports.list {
|
||||
margin: 47px 0px 0px 0px; /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .body .search {
|
||||
#select-board-dialog .selectBoardContainer .search {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.p-Widget.dialogOverlay .dialogContent.select-board-dialog {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.arduino-boards-toolbar-item-container {
|
||||
align-items: center;
|
||||
background: var(--theia-arduino-toolbar-dropdown-background);
|
||||
@@ -264,10 +265,20 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i {
|
||||
|
||||
/* High Contrast Theme rules */
|
||||
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
|
||||
.hc-black.hc-theia.theia-hc #select-board-dialog .selectBoardContainer .body .list .item:hover {
|
||||
.hc-black.hc-theia.theia-hc #select-board-dialog .selectBoardContainer .list .item:hover {
|
||||
outline: 1px dashed var(--theia-focusBorder);
|
||||
}
|
||||
|
||||
.hc-black.hc-theia.theia-hc div#select-board-dialog .selectBoardContainer .body .list .item.selected {
|
||||
.hc-black.hc-theia.theia-hc div#select-board-dialog .selectBoardContainer .list .item.selected {
|
||||
outline: 1px solid var(--theia-focusBorder);
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 400px) {
|
||||
div.dialogContent.select-board-dialog > div.head {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#select-board-dialog .selectBoardContainer .container .content .title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.certificate-uploader-dialog {
|
||||
#certificate-uploader-dialog-container > .dialogBlock {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
|
@@ -9,11 +9,13 @@
|
||||
total = padding + margin = 96px
|
||||
*/
|
||||
max-width: calc(100% - 96px) !important;
|
||||
min-width: unset;
|
||||
max-height: 560px;
|
||||
padding: 0 28px;
|
||||
}
|
||||
|
||||
.p-Widget.dialogOverlay .dialogBlock .dialogTitle {
|
||||
padding: 36px 0 28px;
|
||||
padding: 20px 0;
|
||||
font-weight: 500;
|
||||
background-color: transparent;
|
||||
font-size: var(--theia-ui-font-size2);
|
||||
@@ -28,6 +30,7 @@
|
||||
|
||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent {
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.p-Widget.dialogOverlay .dialogBlock .dialogContent > input {
|
||||
@@ -75,3 +78,10 @@
|
||||
.fa.disabled {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-height: 560px) {
|
||||
.p-Widget.dialogOverlay .dialogBlock {
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
/* Show the dirty indicator on unclosable widgets. On hover, it should still show the dot instead of the X. */
|
||||
/* https://github.com/arduino/arduino-pro-ide/issues/380 */
|
||||
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty > .p-TabBar-tabCloseIcon:hover {
|
||||
background-size: 13px;
|
||||
background-image: var(--theia-icon-circle);
|
||||
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.a-mod-uncloseable.theia-mod-dirty > .p-TabBar-tabCloseIcon:before {
|
||||
content: "\ea71";
|
||||
}
|
||||
|
||||
.monaco-list-row.show-file-icons.focused {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.firmware-uploader-dialog {
|
||||
#firmware-uploader-dialog-container > .dialogBlock {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
}
|
||||
|
||||
.firmware-uploader-dialog .dialogRow > button{
|
||||
width: 33%;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.ide-updater-dialog {
|
||||
#ide-updater-dialog-container > .dialogBlock {
|
||||
width: 546px;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--downloading {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--logo-container {
|
||||
margin-right: 28px;
|
||||
}
|
||||
@@ -23,37 +27,49 @@
|
||||
.dialogContent.ide-updater-dialog
|
||||
.ide-updater-dialog--content
|
||||
.ide-updater-dialog--new-version-text.dialogSection {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin-top: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog-container {
|
||||
.ide-updater-dialog .changelog {
|
||||
color: var(--theia-editor-foreground);
|
||||
background-color: var(--theia-editor-background);
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
height: 180px;
|
||||
overflow: auto;
|
||||
padding: 0 12px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog-container a {
|
||||
.dialogContent.ide-updater-dialog
|
||||
.ide-updater-dialog--content
|
||||
.ide-updater-dialog--new-version-text
|
||||
.dialogRow.changelog-container {
|
||||
align-items: flex-start;
|
||||
border: 1px solid var(--theia-editorWidget-border);
|
||||
border-radius: 2px;
|
||||
overflow: auto;
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog a {
|
||||
color: var(--theia-textLink-foreground);
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog-container a:hover {
|
||||
.ide-updater-dialog .changelog a:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog-container code {
|
||||
.ide-updater-dialog .changelog code {
|
||||
background: var(--theia-textBlockQuote-background);
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.ide-updater-dialog .changelog-container a code {
|
||||
.ide-updater-dialog .changelog a code {
|
||||
color: var(--theia-textLink-foreground);
|
||||
}
|
||||
|
||||
@@ -77,3 +93,14 @@
|
||||
.ide-updater-dialog .buttons-container .push {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.ide-updater-dialog--content {
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#ide-updater-dialog-container .skip-version-button {
|
||||
margin-left: 79px;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
@@ -8,13 +8,35 @@
|
||||
}
|
||||
|
||||
.arduino-list-widget .search-bar {
|
||||
margin: 0px 10px 10px 15px;
|
||||
margin: 0px 10px 5px 15px;
|
||||
}
|
||||
|
||||
.arduino-list-widget .search-bar:focus {
|
||||
border-color: var(--theia-focusBorder);
|
||||
}
|
||||
|
||||
.arduino-list-widget .filter-bar {
|
||||
margin: 0px 10px 5px 15px;
|
||||
}
|
||||
|
||||
.arduino-list-widget .filter-bar > * {
|
||||
padding: 5px 5px 0px 0px;
|
||||
}
|
||||
|
||||
.arduino-list-widget .filter-bar .filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.arduino-list-widget .filter-bar .filter > select {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.arduino-list-widget .filter-bar .filter-label {
|
||||
display: flex;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.filterable-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -22,34 +44,21 @@
|
||||
height: 100%; /* This has top be 100% down to the `scrollContainer`. */
|
||||
}
|
||||
|
||||
.filterable-list-container .items-container {
|
||||
height: 100%; /* This has to be propagated down from the widget. */
|
||||
position: relative; /* To fix the `top` of the vertical toolbar. */
|
||||
}
|
||||
|
||||
.filterable-list-container .items-container > div:nth-child(odd) {
|
||||
.filterable-list-container .items-container > div > div:nth-child(odd) {
|
||||
background-color: var(--theia-sideBar-background);
|
||||
filter: contrast(105%);
|
||||
}
|
||||
|
||||
.filterable-list-container .items-container > div:nth-child(even) {
|
||||
.filterable-list-container .items-container > div > div:nth-child(even) {
|
||||
background-color: var(--theia-sideBar-background);
|
||||
filter: contrast(95%);
|
||||
}
|
||||
|
||||
.filterable-list-container .items-container > div:hover {
|
||||
.filterable-list-container .items-container > div > div:hover {
|
||||
background-color: var(--theia-sideBar-background);
|
||||
filter: contrast(90%);
|
||||
}
|
||||
|
||||
/* Perfect scrollbar does not like if we explicitly set the `background-color` of the contained elements.
|
||||
See above: `.filterable-list-container .items-container > div:nth-child(odd|event)`.
|
||||
We have to increase `z-index` of the scroll-bar thumb. Otherwise, the thumb is not visible.
|
||||
https://github.com/arduino/arduino-pro-ide/issues/82 */
|
||||
.arduino-list-widget .filterable-list-container .items-container .ps__rail-y {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.component-list-item {
|
||||
padding: 10px 10px 10px 15px;
|
||||
font-size: var(--theia-ui-font-size1);
|
||||
@@ -113,7 +122,7 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
|
||||
|
||||
.component-list-item[min-width~="170px"] .footer {
|
||||
padding: 5px 5px 0px 0px;
|
||||
min-height: 30px;
|
||||
min-height: 35px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
@@ -122,10 +131,6 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.component-list-item .footer > * {
|
||||
display: none
|
||||
}
|
||||
|
||||
.component-list-item:hover .footer > * {
|
||||
display: inline-block;
|
||||
margin: 5px 0px 0px 10px;
|
||||
@@ -155,4 +160,4 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
|
||||
|
||||
.hc-black.hc-theia.theia-hc .component-list-item .header .installed:before {
|
||||
border: 1px solid var(--theia-button-border);
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .with-margin {
|
||||
|
@@ -12,7 +12,7 @@
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
right: 14px;
|
||||
top: 50%;
|
||||
transform: translate(0px, -50%);
|
||||
height: calc(100% - 4px);
|
||||
|
@@ -1,73 +1,30 @@
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import {
|
||||
ConnectionStatusService,
|
||||
ConnectionStatus,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import {
|
||||
ApplicationShell as TheiaApplicationShell,
|
||||
DockPanel,
|
||||
DockPanelRenderer as TheiaDockPanelRenderer,
|
||||
Panel,
|
||||
SaveOptions,
|
||||
SHELL_TABBAR_CONTEXT_MENU,
|
||||
TabBar,
|
||||
Widget,
|
||||
SHELL_TABBAR_CONTEXT_MENU,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { Sketch } from '../../../common/protocol';
|
||||
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
||||
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';
|
||||
ConnectionStatus,
|
||||
ConnectionStatusService,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { ToolbarAwareTabBar } from './tab-bars';
|
||||
|
||||
@injectable()
|
||||
export class ApplicationShell extends TheiaApplicationShell {
|
||||
@inject(CommandService)
|
||||
private readonly commandService: CommandService;
|
||||
|
||||
@inject(MessageService)
|
||||
private readonly messageService: MessageService;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
private readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(ConnectionStatusService)
|
||||
private readonly connectionStatusService: ConnectionStatusService;
|
||||
|
||||
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
|
||||
}
|
||||
if (widget instanceof EditorWidget) {
|
||||
// Make the editor un-closeable asynchronously.
|
||||
this.sketchesServiceClient.currentSketch().then((sketch) => {
|
||||
if (CurrentSketch.isValid(sketch)) {
|
||||
if (!this.isSketchFile(widget.editor.uri, sketch.uri)) {
|
||||
return;
|
||||
}
|
||||
if (Sketch.isInSketch(widget.editor.uri, sketch)) {
|
||||
widget.title.closable = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private isSketchFile(uri: URI, sketchUriString: string): boolean {
|
||||
const sketchUri = new URI(sketchUriString);
|
||||
if (uri.parent.isEqual(sketchUri)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override async addWidget(
|
||||
widget: Widget,
|
||||
options: Readonly<TheiaApplicationShell.WidgetOptions> = {}
|
||||
@@ -106,7 +63,7 @@ export class ApplicationShell extends TheiaApplicationShell {
|
||||
return topPanel;
|
||||
}
|
||||
|
||||
override async saveAll(): Promise<void> {
|
||||
override async saveAll(options?: SaveOptions): Promise<void> {
|
||||
if (
|
||||
this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE
|
||||
) {
|
||||
@@ -118,12 +75,7 @@ export class ApplicationShell extends TheiaApplicationShell {
|
||||
);
|
||||
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
|
||||
}
|
||||
await super.saveAll();
|
||||
const options = { execOnlyIfTemp: true, openAfterMove: true };
|
||||
await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
options
|
||||
);
|
||||
return super.saveAll(options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
CommonCommands,
|
||||
} from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application';
|
||||
|
||||
@injectable()
|
||||
export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
|
||||
@@ -48,4 +49,9 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
}
|
||||
|
||||
override onWillStop(): OnWillStopAction | undefined {
|
||||
// This is NOOP here. All window close and app quit requests are handled in the `Close` contribution.
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,78 @@
|
||||
import type { MaybePromise } from '@theia/core';
|
||||
import type { Widget } from '@theia/core/lib/browser';
|
||||
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import deepEqual = require('deep-equal');
|
||||
import {
|
||||
CurrentSketch,
|
||||
SketchesServiceClientImpl,
|
||||
} from '../../../common/protocol/sketches-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class WidgetManager extends TheiaWidgetManager {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
private readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.sketchesServiceClient.onCurrentSketchDidChange((sketch) =>
|
||||
this.maybeSetWidgetUncloseable(
|
||||
sketch,
|
||||
...Array.from(this.widgets.values())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
override getOrCreateWidget<T extends Widget>(
|
||||
factoryId: string,
|
||||
options?: unknown
|
||||
): Promise<T> {
|
||||
const unresolvedWidget = super.getOrCreateWidget<T>(factoryId, options);
|
||||
unresolvedWidget.then(async (widget) => {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
this.maybeSetWidgetUncloseable(sketch, widget);
|
||||
});
|
||||
return unresolvedWidget;
|
||||
}
|
||||
|
||||
private maybeSetWidgetUncloseable(
|
||||
sketch: CurrentSketch,
|
||||
...widgets: Widget[]
|
||||
): void {
|
||||
const sketchFileUris =
|
||||
CurrentSketch.isValid(sketch) &&
|
||||
new Set([sketch.mainFileUri, ...sketch.rootFolderFileUris]);
|
||||
for (const widget of widgets) {
|
||||
if (widget instanceof OutputWidget) {
|
||||
this.setWidgetUncloseable(widget); // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
||||
} else if (widget instanceof EditorWidget) {
|
||||
// Make the editor un-closeable asynchronously.
|
||||
const uri = widget.editor.uri.toString();
|
||||
if (!!sketchFileUris && sketchFileUris.has(uri)) {
|
||||
this.setWidgetUncloseable(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setWidgetUncloseable(widget: Widget): void {
|
||||
const { title } = widget;
|
||||
if (title.closable) {
|
||||
title.closable = false;
|
||||
}
|
||||
// Show the dirty indicator on uncloseable widgets when hovering over the title. Instead of showing the `X` for close.
|
||||
const uncloseableClass = 'a-mod-uncloseable';
|
||||
if (!title.className.includes(uncloseableClass)) {
|
||||
title.className += title.className + ` ${uncloseableClass}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customized to find any existing widget based on `options` deepEquals instead of string equals.
|
||||
* See https://github.com/eclipse-theia/theia/issues/11309.
|
||||
|
@@ -0,0 +1,12 @@
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
import { CommonCommands } from '@theia/core/lib/browser';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { EditorMenuContribution as TheiaEditorMenuContribution } from '@theia/editor/lib/browser/editor-menu';
|
||||
|
||||
@injectable()
|
||||
export class EditorMenuContribution extends TheiaEditorMenuContribution {
|
||||
override registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
registry.unregisterMenuAction(CommonCommands.CLOSE_MAIN_TAB.id);
|
||||
}
|
||||
}
|
@@ -1,9 +1,10 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import {
|
||||
import type {
|
||||
Message,
|
||||
ProgressMessage,
|
||||
ProgressUpdate,
|
||||
} from '@theia/core/lib/common/message-service-protocol';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||
|
||||
@injectable()
|
||||
@@ -34,7 +35,9 @@ export class NotificationManager extends TheiaNotificationManager {
|
||||
this.fireUpdatedEvent();
|
||||
}
|
||||
|
||||
protected override toPlainProgress(update: ProgressUpdate): number | undefined {
|
||||
protected override toPlainProgress(
|
||||
update: ProgressUpdate
|
||||
): number | undefined {
|
||||
if (!update.work) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -43,4 +46,11 @@ export class NotificationManager extends TheiaNotificationManager {
|
||||
}
|
||||
return Math.min((update.work.done / update.work.total) * 100, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* For `public` visibility.
|
||||
*/
|
||||
override getMessageId(message: Message): string {
|
||||
return super.getMessageId(message);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
|
||||
import { OutputEditorFactory as TheiaOutputEditorFactory } from '@theia/output/lib/browser/output-editor-factory';
|
||||
|
||||
@injectable()
|
||||
export class OutputEditorFactory extends TheiaOutputEditorFactory {
|
||||
protected override createOptions(
|
||||
model: MonacoEditorModel,
|
||||
defaultOptions: MonacoEditor.IOptions
|
||||
): MonacoEditor.IOptions {
|
||||
const options = super.createOptions(model, defaultOptions);
|
||||
return {
|
||||
...options,
|
||||
// Taken from https://github.com/microsoft/vscode/blob/35b971c92d210face8c446a1c6f1e470ad2bcb54/src/vs/workbench/contrib/output/browser/outputView.ts#L211-L214
|
||||
// To fix https://github.com/arduino/arduino-ide/issues/1210
|
||||
unicodeHighlight: {
|
||||
nonBasicASCII: false,
|
||||
invisibleCharacters: false,
|
||||
ambiguousCharacters: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Message, Widget } from '@theia/core/lib/browser';
|
||||
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
|
||||
// Patched after https://github.com/eclipse-theia/theia/issues/8361
|
||||
// Remove this module after ATL-222 and the Theia update.
|
||||
@injectable()
|
||||
export class OutputWidget extends TheiaOutputWidget {
|
||||
protected override onAfterShow(msg: Message): void {
|
||||
super.onAfterShow(msg);
|
||||
this.onResize(Widget.ResizeMessage.UnknownSize);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget';
|
||||
|
||||
@injectable()
|
||||
export class PreferencesEditorWidget extends TheiaPreferencesEditorWidget {
|
||||
protected override resetScroll(
|
||||
nodeIDToScrollTo?: string,
|
||||
filterWasCleared = false
|
||||
): void {
|
||||
if (this.scrollBar) {
|
||||
// Absent on widget creation
|
||||
this.doResetScroll(nodeIDToScrollTo, filterWasCleared);
|
||||
} else {
|
||||
// NOOP
|
||||
// Unlike Theia, IDE2 does not start multiple tasks to check if the scrollbar is ready to reset it.
|
||||
// If the "scroll reset" request arrived before the existence of the scrollbar, what to reset?
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
|
||||
export function fullRange(model: monaco.editor.ITextModel): monaco.Range {
|
||||
const lastLine = model.getLineCount();
|
||||
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
|
||||
const end = new monaco.Position(lastLine, lastLineMaxColumn);
|
||||
return monaco.Range.fromPositions(new monaco.Position(1, 1), end);
|
||||
}
|
@@ -14,11 +14,38 @@ export class ComponentListItem<
|
||||
)[0];
|
||||
this.state = {
|
||||
selectedVersion: version,
|
||||
focus: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async install(item: T): Promise<void> {
|
||||
override componentDidUpdate(
|
||||
prevProps: ComponentListItem.Props<T>,
|
||||
prevState: ComponentListItem.State
|
||||
): void {
|
||||
if (this.state.focus !== prevState.focus) {
|
||||
this.props.onFocusDidChange();
|
||||
}
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
const { item, itemRenderer } = this.props;
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => this.setState({ focus: true })}
|
||||
onMouseLeave={() => this.setState({ focus: false })}
|
||||
>
|
||||
{itemRenderer.renderItem(
|
||||
Object.assign(this.state, { item }),
|
||||
this.install.bind(this),
|
||||
this.uninstall.bind(this),
|
||||
this.onVersionChange.bind(this)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private async install(item: T): Promise<void> {
|
||||
const toInstall = this.state.selectedVersion;
|
||||
const version = this.props.item.availableVersions.filter(
|
||||
(version) => version !== this.state.selectedVersion
|
||||
@@ -35,23 +62,13 @@ export class ComponentListItem<
|
||||
}
|
||||
}
|
||||
|
||||
protected async uninstall(item: T): Promise<void> {
|
||||
private async uninstall(item: T): Promise<void> {
|
||||
await this.props.uninstall(item);
|
||||
}
|
||||
|
||||
protected onVersionChange(version: Installable.Version) {
|
||||
private onVersionChange(version: Installable.Version): void {
|
||||
this.setState({ selectedVersion: version });
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
const { item, itemRenderer } = this.props;
|
||||
return itemRenderer.renderItem(
|
||||
Object.assign(this.state, { item }),
|
||||
this.install.bind(this),
|
||||
this.uninstall.bind(this),
|
||||
this.onVersionChange.bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ComponentListItem {
|
||||
@@ -60,9 +77,11 @@ export namespace ComponentListItem {
|
||||
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||
readonly uninstall: (item: T) => Promise<void>;
|
||||
readonly itemRenderer: ListItemRenderer<T>;
|
||||
readonly onFocusDidChange: () => void;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
selectedVersion?: Installable.Version;
|
||||
focus: boolean;
|
||||
}
|
||||
}
|
||||
|
@@ -1,43 +1,147 @@
|
||||
import 'react-virtualized/styles.css';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
|
||||
import {
|
||||
CellMeasurer,
|
||||
CellMeasurerCache,
|
||||
} from 'react-virtualized/dist/commonjs/CellMeasurer';
|
||||
import type {
|
||||
ListRowProps,
|
||||
ListRowRenderer,
|
||||
} from 'react-virtualized/dist/commonjs/List';
|
||||
import List from 'react-virtualized/dist/commonjs/List';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { Installable } from '../../../common/protocol/installable';
|
||||
import { ComponentListItem } from './component-list-item';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
|
||||
function sameAs<T>(
|
||||
left: T[],
|
||||
right: T[],
|
||||
...compareProps: (keyof T)[]
|
||||
): boolean {
|
||||
if (left === right) {
|
||||
return true;
|
||||
}
|
||||
const leftLength = left.length;
|
||||
if (leftLength !== right.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < leftLength; i++) {
|
||||
for (const prop of compareProps) {
|
||||
const leftValue = left[i][prop];
|
||||
const rightValue = right[i][prop];
|
||||
if (leftValue !== rightValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export class ComponentList<T extends ArduinoComponent> extends React.Component<
|
||||
ComponentList.Props<T>
|
||||
> {
|
||||
protected container?: HTMLElement;
|
||||
private readonly cache: CellMeasurerCache;
|
||||
private resizeAllFlag: boolean;
|
||||
private list: List | undefined;
|
||||
private mostRecentWidth: number | undefined;
|
||||
|
||||
constructor(props: ComponentList.Props<T>) {
|
||||
super(props);
|
||||
this.cache = new CellMeasurerCache({
|
||||
defaultHeight: 140,
|
||||
fixedWidth: true,
|
||||
});
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
return (
|
||||
<div className={'items-container'} ref={this.setRef}>
|
||||
{this.props.items.map((item) => this.createItem(item))}
|
||||
</div>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
if (this.mostRecentWidth && this.mostRecentWidth !== width) {
|
||||
this.resizeAllFlag = true;
|
||||
setTimeout(() => this.clearAll(), 0);
|
||||
}
|
||||
this.mostRecentWidth = width;
|
||||
return (
|
||||
<List
|
||||
className={'items-container'}
|
||||
rowRenderer={this.createItem}
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={this.props.items.length}
|
||||
rowHeight={this.cache.rowHeight}
|
||||
deferredMeasurementCache={this.cache}
|
||||
ref={this.setListRef}
|
||||
estimatedRowSize={140}
|
||||
// If default value, then `react-virtualized` will optimize and list item will not receive a `:hover` event.
|
||||
// Hence, install and version `<select>` won't be visible even if the mouse cursor is over the `<div>`.
|
||||
// See https://github.com/bvaughn/react-virtualized/blob/005be24a608add0344284053dae7633be86053b2/source/Grid/Grid.js#L38-L42
|
||||
scrollingResetTimeInterval={0}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
);
|
||||
}
|
||||
|
||||
override componentDidMount(): void {
|
||||
if (this.container && this.props.resolveContainer) {
|
||||
this.props.resolveContainer(this.container);
|
||||
override componentDidUpdate(prevProps: ComponentList.Props<T>): void {
|
||||
if (
|
||||
this.resizeAllFlag ||
|
||||
!sameAs(this.props.items, prevProps.items, 'name', 'installedVersion')
|
||||
) {
|
||||
this.clearAll(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected setRef = (element: HTMLElement | null) => {
|
||||
this.container = element || undefined;
|
||||
private readonly setListRef = (ref: List | null): void => {
|
||||
this.list = ref || undefined;
|
||||
};
|
||||
|
||||
protected createItem(item: T): React.ReactNode {
|
||||
return (
|
||||
<ComponentListItem<T>
|
||||
key={this.props.itemLabel(item)}
|
||||
item={item}
|
||||
itemRenderer={this.props.itemRenderer}
|
||||
install={this.props.install}
|
||||
uninstall={this.props.uninstall}
|
||||
/>
|
||||
);
|
||||
private clearAll(scrollToTop = false): void {
|
||||
this.resizeAllFlag = false;
|
||||
this.cache.clearAll();
|
||||
if (this.list) {
|
||||
this.list.recomputeRowHeights();
|
||||
if (scrollToTop) {
|
||||
this.list.scrollToPosition(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly createItem: ListRowRenderer = ({
|
||||
index,
|
||||
parent,
|
||||
key,
|
||||
style,
|
||||
}: ListRowProps): React.ReactNode => {
|
||||
const item = this.props.items[index];
|
||||
return (
|
||||
<CellMeasurer
|
||||
cache={this.cache}
|
||||
columnIndex={0}
|
||||
key={key}
|
||||
rowIndex={index}
|
||||
parent={parent}
|
||||
>
|
||||
{({ measure, registerChild }) => (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
<div ref={registerChild} style={style}>
|
||||
<ComponentListItem<T>
|
||||
key={this.props.itemLabel(item)}
|
||||
item={item}
|
||||
itemRenderer={this.props.itemRenderer}
|
||||
install={this.props.install}
|
||||
uninstall={this.props.uninstall}
|
||||
onFocusDidChange={() => measure()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export namespace ComponentList {
|
||||
@@ -48,6 +152,5 @@ export namespace ComponentList {
|
||||
readonly itemRenderer: ListItemRenderer<T>;
|
||||
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||
readonly uninstall: (item: T) => Promise<void>;
|
||||
readonly resolveContainer: (element: HTMLElement) => void;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,121 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import {
|
||||
BoardSearch,
|
||||
LibrarySearch,
|
||||
Searchable,
|
||||
} from '../../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export abstract class FilterRenderer<S extends Searchable.Options> {
|
||||
render(
|
||||
options: S,
|
||||
handlePropChange: (prop: keyof S, value: S[keyof S]) => void
|
||||
): React.ReactNode {
|
||||
const props = this.props();
|
||||
return (
|
||||
<div className="filter-bar">
|
||||
{Object.entries(options)
|
||||
.filter(([prop]) => props.includes(prop as keyof S))
|
||||
.map(([prop, value]) => (
|
||||
<div key={prop} className="filter">
|
||||
<div className="filter-label">
|
||||
{`${this.propertyLabel(prop as keyof S)}:`}
|
||||
</div>
|
||||
<select
|
||||
className="theia-select"
|
||||
value={value}
|
||||
onChange={(event) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
handlePropChange(prop as keyof S, event.target.value as any)
|
||||
}
|
||||
>
|
||||
{this.options(prop as keyof S).map((key) => (
|
||||
<option key={key} value={key}>
|
||||
{this.valueLabel(prop as keyof S, key)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
protected abstract props(): (keyof S)[];
|
||||
protected abstract options(prop: keyof S): string[];
|
||||
protected abstract valueLabel(prop: keyof S, key: string): string;
|
||||
protected abstract propertyLabel(prop: keyof S): string;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BoardsFilterRenderer extends FilterRenderer<BoardSearch> {
|
||||
protected props(): (keyof BoardSearch)[] {
|
||||
return ['type'];
|
||||
}
|
||||
protected options(prop: keyof BoardSearch): string[] {
|
||||
switch (prop) {
|
||||
case 'type':
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return BoardSearch.TypeLiterals as any;
|
||||
default:
|
||||
throw new Error(`Unexpected prop: ${prop}`);
|
||||
}
|
||||
}
|
||||
protected valueLabel(prop: keyof BoardSearch, key: string): string {
|
||||
switch (prop) {
|
||||
case 'type':
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (BoardSearch.TypeLabels as any)[key];
|
||||
default:
|
||||
throw new Error(`Unexpected key: ${prop}`);
|
||||
}
|
||||
}
|
||||
protected propertyLabel(prop: keyof BoardSearch): string {
|
||||
switch (prop) {
|
||||
case 'type':
|
||||
return BoardSearch.PropertyLabels[prop];
|
||||
default:
|
||||
throw new Error(`Unexpected key: ${prop}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class LibraryFilterRenderer extends FilterRenderer<LibrarySearch> {
|
||||
protected props(): (keyof LibrarySearch)[] {
|
||||
return ['type', 'topic'];
|
||||
}
|
||||
protected options(prop: keyof LibrarySearch): string[] {
|
||||
switch (prop) {
|
||||
case 'type':
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return LibrarySearch.TypeLiterals as any;
|
||||
case 'topic':
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return LibrarySearch.TopicLiterals as any;
|
||||
default:
|
||||
throw new Error(`Unexpected prop: ${prop}`);
|
||||
}
|
||||
}
|
||||
protected propertyLabel(prop: keyof LibrarySearch): string {
|
||||
switch (prop) {
|
||||
case 'type':
|
||||
case 'topic':
|
||||
return LibrarySearch.PropertyLabels[prop];
|
||||
default:
|
||||
throw new Error(`Unexpected key: ${prop}`);
|
||||
}
|
||||
}
|
||||
protected valueLabel(prop: keyof LibrarySearch, key: string): string {
|
||||
switch (prop) {
|
||||
case 'type':
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (LibrarySearch.TypeLabels as any)[key] as any;
|
||||
case 'topic':
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (LibrarySearch.TopicLabels as any)[key] as any;
|
||||
default:
|
||||
throw new Error(`Unexpected prop: ${prop}`);
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,25 +14,30 @@ import { ComponentList } from './component-list';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
import { ResponseServiceClient } from '../../../common/protocol';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { FilterRenderer } from './filter-renderer';
|
||||
|
||||
export class FilterableListContainer<
|
||||
T extends ArduinoComponent
|
||||
T extends ArduinoComponent,
|
||||
S extends Searchable.Options
|
||||
> extends React.Component<
|
||||
FilterableListContainer.Props<T>,
|
||||
FilterableListContainer.State<T>
|
||||
FilterableListContainer.Props<T, S>,
|
||||
FilterableListContainer.State<T, S>
|
||||
> {
|
||||
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
|
||||
constructor(props: Readonly<FilterableListContainer.Props<T, S>>) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filterText: '',
|
||||
searchOptions: props.defaultSearchOptions,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
override componentDidMount(): void {
|
||||
this.search = debounce(this.search, 500);
|
||||
this.handleFilterTextChange('');
|
||||
this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
|
||||
this.search(this.state.searchOptions);
|
||||
this.props.searchOptionsDidChange((newSearchOptions) => {
|
||||
const { searchOptions } = this.state;
|
||||
this.setSearchOptionsAndUpdate({ ...searchOptions, ...newSearchOptions });
|
||||
});
|
||||
}
|
||||
|
||||
override componentDidUpdate(): void {
|
||||
@@ -44,30 +49,40 @@ export class FilterableListContainer<
|
||||
override render(): React.ReactNode {
|
||||
return (
|
||||
<div className={'filterable-list-container'}>
|
||||
{this.renderSearchFilter()}
|
||||
{this.renderSearchBar()}
|
||||
{this.renderComponentList()}
|
||||
{this.renderSearchFilter()}
|
||||
<div className="filterable-list-container">
|
||||
{this.renderComponentList()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderSearchFilter(): React.ReactNode {
|
||||
return undefined;
|
||||
return (
|
||||
<>
|
||||
{this.props.filterRenderer.render(
|
||||
this.state.searchOptions,
|
||||
this.handlePropChange.bind(this)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderSearchBar(): React.ReactNode {
|
||||
return (
|
||||
<SearchBar
|
||||
resolveFocus={this.props.resolveFocus}
|
||||
filterText={this.state.filterText}
|
||||
onFilterTextChanged={this.handleFilterTextChange}
|
||||
filterText={this.state.searchOptions.query ?? ''}
|
||||
onFilterTextChanged={(query) =>
|
||||
this.handlePropChange('query', query as S['query'])
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderComponentList(): React.ReactNode {
|
||||
const { itemLabel, itemDeprecated, resolveContainer, itemRenderer } =
|
||||
this.props;
|
||||
const { itemLabel, itemDeprecated, itemRenderer } = this.props;
|
||||
return (
|
||||
<ComponentList<T>
|
||||
items={this.state.items}
|
||||
@@ -76,22 +91,26 @@ export class FilterableListContainer<
|
||||
itemRenderer={itemRenderer}
|
||||
install={this.install.bind(this)}
|
||||
uninstall={this.uninstall.bind(this)}
|
||||
resolveContainer={resolveContainer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected handleFilterTextChange = (
|
||||
filterText: string = this.state.filterText
|
||||
) => {
|
||||
this.setState({ filterText });
|
||||
this.search(filterText);
|
||||
protected handlePropChange = (prop: keyof S, value: S[keyof S]): void => {
|
||||
const searchOptions = {
|
||||
...this.state.searchOptions,
|
||||
[prop]: value,
|
||||
};
|
||||
this.setSearchOptionsAndUpdate(searchOptions);
|
||||
};
|
||||
|
||||
protected search(query: string): void {
|
||||
private setSearchOptionsAndUpdate(searchOptions: S) {
|
||||
this.setState({ searchOptions }, () => this.search(searchOptions));
|
||||
}
|
||||
|
||||
protected search(searchOptions: S): void {
|
||||
const { searchable } = this.props;
|
||||
searchable
|
||||
.search({ query: query.trim() })
|
||||
.search(searchOptions)
|
||||
.then((items) => this.setState({ items: this.sort(items) }));
|
||||
}
|
||||
|
||||
@@ -119,7 +138,7 @@ export class FilterableListContainer<
|
||||
` ${item.name}:${version}`,
|
||||
run: ({ progressId }) => install({ item, progressId, version }),
|
||||
});
|
||||
const items = await searchable.search({ query: this.state.filterText });
|
||||
const items = await searchable.search(this.state.searchOptions);
|
||||
this.setState({ items: this.sort(items) });
|
||||
}
|
||||
|
||||
@@ -147,21 +166,25 @@ export class FilterableListContainer<
|
||||
}`,
|
||||
run: ({ progressId }) => uninstall({ item, progressId }),
|
||||
});
|
||||
const items = await searchable.search({ query: this.state.filterText });
|
||||
const items = await searchable.search(this.state.searchOptions);
|
||||
this.setState({ items: this.sort(items) });
|
||||
}
|
||||
}
|
||||
|
||||
export namespace FilterableListContainer {
|
||||
export interface Props<T extends ArduinoComponent> {
|
||||
readonly container: ListWidget<T>;
|
||||
readonly searchable: Searchable<T>;
|
||||
export interface Props<
|
||||
T extends ArduinoComponent,
|
||||
S extends Searchable.Options
|
||||
> {
|
||||
readonly defaultSearchOptions: S;
|
||||
readonly container: ListWidget<T, S>;
|
||||
readonly searchable: Searchable<T, S>;
|
||||
readonly itemLabel: (item: T) => string;
|
||||
readonly itemDeprecated: (item: T) => boolean;
|
||||
readonly itemRenderer: ListItemRenderer<T>;
|
||||
readonly resolveContainer: (element: HTMLElement) => void;
|
||||
readonly filterRenderer: FilterRenderer<S>;
|
||||
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
||||
readonly filterTextChangeEvent: Event<string | undefined>;
|
||||
readonly searchOptionsDidChange: Event<Partial<S> | undefined>;
|
||||
readonly messageService: MessageService;
|
||||
readonly responseService: ResponseServiceClient;
|
||||
readonly install: ({
|
||||
@@ -183,8 +206,8 @@ export namespace FilterableListContainer {
|
||||
readonly commandService: CommandService;
|
||||
}
|
||||
|
||||
export interface State<T> {
|
||||
filterText: string;
|
||||
export interface State<T, S extends Searchable.Options> {
|
||||
searchOptions: S;
|
||||
items: T[];
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import { Installable } from '../../../common/protocol/installable';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { ComponentListItem } from './component-list-item';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { Unknown } from '../../../common/nls';
|
||||
|
||||
@injectable()
|
||||
export class ListItemRenderer<T extends ArduinoComponent> {
|
||||
@@ -13,7 +14,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
||||
|
||||
protected onMoreInfoClick = (
|
||||
event: React.SyntheticEvent<HTMLAnchorElement, Event>
|
||||
) => {
|
||||
): void => {
|
||||
const { target } = event.nativeEvent;
|
||||
if (target instanceof HTMLAnchorElement) {
|
||||
this.windowService.openNewWindow(target.href, { external: true });
|
||||
@@ -27,7 +28,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
||||
uninstall: (item: T) => Promise<void>,
|
||||
onVersionChange: (version: Installable.Version) => void
|
||||
): React.ReactNode {
|
||||
const { item } = input;
|
||||
const { item, focus } = input;
|
||||
let nameAndAuthor: JSX.Element;
|
||||
if (item.name && item.author) {
|
||||
const name = <span className="name">{item.name}</span>;
|
||||
@@ -42,11 +43,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
||||
} else if ((item as any).id) {
|
||||
nameAndAuthor = <span className="name">{(item as any).id}</span>;
|
||||
} else {
|
||||
nameAndAuthor = (
|
||||
<span className="name">
|
||||
{nls.localize('arduino/common/unknown', 'Unknown')}
|
||||
</span>
|
||||
);
|
||||
nameAndAuthor = <span className="name">{Unknown}</span>;
|
||||
}
|
||||
const onClickUninstall = () => uninstall(item);
|
||||
const installedVersion = !!item.installedVersion && (
|
||||
@@ -123,10 +120,12 @@ export class ListItemRenderer<T extends ArduinoComponent> {
|
||||
{description}
|
||||
</div>
|
||||
<div className="info">{moreInfo}</div>
|
||||
<div className="footer">
|
||||
{versions}
|
||||
{installButton}
|
||||
</div>
|
||||
{focus && (
|
||||
<div className="footer">
|
||||
{versions}
|
||||
{installButton}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -3,13 +3,20 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
|
||||
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||
import { ListWidget } from './list-widget';
|
||||
import { Searchable } from '../../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent>
|
||||
extends AbstractViewContribution<ListWidget<T>>
|
||||
export abstract class ListWidgetFrontendContribution<
|
||||
T extends ArduinoComponent,
|
||||
S extends Searchable.Options
|
||||
>
|
||||
extends AbstractViewContribution<ListWidget<T, S>>
|
||||
implements FrontendApplicationContribution
|
||||
{
|
||||
async initializeLayout(): Promise<void> {}
|
||||
async initializeLayout(): Promise<void> {
|
||||
// TS requires at least one method from `FrontendApplicationContribution`.
|
||||
// Expected to be empty.
|
||||
}
|
||||
|
||||
override registerMenus(): void {
|
||||
// NOOP
|
||||
|
@@ -6,9 +6,7 @@ import {
|
||||
} from '@theia/core/shared/inversify';
|
||||
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
@@ -21,10 +19,12 @@ import {
|
||||
import { FilterableListContainer } from './filterable-list-container';
|
||||
import { ListItemRenderer } from './list-item-renderer';
|
||||
import { NotificationCenter } from '../../notification-center';
|
||||
import { FilterRenderer } from './filter-renderer';
|
||||
|
||||
@injectable()
|
||||
export abstract class ListWidget<
|
||||
T extends ArduinoComponent
|
||||
T extends ArduinoComponent,
|
||||
S extends Searchable.Options
|
||||
> extends ReactWidget {
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
@@ -42,9 +42,8 @@ export abstract class ListWidget<
|
||||
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
||||
*/
|
||||
protected focusNode: HTMLElement | undefined;
|
||||
protected readonly deferredContainer = new Deferred<HTMLElement>();
|
||||
protected readonly filterTextChangeEmitter = new Emitter<
|
||||
string | undefined
|
||||
protected readonly searchOptionsChangeEmitter = new Emitter<
|
||||
Partial<S> | undefined
|
||||
>();
|
||||
/**
|
||||
* Instead of running an `update` from the `postConstruct` `init` method,
|
||||
@@ -52,7 +51,7 @@ export abstract class ListWidget<
|
||||
*/
|
||||
protected firstActivate = true;
|
||||
|
||||
constructor(protected options: ListWidget.Options<T>) {
|
||||
constructor(protected options: ListWidget.Options<T, S>) {
|
||||
super();
|
||||
const { id, label, iconClass } = options;
|
||||
this.id = id;
|
||||
@@ -62,10 +61,8 @@ export abstract class ListWidget<
|
||||
this.title.closable = true;
|
||||
this.addClass('arduino-list-widget');
|
||||
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
||||
this.scrollOptions = {
|
||||
suppressScrollX: true,
|
||||
};
|
||||
this.toDispose.push(this.filterTextChangeEmitter);
|
||||
this.scrollOptions = undefined;
|
||||
this.toDispose.push(this.searchOptionsChangeEmitter);
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
@@ -77,10 +74,6 @@ export abstract class ListWidget<
|
||||
]);
|
||||
}
|
||||
|
||||
protected override getScrollContainer(): MaybePromise<HTMLElement> {
|
||||
return this.deferredContainer.promise;
|
||||
}
|
||||
|
||||
protected override onAfterShow(message: Message): void {
|
||||
this.maybeUpdateOnFirstRender();
|
||||
super.onAfterShow(message);
|
||||
@@ -109,7 +102,7 @@ export abstract class ListWidget<
|
||||
this.updateScrollBar();
|
||||
}
|
||||
|
||||
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
||||
protected onFocusResolved = (element: HTMLElement | undefined): void => {
|
||||
this.focusNode = element;
|
||||
};
|
||||
|
||||
@@ -137,9 +130,9 @@ export abstract class ListWidget<
|
||||
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<FilterableListContainer<T>
|
||||
<FilterableListContainer<T, S>
|
||||
defaultSearchOptions={this.options.defaultSearchOptions}
|
||||
container={this}
|
||||
resolveContainer={this.deferredContainer.resolve}
|
||||
resolveFocus={this.onFocusResolved}
|
||||
searchable={this.options.searchable}
|
||||
install={this.install.bind(this)}
|
||||
@@ -147,7 +140,8 @@ export abstract class ListWidget<
|
||||
itemLabel={this.options.itemLabel}
|
||||
itemDeprecated={this.options.itemDeprecated}
|
||||
itemRenderer={this.options.itemRenderer}
|
||||
filterTextChangeEvent={this.filterTextChangeEmitter.event}
|
||||
filterRenderer={this.options.filterRenderer}
|
||||
searchOptionsDidChange={this.searchOptionsChangeEmitter.event}
|
||||
messageService={this.messageService}
|
||||
commandService={this.commandService}
|
||||
responseService={this.responseService}
|
||||
@@ -159,10 +153,8 @@ export abstract class ListWidget<
|
||||
* If `filterText` is defined, sets the filter text to the argument.
|
||||
* If it is `undefined`, updates the view state by re-running the search with the current `filterText` term.
|
||||
*/
|
||||
refresh(filterText: string | undefined): void {
|
||||
this.deferredContainer.promise.then(() =>
|
||||
this.filterTextChangeEmitter.fire(filterText)
|
||||
);
|
||||
refresh(searchOptions: Partial<S> | undefined): void {
|
||||
this.searchOptionsChangeEmitter.fire(searchOptions);
|
||||
}
|
||||
|
||||
updateScrollBar(): void {
|
||||
@@ -173,14 +165,19 @@ export abstract class ListWidget<
|
||||
}
|
||||
|
||||
export namespace ListWidget {
|
||||
export interface Options<T extends ArduinoComponent> {
|
||||
export interface Options<
|
||||
T extends ArduinoComponent,
|
||||
S extends Searchable.Options
|
||||
> {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly iconClass: string;
|
||||
readonly installable: Installable<T>;
|
||||
readonly searchable: Searchable<T>;
|
||||
readonly searchable: Searchable<T, S>;
|
||||
readonly itemLabel: (item: T) => string;
|
||||
readonly itemDeprecated: (item: T) => boolean;
|
||||
readonly itemRenderer: ListItemRenderer<T>;
|
||||
readonly filterRenderer: FilterRenderer<S>;
|
||||
readonly defaultSearchOptions: S;
|
||||
}
|
||||
}
|
||||
|
21
arduino-ide-extension/src/common/nls.ts
Normal file
21
arduino-ide-extension/src/common/nls.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
|
||||
export const Unknown = nls.localize('arduino/common/unknown', 'Unknown');
|
||||
export const Later = nls.localize('arduino/common/later', 'Later');
|
||||
export const Updatable = nls.localize('arduino/common/updateable', 'Updatable');
|
||||
export const All = nls.localize('arduino/common/all', 'All');
|
||||
export const Type = nls.localize('arduino/common/type', 'Type');
|
||||
export const Partner = nls.localize('arduino/common/partner', 'Partner');
|
||||
export const Contributed = nls.localize(
|
||||
'arduino/common/contributed',
|
||||
'Contributed'
|
||||
);
|
||||
export const Recommended = nls.localize(
|
||||
'arduino/common/recommended',
|
||||
'Recommended'
|
||||
);
|
||||
export const Retired = nls.localize('arduino/common/retired', 'Retired');
|
||||
export const InstallManually = nls.localize(
|
||||
'arduino/common/installManually',
|
||||
'Install Manually'
|
||||
);
|
@@ -7,11 +7,13 @@ export interface ArduinoComponent {
|
||||
readonly summary: string;
|
||||
readonly description: string;
|
||||
readonly moreInfoLink?: string;
|
||||
|
||||
readonly availableVersions: Installable.Version[];
|
||||
readonly installable: boolean;
|
||||
|
||||
readonly installedVersion?: Installable.Version;
|
||||
/**
|
||||
* This is the `Type` in IDE (1.x) UI.
|
||||
*/
|
||||
readonly types: string[];
|
||||
}
|
||||
export namespace ArduinoComponent {
|
||||
export function is(arg: any): arg is ArduinoComponent {
|
||||
|
@@ -2,10 +2,12 @@ import { naturalCompare } from './../utils';
|
||||
import { Searchable } from './searchable';
|
||||
import { Installable } from './installable';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { All, Contributed, Partner, Type, Updatable } from '../nls';
|
||||
|
||||
export type AvailablePorts = Record<string, [Port, Array<Board>]>;
|
||||
export namespace AvailablePorts {
|
||||
export function byProtocol(
|
||||
export function groupByProtocol(
|
||||
availablePorts: AvailablePorts
|
||||
): Map<string, AvailablePorts> {
|
||||
const grouped = new Map<string, AvailablePorts>();
|
||||
@@ -20,11 +22,27 @@ export namespace AvailablePorts {
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
export function split(
|
||||
state: AvailablePorts
|
||||
): Readonly<{ boards: Board[]; ports: Port[] }> {
|
||||
const availablePorts: Port[] = [];
|
||||
const attachedBoards: Board[] = [];
|
||||
for (const key of Object.keys(state)) {
|
||||
const [port, boards] = state[key];
|
||||
availablePorts.push(port);
|
||||
attachedBoards.push(...boards);
|
||||
}
|
||||
return {
|
||||
boards: attachedBoards,
|
||||
ports: availablePorts,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface AttachedBoardsChangeEvent {
|
||||
readonly oldState: Readonly<{ boards: Board[]; ports: Port[] }>;
|
||||
readonly newState: Readonly<{ boards: Board[]; ports: Port[] }>;
|
||||
readonly uploadInProgress: boolean;
|
||||
}
|
||||
export namespace AttachedBoardsChangeEvent {
|
||||
export function isEmpty(event: AttachedBoardsChangeEvent): boolean {
|
||||
@@ -115,17 +133,7 @@ export const BoardsServicePath = '/services/boards-service';
|
||||
export const BoardsService = Symbol('BoardsService');
|
||||
export interface BoardsService
|
||||
extends Installable<BoardsPackage>,
|
||||
Searchable<BoardsPackage> {
|
||||
/**
|
||||
* Deprecated. `getState` should be used to correctly map a board with a port.
|
||||
* @deprecated
|
||||
*/
|
||||
getAttachedBoards(): Promise<Board[]>;
|
||||
/**
|
||||
* Deprecated. `getState` should be used to correctly map a board with a port.
|
||||
* @deprecated
|
||||
*/
|
||||
getAvailablePorts(): Promise<Port[]>;
|
||||
Searchable<BoardsPackage, BoardSearch> {
|
||||
getState(): Promise<AvailablePorts>;
|
||||
getBoardDetails(options: { fqbn: string }): Promise<BoardDetails | undefined>;
|
||||
getBoardPackage(options: { id: string }): Promise<BoardsPackage | undefined>;
|
||||
@@ -139,29 +147,90 @@ export interface BoardsService
|
||||
}): Promise<BoardUserField[]>;
|
||||
}
|
||||
|
||||
export interface BoardSearch extends Searchable.Options {
|
||||
readonly type?: BoardSearch.Type;
|
||||
}
|
||||
export namespace BoardSearch {
|
||||
export const TypeLiterals = [
|
||||
'All',
|
||||
'Updatable',
|
||||
'Arduino',
|
||||
'Contributed',
|
||||
'Arduino Certified',
|
||||
'Partner',
|
||||
'Arduino@Heart',
|
||||
] as const;
|
||||
export type Type = typeof TypeLiterals[number];
|
||||
export const TypeLabels: Record<Type, string> = {
|
||||
All: All,
|
||||
Updatable: Updatable,
|
||||
Arduino: 'Arduino',
|
||||
Contributed: Contributed,
|
||||
'Arduino Certified': nls.localize(
|
||||
'arduino/boardsType/arduinoCertified',
|
||||
'Arduino Certified'
|
||||
),
|
||||
Partner: Partner,
|
||||
'Arduino@Heart': 'Arduino@Heart',
|
||||
};
|
||||
export const PropertyLabels: Record<
|
||||
keyof Omit<BoardSearch, 'query'>,
|
||||
string
|
||||
> = {
|
||||
type: Type,
|
||||
};
|
||||
}
|
||||
|
||||
export interface Port {
|
||||
// id is the combination of address and protocol
|
||||
// formatted like "<address>|<protocol>" used
|
||||
// to univocally recognize a port
|
||||
readonly id: string;
|
||||
readonly address: string;
|
||||
readonly addressLabel: string;
|
||||
readonly protocol: string;
|
||||
readonly protocolLabel: string;
|
||||
readonly properties?: Record<string, string>;
|
||||
}
|
||||
export namespace Port {
|
||||
export function is(arg: any): arg is Port {
|
||||
return (
|
||||
!!arg &&
|
||||
'address' in arg &&
|
||||
typeof arg['address'] === 'string' &&
|
||||
'protocol' in arg &&
|
||||
typeof arg['protocol'] === 'string'
|
||||
);
|
||||
export type Properties = Record<string, string>;
|
||||
export namespace Properties {
|
||||
export function create(
|
||||
properties: [string, string][] | undefined
|
||||
): Properties {
|
||||
if (!properties) {
|
||||
return {};
|
||||
}
|
||||
return properties.reduce((acc, curr) => {
|
||||
const [key, value] = curr;
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
}
|
||||
}
|
||||
export function is(arg: unknown): arg is Port {
|
||||
if (typeof arg === 'object') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const object = arg as any;
|
||||
return (
|
||||
'address' in object &&
|
||||
typeof object['address'] === 'string' &&
|
||||
'addressLabel' in object &&
|
||||
typeof object['addressLabel'] === 'string' &&
|
||||
'protocol' in object &&
|
||||
typeof object['protocol'] === 'string' &&
|
||||
'protocolLabel' in object &&
|
||||
typeof object['protocolLabel'] === 'string'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function toString(port: Port): string {
|
||||
return `${port.addressLabel} ${port.protocolLabel}`;
|
||||
/**
|
||||
* Key is the combination of address and protocol formatted like `'${address}|${protocol}'` used to uniquely identify a port.
|
||||
*/
|
||||
export function keyOf({ address, protocol }: Port): string {
|
||||
return `${address}|${protocol}`;
|
||||
}
|
||||
|
||||
export function toString({ addressLabel, protocolLabel }: Port): string {
|
||||
return `${addressLabel} ${protocolLabel}`;
|
||||
}
|
||||
|
||||
export function compare(left: Port, right: Port): number {
|
||||
@@ -190,6 +259,32 @@ export namespace Port {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// See https://github.com/arduino/arduino-ide/commit/79ea0fa9a6ad2b01eaac22cef2f494d3b68284e6#diff-fb37f20bea00881acee3aafddb1ecefcecf41ce59845ca1510da79e918ee0837L338-L348
|
||||
// See https://github.com/arduino/arduino-ide/commit/79ea0fa9a6ad2b01eaac22cef2f494d3b68284e6#diff-e42c82bb67e277cfa4598239952afd65db44dba55dc7d68df619dfccfa648279L441-L455
|
||||
// See https://github.com/arduino/arduino-ide/commit/74bfdc4c56d7a1577a4e800a378c21b82c1da5f8#diff-e42c82bb67e277cfa4598239952afd65db44dba55dc7d68df619dfccfa648279L405-R424
|
||||
/**
|
||||
* All ports with `'serial'` or `'network'` `protocol`, or any other port `protocol` that has at least one recognized board connected to.
|
||||
*/
|
||||
export function visiblePorts(
|
||||
boardsHaystack: ReadonlyArray<Board>
|
||||
): (port: Port) => boolean {
|
||||
return (port: Port) => {
|
||||
if (port.protocol === 'serial' || port.protocol === 'network') {
|
||||
// Allow all `serial` and `network` boards.
|
||||
// IDE2 must support better label for unrecognized `network` boards: https://github.com/arduino/arduino-ide/issues/1331
|
||||
return true;
|
||||
}
|
||||
// All other ports with different protocol are
|
||||
// only shown if there is a recognized board
|
||||
// connected
|
||||
for (const board of boardsHaystack) {
|
||||
if (board.port?.address === port.address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface BoardsPackage extends ArduinoComponent {
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
||||
import type { Location } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import type {
|
||||
Location,
|
||||
Range,
|
||||
Position,
|
||||
} from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import type {
|
||||
BoardUserField,
|
||||
Port,
|
||||
@@ -15,11 +19,41 @@ export const CompilerWarningLiterals = [
|
||||
] as const;
|
||||
export type CompilerWarnings = typeof CompilerWarningLiterals[number];
|
||||
export namespace CoreError {
|
||||
export interface ErrorLocation {
|
||||
export interface ErrorLocationRef {
|
||||
readonly message: string;
|
||||
readonly location: Location;
|
||||
readonly details?: string;
|
||||
}
|
||||
export namespace ErrorLocationRef {
|
||||
export function equals(
|
||||
left: ErrorLocationRef,
|
||||
right: ErrorLocationRef
|
||||
): boolean {
|
||||
return (
|
||||
left.message === right.message &&
|
||||
left.details === right.details &&
|
||||
equalsLocation(left.location, right.location)
|
||||
);
|
||||
}
|
||||
function equalsLocation(left: Location, right: Location): boolean {
|
||||
return left.uri === right.uri && equalsRange(left.range, right.range);
|
||||
}
|
||||
function equalsRange(left: Range, right: Range): boolean {
|
||||
return (
|
||||
equalsPosition(left.start, right.start) &&
|
||||
equalsPosition(left.end, right.end)
|
||||
);
|
||||
}
|
||||
function equalsPosition(left: Position, right: Position): boolean {
|
||||
return left.character === right.character && left.line === right.line;
|
||||
}
|
||||
}
|
||||
export interface ErrorLocation extends ErrorLocationRef {
|
||||
/**
|
||||
* The range of the error location source from the CLI output.
|
||||
*/
|
||||
readonly rangesInOutput: Range[]; // The same error might show up multiple times in the CLI output: https://github.com/arduino/arduino-cli/issues/1761
|
||||
}
|
||||
export const Codes = {
|
||||
Verify: 4001,
|
||||
Upload: 4002,
|
||||
|
@@ -56,16 +56,16 @@ export interface IDEUpdater extends JsonRpcServer<IDEUpdaterClient> {
|
||||
|
||||
export const IDEUpdaterClient = Symbol('IDEUpdaterClient');
|
||||
export interface IDEUpdaterClient {
|
||||
onError: Event<Error>;
|
||||
onCheckingForUpdate: Event<void>;
|
||||
onUpdateAvailable: Event<UpdateInfo>;
|
||||
onUpdateNotAvailable: Event<UpdateInfo>;
|
||||
onDownloadProgressChanged: Event<ProgressInfo>;
|
||||
onDownloadFinished: Event<UpdateInfo>;
|
||||
notifyError(message: Error): void;
|
||||
notifyCheckingForUpdate(message: void): void;
|
||||
notifyUpdateAvailable(message: UpdateInfo): void;
|
||||
notifyUpdateNotAvailable(message: UpdateInfo): void;
|
||||
onUpdaterDidFail: Event<Error>;
|
||||
onUpdaterDidCheckForUpdate: Event<void>;
|
||||
onUpdaterDidFindUpdateAvailable: Event<UpdateInfo>;
|
||||
onUpdaterDidNotFindUpdateAvailable: Event<UpdateInfo>;
|
||||
onDownloadProgressDidChange: Event<ProgressInfo>;
|
||||
onDownloadDidFinish: Event<UpdateInfo>;
|
||||
notifyUpdaterFailed(message: Error): void;
|
||||
notifyCheckedForUpdate(message: void): void;
|
||||
notifyUpdateAvailableFound(message: UpdateInfo): void;
|
||||
notifyUpdateAvailableNotFound(message: UpdateInfo): void;
|
||||
notifyDownloadProgressChanged(message: ProgressInfo): void;
|
||||
notifyDownloadFinished(message: UpdateInfo): void;
|
||||
}
|
||||
|
@@ -27,15 +27,56 @@ export namespace Installable {
|
||||
export namespace Version {
|
||||
/**
|
||||
* Most recent version comes first, then the previous versions. (`1.8.1`, `1.6.3`, `1.6.2`, `1.6.1` and so on.)
|
||||
*
|
||||
* If `coerce` is `true` tries to convert any invalid semver strings to a valid semver based on [these](https://github.com/npm/node-semver#coercion) rules.
|
||||
*/
|
||||
export const COMPARATOR = (left: Version, right: Version): number => {
|
||||
if (semver.valid(left) && semver.valid(right)) {
|
||||
return semver.compare(left, right);
|
||||
export const COMPARATOR = (
|
||||
left: Version,
|
||||
right: Version,
|
||||
coerce = false
|
||||
): number => {
|
||||
const validLeft = semver.parse(left);
|
||||
const validRight = semver.parse(right);
|
||||
if (validLeft && validRight) {
|
||||
return semver.compare(validLeft, validRight);
|
||||
}
|
||||
if (coerce) {
|
||||
const coercedLeft = validLeft ?? semver.coerce(left);
|
||||
const coercedRight = validRight ?? semver.coerce(right);
|
||||
if (coercedLeft && coercedRight) {
|
||||
return semver.compare(coercedLeft, coercedRight);
|
||||
}
|
||||
}
|
||||
return naturalCompare(left, right);
|
||||
};
|
||||
}
|
||||
|
||||
export const Installed = <T extends ArduinoComponent>({
|
||||
installedVersion,
|
||||
}: T): boolean => {
|
||||
return !!installedVersion;
|
||||
};
|
||||
|
||||
export const Updateable = <T extends ArduinoComponent>(item: T): boolean => {
|
||||
const { installedVersion } = item;
|
||||
if (!installedVersion) {
|
||||
return false;
|
||||
}
|
||||
const latestVersion = item.availableVersions[0];
|
||||
if (!latestVersion) {
|
||||
console.warn(
|
||||
`Installed version ${installedVersion} is available for ${item.name}, but no available versions were available. Skipping.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
const result = Installable.Version.COMPARATOR(
|
||||
latestVersion,
|
||||
installedVersion,
|
||||
true
|
||||
);
|
||||
return result > 0;
|
||||
};
|
||||
|
||||
export async function installWithProgress<
|
||||
T extends ArduinoComponent
|
||||
>(options: {
|
||||
@@ -44,6 +85,7 @@ export namespace Installable {
|
||||
responseService: ResponseServiceClient;
|
||||
item: T;
|
||||
version: Installable.Version;
|
||||
keepOutput?: boolean;
|
||||
}): Promise<void> {
|
||||
const { item, version } = options;
|
||||
return ExecuteWithProgress.doWithProgress({
|
||||
@@ -65,6 +107,7 @@ export namespace Installable {
|
||||
messageService: MessageService;
|
||||
responseService: ResponseServiceClient;
|
||||
item: T;
|
||||
keepOutput?: boolean;
|
||||
}): Promise<void> {
|
||||
const { item } = options;
|
||||
return ExecuteWithProgress.doWithProgress({
|
||||
|
@@ -1,13 +1,24 @@
|
||||
import { Searchable } from './searchable';
|
||||
import { Installable } from './installable';
|
||||
import { ArduinoComponent } from './arduino-component';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import {
|
||||
All,
|
||||
Contributed,
|
||||
Partner,
|
||||
Recommended,
|
||||
Retired,
|
||||
Type,
|
||||
Updatable,
|
||||
} from '../nls';
|
||||
|
||||
export const LibraryServicePath = '/services/library-service';
|
||||
export const LibraryService = Symbol('LibraryService');
|
||||
export interface LibraryService
|
||||
extends Installable<LibraryPackage>,
|
||||
Searchable<LibraryPackage> {
|
||||
Searchable<LibraryPackage, LibrarySearch> {
|
||||
list(options: LibraryService.List.Options): Promise<LibraryPackage[]>;
|
||||
search(options: LibrarySearch): Promise<LibraryPackage[]>;
|
||||
/**
|
||||
* When `installDependencies` is not set, it is `true` by default. If you want to skip the installation of required dependencies, set it to `false`.
|
||||
*/
|
||||
@@ -17,6 +28,7 @@ export interface LibraryService
|
||||
version?: Installable.Version;
|
||||
installDependencies?: boolean;
|
||||
noOverwrite?: boolean;
|
||||
installLocation?: LibraryLocation.BUILTIN | LibraryLocation.USER;
|
||||
}): Promise<void>;
|
||||
installZip(options: {
|
||||
zipUri: string;
|
||||
@@ -38,6 +50,86 @@ export interface LibraryService
|
||||
}): Promise<LibraryDependency[]>;
|
||||
}
|
||||
|
||||
export interface LibrarySearch extends Searchable.Options {
|
||||
readonly type?: LibrarySearch.Type;
|
||||
readonly topic?: LibrarySearch.Topic;
|
||||
}
|
||||
export namespace LibrarySearch {
|
||||
export const TypeLiterals = [
|
||||
'All',
|
||||
'Updatable',
|
||||
'Installed',
|
||||
'Arduino',
|
||||
'Partner',
|
||||
'Recommended',
|
||||
'Contributed',
|
||||
'Retired',
|
||||
] as const;
|
||||
export type Type = typeof TypeLiterals[number];
|
||||
export const TypeLabels: Record<Type, string> = {
|
||||
All: All,
|
||||
Updatable: Updatable,
|
||||
Installed: nls.localize('arduino/libraryType/installed', 'Installed'),
|
||||
Arduino: 'Arduino',
|
||||
Partner: Partner,
|
||||
Recommended: Recommended,
|
||||
Contributed: Contributed,
|
||||
Retired: Retired,
|
||||
};
|
||||
export const TopicLiterals = [
|
||||
'All',
|
||||
'Communication',
|
||||
'Data Processing',
|
||||
'Data Storage',
|
||||
'Device Control',
|
||||
'Display',
|
||||
'Other',
|
||||
'Sensors',
|
||||
'Signal Input/Output',
|
||||
'Timing',
|
||||
'Uncategorized',
|
||||
] as const;
|
||||
export type Topic = typeof TopicLiterals[number];
|
||||
export const TopicLabels: Record<Topic, string> = {
|
||||
All: All,
|
||||
Communication: nls.localize(
|
||||
'arduino/libraryTopic/communication',
|
||||
'Communication'
|
||||
),
|
||||
'Data Processing': nls.localize(
|
||||
'arduino/libraryTopic/dataProcessing',
|
||||
'Data Processing'
|
||||
),
|
||||
'Data Storage': nls.localize(
|
||||
'arduino/libraryTopic/dataStorage',
|
||||
'Data Storage'
|
||||
),
|
||||
'Device Control': nls.localize(
|
||||
'arduino/libraryTopic/deviceControl',
|
||||
'Device Control'
|
||||
),
|
||||
Display: nls.localize('arduino/libraryTopic/display', 'Display'),
|
||||
Other: nls.localize('arduino/libraryTopic/other', 'Other'),
|
||||
Sensors: nls.localize('arduino/libraryTopic/sensors', 'Sensors'),
|
||||
'Signal Input/Output': nls.localize(
|
||||
'arduino/libraryTopic/signalInputOutput',
|
||||
'Signal Input/Output'
|
||||
),
|
||||
Timing: nls.localize('arduino/libraryTopic/timing', 'Timing'),
|
||||
Uncategorized: nls.localize(
|
||||
'arduino/libraryTopic/uncategorized',
|
||||
'Uncategorized'
|
||||
),
|
||||
};
|
||||
export const PropertyLabels: Record<
|
||||
keyof Omit<LibrarySearch, 'query'>,
|
||||
string
|
||||
> = {
|
||||
topic: nls.localize('arduino/librarySearchProperty/topic', 'Topic'),
|
||||
type: Type,
|
||||
};
|
||||
}
|
||||
|
||||
export namespace LibraryService {
|
||||
export namespace List {
|
||||
export interface Options {
|
||||
@@ -50,7 +142,7 @@ export enum LibraryLocation {
|
||||
/**
|
||||
* In the `libraries` subdirectory of the Arduino IDE installation.
|
||||
*/
|
||||
IDE_BUILTIN = 0,
|
||||
BUILTIN = 0,
|
||||
|
||||
/**
|
||||
* In the `libraries` subdirectory of the user directory (sketchbook).
|
||||
@@ -85,6 +177,10 @@ export interface LibraryPackage extends ArduinoComponent {
|
||||
readonly exampleUris: string[];
|
||||
readonly location: LibraryLocation;
|
||||
readonly installDirUri?: string;
|
||||
/**
|
||||
* This is the `Topic` in the IDE (1.x) UI.
|
||||
*/
|
||||
readonly category: string;
|
||||
}
|
||||
export namespace LibraryPackage {
|
||||
export function is(arg: any): arg is LibraryPackage {
|
||||
|
@@ -39,7 +39,7 @@ export namespace ExecuteWithProgress {
|
||||
);
|
||||
}
|
||||
|
||||
async function withProgress<T>(
|
||||
export async function withProgress<T>(
|
||||
text: string,
|
||||
messageService: MessageService,
|
||||
cb: (progress: Progress, token: CancellationToken) => Promise<T>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export interface Searchable<T> {
|
||||
search(options: Searchable.Options): Promise<T[]>;
|
||||
export interface Searchable<T, O extends Searchable.Options> {
|
||||
search(options: O): Promise<T[]>;
|
||||
}
|
||||
export namespace Searchable {
|
||||
export interface Options {
|
||||
|
@@ -10,7 +10,7 @@ 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, SketchRef } from './sketches-service';
|
||||
import { SketchContainer, SketchesError, SketchRef } from './sketches-service';
|
||||
import {
|
||||
ARDUINO_CLOUD_FOLDER,
|
||||
REMOTE_SKETCHBOOK_FOLDER,
|
||||
@@ -79,6 +79,7 @@ export class SketchesServiceClientImpl
|
||||
this.sketches.set(sketch.uri, sketch);
|
||||
}
|
||||
this.toDispose.push(
|
||||
// Watch changes in the sketchbook to update `File` > `Sketchbook` menu items.
|
||||
this.fileService.watch(new URI(sketchDirUri), {
|
||||
recursive: true,
|
||||
excludes: [],
|
||||
@@ -87,6 +88,45 @@ export class SketchesServiceClientImpl
|
||||
this.toDispose.push(
|
||||
this.fileService.onDidFilesChange(async (event) => {
|
||||
for (const { type, resource } of event.changes) {
|
||||
// The file change events have higher precedence in the current sketch over the sketchbook.
|
||||
if (
|
||||
CurrentSketch.isValid(this._currentSketch) &&
|
||||
new URI(this._currentSketch.uri).isEqualOrParent(resource)
|
||||
) {
|
||||
// https://github.com/arduino/arduino-ide/pull/1351#pullrequestreview-1086666656
|
||||
// On a sketch file rename, the FS watcher will contain two changes:
|
||||
// - Deletion of the original file,
|
||||
// - Update of the new file,
|
||||
// Hence, `UPDATE` events must be processed but only and if only there is a `DELETED` change in the same event.
|
||||
// Otherwise, IDE2 would ask CLI to reload the sketch content on every save event in IDE2.
|
||||
if (
|
||||
type === FileChangeType.UPDATED &&
|
||||
event.changes.length === 1
|
||||
) {
|
||||
// If the event contains only one `UPDATE` change, it cannot be a rename.
|
||||
return;
|
||||
}
|
||||
|
||||
let reloadedSketch: Sketch | undefined = undefined;
|
||||
try {
|
||||
reloadedSketch = await this.sketchService.loadSketch(
|
||||
this._currentSketch.uri
|
||||
);
|
||||
} catch (err) {
|
||||
if (!SketchesError.NotFound.is(err)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reloadedSketch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Sketch.sameAs(this._currentSketch, reloadedSketch)) {
|
||||
this.useCurrentSketch(reloadedSketch, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We track main sketch files changes only. // TODO: check sketch folder changes. One can rename the folder without renaming the `.ino` file.
|
||||
if (sketchbookUri.isEqualOrParent(resource)) {
|
||||
if (Sketch.isSketchFile(resource)) {
|
||||
@@ -97,7 +137,7 @@ export class SketchesServiceClientImpl
|
||||
);
|
||||
if (!this.sketches.has(toAdd.uri)) {
|
||||
console.log(
|
||||
`New sketch '${toAdd.name}' was crated in sketchbook '${sketchDirUri}'.`
|
||||
`New sketch '${toAdd.name}' was created in sketchbook '${sketchDirUri}'.`
|
||||
);
|
||||
this.sketches.set(toAdd.uri, toAdd);
|
||||
this.fireSoon(toAdd, 'created');
|
||||
@@ -125,12 +165,31 @@ export class SketchesServiceClientImpl
|
||||
.reachedState('started_contributions')
|
||||
.then(async () => {
|
||||
const currentSketch = await this.loadCurrentSketch();
|
||||
this._currentSketch = currentSketch;
|
||||
this.currentSketchDidChangeEmitter.fire(this._currentSketch);
|
||||
this.currentSketchLoaded.resolve(this._currentSketch);
|
||||
if (CurrentSketch.isValid(currentSketch)) {
|
||||
this.toDispose.pushAll([
|
||||
// Watch the file changes of the current sketch
|
||||
this.fileService.watch(new URI(currentSketch.uri), {
|
||||
recursive: true,
|
||||
excludes: [],
|
||||
}),
|
||||
]);
|
||||
}
|
||||
this.useCurrentSketch(currentSketch);
|
||||
});
|
||||
}
|
||||
|
||||
private useCurrentSketch(
|
||||
currentSketch: CurrentSketch,
|
||||
reassignPromise = false
|
||||
) {
|
||||
this._currentSketch = currentSketch;
|
||||
if (reassignPromise) {
|
||||
this.currentSketchLoaded = new Deferred();
|
||||
}
|
||||
this.currentSketchLoaded.resolve(this._currentSketch);
|
||||
this.currentSketchDidChangeEmitter.fire(this._currentSketch);
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
@@ -95,6 +95,11 @@ export interface SketchesService {
|
||||
* Based on https://github.com/arduino/arduino-cli/blob/550179eefd2d2bca299d50a4af9e9bfcfebec649/arduino/builder/builder.go#L30-L38
|
||||
*/
|
||||
getIdeTempFolderUri(sketch: Sketch): Promise<string>;
|
||||
|
||||
/**
|
||||
* Notifies the backend to recursively delete the sketch folder with all its content.
|
||||
*/
|
||||
notifyDeleteSketch(sketch: Sketch): void;
|
||||
}
|
||||
|
||||
export interface SketchRef {
|
||||
@@ -157,6 +162,74 @@ export namespace Sketch {
|
||||
const { mainFileUri, otherSketchFileUris, additionalFileUris } = sketch;
|
||||
return [mainFileUri, ...otherSketchFileUris, ...additionalFileUris];
|
||||
}
|
||||
const primitiveProps: Array<keyof Sketch> = ['name', 'uri', 'mainFileUri'];
|
||||
const arrayProps: Array<keyof Sketch> = [
|
||||
'additionalFileUris',
|
||||
'otherSketchFileUris',
|
||||
'rootFolderFileUris',
|
||||
];
|
||||
export function sameAs(left: Sketch, right: Sketch): boolean {
|
||||
for (const prop of primitiveProps) {
|
||||
const leftValue = left[prop];
|
||||
const rightValue = right[prop];
|
||||
assertIsNotArray(leftValue, prop, left);
|
||||
assertIsNotArray(rightValue, prop, right);
|
||||
if (leftValue !== rightValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const prop of arrayProps) {
|
||||
const leftValue = left[prop];
|
||||
const rightValue = right[prop];
|
||||
assertIsArray(leftValue, prop, left);
|
||||
assertIsArray(rightValue, prop, right);
|
||||
if (leftValue.length !== rightValue.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const prop of arrayProps) {
|
||||
const leftValue = left[prop];
|
||||
const rightValue = right[prop];
|
||||
assertIsArray(leftValue, prop, left);
|
||||
assertIsArray(rightValue, prop, right);
|
||||
if (
|
||||
toSortedString(leftValue as string[]) !==
|
||||
toSortedString(rightValue as string[])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function toSortedString(array: string[]): string {
|
||||
return array.slice().sort().join(',');
|
||||
}
|
||||
function assertIsNotArray(
|
||||
toTest: unknown,
|
||||
prop: keyof Sketch,
|
||||
object: Sketch
|
||||
): void {
|
||||
if (Array.isArray(toTest)) {
|
||||
throw new Error(
|
||||
`Expected a non-array type. Got: ${toTest}. Property was: ${prop}. Object was: ${JSON.stringify(
|
||||
object
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
function assertIsArray(
|
||||
toTest: unknown,
|
||||
prop: keyof Sketch,
|
||||
object: Sketch
|
||||
): void {
|
||||
if (!Array.isArray(toTest)) {
|
||||
throw new Error(
|
||||
`Expected an array type. Got: ${toTest}. Property was: ${prop}. Object was: ${JSON.stringify(
|
||||
object
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SketchContainer {
|
||||
|
@@ -4,31 +4,6 @@ import { ElectronMenuContribution as TheiaElectronMenuContribution } from '@thei
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
|
||||
import { ElectronMenuContribution } from './electron-menu-contribution';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
|
||||
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
||||
import * as dialogs from '@theia/core/lib/browser/dialogs';
|
||||
|
||||
Object.assign(dialogs, {
|
||||
confirmExit: async () => {
|
||||
const messageBoxResult = await remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
message: nls.localize(
|
||||
'theia/core/quitMessage',
|
||||
'Any unsaved changes will not be saved.'
|
||||
),
|
||||
title: nls.localize(
|
||||
'theia/core/quitTitle',
|
||||
'Are you sure you want to quit?'
|
||||
),
|
||||
type: 'question',
|
||||
buttons: [dialogs.Dialog.CANCEL, dialogs.Dialog.YES],
|
||||
}
|
||||
);
|
||||
return messageBoxResult.response === 1;
|
||||
},
|
||||
});
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ElectronMenuContribution).toSelf().inSingletonScope();
|
||||
|
@@ -16,6 +16,7 @@ import {
|
||||
ElectronMainWindowServiceExt,
|
||||
electronMainWindowServiceExtPath,
|
||||
} from '../electron-common/electron-main-window-service-ext';
|
||||
import { IsTempSketch } from '../node/is-temp-sketch';
|
||||
import { ElectronMainWindowServiceExtImpl } from './electron-main-window-service-ext-impl';
|
||||
import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl';
|
||||
import { ElectronMainApplication } from './theia/electron-main-application';
|
||||
@@ -62,4 +63,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
bind(IsTempSketch).toSelf().inSingletonScope();
|
||||
});
|
||||
|
@@ -19,13 +19,13 @@ export class IDEUpdaterImpl implements IDEUpdater {
|
||||
|
||||
constructor() {
|
||||
this.updater.on('checking-for-update', (e) => {
|
||||
this.clients.forEach((c) => c.notifyCheckingForUpdate(e));
|
||||
this.clients.forEach((c) => c.notifyCheckedForUpdate(e));
|
||||
});
|
||||
this.updater.on('update-available', (e) => {
|
||||
this.clients.forEach((c) => c.notifyUpdateAvailable(e));
|
||||
this.clients.forEach((c) => c.notifyUpdateAvailableFound(e));
|
||||
});
|
||||
this.updater.on('update-not-available', (e) => {
|
||||
this.clients.forEach((c) => c.notifyUpdateNotAvailable(e));
|
||||
this.clients.forEach((c) => c.notifyUpdateAvailableNotFound(e));
|
||||
});
|
||||
this.updater.on('download-progress', (e) => {
|
||||
this.clients.forEach((c) => c.notifyDownloadProgressChanged(e));
|
||||
@@ -34,7 +34,7 @@ export class IDEUpdaterImpl implements IDEUpdater {
|
||||
this.clients.forEach((c) => c.notifyDownloadFinished(e));
|
||||
});
|
||||
this.updater.on('error', (e) => {
|
||||
this.clients.forEach((c) => c.notifyError(e));
|
||||
this.clients.forEach((c) => c.notifyUpdaterFailed(e));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,10 +58,8 @@ export class IDEUpdaterImpl implements IDEUpdater {
|
||||
this.isAlreadyChecked = true;
|
||||
}
|
||||
|
||||
const {
|
||||
updateInfo,
|
||||
cancellationToken,
|
||||
} = await this.updater.checkForUpdates();
|
||||
const { updateInfo, cancellationToken } =
|
||||
await this.updater.checkForUpdates();
|
||||
|
||||
this.cancellationToken = cancellationToken;
|
||||
if (this.updater.currentVersion.compare(updateInfo.version) === -1) {
|
||||
@@ -104,7 +102,7 @@ export class IDEUpdaterImpl implements IDEUpdater {
|
||||
await this.updater.downloadUpdate(this.cancellationToken);
|
||||
} catch (e) {
|
||||
if (e.message === 'cancelled') return;
|
||||
this.clients.forEach((c) => c.notifyError(e));
|
||||
this.clients.forEach((c) => c.notifyUpdaterFailed(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
@@ -22,6 +22,7 @@ import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import * as os from '@theia/core/lib/common/os';
|
||||
import { Restart } from '@theia/core/lib/electron-common/messaging/electron-messages';
|
||||
import { TheiaBrowserWindowOptions } from '@theia/core/lib/electron-main/theia-electron-window';
|
||||
import { IsTempSketch } from '../../node/is-temp-sketch';
|
||||
|
||||
app.commandLine.appendSwitch('disable-http-cache');
|
||||
|
||||
@@ -54,6 +55,8 @@ const APP_STARTED_WITH_CONTENT_TRACE =
|
||||
|
||||
@injectable()
|
||||
export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
@inject(IsTempSketch)
|
||||
private readonly isTempSketch: IsTempSketch;
|
||||
private startup = false;
|
||||
private _firstWindowId: number | undefined;
|
||||
private openFilePromise = new Deferred();
|
||||
@@ -176,6 +179,12 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
);
|
||||
for (const workspace of workspaces) {
|
||||
if (await this.isValidSketchPath(workspace.file)) {
|
||||
if (this.isTempSketch.is(workspace.file)) {
|
||||
console.info(
|
||||
`Skipped opening sketch. The sketch was detected as temporary. Workspace path: ${workspace.file}.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
useDefault = false;
|
||||
await this.openSketch(workspace);
|
||||
}
|
||||
@@ -405,6 +414,15 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
|
||||
const workspaceUri = URI.file(workspace);
|
||||
const bounds = window.getNormalBounds();
|
||||
const now = Date.now();
|
||||
// Do not try to reopen the sketch if it was temp.
|
||||
// Unfortunately, IDE2 has two different logic of restoring recent sketches: the Theia default `recentworkspace.json` and there is the `recent-sketches.json`.
|
||||
const file = workspaceUri.fsPath;
|
||||
if (this.isTempSketch.is(file)) {
|
||||
console.info(
|
||||
`Ignored marking workspace as a closed sketch. The sketch was detected as temporary. Workspace URI: ${workspaceUri.toString()}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`Marking workspace as a closed sketch. Workspace URI: ${workspaceUri.toString()}. Date: ${now}.`
|
||||
);
|
||||
|
@@ -109,6 +109,7 @@ import {
|
||||
SurveyNotificationService,
|
||||
SurveyNotificationServicePath,
|
||||
} from '../common/protocol/survey-service';
|
||||
import { IsTempSketch } from './is-temp-sketch';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(BackendApplication).toSelf().inSingletonScope();
|
||||
@@ -419,4 +420,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
bind(IsTempSketch).toSelf().inSingletonScope();
|
||||
});
|
||||
|
@@ -3,6 +3,7 @@ import { sha256 } from 'hash.js';
|
||||
import { randomBytes } from 'crypto';
|
||||
import btoa = require('btoa'); // TODO: check why we cannot
|
||||
import { AuthenticationSession } from './types';
|
||||
import { Unknown } from '../../common/nls';
|
||||
|
||||
export interface IToken {
|
||||
accessToken: string; // When unable to refresh due to network problems, the access token becomes undefined
|
||||
@@ -62,10 +63,10 @@ export function token2IToken(token: Token): IToken {
|
||||
sessionId: parsedIdToken.sub,
|
||||
scope: token.scope,
|
||||
account: {
|
||||
id: parsedIdToken.sub || 'unknown',
|
||||
email: parsedIdToken.email || 'unknown',
|
||||
nickname: parsedIdToken.nickname || 'unknown',
|
||||
picture: parsedIdToken.picture || 'unknown',
|
||||
id: parsedIdToken.sub || Unknown,
|
||||
email: parsedIdToken.email || Unknown,
|
||||
nickname: parsedIdToken.nickname || Unknown,
|
||||
picture: parsedIdToken.picture || Unknown,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -1,27 +1,30 @@
|
||||
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
||||
import { ClientDuplexStream } from '@grpc/grpc-js';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { deepClone } from '@theia/core/lib/common/objects';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node';
|
||||
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
||||
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { v4 } from 'uuid';
|
||||
import { Unknown } from '../common/nls';
|
||||
import {
|
||||
AttachedBoardsChangeEvent,
|
||||
AvailablePorts,
|
||||
Board,
|
||||
NotificationServiceServer,
|
||||
Port,
|
||||
} from '../common/protocol';
|
||||
import {
|
||||
BoardListWatchRequest,
|
||||
BoardListWatchResponse,
|
||||
DetectedPort as RpcDetectedPort,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/board_pb';
|
||||
import {
|
||||
Board,
|
||||
Port,
|
||||
NotificationServiceServer,
|
||||
AvailablePorts,
|
||||
AttachedBoardsChangeEvent,
|
||||
} from '../common/protocol';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
|
||||
import { v4 } from 'uuid';
|
||||
import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
|
||||
import { CoreClientAware } from './core-client-provider';
|
||||
import { ServiceError } from './service-error';
|
||||
import { BackendApplicationContribution } from '@theia/core/lib/node';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
|
||||
type Duplex = ClientDuplexStream<BoardListWatchRequest, BoardListWatchResponse>;
|
||||
interface StreamWrapper extends Disposable {
|
||||
@@ -54,6 +57,8 @@ export class BoardDiscovery
|
||||
private readonly onStreamDidCancelEmitter = new Emitter<void>(); // when the watcher is canceled by the IDE2
|
||||
private readonly toDisposeOnStopWatch = new DisposableCollection();
|
||||
|
||||
private uploadInProgress = false;
|
||||
|
||||
/**
|
||||
* Keys are the `address` of the ports.
|
||||
*
|
||||
@@ -123,6 +128,10 @@ export class BoardDiscovery
|
||||
});
|
||||
}
|
||||
|
||||
setUploadInProgress(uploadInProgress: boolean): void {
|
||||
this.uploadInProgress = uploadInProgress;
|
||||
}
|
||||
|
||||
private createTimeout(
|
||||
after: number,
|
||||
onTimeout: (error: Error) => void
|
||||
@@ -228,102 +237,7 @@ export class BoardDiscovery
|
||||
this.logger.info('start new deferred');
|
||||
const { client, instance } = await this.coreClient;
|
||||
const wrapper = await this.createWrapper(client);
|
||||
wrapper.stream.on('data', async (resp: BoardListWatchResponse) => {
|
||||
this.logger.info('onData', this.toJson(resp));
|
||||
if (resp.getEventType() === 'quit') {
|
||||
this.logger.info('quit received');
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const detectedPort = resp.getPort();
|
||||
if (detectedPort) {
|
||||
let eventType: 'add' | 'remove' | 'unknown' = 'unknown';
|
||||
if (resp.getEventType() === 'add') {
|
||||
eventType = 'add';
|
||||
} else if (resp.getEventType() === 'remove') {
|
||||
eventType = 'remove';
|
||||
} else {
|
||||
eventType = 'unknown';
|
||||
}
|
||||
|
||||
if (eventType === 'unknown') {
|
||||
throw new Error(`Unexpected event type: '${resp.getEventType()}'`);
|
||||
}
|
||||
|
||||
const oldState = deepClone(this._availablePorts);
|
||||
const newState = deepClone(this._availablePorts);
|
||||
|
||||
const address = (detectedPort as any).getPort().getAddress();
|
||||
const protocol = (detectedPort as any).getPort().getProtocol();
|
||||
// Different discoveries can detect the same port with different
|
||||
// protocols, so we consider the combination of address and protocol
|
||||
// to be the id of a certain port to distinguish it from others.
|
||||
// If we'd use only the address of a port to store it in a map
|
||||
// we can have conflicts the same port is found with multiple
|
||||
// protocols.
|
||||
const portID = `${address}|${protocol}`;
|
||||
const label = (detectedPort as any).getPort().getLabel();
|
||||
const protocolLabel = (detectedPort as any)
|
||||
.getPort()
|
||||
.getProtocolLabel();
|
||||
const port = {
|
||||
id: portID,
|
||||
address,
|
||||
addressLabel: label,
|
||||
protocol,
|
||||
protocolLabel,
|
||||
};
|
||||
const boards: Board[] = [];
|
||||
for (const item of detectedPort.getMatchingBoardsList()) {
|
||||
boards.push({
|
||||
fqbn: item.getFqbn(),
|
||||
name: item.getName() || 'unknown',
|
||||
port,
|
||||
});
|
||||
}
|
||||
|
||||
if (eventType === 'add') {
|
||||
if (newState[portID]) {
|
||||
const [, knownBoards] = newState[portID];
|
||||
this.logger.warn(
|
||||
`Port '${Port.toString(
|
||||
port
|
||||
)}' was already available. Known boards before override: ${JSON.stringify(
|
||||
knownBoards
|
||||
)}`
|
||||
);
|
||||
}
|
||||
newState[portID] = [port, boards];
|
||||
} else if (eventType === 'remove') {
|
||||
if (!newState[portID]) {
|
||||
this.logger.warn(
|
||||
`Port '${Port.toString(port)}' was not available. Skipping`
|
||||
);
|
||||
return;
|
||||
}
|
||||
delete newState[portID];
|
||||
}
|
||||
|
||||
const oldAvailablePorts = this.getAvailablePorts(oldState);
|
||||
const oldAttachedBoards = this.getAttachedBoards(oldState);
|
||||
const newAvailablePorts = this.getAvailablePorts(newState);
|
||||
const newAttachedBoards = this.getAttachedBoards(newState);
|
||||
const event: AttachedBoardsChangeEvent = {
|
||||
oldState: {
|
||||
ports: oldAvailablePorts,
|
||||
boards: oldAttachedBoards,
|
||||
},
|
||||
newState: {
|
||||
ports: newAvailablePorts,
|
||||
boards: newAttachedBoards,
|
||||
},
|
||||
};
|
||||
|
||||
this._availablePorts = newState;
|
||||
this.notificationService.notifyAttachedBoardsDidChange(event);
|
||||
}
|
||||
});
|
||||
wrapper.stream.on('data', (resp) => this.onBoardListWatchResponse(resp));
|
||||
this.logger.info('start request start watch');
|
||||
await this.requestStartWatch(
|
||||
new BoardListWatchRequest().setInstance(instance),
|
||||
@@ -334,21 +248,124 @@ export class BoardDiscovery
|
||||
this.logger.info('start resolved watching');
|
||||
}
|
||||
|
||||
getAttachedBoards(state: AvailablePorts = this.availablePorts): Board[] {
|
||||
const attachedBoards: Board[] = [];
|
||||
for (const portID of Object.keys(state)) {
|
||||
const [, boards] = state[portID];
|
||||
attachedBoards.push(...boards);
|
||||
// XXX: make this `protected` and override for tests if IDE2 wants to mock events from the CLI.
|
||||
private onBoardListWatchResponse(resp: BoardListWatchResponse): void {
|
||||
this.logger.info(this.toJson(resp));
|
||||
const eventType = EventType.parse(resp.getEventType());
|
||||
|
||||
if (eventType === EventType.Quit) {
|
||||
this.logger.info('quit received');
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
const detectedPort = resp.getPort();
|
||||
if (detectedPort) {
|
||||
const { port, boards } = this.fromRpc(detectedPort);
|
||||
if (!port) {
|
||||
if (!!boards.length) {
|
||||
console.warn(
|
||||
`Could not detect the port, but unexpectedly received discovered boards. This is most likely a bug! Response was: ${this.toJson(
|
||||
resp
|
||||
)}`
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const oldState = deepClone(this._availablePorts);
|
||||
const newState = deepClone(this._availablePorts);
|
||||
const key = Port.keyOf(port);
|
||||
|
||||
if (eventType === EventType.Add) {
|
||||
if (newState[key]) {
|
||||
const [, knownBoards] = newState[key];
|
||||
this.logger.warn(
|
||||
`Port '${Port.toString(
|
||||
port
|
||||
)}' was already available. Known boards before override: ${JSON.stringify(
|
||||
knownBoards
|
||||
)}`
|
||||
);
|
||||
}
|
||||
newState[key] = [port, boards];
|
||||
} else if (eventType === EventType.Remove) {
|
||||
if (!newState[key]) {
|
||||
this.logger.warn(
|
||||
`Port '${Port.toString(port)}' was not available. Skipping`
|
||||
);
|
||||
return;
|
||||
}
|
||||
delete newState[key];
|
||||
}
|
||||
|
||||
const event: AttachedBoardsChangeEvent = {
|
||||
oldState: {
|
||||
...AvailablePorts.split(oldState),
|
||||
},
|
||||
newState: {
|
||||
...AvailablePorts.split(newState),
|
||||
},
|
||||
uploadInProgress: this.uploadInProgress,
|
||||
};
|
||||
|
||||
this._availablePorts = newState;
|
||||
this.notificationService.notifyAttachedBoardsDidChange(event);
|
||||
}
|
||||
return attachedBoards;
|
||||
}
|
||||
|
||||
getAvailablePorts(state: AvailablePorts = this.availablePorts): Port[] {
|
||||
const availablePorts: Port[] = [];
|
||||
for (const portID of Object.keys(state)) {
|
||||
const [port] = state[portID];
|
||||
availablePorts.push(port);
|
||||
}
|
||||
return availablePorts;
|
||||
private fromRpc(detectedPort: RpcDetectedPort): DetectedPort {
|
||||
const rpcPort = detectedPort.getPort();
|
||||
const port = rpcPort && this.fromRpcPort(rpcPort);
|
||||
const boards = detectedPort.getMatchingBoardsList().map(
|
||||
(board) =>
|
||||
({
|
||||
fqbn: board.getFqbn(),
|
||||
name: board.getName() || Unknown,
|
||||
port,
|
||||
} as Board)
|
||||
);
|
||||
return {
|
||||
boards,
|
||||
port,
|
||||
};
|
||||
}
|
||||
|
||||
private fromRpcPort(rpcPort: RpcPort): Port {
|
||||
const port = {
|
||||
address: rpcPort.getAddress(),
|
||||
addressLabel: rpcPort.getLabel(),
|
||||
protocol: rpcPort.getProtocol(),
|
||||
protocolLabel: rpcPort.getProtocolLabel(),
|
||||
properties: Port.Properties.create(rpcPort.getPropertiesMap().toObject()),
|
||||
};
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
Add,
|
||||
Remove,
|
||||
Quit,
|
||||
}
|
||||
namespace EventType {
|
||||
export function parse(type: string): EventType {
|
||||
const normalizedType = type.toLowerCase();
|
||||
switch (normalizedType) {
|
||||
case 'add':
|
||||
return EventType.Add;
|
||||
case 'remove':
|
||||
return EventType.Remove;
|
||||
case 'quit':
|
||||
return EventType.Quit;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unexpected 'BoardListWatchResponse' event type: '${type}.'`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface DetectedPort {
|
||||
port: Port | undefined;
|
||||
boards: Board[];
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ import {
|
||||
Installable,
|
||||
BoardsPackage,
|
||||
Board,
|
||||
Port,
|
||||
BoardDetails,
|
||||
Tool,
|
||||
ConfigOption,
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
AvailablePorts,
|
||||
BoardWithPackage,
|
||||
BoardUserField,
|
||||
BoardSearch,
|
||||
} from '../common/protocol';
|
||||
import {
|
||||
PlatformInstallRequest,
|
||||
@@ -41,6 +41,8 @@ import {
|
||||
SupportedUserFieldsResponse,
|
||||
} from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
||||
import { ExecuteWithProgress } from './grpc-progressible';
|
||||
import { ServiceError } from './service-error';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceImpl
|
||||
@@ -63,14 +65,6 @@ export class BoardsServiceImpl
|
||||
return this.boardDiscovery.availablePorts;
|
||||
}
|
||||
|
||||
async getAttachedBoards(): Promise<Board[]> {
|
||||
return this.boardDiscovery.getAttachedBoards();
|
||||
}
|
||||
|
||||
async getAvailablePorts(): Promise<Port[]> {
|
||||
return this.boardDiscovery.getAvailablePorts();
|
||||
}
|
||||
|
||||
async getBoardDetails(options: {
|
||||
fqbn: string;
|
||||
}): Promise<BoardDetails | undefined> {
|
||||
@@ -84,19 +78,7 @@ export class BoardsServiceImpl
|
||||
(resolve, reject) =>
|
||||
client.boardDetails(detailsReq, (err, resp) => {
|
||||
if (err) {
|
||||
// Required cores are not installed manually: https://github.com/arduino/arduino-cli/issues/954
|
||||
if (
|
||||
(err.message.indexOf('missing platform release') !== -1 &&
|
||||
err.message.indexOf('referenced by board') !== -1) ||
|
||||
// Platform is not installed.
|
||||
(err.message.indexOf('platform') !== -1 &&
|
||||
err.message.indexOf('not installed') !== -1)
|
||||
) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
// It's a hack to handle https://github.com/arduino/arduino-cli/issues/1262 gracefully.
|
||||
if (err.message.indexOf('unknown package') !== -1) {
|
||||
if (isMissingPlatformError(err)) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
@@ -249,29 +231,41 @@ export class BoardsServiceImpl
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const supportedUserFieldsReq = new SupportedUserFieldsRequest();
|
||||
supportedUserFieldsReq.setInstance(instance);
|
||||
supportedUserFieldsReq.setFqbn(options.fqbn);
|
||||
supportedUserFieldsReq.setProtocol(options.protocol);
|
||||
const req = new SupportedUserFieldsRequest();
|
||||
req.setInstance(instance);
|
||||
req.setFqbn(options.fqbn);
|
||||
req.setProtocol(options.protocol);
|
||||
|
||||
const supportedUserFieldsResp =
|
||||
await new Promise<SupportedUserFieldsResponse>((resolve, reject) => {
|
||||
client.supportedUserFields(supportedUserFieldsReq, (err, resp) => {
|
||||
!!err ? reject(err) : resolve(resp);
|
||||
const resp = await new Promise<SupportedUserFieldsResponse | undefined>(
|
||||
(resolve, reject) => {
|
||||
client.supportedUserFields(req, (err, resp) => {
|
||||
if (err) {
|
||||
if (isMissingPlatformError(err)) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(resp);
|
||||
});
|
||||
});
|
||||
return supportedUserFieldsResp.getUserFieldsList().map((e) => {
|
||||
return {
|
||||
toolId: e.getToolId(),
|
||||
name: e.getName(),
|
||||
label: e.getLabel(),
|
||||
secret: e.getSecret(),
|
||||
value: '',
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (!resp) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return resp.getUserFieldsList().map((e) => ({
|
||||
toolId: e.getToolId(),
|
||||
name: e.getName(),
|
||||
label: e.getLabel(),
|
||||
secret: e.getSecret(),
|
||||
value: '',
|
||||
}));
|
||||
}
|
||||
|
||||
async search(options: { query?: string }): Promise<BoardsPackage[]> {
|
||||
async search(options: BoardSearch): Promise<BoardsPackage[]> {
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
@@ -317,8 +311,12 @@ export class BoardsServiceImpl
|
||||
.map((b) => b.getName())
|
||||
.join(', '),
|
||||
installable: true,
|
||||
types: platform.getTypeList(),
|
||||
deprecated: platform.getDeprecated(),
|
||||
summary: 'Boards included in this package:',
|
||||
summary: nls.localize(
|
||||
'arduino/component/boardsIncluded',
|
||||
'Boards included in this package:'
|
||||
),
|
||||
installedVersion,
|
||||
boards: platform
|
||||
.getBoardsList()
|
||||
@@ -384,7 +382,29 @@ export class BoardsServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
return [...packages.values()];
|
||||
const filter = this.typePredicate(options);
|
||||
return [...packages.values()].filter(filter);
|
||||
}
|
||||
|
||||
private typePredicate(
|
||||
options: BoardSearch
|
||||
): (item: BoardsPackage) => boolean {
|
||||
const { type } = options;
|
||||
if (!type || type === 'All') {
|
||||
return () => true;
|
||||
}
|
||||
switch (options.type) {
|
||||
case 'Updatable':
|
||||
return Installable.Updateable;
|
||||
case 'Arduino':
|
||||
case 'Partner':
|
||||
case 'Arduino@Heart':
|
||||
case 'Contributed':
|
||||
case 'Arduino Certified':
|
||||
return ({ types }: BoardsPackage) => !!types && types?.includes(type);
|
||||
default:
|
||||
throw new Error(`Unhandled type: ${options.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
async install(options: {
|
||||
@@ -486,3 +506,30 @@ export class BoardsServiceImpl
|
||||
console.info('<<< Boards package uninstallation done.', item);
|
||||
}
|
||||
}
|
||||
|
||||
function isMissingPlatformError(error: unknown): boolean {
|
||||
if (ServiceError.is(error)) {
|
||||
const message = error.details;
|
||||
// TODO: check gRPC status code? `9 FAILED_PRECONDITION` (https://grpc.github.io/grpc/core/md_doc_statuscodes.html)
|
||||
|
||||
// When installing a 3rd party core that depends on a missing Arduino core.
|
||||
// https://github.com/arduino/arduino-cli/issues/954
|
||||
if (
|
||||
message.includes('missing platform release') &&
|
||||
message.includes('referenced by board')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// When the platform is not installed.
|
||||
if (message.includes('platform') && message.includes('not installed')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// It's a hack to handle https://github.com/arduino/arduino-cli/issues/1262 gracefully.
|
||||
if (message.includes('unknown package')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@@ -147,189 +147,10 @@ function styleJson({
|
||||
UseTab,
|
||||
}: ClangFormatOptions): Record<string, unknown> {
|
||||
// Source: https://github.com/arduino/tooling-project-assets/tree/main/other/clang-format-configuration
|
||||
const defaultConfig = require('../../src/node/default-formatter-config.json');
|
||||
return {
|
||||
AccessModifierOffset: -2,
|
||||
AlignAfterOpenBracket: 'Align',
|
||||
AlignArrayOfStructures: 'None',
|
||||
AlignConsecutiveAssignments: 'None',
|
||||
AlignConsecutiveBitFields: 'None',
|
||||
AlignConsecutiveDeclarations: 'None',
|
||||
AlignConsecutiveMacros: 'None',
|
||||
AlignEscapedNewlines: 'DontAlign',
|
||||
AlignOperands: 'Align',
|
||||
AlignTrailingComments: true,
|
||||
AllowAllArgumentsOnNextLine: true,
|
||||
AllowAllConstructorInitializersOnNextLine: true,
|
||||
AllowAllParametersOfDeclarationOnNextLine: true,
|
||||
AllowShortBlocksOnASingleLine: 'Always',
|
||||
AllowShortCaseLabelsOnASingleLine: true,
|
||||
AllowShortEnumsOnASingleLine: true,
|
||||
AllowShortFunctionsOnASingleLine: 'Empty',
|
||||
AllowShortIfStatementsOnASingleLine: 'AllIfsAndElse',
|
||||
AllowShortLambdasOnASingleLine: 'Empty',
|
||||
AllowShortLoopsOnASingleLine: true,
|
||||
AlwaysBreakAfterDefinitionReturnType: 'None',
|
||||
AlwaysBreakAfterReturnType: 'None',
|
||||
AlwaysBreakBeforeMultilineStrings: false,
|
||||
AlwaysBreakTemplateDeclarations: 'No',
|
||||
AttributeMacros: ['__capability'],
|
||||
BasedOnStyle: 'LLVM',
|
||||
BinPackArguments: true,
|
||||
BinPackParameters: true,
|
||||
BitFieldColonSpacing: 'Both',
|
||||
BraceWrapping: {
|
||||
AfterCaseLabel: false,
|
||||
AfterClass: false,
|
||||
AfterControlStatement: 'Never',
|
||||
AfterEnum: false,
|
||||
AfterFunction: false,
|
||||
AfterNamespace: false,
|
||||
AfterObjCDeclaration: false,
|
||||
AfterStruct: false,
|
||||
AfterUnion: false,
|
||||
AfterExternBlock: false,
|
||||
BeforeCatch: false,
|
||||
BeforeElse: false,
|
||||
BeforeLambdaBody: false,
|
||||
BeforeWhile: false,
|
||||
IndentBraces: false,
|
||||
SplitEmptyFunction: true,
|
||||
SplitEmptyRecord: true,
|
||||
SplitEmptyNamespace: true,
|
||||
},
|
||||
BreakAfterJavaFieldAnnotations: false,
|
||||
BreakBeforeBinaryOperators: 'NonAssignment',
|
||||
BreakBeforeBraces: 'Attach',
|
||||
BreakBeforeConceptDeclarations: false,
|
||||
BreakBeforeInheritanceComma: false,
|
||||
BreakBeforeTernaryOperators: true,
|
||||
BreakConstructorInitializers: 'BeforeColon',
|
||||
BreakConstructorInitializersBeforeComma: false,
|
||||
BreakInheritanceList: 'BeforeColon',
|
||||
BreakStringLiterals: false,
|
||||
ColumnLimit: 0,
|
||||
CommentPragmas: '',
|
||||
CompactNamespaces: false,
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false,
|
||||
ConstructorInitializerIndentWidth: 2,
|
||||
ContinuationIndentWidth: 2,
|
||||
Cpp11BracedListStyle: false,
|
||||
DeriveLineEnding: true,
|
||||
DerivePointerAlignment: true,
|
||||
DisableFormat: false,
|
||||
EmptyLineAfterAccessModifier: 'Leave',
|
||||
EmptyLineBeforeAccessModifier: 'Leave',
|
||||
ExperimentalAutoDetectBinPacking: false,
|
||||
FixNamespaceComments: false,
|
||||
ForEachMacros: ['foreach', 'Q_FOREACH', 'BOOST_FOREACH'],
|
||||
IfMacros: ['KJ_IF_MAYBE'],
|
||||
IncludeBlocks: 'Preserve',
|
||||
IncludeCategories: [
|
||||
{
|
||||
Regex: '^"(llvm|llvm-c|clang|clang-c)/',
|
||||
Priority: 2,
|
||||
SortPriority: 0,
|
||||
CaseSensitive: false,
|
||||
},
|
||||
{
|
||||
Regex: '^(<|"(gtest|gmock|isl|json)/)',
|
||||
Priority: 3,
|
||||
SortPriority: 0,
|
||||
CaseSensitive: false,
|
||||
},
|
||||
{ Regex: '.*', Priority: 1, SortPriority: 0, CaseSensitive: false },
|
||||
],
|
||||
IncludeIsMainRegex: '',
|
||||
IncludeIsMainSourceRegex: '',
|
||||
IndentAccessModifiers: false,
|
||||
IndentCaseBlocks: true,
|
||||
IndentCaseLabels: true,
|
||||
IndentExternBlock: 'Indent',
|
||||
IndentGotoLabels: false,
|
||||
IndentPPDirectives: 'None',
|
||||
IndentRequires: true,
|
||||
IndentWidth: 2,
|
||||
IndentWrappedFunctionNames: false,
|
||||
InsertTrailingCommas: 'None',
|
||||
JavaScriptQuotes: 'Leave',
|
||||
JavaScriptWrapImports: true,
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true,
|
||||
LambdaBodyIndentation: 'Signature',
|
||||
Language: 'Cpp',
|
||||
MacroBlockBegin: '',
|
||||
MacroBlockEnd: '',
|
||||
MaxEmptyLinesToKeep: 100000,
|
||||
NamespaceIndentation: 'None',
|
||||
ObjCBinPackProtocolList: 'Auto',
|
||||
ObjCBlockIndentWidth: 2,
|
||||
ObjCBreakBeforeNestedBlockParam: true,
|
||||
ObjCSpaceAfterProperty: false,
|
||||
ObjCSpaceBeforeProtocolList: true,
|
||||
PPIndentWidth: -1,
|
||||
PackConstructorInitializers: 'BinPack',
|
||||
PenaltyBreakAssignment: 1,
|
||||
PenaltyBreakBeforeFirstCallParameter: 1,
|
||||
PenaltyBreakComment: 1,
|
||||
PenaltyBreakFirstLessLess: 1,
|
||||
PenaltyBreakOpenParenthesis: 1,
|
||||
PenaltyBreakString: 1,
|
||||
PenaltyBreakTemplateDeclaration: 1,
|
||||
PenaltyExcessCharacter: 1,
|
||||
PenaltyIndentedWhitespace: 1,
|
||||
PenaltyReturnTypeOnItsOwnLine: 1,
|
||||
PointerAlignment: 'Right',
|
||||
QualifierAlignment: 'Leave',
|
||||
ReferenceAlignment: 'Pointer',
|
||||
ReflowComments: false,
|
||||
RemoveBracesLLVM: false,
|
||||
SeparateDefinitionBlocks: 'Leave',
|
||||
ShortNamespaceLines: 0,
|
||||
SortIncludes: 'Never',
|
||||
SortJavaStaticImport: 'Before',
|
||||
SortUsingDeclarations: false,
|
||||
SpaceAfterCStyleCast: false,
|
||||
SpaceAfterLogicalNot: false,
|
||||
SpaceAfterTemplateKeyword: false,
|
||||
SpaceAroundPointerQualifiers: 'Default',
|
||||
SpaceBeforeAssignmentOperators: true,
|
||||
SpaceBeforeCaseColon: false,
|
||||
SpaceBeforeCpp11BracedList: false,
|
||||
SpaceBeforeCtorInitializerColon: true,
|
||||
SpaceBeforeInheritanceColon: true,
|
||||
SpaceBeforeParens: 'ControlStatements',
|
||||
SpaceBeforeParensOptions: {
|
||||
AfterControlStatements: true,
|
||||
AfterForeachMacros: true,
|
||||
AfterFunctionDefinitionName: false,
|
||||
AfterFunctionDeclarationName: false,
|
||||
AfterIfMacros: true,
|
||||
AfterOverloadedOperator: false,
|
||||
BeforeNonEmptyParentheses: false,
|
||||
},
|
||||
SpaceBeforeRangeBasedForLoopColon: true,
|
||||
SpaceBeforeSquareBrackets: false,
|
||||
SpaceInEmptyBlock: false,
|
||||
SpaceInEmptyParentheses: false,
|
||||
SpacesBeforeTrailingComments: 2,
|
||||
SpacesInAngles: 'Leave',
|
||||
SpacesInCStyleCastParentheses: false,
|
||||
SpacesInConditionalStatement: false,
|
||||
SpacesInContainerLiterals: false,
|
||||
SpacesInLineCommentPrefix: { Minimum: 0, Maximum: -1 },
|
||||
SpacesInParentheses: false,
|
||||
SpacesInSquareBrackets: false,
|
||||
Standard: 'Auto',
|
||||
StatementAttributeLikeMacros: ['Q_EMIT'],
|
||||
StatementMacros: ['Q_UNUSED', 'QT_REQUIRE_VERSION'],
|
||||
...defaultConfig,
|
||||
TabWidth,
|
||||
UseCRLF: false,
|
||||
UseTab,
|
||||
WhitespaceSensitiveMacros: [
|
||||
'STRINGIZE',
|
||||
'PP_STRINGIZE',
|
||||
'BOOST_PP_STRINGIZE',
|
||||
'NS_SWIFT_NAME',
|
||||
'CF_SWIFT_NAME',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
@@ -5,25 +5,41 @@ import {
|
||||
Range,
|
||||
Position,
|
||||
} from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import type { CoreError } from '../common/protocol';
|
||||
import { CoreError } from '../common/protocol';
|
||||
import { Sketch } from '../common/protocol/sketches-service';
|
||||
|
||||
export interface ErrorSource {
|
||||
export interface OutputSource {
|
||||
readonly content: string | ReadonlyArray<Uint8Array>;
|
||||
readonly sketch?: Sketch;
|
||||
}
|
||||
|
||||
export function tryParseError(source: ErrorSource): CoreError.ErrorLocation[] {
|
||||
const { content, sketch } = source;
|
||||
const err =
|
||||
typeof content === 'string'
|
||||
export namespace OutputSource {
|
||||
export function content(source: OutputSource): string {
|
||||
const { content } = source;
|
||||
return typeof content === 'string'
|
||||
? content
|
||||
: Buffer.concat(content).toString('utf8');
|
||||
}
|
||||
}
|
||||
|
||||
export function tryParseError(source: OutputSource): CoreError.ErrorLocation[] {
|
||||
const { sketch } = source;
|
||||
const content = OutputSource.content(source);
|
||||
if (sketch) {
|
||||
return tryParse(err)
|
||||
return tryParse(content)
|
||||
.map(remapErrorMessages)
|
||||
.filter(isLocationInSketch(sketch))
|
||||
.map(toErrorInfo);
|
||||
.map(toErrorInfo)
|
||||
.reduce((acc, curr) => {
|
||||
const existingRef = acc.find((candidate) =>
|
||||
CoreError.ErrorLocationRef.equals(candidate, curr)
|
||||
);
|
||||
if (existingRef) {
|
||||
existingRef.rangesInOutput.push(...curr.rangesInOutput);
|
||||
} else {
|
||||
acc.push(curr);
|
||||
}
|
||||
return acc;
|
||||
}, [] as CoreError.ErrorLocation[]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -35,6 +51,7 @@ interface ParseResult {
|
||||
readonly errorPrefix: string;
|
||||
readonly error: string;
|
||||
readonly message?: string;
|
||||
readonly rangeInOutput?: Range | undefined;
|
||||
}
|
||||
namespace ParseResult {
|
||||
export function keyOf(result: ParseResult): string {
|
||||
@@ -64,6 +81,7 @@ function toErrorInfo({
|
||||
path,
|
||||
line,
|
||||
column,
|
||||
rangeInOutput,
|
||||
}: ParseResult): CoreError.ErrorLocation {
|
||||
return {
|
||||
message: error,
|
||||
@@ -72,6 +90,7 @@ function toErrorInfo({
|
||||
uri: FileUri.create(path).toString(),
|
||||
range: range(line, column),
|
||||
},
|
||||
rangesInOutput: rangeInOutput ? [rangeInOutput] : [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,48 +105,50 @@ function range(line: number, column?: number): Range {
|
||||
};
|
||||
}
|
||||
|
||||
export function tryParse(raw: string): ParseResult[] {
|
||||
function tryParse(content: string): ParseResult[] {
|
||||
// Shamelessly stolen from the Java IDE: https://github.com/arduino/Arduino/blob/43b0818f7fa8073301db1b80ac832b7b7596b828/arduino-core/src/cc/arduino/Compiler.java#L137
|
||||
const re = new RegExp(
|
||||
'(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*((fatal)?\\s*error:\\s*)(.*)\\s*',
|
||||
'gm'
|
||||
);
|
||||
return [
|
||||
...new Map(
|
||||
Array.from(raw.matchAll(re) ?? [])
|
||||
.map((match) => {
|
||||
const [, path, rawLine, rawColumn, errorPrefix, , error] = match.map(
|
||||
(match) => (match ? match.trim() : match)
|
||||
return Array.from(content.matchAll(re) ?? [])
|
||||
.map((match) => {
|
||||
const { index: start } = match;
|
||||
const [, path, rawLine, rawColumn, errorPrefix, , error] = match.map(
|
||||
(match) => (match ? match.trim() : match)
|
||||
);
|
||||
const line = Number.parseInt(rawLine, 10);
|
||||
if (!Number.isInteger(line)) {
|
||||
console.warn(
|
||||
`Could not parse line number. Raw input: <${rawLine}>, parsed integer: <${line}>.`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
let column: number | undefined = undefined;
|
||||
if (rawColumn) {
|
||||
const normalizedRawColumn = rawColumn.slice(-1); // trims the leading colon => `:3` will be `3`
|
||||
column = Number.parseInt(normalizedRawColumn, 10);
|
||||
if (!Number.isInteger(column)) {
|
||||
console.warn(
|
||||
`Could not parse column number. Raw input: <${normalizedRawColumn}>, parsed integer: <${column}>.`
|
||||
);
|
||||
const line = Number.parseInt(rawLine, 10);
|
||||
if (!Number.isInteger(line)) {
|
||||
console.warn(
|
||||
`Could not parse line number. Raw input: <${rawLine}>, parsed integer: <${line}>.`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
let column: number | undefined = undefined;
|
||||
if (rawColumn) {
|
||||
const normalizedRawColumn = rawColumn.slice(-1); // trims the leading colon => `:3` will be `3`
|
||||
column = Number.parseInt(normalizedRawColumn, 10);
|
||||
if (!Number.isInteger(column)) {
|
||||
console.warn(
|
||||
`Could not parse column number. Raw input: <${normalizedRawColumn}>, parsed integer: <${column}>.`
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
path,
|
||||
line,
|
||||
column,
|
||||
errorPrefix,
|
||||
error,
|
||||
};
|
||||
})
|
||||
.filter(notEmpty)
|
||||
.map((result) => [ParseResult.keyOf(result), result])
|
||||
).values(),
|
||||
];
|
||||
}
|
||||
}
|
||||
const rangeInOutput = findRangeInOutput(
|
||||
start,
|
||||
{ path, rawLine, rawColumn },
|
||||
content
|
||||
);
|
||||
return {
|
||||
path,
|
||||
line,
|
||||
column,
|
||||
errorPrefix,
|
||||
error,
|
||||
rangeInOutput,
|
||||
};
|
||||
})
|
||||
.filter(notEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,3 +182,47 @@ const KnownErrors: Record<string, { error: string; message?: string }> = {
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
function findRangeInOutput(
|
||||
startIndex: number | undefined,
|
||||
groups: { path: string; rawLine: string; rawColumn: string | null },
|
||||
content: string // TODO? lines: string[]? can this code break line on `\n`? const lines = content.split(/\r?\n/) ?? [];
|
||||
): Range | undefined {
|
||||
if (startIndex === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// /path/to/location/Sketch/Sketch.ino:36:42
|
||||
const offset =
|
||||
groups.path.length +
|
||||
':'.length +
|
||||
groups.rawLine.length +
|
||||
(groups.rawColumn ? groups.rawColumn.length : 0);
|
||||
const start = toPosition(startIndex, content);
|
||||
if (!start) {
|
||||
return undefined;
|
||||
}
|
||||
const end = toPosition(startIndex + offset, content);
|
||||
if (!end) {
|
||||
return undefined;
|
||||
}
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
function toPosition(offset: number, content: string): Position | undefined {
|
||||
let line = 0;
|
||||
let character = 0;
|
||||
const length = content.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const c = content.charAt(i);
|
||||
if (i === offset) {
|
||||
return { line, character };
|
||||
}
|
||||
if (c === '\n') {
|
||||
line++;
|
||||
character = 0;
|
||||
} else {
|
||||
character++;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ interface IArduinoCoreServiceService extends grpc.ServiceDefinition<grpc.Untyped
|
||||
platformList: IArduinoCoreServiceService_IPlatformList;
|
||||
libraryDownload: IArduinoCoreServiceService_ILibraryDownload;
|
||||
libraryInstall: IArduinoCoreServiceService_ILibraryInstall;
|
||||
libraryUpgrade: IArduinoCoreServiceService_ILibraryUpgrade;
|
||||
zipLibraryInstall: IArduinoCoreServiceService_IZipLibraryInstall;
|
||||
gitLibraryInstall: IArduinoCoreServiceService_IGitLibraryInstall;
|
||||
libraryUninstall: IArduinoCoreServiceService_ILibraryUninstall;
|
||||
@@ -348,6 +349,15 @@ interface IArduinoCoreServiceService_ILibraryInstall extends grpc.MethodDefiniti
|
||||
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
|
||||
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
|
||||
}
|
||||
interface IArduinoCoreServiceService_ILibraryUpgrade extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse> {
|
||||
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/LibraryUpgrade";
|
||||
requestStream: false;
|
||||
responseStream: true;
|
||||
requestSerialize: grpc.serialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest>;
|
||||
requestDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest>;
|
||||
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
|
||||
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
|
||||
}
|
||||
interface IArduinoCoreServiceService_IZipLibraryInstall extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse> {
|
||||
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/ZipLibraryInstall";
|
||||
requestStream: false;
|
||||
@@ -465,6 +475,7 @@ export interface IArduinoCoreServiceServer {
|
||||
platformList: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_core_pb.PlatformListRequest, cc_arduino_cli_commands_v1_core_pb.PlatformListResponse>;
|
||||
libraryDownload: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadResponse>;
|
||||
libraryInstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
|
||||
libraryUpgrade: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
|
||||
zipLibraryInstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
|
||||
gitLibraryInstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallResponse>;
|
||||
libraryUninstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryUninstallRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryUninstallResponse>;
|
||||
@@ -557,6 +568,8 @@ export interface IArduinoCoreServiceClient {
|
||||
libraryDownload(request: cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadResponse>;
|
||||
libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
|
||||
libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
|
||||
libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
|
||||
libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
|
||||
zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
|
||||
zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
|
||||
gitLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallResponse>;
|
||||
@@ -663,6 +676,8 @@ export class ArduinoCoreServiceClient extends grpc.Client implements IArduinoCor
|
||||
public libraryDownload(request: cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadResponse>;
|
||||
public libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
|
||||
public libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
|
||||
public libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
|
||||
public libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
|
||||
public zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
|
||||
public zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
|
||||
public gitLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallResponse>;
|
||||
|
@@ -489,6 +489,28 @@ function deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeAllResponse(buffer
|
||||
return cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeAllResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest(arg) {
|
||||
if (!(arg instanceof cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest)) {
|
||||
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.LibraryUpgradeRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest(buffer_arg) {
|
||||
return cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse(arg) {
|
||||
if (!(arg instanceof cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse)) {
|
||||
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.LibraryUpgradeResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse(buffer_arg) {
|
||||
return cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_cc_arduino_cli_commands_v1_ListProgrammersAvailableForUploadRequest(arg) {
|
||||
if (!(arg instanceof cc_arduino_cli_commands_v1_upload_pb.ListProgrammersAvailableForUploadRequest)) {
|
||||
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.ListProgrammersAvailableForUploadRequest');
|
||||
@@ -1325,6 +1347,18 @@ libraryInstall: {
|
||||
responseSerialize: serialize_cc_arduino_cli_commands_v1_LibraryInstallResponse,
|
||||
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_LibraryInstallResponse,
|
||||
},
|
||||
// Upgrade a library to the newest version available.
|
||||
libraryUpgrade: {
|
||||
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/LibraryUpgrade',
|
||||
requestStream: false,
|
||||
responseStream: true,
|
||||
requestType: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest,
|
||||
responseType: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse,
|
||||
requestSerialize: serialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest,
|
||||
requestDeserialize: deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest,
|
||||
responseSerialize: serialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse,
|
||||
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse,
|
||||
},
|
||||
// Install a library from a Zip File
|
||||
zipLibraryInstall: {
|
||||
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/ZipLibraryInstall',
|
||||
|
@@ -159,6 +159,11 @@ export class Platform extends jspb.Message {
|
||||
getDeprecated(): boolean;
|
||||
setDeprecated(value: boolean): Platform;
|
||||
|
||||
clearTypeList(): void;
|
||||
getTypeList(): Array<string>;
|
||||
setTypeList(value: Array<string>): Platform;
|
||||
addType(value: string, index?: number): string;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): Platform.AsObject;
|
||||
@@ -182,6 +187,7 @@ export namespace Platform {
|
||||
boardsList: Array<Board.AsObject>,
|
||||
manuallyInstalled: boolean,
|
||||
deprecated: boolean,
|
||||
typeList: Array<string>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -987,7 +987,7 @@ proto.cc.arduino.cli.commands.v1.Programmer.prototype.setName = function(value)
|
||||
* @private {!Array<number>}
|
||||
* @const
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.Platform.repeatedFields_ = [8];
|
||||
proto.cc.arduino.cli.commands.v1.Platform.repeatedFields_ = [8,11];
|
||||
|
||||
|
||||
|
||||
@@ -1030,7 +1030,8 @@ proto.cc.arduino.cli.commands.v1.Platform.toObject = function(includeInstance, m
|
||||
boardsList: jspb.Message.toObjectList(msg.getBoardsList(),
|
||||
proto.cc.arduino.cli.commands.v1.Board.toObject, includeInstance),
|
||||
manuallyInstalled: jspb.Message.getBooleanFieldWithDefault(msg, 9, false),
|
||||
deprecated: jspb.Message.getBooleanFieldWithDefault(msg, 10, false)
|
||||
deprecated: jspb.Message.getBooleanFieldWithDefault(msg, 10, false),
|
||||
typeList: (f = jspb.Message.getRepeatedField(msg, 11)) == null ? undefined : f
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -1108,6 +1109,10 @@ proto.cc.arduino.cli.commands.v1.Platform.deserializeBinaryFromReader = function
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setDeprecated(value);
|
||||
break;
|
||||
case 11:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.addType(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -1208,6 +1213,13 @@ proto.cc.arduino.cli.commands.v1.Platform.serializeBinaryToWriter = function(mes
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getTypeList();
|
||||
if (f.length > 0) {
|
||||
writer.writeRepeatedString(
|
||||
11,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1411,6 +1423,43 @@ proto.cc.arduino.cli.commands.v1.Platform.prototype.setDeprecated = function(val
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* repeated string type = 11;
|
||||
* @return {!Array<string>}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.Platform.prototype.getTypeList = function() {
|
||||
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 11));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Array<string>} value
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.Platform} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.Platform.prototype.setTypeList = function(value) {
|
||||
return jspb.Message.setField(this, 11, value || []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {number=} opt_index
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.Platform} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.Platform.prototype.addType = function(value, opt_index) {
|
||||
return jspb.Message.addToRepeatedField(this, 11, value, opt_index);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the list making it empty but non-null.
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.Platform} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.Platform.prototype.clearTypeList = function() {
|
||||
return this.setTypeList([]);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -82,6 +82,9 @@ export class LibraryInstallRequest extends jspb.Message {
|
||||
getNoOverwrite(): boolean;
|
||||
setNoOverwrite(value: boolean): LibraryInstallRequest;
|
||||
|
||||
getInstallLocation(): LibraryInstallLocation;
|
||||
setInstallLocation(value: LibraryInstallLocation): LibraryInstallRequest;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): LibraryInstallRequest.AsObject;
|
||||
@@ -100,6 +103,7 @@ export namespace LibraryInstallRequest {
|
||||
version: string,
|
||||
noDeps: boolean,
|
||||
noOverwrite: boolean,
|
||||
installLocation: LibraryInstallLocation,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +138,69 @@ export namespace LibraryInstallResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export class LibraryUpgradeRequest extends jspb.Message {
|
||||
|
||||
hasInstance(): boolean;
|
||||
clearInstance(): void;
|
||||
getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined;
|
||||
setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryUpgradeRequest;
|
||||
|
||||
getName(): string;
|
||||
setName(value: string): LibraryUpgradeRequest;
|
||||
|
||||
getNoDeps(): boolean;
|
||||
setNoDeps(value: boolean): LibraryUpgradeRequest;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): LibraryUpgradeRequest.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: LibraryUpgradeRequest): LibraryUpgradeRequest.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: LibraryUpgradeRequest, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): LibraryUpgradeRequest;
|
||||
static deserializeBinaryFromReader(message: LibraryUpgradeRequest, reader: jspb.BinaryReader): LibraryUpgradeRequest;
|
||||
}
|
||||
|
||||
export namespace LibraryUpgradeRequest {
|
||||
export type AsObject = {
|
||||
instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject,
|
||||
name: string,
|
||||
noDeps: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
export class LibraryUpgradeResponse extends jspb.Message {
|
||||
|
||||
hasProgress(): boolean;
|
||||
clearProgress(): void;
|
||||
getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined;
|
||||
setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): LibraryUpgradeResponse;
|
||||
|
||||
|
||||
hasTaskProgress(): boolean;
|
||||
clearTaskProgress(): void;
|
||||
getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined;
|
||||
setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): LibraryUpgradeResponse;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): LibraryUpgradeResponse.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: LibraryUpgradeResponse): LibraryUpgradeResponse.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: LibraryUpgradeResponse, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): LibraryUpgradeResponse;
|
||||
static deserializeBinaryFromReader(message: LibraryUpgradeResponse, reader: jspb.BinaryReader): LibraryUpgradeResponse;
|
||||
}
|
||||
|
||||
export namespace LibraryUpgradeResponse {
|
||||
export type AsObject = {
|
||||
progress?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress.AsObject,
|
||||
taskProgress?: cc_arduino_cli_commands_v1_common_pb.TaskProgress.AsObject,
|
||||
}
|
||||
}
|
||||
|
||||
export class LibraryUninstallRequest extends jspb.Message {
|
||||
|
||||
hasInstance(): boolean;
|
||||
@@ -899,6 +966,11 @@ export namespace GitLibraryInstallResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export enum LibraryInstallLocation {
|
||||
LIBRARY_INSTALL_LOCATION_USER = 0,
|
||||
LIBRARY_INSTALL_LOCATION_BUILTIN = 1,
|
||||
}
|
||||
|
||||
export enum LibrarySearchStatus {
|
||||
LIBRARY_SEARCH_STATUS_FAILED = 0,
|
||||
LIBRARY_SEARCH_STATUS_SUCCESS = 1,
|
||||
@@ -910,7 +982,7 @@ export enum LibraryLayout {
|
||||
}
|
||||
|
||||
export enum LibraryLocation {
|
||||
LIBRARY_LOCATION_IDE_BUILTIN = 0,
|
||||
LIBRARY_LOCATION_BUILTIN = 0,
|
||||
LIBRARY_LOCATION_USER = 1,
|
||||
LIBRARY_LOCATION_PLATFORM_BUILTIN = 2,
|
||||
LIBRARY_LOCATION_REFERENCED_PLATFORM_BUILTIN = 3,
|
||||
|
@@ -26,6 +26,7 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryDependency', null, gl
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryDependencyStatus', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryDownloadRequest', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryDownloadResponse', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryInstallLocation', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryInstallRequest', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryInstallResponse', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryLayout', null, global);
|
||||
@@ -42,6 +43,8 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUninstallRequest', nu
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUninstallResponse', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeAllRequest', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeAllResponse', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.SearchedLibrary', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.ZipLibraryInstallRequest', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.ZipLibraryInstallResponse', null, global);
|
||||
@@ -129,6 +132,48 @@ if (goog.DEBUG && !COMPILED) {
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryInstallResponse.displayName = 'proto.cc.arduino.cli.commands.v1.LibraryInstallResponse';
|
||||
}
|
||||
/**
|
||||
* 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.LibraryUpgradeRequest = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.displayName = 'proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest';
|
||||
}
|
||||
/**
|
||||
* 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.LibraryUpgradeResponse = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.displayName = 'proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
@@ -968,7 +1013,8 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.toObject = function(inclu
|
||||
name: jspb.Message.getFieldWithDefault(msg, 2, ""),
|
||||
version: jspb.Message.getFieldWithDefault(msg, 3, ""),
|
||||
noDeps: jspb.Message.getBooleanFieldWithDefault(msg, 4, false),
|
||||
noOverwrite: jspb.Message.getBooleanFieldWithDefault(msg, 5, false)
|
||||
noOverwrite: jspb.Message.getBooleanFieldWithDefault(msg, 5, false),
|
||||
installLocation: jspb.Message.getFieldWithDefault(msg, 6, 0)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@@ -1026,6 +1072,10 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.deserializeBinaryFromRead
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setNoOverwrite(value);
|
||||
break;
|
||||
case 6:
|
||||
var value = /** @type {!proto.cc.arduino.cli.commands.v1.LibraryInstallLocation} */ (reader.readEnum());
|
||||
msg.setInstallLocation(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@@ -1091,6 +1141,13 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.serializeBinaryToWriter =
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getInstallLocation();
|
||||
if (f !== 0.0) {
|
||||
writer.writeEnum(
|
||||
6,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1203,6 +1260,24 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.prototype.setNoOverwrite
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional LibraryInstallLocation install_location = 6;
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryInstallLocation}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.prototype.getInstallLocation = function() {
|
||||
return /** @type {!proto.cc.arduino.cli.commands.v1.LibraryInstallLocation} */ (jspb.Message.getFieldWithDefault(this, 6, 0));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!proto.cc.arduino.cli.commands.v1.LibraryInstallLocation} value
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryInstallRequest} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.prototype.setInstallLocation = function(value) {
|
||||
return jspb.Message.setProto3EnumField(this, 6, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1408,6 +1483,419 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallResponse.prototype.hasTaskProgres
|
||||
|
||||
|
||||
|
||||
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_<name>, 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.LibraryUpgradeRequest.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.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.LibraryUpgradeRequest} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f),
|
||||
name: jspb.Message.getFieldWithDefault(msg, 2, ""),
|
||||
noDeps: jspb.Message.getBooleanFieldWithDefault(msg, 3, false)
|
||||
};
|
||||
|
||||
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.LibraryUpgradeRequest}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest;
|
||||
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.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.LibraryUpgradeRequest} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = new cc_arduino_cli_commands_v1_common_pb.Instance;
|
||||
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.setName(value);
|
||||
break;
|
||||
case 3:
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setNoDeps(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.LibraryUpgradeRequest.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.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.LibraryUpgradeRequest} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getInstance();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
1,
|
||||
f,
|
||||
cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = message.getName();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
2,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = message.getNoDeps();
|
||||
if (f) {
|
||||
writer.writeBool(
|
||||
3,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional Instance instance = 1;
|
||||
* @return {?proto.cc.arduino.cli.commands.v1.Instance}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.getInstance = function() {
|
||||
return /** @type{?proto.cc.arduino.cli.commands.v1.Instance} */ (
|
||||
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Instance, 1));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.cc.arduino.cli.commands.v1.Instance|undefined} value
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.setInstance = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.clearInstance = function() {
|
||||
return this.setInstance(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.hasInstance = function() {
|
||||
return jspb.Message.getField(this, 1) != null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string name = 2;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.getName = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.setName = function(value) {
|
||||
return jspb.Message.setProto3StringField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional bool no_deps = 3;
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.getNoDeps = function() {
|
||||
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.setNoDeps = function(value) {
|
||||
return jspb.Message.setProto3BooleanField(this, 3, 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_<name>, 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.LibraryUpgradeResponse.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.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.LibraryUpgradeResponse} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
progress: (f = msg.getProgress()) && cc_arduino_cli_commands_v1_common_pb.DownloadProgress.toObject(includeInstance, f),
|
||||
taskProgress: (f = msg.getTaskProgress()) && cc_arduino_cli_commands_v1_common_pb.TaskProgress.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
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.LibraryUpgradeResponse}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse;
|
||||
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.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.LibraryUpgradeResponse} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = new cc_arduino_cli_commands_v1_common_pb.DownloadProgress;
|
||||
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.DownloadProgress.deserializeBinaryFromReader);
|
||||
msg.setProgress(value);
|
||||
break;
|
||||
case 2:
|
||||
var value = new cc_arduino_cli_commands_v1_common_pb.TaskProgress;
|
||||
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.TaskProgress.deserializeBinaryFromReader);
|
||||
msg.setTaskProgress(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.LibraryUpgradeResponse.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.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.LibraryUpgradeResponse} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getProgress();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
1,
|
||||
f,
|
||||
cc_arduino_cli_commands_v1_common_pb.DownloadProgress.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = message.getTaskProgress();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
2,
|
||||
f,
|
||||
cc_arduino_cli_commands_v1_common_pb.TaskProgress.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional DownloadProgress progress = 1;
|
||||
* @return {?proto.cc.arduino.cli.commands.v1.DownloadProgress}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.getProgress = function() {
|
||||
return /** @type{?proto.cc.arduino.cli.commands.v1.DownloadProgress} */ (
|
||||
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.DownloadProgress, 1));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.cc.arduino.cli.commands.v1.DownloadProgress|undefined} value
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.setProgress = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.clearProgress = function() {
|
||||
return this.setProgress(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.hasProgress = function() {
|
||||
return jspb.Message.getField(this, 1) != null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional TaskProgress task_progress = 2;
|
||||
* @return {?proto.cc.arduino.cli.commands.v1.TaskProgress}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.getTaskProgress = function() {
|
||||
return /** @type{?proto.cc.arduino.cli.commands.v1.TaskProgress} */ (
|
||||
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.TaskProgress, 2));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?proto.cc.arduino.cli.commands.v1.TaskProgress|undefined} value
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.setTaskProgress = function(value) {
|
||||
return jspb.Message.setWrapperField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the message field making it undefined.
|
||||
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.clearTaskProgress = function() {
|
||||
return this.setTaskProgress(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.hasTaskProgress = function() {
|
||||
return jspb.Message.getField(this, 2) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
@@ -6600,6 +7088,14 @@ proto.cc.arduino.cli.commands.v1.GitLibraryInstallResponse.prototype.hasTaskProg
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryInstallLocation = {
|
||||
LIBRARY_INSTALL_LOCATION_USER: 0,
|
||||
LIBRARY_INSTALL_LOCATION_BUILTIN: 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
@@ -6620,7 +7116,7 @@ proto.cc.arduino.cli.commands.v1.LibraryLayout = {
|
||||
* @enum {number}
|
||||
*/
|
||||
proto.cc.arduino.cli.commands.v1.LibraryLocation = {
|
||||
LIBRARY_LOCATION_IDE_BUILTIN: 0,
|
||||
LIBRARY_LOCATION_BUILTIN: 0,
|
||||
LIBRARY_LOCATION_USER: 1,
|
||||
LIBRARY_LOCATION_PLATFORM_BUILTIN: 2,
|
||||
LIBRARY_LOCATION_REFERENCED_PLATFORM_BUILTIN: 3,
|
||||
|
@@ -144,7 +144,7 @@ export class CoreClientProvider {
|
||||
await this.updateIndexes(client); // TODO: this should run without the 3rd party URLs
|
||||
await this.initInstance(client);
|
||||
console.info(
|
||||
`Downloaded the primary packages indexes, and successfully initialized the core gRPC client.`
|
||||
`Downloaded the primary package indexes, and successfully initialized the core gRPC client.`
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user