mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-13 06:16:33 +00:00
Show 'progress' indicator during verify/upload.
Closes #575 Closes #1175 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
27a2a6ca03
commit
e156dcc213
@ -4,11 +4,8 @@ import URI from '@theia/core/lib/common/uri';
|
|||||||
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import {
|
import { LibraryService, ResponseServiceClient } from '../../common/protocol';
|
||||||
Installable,
|
import { ExecuteWithProgress } from '../../common/protocol/progressible';
|
||||||
LibraryService,
|
|
||||||
ResponseServiceClient,
|
|
||||||
} from '../../common/protocol';
|
|
||||||
import {
|
import {
|
||||||
SketchContribution,
|
SketchContribution,
|
||||||
Command,
|
Command,
|
||||||
@ -88,7 +85,7 @@ export class AddZipLibrary extends SketchContribution {
|
|||||||
|
|
||||||
private async doInstall(zipUri: string, overwrite?: boolean): Promise<void> {
|
private async doInstall(zipUri: string, overwrite?: boolean): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await Installable.doWithProgress({
|
await ExecuteWithProgress.doWithProgress({
|
||||||
messageService: this.messageService,
|
messageService: this.messageService,
|
||||||
progressText:
|
progressText:
|
||||||
nls.localize('arduino/common/processing', 'Processing') +
|
nls.localize('arduino/common/processing', 'Processing') +
|
||||||
|
@ -1,23 +1,16 @@
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { CoreService } from '../../common/protocol';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
|
||||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
|
||||||
import {
|
import {
|
||||||
CoreServiceContribution,
|
|
||||||
Command,
|
Command,
|
||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
|
CoreServiceContribution,
|
||||||
MenuModelRegistry,
|
MenuModelRegistry,
|
||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { nls } from '@theia/core/lib/common';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BurnBootloader extends CoreServiceContribution {
|
export class BurnBootloader extends CoreServiceContribution {
|
||||||
@inject(BoardsDataStore)
|
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
|
||||||
|
|
||||||
override registerCommands(registry: CommandRegistry): void {
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
|
registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
|
||||||
execute: () => this.burnBootloader(),
|
execute: () => this.burnBootloader(),
|
||||||
@ -35,32 +28,19 @@ export class BurnBootloader extends CoreServiceContribution {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async burnBootloader(): Promise<void> {
|
private async burnBootloader(): Promise<void> {
|
||||||
|
const options = await this.options();
|
||||||
try {
|
try {
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
await this.doWithProgress({
|
||||||
const port = boardsConfig.selectedPort;
|
progressText: nls.localize(
|
||||||
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
'arduino/bootloader/burningBootloader',
|
||||||
await Promise.all([
|
'Burning bootloader...'
|
||||||
this.boardsDataStore.appendConfigToFqbn(
|
),
|
||||||
boardsConfig.selectedBoard?.fqbn
|
task: (progressId, coreService) =>
|
||||||
),
|
coreService.burnBootloader({
|
||||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
...options,
|
||||||
this.preferences.get('arduino.upload.verify'),
|
progressId,
|
||||||
this.preferences.get('arduino.upload.verbose'),
|
}),
|
||||||
]);
|
|
||||||
|
|
||||||
const board = {
|
|
||||||
...boardsConfig.selectedBoard,
|
|
||||||
name: boardsConfig.selectedBoard?.name || '',
|
|
||||||
fqbn,
|
|
||||||
};
|
|
||||||
this.outputChannelManager.getChannel('Arduino').clear();
|
|
||||||
await this.coreService.burnBootloader({
|
|
||||||
board,
|
|
||||||
programmer,
|
|
||||||
port,
|
|
||||||
verify,
|
|
||||||
verbose,
|
|
||||||
});
|
});
|
||||||
this.messageService.info(
|
this.messageService.info(
|
||||||
nls.localize(
|
nls.localize(
|
||||||
@ -75,6 +55,27 @@ export class BurnBootloader extends CoreServiceContribution {
|
|||||||
this.handleError(e);
|
this.handleError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async options(): Promise<CoreService.Options.Bootloader> {
|
||||||
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
|
const port = boardsConfig.selectedPort;
|
||||||
|
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
||||||
|
await Promise.all([
|
||||||
|
this.boardsDataStore.appendConfigToFqbn(
|
||||||
|
boardsConfig.selectedBoard?.fqbn
|
||||||
|
),
|
||||||
|
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||||
|
this.preferences.get('arduino.upload.verify'),
|
||||||
|
this.preferences.get('arduino.upload.verbose'),
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
fqbn,
|
||||||
|
programmer,
|
||||||
|
port,
|
||||||
|
verify,
|
||||||
|
verbose,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace BurnBootloader {
|
export namespace BurnBootloader {
|
||||||
|
@ -49,13 +49,16 @@ import {
|
|||||||
Sketch,
|
Sketch,
|
||||||
CoreService,
|
CoreService,
|
||||||
CoreError,
|
CoreError,
|
||||||
|
ResponseServiceClient,
|
||||||
} from '../../common/protocol';
|
} from '../../common/protocol';
|
||||||
import { ArduinoPreferences } from '../arduino-preferences';
|
import { ArduinoPreferences } from '../arduino-preferences';
|
||||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
import { CoreErrorHandler } from './core-error-handler';
|
|
||||||
import { nls } from '@theia/core';
|
import { nls } from '@theia/core';
|
||||||
import { OutputChannelManager } from '../theia/output/output-channel';
|
import { OutputChannelManager } from '../theia/output/output-channel';
|
||||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
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';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Command,
|
Command,
|
||||||
@ -167,18 +170,23 @@ export abstract class SketchContribution extends Contribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CoreServiceContribution extends SketchContribution {
|
export abstract class CoreServiceContribution extends SketchContribution {
|
||||||
@inject(CoreService)
|
@inject(BoardsDataStore)
|
||||||
protected readonly coreService: CoreService;
|
protected readonly boardsDataStore: BoardsDataStore;
|
||||||
|
|
||||||
@inject(CoreErrorHandler)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly coreErrorHandler: CoreErrorHandler;
|
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
@inject(CoreService)
|
||||||
|
private readonly coreService: CoreService;
|
||||||
|
|
||||||
@inject(ClipboardService)
|
@inject(ClipboardService)
|
||||||
private readonly clipboardService: ClipboardService;
|
private readonly clipboardService: ClipboardService;
|
||||||
|
|
||||||
|
@inject(ResponseServiceClient)
|
||||||
|
private readonly responseService: ResponseServiceClient;
|
||||||
|
|
||||||
protected handleError(error: unknown): void {
|
protected handleError(error: unknown): void {
|
||||||
this.coreErrorHandler.tryHandle(error);
|
|
||||||
this.tryToastErrorMessage(error);
|
this.tryToastErrorMessage(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +222,25 @@ export class CoreServiceContribution extends SketchContribution {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async doWithProgress<T>(options: {
|
||||||
|
progressText: string;
|
||||||
|
keepOutput?: boolean;
|
||||||
|
task: (progressId: string, coreService: CoreService) => Promise<T>;
|
||||||
|
}): Promise<T> {
|
||||||
|
const { progressText, keepOutput, task } = options;
|
||||||
|
this.outputChannelManager
|
||||||
|
.getChannel('Arduino')
|
||||||
|
.show({ preserveFocus: true });
|
||||||
|
const result = await ExecuteWithProgress.doWithProgress({
|
||||||
|
messageService: this.messageService,
|
||||||
|
responseService: this.responseService,
|
||||||
|
progressText,
|
||||||
|
run: ({ progressId }) => task(progressId, this.coreService),
|
||||||
|
keepOutput,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Contribution {
|
export namespace Contribution {
|
||||||
|
@ -3,56 +3,47 @@ import { Emitter } from '@theia/core/lib/common/event';
|
|||||||
import { BoardUserField, CoreService } from '../../common/protocol';
|
import { BoardUserField, CoreService } from '../../common/protocol';
|
||||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
|
||||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
|
||||||
import {
|
import {
|
||||||
CoreServiceContribution,
|
|
||||||
Command,
|
Command,
|
||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
MenuModelRegistry,
|
MenuModelRegistry,
|
||||||
KeybindingRegistry,
|
KeybindingRegistry,
|
||||||
TabBarToolbarRegistry,
|
TabBarToolbarRegistry,
|
||||||
|
CoreServiceContribution,
|
||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
|
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
|
||||||
import { DisposableCollection, nls } from '@theia/core/lib/common';
|
import { DisposableCollection, nls } from '@theia/core/lib/common';
|
||||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import type { VerifySketchParams } from './verify-sketch';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UploadSketch extends CoreServiceContribution {
|
export class UploadSketch extends CoreServiceContribution {
|
||||||
@inject(MenuModelRegistry)
|
@inject(MenuModelRegistry)
|
||||||
protected readonly menuRegistry: MenuModelRegistry;
|
private readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
@inject(BoardsDataStore)
|
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
|
||||||
|
|
||||||
@inject(UserFieldsDialog)
|
@inject(UserFieldsDialog)
|
||||||
protected readonly userFieldsDialog: UserFieldsDialog;
|
private readonly userFieldsDialog: UserFieldsDialog;
|
||||||
|
|
||||||
protected cachedUserFields: Map<string, BoardUserField[]> = new Map();
|
private boardRequiresUserFields = false;
|
||||||
|
private readonly cachedUserFields: Map<string, BoardUserField[]> = new Map();
|
||||||
|
private readonly menuActionsDisposables = new DisposableCollection();
|
||||||
|
|
||||||
protected readonly onDidChangeEmitter = new Emitter<Readonly<void>>();
|
private readonly onDidChangeEmitter = new Emitter<void>();
|
||||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
private readonly onDidChange = this.onDidChangeEmitter.event;
|
||||||
|
private uploadInProgress = false;
|
||||||
protected uploadInProgress = false;
|
|
||||||
protected boardRequiresUserFields = false;
|
|
||||||
|
|
||||||
protected readonly menuActionsDisposables = new DisposableCollection();
|
|
||||||
|
|
||||||
protected override init(): void {
|
protected override init(): void {
|
||||||
super.init();
|
super.init();
|
||||||
this.boardsServiceClientImpl.onBoardsConfigChanged(async () => {
|
this.boardsServiceProvider.onBoardsConfigChanged(async () => {
|
||||||
const userFields =
|
const userFields =
|
||||||
await this.boardsServiceClientImpl.selectedBoardUserFields();
|
await this.boardsServiceProvider.selectedBoardUserFields();
|
||||||
this.boardRequiresUserFields = userFields.length > 0;
|
this.boardRequiresUserFields = userFields.length > 0;
|
||||||
this.registerMenus(this.menuRegistry);
|
this.registerMenus(this.menuRegistry);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectedFqbnAddress(): string {
|
private selectedFqbnAddress(): string {
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
const fqbn = boardsConfig.selectedBoard?.fqbn;
|
const fqbn = boardsConfig.selectedBoard?.fqbn;
|
||||||
if (!fqbn) {
|
if (!fqbn) {
|
||||||
return '';
|
return '';
|
||||||
@ -76,7 +67,7 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
if (this.boardRequiresUserFields && !this.cachedUserFields.has(key)) {
|
if (this.boardRequiresUserFields && !this.cachedUserFields.has(key)) {
|
||||||
// Deep clone the array of board fields to avoid editing the cached ones
|
// Deep clone the array of board fields to avoid editing the cached ones
|
||||||
this.userFieldsDialog.value = (
|
this.userFieldsDialog.value = (
|
||||||
await this.boardsServiceClientImpl.selectedBoardUserFields()
|
await this.boardsServiceProvider.selectedBoardUserFields()
|
||||||
).map((f) => ({ ...f }));
|
).map((f) => ({ ...f }));
|
||||||
const result = await this.userFieldsDialog.open();
|
const result = await this.userFieldsDialog.open();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -98,8 +89,7 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
const cached = this.cachedUserFields.get(key);
|
const cached = this.cachedUserFields.get(key);
|
||||||
// Deep clone the array of board fields to avoid editing the cached ones
|
// Deep clone the array of board fields to avoid editing the cached ones
|
||||||
this.userFieldsDialog.value = (
|
this.userFieldsDialog.value = (
|
||||||
cached ??
|
cached ?? (await this.boardsServiceProvider.selectedBoardUserFields())
|
||||||
(await this.boardsServiceClientImpl.selectedBoardUserFields())
|
|
||||||
).map((f) => ({ ...f }));
|
).map((f) => ({ ...f }));
|
||||||
|
|
||||||
const result = await this.userFieldsDialog.open();
|
const result = await this.userFieldsDialog.open();
|
||||||
@ -130,7 +120,6 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
|
|
||||||
override registerMenus(registry: MenuModelRegistry): void {
|
override registerMenus(registry: MenuModelRegistry): void {
|
||||||
this.menuActionsDisposables.dispose();
|
this.menuActionsDisposables.dispose();
|
||||||
|
|
||||||
this.menuActionsDisposables.push(
|
this.menuActionsDisposables.push(
|
||||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||||
@ -153,7 +142,7 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
new PlaceholderMenuNode(
|
new PlaceholderMenuNode(
|
||||||
ArduinoMenus.SKETCH__MAIN_GROUP,
|
ArduinoMenus.SKETCH__MAIN_GROUP,
|
||||||
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
|
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
|
||||||
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label!,
|
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
|
||||||
{ order: '2' }
|
{ order: '2' }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -193,57 +182,42 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async uploadSketch(usingProgrammer = false): Promise<void> {
|
async uploadSketch(usingProgrammer = false): Promise<void> {
|
||||||
// even with buttons disabled, better to double check if an upload is already in progress
|
|
||||||
if (this.uploadInProgress) {
|
if (this.uploadInProgress) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
|
||||||
if (!CurrentSketch.isValid(sketch)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// toggle the toolbar button and menu item state.
|
// toggle the toolbar button and menu item state.
|
||||||
// uploadInProgress will be set to false whether the upload fails or not
|
// uploadInProgress will be set to false whether the upload fails or not
|
||||||
this.uploadInProgress = true;
|
this.uploadInProgress = true;
|
||||||
this.coreErrorHandler.reset();
|
|
||||||
this.onDidChangeEmitter.fire();
|
this.onDidChangeEmitter.fire();
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
|
||||||
const [
|
|
||||||
fqbn,
|
|
||||||
{ selectedProgrammer },
|
|
||||||
verify,
|
|
||||||
uploadVerbose,
|
|
||||||
sourceOverride,
|
|
||||||
optimizeForDebug,
|
|
||||||
compileVerbose,
|
|
||||||
] = await Promise.all([
|
|
||||||
this.boardsDataStore.appendConfigToFqbn(
|
|
||||||
boardsConfig.selectedBoard?.fqbn
|
|
||||||
),
|
|
||||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
|
||||||
this.preferences.get('arduino.upload.verify'),
|
|
||||||
this.preferences.get('arduino.upload.verbose'),
|
|
||||||
this.sourceOverride(),
|
|
||||||
this.commandService.executeCommand<boolean>(
|
|
||||||
'arduino-is-optimize-for-debug'
|
|
||||||
),
|
|
||||||
this.preferences.get('arduino.compile.verbose'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const verbose = { compile: compileVerbose, upload: uploadVerbose };
|
const verifyOptions =
|
||||||
const board = {
|
await this.commandService.executeCommand<CoreService.Options.Compile>(
|
||||||
...boardsConfig.selectedBoard,
|
'arduino-verify-sketch',
|
||||||
name: boardsConfig.selectedBoard?.name || '',
|
<VerifySketchParams>{
|
||||||
fqbn,
|
exportBinaries: false,
|
||||||
};
|
silent: true,
|
||||||
let options: CoreService.Upload.Options | undefined = undefined;
|
}
|
||||||
const { selectedPort } = boardsConfig;
|
);
|
||||||
const port = selectedPort;
|
if (!verifyOptions) {
|
||||||
const userFields =
|
return;
|
||||||
this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
|
}
|
||||||
if (userFields.length === 0 && this.boardRequiresUserFields) {
|
|
||||||
|
const uploadOptions = await this.uploadOptions(
|
||||||
|
usingProgrammer,
|
||||||
|
verifyOptions
|
||||||
|
);
|
||||||
|
if (!uploadOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This does not belong here.
|
||||||
|
// IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message.
|
||||||
|
if (
|
||||||
|
uploadOptions.userFields.length === 0 &&
|
||||||
|
this.boardRequiresUserFields
|
||||||
|
) {
|
||||||
this.messageService.error(
|
this.messageService.error(
|
||||||
nls.localize(
|
nls.localize(
|
||||||
'arduino/sketch/userFieldsNotFoundError',
|
'arduino/sketch/userFieldsNotFoundError',
|
||||||
@ -253,37 +227,13 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usingProgrammer) {
|
await this.doWithProgress({
|
||||||
const programmer = selectedProgrammer;
|
progressText: nls.localize('arduino/sketch/uploading', 'Uploading...'),
|
||||||
options = {
|
task: (progressId, coreService) =>
|
||||||
sketch,
|
coreService.upload({ ...uploadOptions, progressId }),
|
||||||
board,
|
keepOutput: true,
|
||||||
optimizeForDebug: Boolean(optimizeForDebug),
|
});
|
||||||
programmer,
|
|
||||||
port,
|
|
||||||
verbose,
|
|
||||||
verify,
|
|
||||||
sourceOverride,
|
|
||||||
userFields,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
options = {
|
|
||||||
sketch,
|
|
||||||
board,
|
|
||||||
optimizeForDebug: Boolean(optimizeForDebug),
|
|
||||||
port,
|
|
||||||
verbose,
|
|
||||||
verify,
|
|
||||||
sourceOverride,
|
|
||||||
userFields,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.outputChannelManager.getChannel('Arduino').clear();
|
|
||||||
if (usingProgrammer) {
|
|
||||||
await this.coreService.uploadUsingProgrammer(options);
|
|
||||||
} else {
|
|
||||||
await this.coreService.upload(options);
|
|
||||||
}
|
|
||||||
this.messageService.info(
|
this.messageService.info(
|
||||||
nls.localize('arduino/sketch/doneUploading', 'Done uploading.'),
|
nls.localize('arduino/sketch/doneUploading', 'Done uploading.'),
|
||||||
{ timeout: 3000 }
|
{ timeout: 3000 }
|
||||||
@ -295,6 +245,52 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
this.onDidChangeEmitter.fire();
|
this.onDidChangeEmitter.fire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async uploadOptions(
|
||||||
|
usingProgrammer: boolean,
|
||||||
|
verifyOptions: CoreService.Options.Compile
|
||||||
|
): Promise<CoreService.Options.Upload | undefined> {
|
||||||
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (!CurrentSketch.isValid(sketch)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const userFields = this.userFields();
|
||||||
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
|
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
||||||
|
await Promise.all([
|
||||||
|
verifyOptions.fqbn, // already decorated FQBN
|
||||||
|
this.boardsDataStore.getData(this.sanitizeFqbn(verifyOptions.fqbn)),
|
||||||
|
this.preferences.get('arduino.upload.verify'),
|
||||||
|
this.preferences.get('arduino.upload.verbose'),
|
||||||
|
]);
|
||||||
|
const port = boardsConfig.selectedPort;
|
||||||
|
return {
|
||||||
|
sketch,
|
||||||
|
fqbn,
|
||||||
|
...(usingProgrammer && { programmer }),
|
||||||
|
port,
|
||||||
|
verbose,
|
||||||
|
verify,
|
||||||
|
userFields,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private userFields() {
|
||||||
|
return this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to
|
||||||
|
* `VENDOR:ARCHITECTURE:BOARD_ID` format.
|
||||||
|
* See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties).
|
||||||
|
*/
|
||||||
|
private sanitizeFqbn(fqbn: string | undefined): string | undefined {
|
||||||
|
if (!fqbn) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const [vendor, arch, id] = fqbn.split(':');
|
||||||
|
return `${vendor}:${arch}:${id}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace UploadSketch {
|
export namespace UploadSketch {
|
||||||
@ -302,7 +298,7 @@ export namespace UploadSketch {
|
|||||||
export const UPLOAD_SKETCH: Command = {
|
export const UPLOAD_SKETCH: Command = {
|
||||||
id: 'arduino-upload-sketch',
|
id: 'arduino-upload-sketch',
|
||||||
};
|
};
|
||||||
export const UPLOAD_WITH_CONFIGURATION: Command = {
|
export const UPLOAD_WITH_CONFIGURATION: Command & { label: string } = {
|
||||||
id: 'arduino-upload-with-configuration-sketch',
|
id: 'arduino-upload-with-configuration-sketch',
|
||||||
label: nls.localize(
|
label: nls.localize(
|
||||||
'arduino/sketch/configureAndUpload',
|
'arduino/sketch/configureAndUpload',
|
||||||
|
@ -2,8 +2,6 @@ import { inject, injectable } from '@theia/core/shared/inversify';
|
|||||||
import { Emitter } from '@theia/core/lib/common/event';
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
|
||||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
|
||||||
import {
|
import {
|
||||||
CoreServiceContribution,
|
CoreServiceContribution,
|
||||||
Command,
|
Command,
|
||||||
@ -14,27 +12,36 @@ import {
|
|||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import { CoreService } from '../../common/protocol';
|
||||||
|
import { CoreErrorHandler } from './core-error-handler';
|
||||||
|
|
||||||
|
export interface VerifySketchParams {
|
||||||
|
/**
|
||||||
|
* Same as `CoreService.Options.Compile#exportBinaries`
|
||||||
|
*/
|
||||||
|
readonly exportBinaries?: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, there won't be any UI indication of the verify command. It's `false` by default.
|
||||||
|
*/
|
||||||
|
readonly silent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class VerifySketch extends CoreServiceContribution {
|
export class VerifySketch extends CoreServiceContribution {
|
||||||
@inject(BoardsDataStore)
|
@inject(CoreErrorHandler)
|
||||||
protected readonly boardsDataStore: BoardsDataStore;
|
private readonly coreErrorHandler: CoreErrorHandler;
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
private readonly onDidChangeEmitter = new Emitter<void>();
|
||||||
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
|
private readonly onDidChange = this.onDidChangeEmitter.event;
|
||||||
|
private verifyInProgress = false;
|
||||||
protected readonly onDidChangeEmitter = new Emitter<Readonly<void>>();
|
|
||||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
|
||||||
|
|
||||||
protected verifyInProgress = false;
|
|
||||||
|
|
||||||
override registerCommands(registry: CommandRegistry): void {
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
||||||
execute: () => this.verifySketch(),
|
execute: (params?: VerifySketchParams) => this.verifySketch(params),
|
||||||
isEnabled: () => !this.verifyInProgress,
|
isEnabled: () => !this.verifyInProgress,
|
||||||
});
|
});
|
||||||
registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, {
|
registry.registerCommand(VerifySketch.Commands.EXPORT_BINARIES, {
|
||||||
execute: () => this.verifySketch(true),
|
execute: () => this.verifySketch({ exportBinaries: true }),
|
||||||
isEnabled: () => !this.verifyInProgress,
|
isEnabled: () => !this.verifyInProgress,
|
||||||
});
|
});
|
||||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
||||||
@ -84,61 +91,87 @@ export class VerifySketch extends CoreServiceContribution {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifySketch(exportBinaries?: boolean): Promise<void> {
|
protected override handleError(error: unknown): void {
|
||||||
// even with buttons disabled, better to double check if a verify is already in progress
|
this.coreErrorHandler.tryHandle(error);
|
||||||
|
super.handleError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async verifySketch(
|
||||||
|
params?: VerifySketchParams
|
||||||
|
): Promise<CoreService.Options.Compile | undefined> {
|
||||||
if (this.verifyInProgress) {
|
if (this.verifyInProgress) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggle the toolbar button and menu item state.
|
|
||||||
// verifyInProgress will be set to false whether the compilation fails or not
|
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
|
||||||
if (!CurrentSketch.isValid(sketch)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
this.verifyInProgress = true;
|
if (!params?.silent) {
|
||||||
|
this.verifyInProgress = true;
|
||||||
|
this.onDidChangeEmitter.fire();
|
||||||
|
}
|
||||||
this.coreErrorHandler.reset();
|
this.coreErrorHandler.reset();
|
||||||
this.onDidChangeEmitter.fire();
|
|
||||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
const options = await this.options(params?.exportBinaries);
|
||||||
const [fqbn, sourceOverride] = await Promise.all([
|
if (!options) {
|
||||||
this.boardsDataStore.appendConfigToFqbn(
|
return undefined;
|
||||||
boardsConfig.selectedBoard?.fqbn
|
}
|
||||||
|
|
||||||
|
await this.doWithProgress({
|
||||||
|
progressText: nls.localize(
|
||||||
|
'arduino/sketch/compile',
|
||||||
|
'Compiling sketch...'
|
||||||
),
|
),
|
||||||
this.sourceOverride(),
|
task: (progressId, coreService) =>
|
||||||
]);
|
coreService.compile({
|
||||||
const board = {
|
...options,
|
||||||
...boardsConfig.selectedBoard,
|
progressId,
|
||||||
name: boardsConfig.selectedBoard?.name || '',
|
}),
|
||||||
fqbn,
|
|
||||||
};
|
|
||||||
const verbose = this.preferences.get('arduino.compile.verbose');
|
|
||||||
const compilerWarnings = this.preferences.get('arduino.compile.warnings');
|
|
||||||
const optimizeForDebug =
|
|
||||||
await this.commandService.executeCommand<boolean>(
|
|
||||||
'arduino-is-optimize-for-debug'
|
|
||||||
);
|
|
||||||
this.outputChannelManager.getChannel('Arduino').clear();
|
|
||||||
await this.coreService.compile({
|
|
||||||
sketch,
|
|
||||||
board,
|
|
||||||
optimizeForDebug: Boolean(optimizeForDebug),
|
|
||||||
verbose,
|
|
||||||
exportBinaries,
|
|
||||||
sourceOverride,
|
|
||||||
compilerWarnings,
|
|
||||||
});
|
});
|
||||||
this.messageService.info(
|
this.messageService.info(
|
||||||
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),
|
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),
|
||||||
{ timeout: 3000 }
|
{ timeout: 3000 }
|
||||||
);
|
);
|
||||||
|
// Returns with the used options for the compilation
|
||||||
|
// so that follow-up tasks (such as upload) can reuse the compiled code.
|
||||||
|
// Note that the `fqbn` is already decorated with the board settings, if any.
|
||||||
|
return options;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.handleError(e);
|
this.handleError(e);
|
||||||
|
return undefined;
|
||||||
} finally {
|
} finally {
|
||||||
this.verifyInProgress = false;
|
this.verifyInProgress = false;
|
||||||
this.onDidChangeEmitter.fire();
|
if (!params?.silent) {
|
||||||
|
this.onDidChangeEmitter.fire();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async options(
|
||||||
|
exportBinaries?: boolean
|
||||||
|
): Promise<CoreService.Options.Compile | undefined> {
|
||||||
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (!CurrentSketch.isValid(sketch)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
|
const [fqbn, sourceOverride, optimizeForDebug] = await Promise.all([
|
||||||
|
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||||
|
this.sourceOverride(),
|
||||||
|
this.commandService.executeCommand<boolean>(
|
||||||
|
'arduino-is-optimize-for-debug'
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
const verbose = this.preferences.get('arduino.compile.verbose');
|
||||||
|
const compilerWarnings = this.preferences.get('arduino.compile.warnings');
|
||||||
|
return {
|
||||||
|
sketch,
|
||||||
|
fqbn,
|
||||||
|
optimizeForDebug: Boolean(optimizeForDebug),
|
||||||
|
verbose,
|
||||||
|
exportBinaries,
|
||||||
|
sourceOverride,
|
||||||
|
compilerWarnings,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace VerifySketch {
|
export namespace VerifySketch {
|
||||||
|
@ -5,6 +5,7 @@ import { CommandService } from '@theia/core/lib/common/command';
|
|||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||||
import { Searchable } from '../../../common/protocol/searchable';
|
import { Searchable } from '../../../common/protocol/searchable';
|
||||||
|
import { ExecuteWithProgress } from '../../../common/protocol/progressible';
|
||||||
import { Installable } from '../../../common/protocol/installable';
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
import { SearchBar } from './search-bar';
|
import { SearchBar } from './search-bar';
|
||||||
@ -111,7 +112,7 @@ export class FilterableListContainer<
|
|||||||
version: Installable.Version
|
version: Installable.Version
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { install, searchable } = this.props;
|
const { install, searchable } = this.props;
|
||||||
await Installable.doWithProgress({
|
await ExecuteWithProgress.doWithProgress({
|
||||||
...this.props,
|
...this.props,
|
||||||
progressText:
|
progressText:
|
||||||
nls.localize('arduino/common/processing', 'Processing') +
|
nls.localize('arduino/common/processing', 'Processing') +
|
||||||
@ -137,7 +138,7 @@ export class FilterableListContainer<
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { uninstall, searchable } = this.props;
|
const { uninstall, searchable } = this.props;
|
||||||
await Installable.doWithProgress({
|
await ExecuteWithProgress.doWithProgress({
|
||||||
...this.props,
|
...this.props,
|
||||||
progressText:
|
progressText:
|
||||||
nls.localize('arduino/common/processing', 'Processing') +
|
nls.localize('arduino/common/processing', 'Processing') +
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
||||||
import type { Location } from '@theia/core/shared/vscode-languageserver-protocol';
|
import type { Location } from '@theia/core/shared/vscode-languageserver-protocol';
|
||||||
import type {
|
import type {
|
||||||
Board,
|
|
||||||
BoardUserField,
|
BoardUserField,
|
||||||
Port,
|
Port,
|
||||||
} from '../../common/protocol/boards-service';
|
} from '../../common/protocol/boards-service';
|
||||||
@ -60,46 +59,39 @@ export namespace CoreError {
|
|||||||
export const CoreServicePath = '/services/core-service';
|
export const CoreServicePath = '/services/core-service';
|
||||||
export const CoreService = Symbol('CoreService');
|
export const CoreService = Symbol('CoreService');
|
||||||
export interface CoreService {
|
export interface CoreService {
|
||||||
compile(
|
compile(options: CoreService.Options.Compile): Promise<void>;
|
||||||
options: CoreService.Compile.Options &
|
upload(options: CoreService.Options.Upload): Promise<void>;
|
||||||
Readonly<{
|
burnBootloader(options: CoreService.Options.Bootloader): Promise<void>;
|
||||||
exportBinaries?: boolean;
|
|
||||||
compilerWarnings?: CompilerWarnings;
|
|
||||||
}>
|
|
||||||
): Promise<void>;
|
|
||||||
upload(options: CoreService.Upload.Options): Promise<void>;
|
|
||||||
uploadUsingProgrammer(options: CoreService.Upload.Options): Promise<void>;
|
|
||||||
burnBootloader(options: CoreService.Bootloader.Options): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace CoreService {
|
export namespace CoreService {
|
||||||
export namespace Compile {
|
export namespace Options {
|
||||||
export interface Options {
|
export interface Base {
|
||||||
|
readonly fqbn?: string | undefined;
|
||||||
|
readonly verbose: boolean; // TODO: (API) why not optional with a default false?
|
||||||
|
readonly progressId?: string;
|
||||||
|
}
|
||||||
|
export interface SketchBased {
|
||||||
readonly sketch: Sketch;
|
readonly sketch: Sketch;
|
||||||
readonly board?: Board;
|
|
||||||
readonly optimizeForDebug: boolean;
|
|
||||||
readonly verbose: boolean;
|
|
||||||
readonly sourceOverride: Record<string, string>;
|
|
||||||
}
|
}
|
||||||
}
|
export interface BoardBased {
|
||||||
|
|
||||||
export namespace Upload {
|
|
||||||
export interface Options extends Omit<Compile.Options, 'verbose'> {
|
|
||||||
readonly port?: Port;
|
readonly port?: Port;
|
||||||
readonly programmer?: Programmer | undefined;
|
readonly programmer?: Programmer | undefined;
|
||||||
readonly verify: boolean;
|
/**
|
||||||
|
* For the _Verify after upload_ setting.
|
||||||
|
*/
|
||||||
|
readonly verify: boolean; // TODO: (API) why not optional with false as the default value?
|
||||||
|
}
|
||||||
|
export interface Compile extends Base, SketchBased {
|
||||||
|
readonly optimizeForDebug: boolean; // TODO: (API) make this optional
|
||||||
|
readonly sourceOverride: Record<string, string>; // TODO: (API) make this optional
|
||||||
|
readonly exportBinaries?: boolean;
|
||||||
|
readonly compilerWarnings?: CompilerWarnings;
|
||||||
|
}
|
||||||
|
export interface Upload extends Base, SketchBased, BoardBased {
|
||||||
readonly userFields: BoardUserField[];
|
readonly userFields: BoardUserField[];
|
||||||
readonly verbose: { compile: boolean; upload: boolean };
|
readonly usingProgrammer?: boolean;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Bootloader {
|
|
||||||
export interface Options {
|
|
||||||
readonly board?: Board;
|
|
||||||
readonly port?: Port;
|
|
||||||
readonly programmer?: Programmer | undefined;
|
|
||||||
readonly verbose: boolean;
|
|
||||||
readonly verify: boolean;
|
|
||||||
}
|
}
|
||||||
|
export interface Bootloader extends Base, BoardBased {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import type { Progress } from '@theia/core/lib/common/message-service-protocol';
|
import { ExecuteWithProgress } from './progressible';
|
||||||
import {
|
import { naturalCompare } from '../utils';
|
||||||
CancellationToken,
|
|
||||||
CancellationTokenSource,
|
|
||||||
} from '@theia/core/lib/common/cancellation';
|
|
||||||
import { naturalCompare } from './../utils';
|
|
||||||
import type { ArduinoComponent } from './arduino-component';
|
import type { ArduinoComponent } from './arduino-component';
|
||||||
import type { MessageService } from '@theia/core/lib/common/message-service';
|
import type { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import type { ResponseServiceClient } from './response-service';
|
import type { ResponseServiceClient } from './response-service';
|
||||||
@ -32,7 +28,7 @@ export namespace Installable {
|
|||||||
/**
|
/**
|
||||||
* Most recent version comes first, then the previous versions. (`1.8.1`, `1.6.3`, `1.6.2`, `1.6.1` and so on.)
|
* Most recent version comes first, then the previous versions. (`1.8.1`, `1.6.3`, `1.6.2`, `1.6.1` and so on.)
|
||||||
*/
|
*/
|
||||||
export const COMPARATOR = (left: Version, right: Version) => {
|
export const COMPARATOR = (left: Version, right: Version): number => {
|
||||||
if (semver.valid(left) && semver.valid(right)) {
|
if (semver.valid(left) && semver.valid(right)) {
|
||||||
return semver.compare(left, right);
|
return semver.compare(left, right);
|
||||||
}
|
}
|
||||||
@ -50,7 +46,7 @@ export namespace Installable {
|
|||||||
version: Installable.Version;
|
version: Installable.Version;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { item, version } = options;
|
const { item, version } = options;
|
||||||
return doWithProgress({
|
return ExecuteWithProgress.doWithProgress({
|
||||||
...options,
|
...options,
|
||||||
progressText: `Processing ${item.name}:${version}`,
|
progressText: `Processing ${item.name}:${version}`,
|
||||||
run: ({ progressId }) =>
|
run: ({ progressId }) =>
|
||||||
@ -71,7 +67,7 @@ export namespace Installable {
|
|||||||
item: T;
|
item: T;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { item } = options;
|
const { item } = options;
|
||||||
return doWithProgress({
|
return ExecuteWithProgress.doWithProgress({
|
||||||
...options,
|
...options,
|
||||||
progressText: `Processing ${item.name}${
|
progressText: `Processing ${item.name}${
|
||||||
item.installedVersion ? `:${item.installedVersion}` : ''
|
item.installedVersion ? `:${item.installedVersion}` : ''
|
||||||
@ -83,51 +79,4 @@ export namespace Installable {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doWithProgress(options: {
|
|
||||||
run: ({ progressId }: { progressId: string }) => Promise<void>;
|
|
||||||
messageService: MessageService;
|
|
||||||
responseService: ResponseServiceClient;
|
|
||||||
progressText: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
return withProgress(
|
|
||||||
options.progressText,
|
|
||||||
options.messageService,
|
|
||||||
async (progress, _) => {
|
|
||||||
const progressId = progress.id;
|
|
||||||
const toDispose = options.responseService.onProgressDidChange(
|
|
||||||
(progressMessage) => {
|
|
||||||
if (progressId === progressMessage.progressId) {
|
|
||||||
const { message, work } = progressMessage;
|
|
||||||
progress.report({ message, work });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
options.responseService.clearOutput();
|
|
||||||
await options.run({ progressId });
|
|
||||||
} finally {
|
|
||||||
toDispose.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function withProgress(
|
|
||||||
text: string,
|
|
||||||
messageService: MessageService,
|
|
||||||
cb: (progress: Progress, token: CancellationToken) => Promise<void>
|
|
||||||
): Promise<void> {
|
|
||||||
const cancellationSource = new CancellationTokenSource();
|
|
||||||
const { token } = cancellationSource;
|
|
||||||
const progress = await messageService.showProgress(
|
|
||||||
{ text, options: { cancelable: false } },
|
|
||||||
() => cancellationSource.cancel()
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
await cb(progress, token);
|
|
||||||
} finally {
|
|
||||||
progress.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
60
arduino-ide-extension/src/common/protocol/progressible.ts
Normal file
60
arduino-ide-extension/src/common/protocol/progressible.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||||
|
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
|
||||||
|
import type { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
|
import type { Progress } from '@theia/core/lib/common/message-service-protocol';
|
||||||
|
import type { ResponseServiceClient } from './response-service';
|
||||||
|
|
||||||
|
export namespace ExecuteWithProgress {
|
||||||
|
export async function doWithProgress<T>(options: {
|
||||||
|
run: ({ progressId }: { progressId: string }) => Promise<T>;
|
||||||
|
messageService: MessageService;
|
||||||
|
responseService: ResponseServiceClient;
|
||||||
|
progressText: string;
|
||||||
|
keepOutput?: boolean;
|
||||||
|
}): Promise<T> {
|
||||||
|
return withProgress(
|
||||||
|
options.progressText,
|
||||||
|
options.messageService,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
async (progress, _token) => {
|
||||||
|
const progressId = progress.id;
|
||||||
|
const toDispose = options.responseService.onProgressDidChange(
|
||||||
|
(progressMessage) => {
|
||||||
|
if (progressId === progressMessage.progressId) {
|
||||||
|
const { message, work } = progressMessage;
|
||||||
|
progress.report({ message, work });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
if (!options.keepOutput) {
|
||||||
|
options.responseService.clearOutput();
|
||||||
|
}
|
||||||
|
const result = await options.run({ progressId });
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
toDispose.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withProgress<T>(
|
||||||
|
text: string,
|
||||||
|
messageService: MessageService,
|
||||||
|
cb: (progress: Progress, token: CancellationToken) => Promise<T>
|
||||||
|
): Promise<T> {
|
||||||
|
const cancellationSource = new CancellationTokenSource();
|
||||||
|
const { token } = cancellationSource;
|
||||||
|
const progress = await messageService.showProgress(
|
||||||
|
{ text, options: { cancelable: false } },
|
||||||
|
() => cancellationSource.cancel()
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const result = await cb(progress, token);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
progress.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,5 +46,5 @@ export interface ResponseService {
|
|||||||
export const ResponseServiceClient = Symbol('ResponseServiceClient');
|
export const ResponseServiceClient = Symbol('ResponseServiceClient');
|
||||||
export interface ResponseServiceClient extends ResponseService {
|
export interface ResponseServiceClient extends ResponseService {
|
||||||
onProgressDidChange: Event<ProgressMessage>;
|
onProgressDidChange: Event<ProgressMessage>;
|
||||||
clearOutput: () => void;
|
clearOutput: () => void; // TODO: this should not belong here.
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,12 @@ import { tryParseError } from './cli-error-parser';
|
|||||||
import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||||
import { firstToUpperCase, notEmpty } from '../common/utils';
|
import { firstToUpperCase, notEmpty } from '../common/utils';
|
||||||
import { ServiceError } from './service-error';
|
import { ServiceError } from './service-error';
|
||||||
|
import { ExecuteWithProgress, ProgressResponse } from './grpc-progressible';
|
||||||
|
|
||||||
|
namespace Uploadable {
|
||||||
|
export type Request = UploadRequest | UploadUsingProgrammerRequest;
|
||||||
|
export type Response = UploadResponse | UploadUsingProgrammerResponse;
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||||
@ -45,27 +51,27 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
@inject(CommandService)
|
@inject(CommandService)
|
||||||
private readonly commandService: CommandService;
|
private readonly commandService: CommandService;
|
||||||
|
|
||||||
async compile(
|
async compile(options: CoreService.Options.Compile): Promise<void> {
|
||||||
options: CoreService.Compile.Options & {
|
|
||||||
exportBinaries?: boolean;
|
|
||||||
compilerWarnings?: CompilerWarnings;
|
|
||||||
}
|
|
||||||
): Promise<void> {
|
|
||||||
const coreClient = await this.coreClient;
|
const coreClient = await this.coreClient;
|
||||||
const { client, instance } = coreClient;
|
const { client, instance } = coreClient;
|
||||||
let buildPath: string | undefined = undefined;
|
let buildPath: string | undefined = undefined;
|
||||||
const handler = this.createOnDataHandler<CompileResponse>((response) => {
|
const progressHandler = this.createProgressHandler(options);
|
||||||
|
const buildPathHandler = (response: CompileResponse) => {
|
||||||
const currentBuildPath = response.getBuildPath();
|
const currentBuildPath = response.getBuildPath();
|
||||||
if (!buildPath && currentBuildPath) {
|
if (currentBuildPath) {
|
||||||
buildPath = currentBuildPath;
|
buildPath = currentBuildPath;
|
||||||
} else {
|
} else {
|
||||||
if (!!currentBuildPath && currentBuildPath !== buildPath) {
|
if (!!buildPath && currentBuildPath !== buildPath) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The CLI has already provided a build path: <${buildPath}>, and there is a new build path value: <${currentBuildPath}>.`
|
`The CLI has already provided a build path: <${buildPath}>, and IDE2 received a new build path value: <${currentBuildPath}>.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
const handler = this.createOnDataHandler<CompileResponse>(
|
||||||
|
progressHandler,
|
||||||
|
buildPathHandler
|
||||||
|
);
|
||||||
const request = this.compileRequest(options, instance);
|
const request = this.compileRequest(options, instance);
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
client
|
client
|
||||||
@ -132,20 +138,20 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private compileRequest(
|
private compileRequest(
|
||||||
options: CoreService.Compile.Options & {
|
options: CoreService.Options.Compile & {
|
||||||
exportBinaries?: boolean;
|
exportBinaries?: boolean;
|
||||||
compilerWarnings?: CompilerWarnings;
|
compilerWarnings?: CompilerWarnings;
|
||||||
},
|
},
|
||||||
instance: Instance
|
instance: Instance
|
||||||
): CompileRequest {
|
): CompileRequest {
|
||||||
const { sketch, board, compilerWarnings } = options;
|
const { sketch, fqbn, compilerWarnings } = options;
|
||||||
const sketchUri = sketch.uri;
|
const sketchUri = sketch.uri;
|
||||||
const sketchPath = FileUri.fsPath(sketchUri);
|
const sketchPath = FileUri.fsPath(sketchUri);
|
||||||
const request = new CompileRequest();
|
const request = new CompileRequest();
|
||||||
request.setInstance(instance);
|
request.setInstance(instance);
|
||||||
request.setSketchPath(sketchPath);
|
request.setSketchPath(sketchPath);
|
||||||
if (board?.fqbn) {
|
if (fqbn) {
|
||||||
request.setFqbn(board.fqbn);
|
request.setFqbn(fqbn);
|
||||||
}
|
}
|
||||||
if (compilerWarnings) {
|
if (compilerWarnings) {
|
||||||
request.setWarnings(compilerWarnings.toLowerCase());
|
request.setWarnings(compilerWarnings.toLowerCase());
|
||||||
@ -163,60 +169,44 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
upload(options: CoreService.Upload.Options): Promise<void> {
|
upload(options: CoreService.Options.Upload): Promise<void> {
|
||||||
|
const { usingProgrammer } = options;
|
||||||
return this.doUpload(
|
return this.doUpload(
|
||||||
options,
|
options,
|
||||||
() => new UploadRequest(),
|
usingProgrammer
|
||||||
(client, req) => client.upload(req),
|
? new UploadUsingProgrammerRequest()
|
||||||
(message: string, locations: CoreError.ErrorLocation[]) =>
|
: new UploadRequest(),
|
||||||
CoreError.UploadFailed(message, locations),
|
(client) =>
|
||||||
'upload'
|
(usingProgrammer ? client.uploadUsingProgrammer : client.upload).bind(
|
||||||
|
client
|
||||||
|
),
|
||||||
|
usingProgrammer
|
||||||
|
? CoreError.UploadUsingProgrammerFailed
|
||||||
|
: CoreError.UploadFailed,
|
||||||
|
`upload${usingProgrammer ? ' using programmer' : ''}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadUsingProgrammer(
|
protected async doUpload<
|
||||||
options: CoreService.Upload.Options
|
REQ extends Uploadable.Request,
|
||||||
): Promise<void> {
|
RESP extends Uploadable.Response
|
||||||
return this.doUpload(
|
>(
|
||||||
options,
|
options: CoreService.Options.Upload,
|
||||||
() => new UploadUsingProgrammerRequest(),
|
request: REQ,
|
||||||
(client, req) => client.uploadUsingProgrammer(req),
|
responseFactory: (
|
||||||
(message: string, locations: CoreError.ErrorLocation[]) =>
|
client: ArduinoCoreServiceClient
|
||||||
CoreError.UploadUsingProgrammerFailed(message, locations),
|
) => (request: REQ) => ClientReadableStream<RESP>,
|
||||||
'upload using programmer'
|
errorCtor: ApplicationError.Constructor<number, CoreError.ErrorLocation[]>,
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async doUpload(
|
|
||||||
options: CoreService.Upload.Options,
|
|
||||||
requestFactory: () => UploadRequest | UploadUsingProgrammerRequest,
|
|
||||||
responseHandler: (
|
|
||||||
client: ArduinoCoreServiceClient,
|
|
||||||
request: UploadRequest | UploadUsingProgrammerRequest
|
|
||||||
) => ClientReadableStream<UploadResponse | UploadUsingProgrammerResponse>,
|
|
||||||
errorHandler: (
|
|
||||||
message: string,
|
|
||||||
locations: CoreError.ErrorLocation[]
|
|
||||||
) => ApplicationError<number, CoreError.ErrorLocation[]>,
|
|
||||||
task: string
|
task: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.compile({
|
|
||||||
...options,
|
|
||||||
verbose: options.verbose.compile,
|
|
||||||
exportBinaries: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const coreClient = await this.coreClient;
|
const coreClient = await this.coreClient;
|
||||||
const { client, instance } = coreClient;
|
const { client, instance } = coreClient;
|
||||||
const request = this.uploadOrUploadUsingProgrammerRequest(
|
const progressHandler = this.createProgressHandler(options);
|
||||||
options,
|
const handler = this.createOnDataHandler(progressHandler);
|
||||||
instance,
|
const grpcCall = responseFactory(client);
|
||||||
requestFactory
|
|
||||||
);
|
|
||||||
const handler = this.createOnDataHandler();
|
|
||||||
return this.notifyUploadWillStart(options).then(() =>
|
return this.notifyUploadWillStart(options).then(() =>
|
||||||
new Promise<void>((resolve, reject) => {
|
new Promise<void>((resolve, reject) => {
|
||||||
responseHandler(client, request)
|
grpcCall(this.initUploadRequest(request, options, instance))
|
||||||
.on('data', handler.onData)
|
.on('data', handler.onData)
|
||||||
.on('error', (error) => {
|
.on('error', (error) => {
|
||||||
if (!ServiceError.is(error)) {
|
if (!ServiceError.is(error)) {
|
||||||
@ -231,7 +221,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
);
|
);
|
||||||
this.sendResponse(error.details, OutputMessage.Severity.Error);
|
this.sendResponse(error.details, OutputMessage.Severity.Error);
|
||||||
reject(
|
reject(
|
||||||
errorHandler(
|
errorCtor(
|
||||||
message,
|
message,
|
||||||
tryParseError({
|
tryParseError({
|
||||||
content: handler.stderr,
|
content: handler.stderr,
|
||||||
@ -249,24 +239,23 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private uploadOrUploadUsingProgrammerRequest(
|
private initUploadRequest<REQ extends Uploadable.Request>(
|
||||||
options: CoreService.Upload.Options,
|
request: REQ,
|
||||||
instance: Instance,
|
options: CoreService.Options.Upload,
|
||||||
requestFactory: () => UploadRequest | UploadUsingProgrammerRequest
|
instance: Instance
|
||||||
): UploadRequest | UploadUsingProgrammerRequest {
|
): REQ {
|
||||||
const { sketch, board, port, programmer } = options;
|
const { sketch, fqbn, port, programmer } = options;
|
||||||
const sketchPath = FileUri.fsPath(sketch.uri);
|
const sketchPath = FileUri.fsPath(sketch.uri);
|
||||||
const request = requestFactory();
|
|
||||||
request.setInstance(instance);
|
request.setInstance(instance);
|
||||||
request.setSketchPath(sketchPath);
|
request.setSketchPath(sketchPath);
|
||||||
if (board?.fqbn) {
|
if (fqbn) {
|
||||||
request.setFqbn(board.fqbn);
|
request.setFqbn(fqbn);
|
||||||
}
|
}
|
||||||
request.setPort(this.createPort(port));
|
request.setPort(this.createPort(port));
|
||||||
if (programmer) {
|
if (programmer) {
|
||||||
request.setProgrammer(programmer.id);
|
request.setProgrammer(programmer.id);
|
||||||
}
|
}
|
||||||
request.setVerbose(options.verbose.upload);
|
request.setVerbose(options.verbose);
|
||||||
request.setVerify(options.verify);
|
request.setVerify(options.verify);
|
||||||
|
|
||||||
options.userFields.forEach((e) => {
|
options.userFields.forEach((e) => {
|
||||||
@ -275,10 +264,11 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
async burnBootloader(options: CoreService.Bootloader.Options): Promise<void> {
|
async burnBootloader(options: CoreService.Options.Bootloader): Promise<void> {
|
||||||
const coreClient = await this.coreClient;
|
const coreClient = await this.coreClient;
|
||||||
const { client, instance } = coreClient;
|
const { client, instance } = coreClient;
|
||||||
const handler = this.createOnDataHandler();
|
const progressHandler = this.createProgressHandler(options);
|
||||||
|
const handler = this.createOnDataHandler(progressHandler);
|
||||||
const request = this.burnBootloaderRequest(options, instance);
|
const request = this.burnBootloaderRequest(options, instance);
|
||||||
return this.notifyUploadWillStart(options).then(() =>
|
return this.notifyUploadWillStart(options).then(() =>
|
||||||
new Promise<void>((resolve, reject) => {
|
new Promise<void>((resolve, reject) => {
|
||||||
@ -315,14 +305,14 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private burnBootloaderRequest(
|
private burnBootloaderRequest(
|
||||||
options: CoreService.Bootloader.Options,
|
options: CoreService.Options.Bootloader,
|
||||||
instance: Instance
|
instance: Instance
|
||||||
): BurnBootloaderRequest {
|
): BurnBootloaderRequest {
|
||||||
const { board, port, programmer } = options;
|
const { fqbn, port, programmer } = options;
|
||||||
const request = new BurnBootloaderRequest();
|
const request = new BurnBootloaderRequest();
|
||||||
request.setInstance(instance);
|
request.setInstance(instance);
|
||||||
if (board?.fqbn) {
|
if (fqbn) {
|
||||||
request.setFqbn(board.fqbn);
|
request.setFqbn(fqbn);
|
||||||
}
|
}
|
||||||
request.setPort(this.createPort(port));
|
request.setPort(this.createPort(port));
|
||||||
if (programmer) {
|
if (programmer) {
|
||||||
@ -333,8 +323,24 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createProgressHandler<R extends ProgressResponse>(
|
||||||
|
options: CoreService.Options.Base
|
||||||
|
): (response: R) => void {
|
||||||
|
// If client did not provide the progress ID, do nothing.
|
||||||
|
if (!options.progressId) {
|
||||||
|
return () => {
|
||||||
|
/* NOOP */
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ExecuteWithProgress.createDataCallback<R>({
|
||||||
|
progressId: options.progressId,
|
||||||
|
responseService: this.responseService,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private createOnDataHandler<R extends StreamingResponse>(
|
private createOnDataHandler<R extends StreamingResponse>(
|
||||||
onResponse?: (response: R) => void
|
// TODO: why not creating a composite handler with progress, `build_path`, and out/err stream handlers?
|
||||||
|
...handlers: ((response: R) => void)[]
|
||||||
): Disposable & {
|
): Disposable & {
|
||||||
stderr: Buffer[];
|
stderr: Buffer[];
|
||||||
onData: (response: R) => void;
|
onData: (response: R) => void;
|
||||||
@ -347,14 +353,14 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const onData = StreamingResponse.createOnDataHandler(
|
const onData = StreamingResponse.createOnDataHandler({
|
||||||
stderr,
|
stderr,
|
||||||
(out, err) => {
|
onData: (out, err) => {
|
||||||
buffer.addChunk(out);
|
buffer.addChunk(out);
|
||||||
buffer.addChunk(err, OutputMessage.Severity.Error);
|
buffer.addChunk(err, OutputMessage.Severity.Error);
|
||||||
},
|
},
|
||||||
onResponse
|
handlers,
|
||||||
);
|
});
|
||||||
return {
|
return {
|
||||||
dispose: () => buffer.dispose(),
|
dispose: () => buffer.dispose(),
|
||||||
stderr,
|
stderr,
|
||||||
@ -391,7 +397,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
|
|
||||||
private mergeSourceOverrides(
|
private mergeSourceOverrides(
|
||||||
req: { getSourceOverrideMap(): jspb.Map<string, string> },
|
req: { getSourceOverrideMap(): jspb.Map<string, string> },
|
||||||
options: CoreService.Compile.Options
|
options: CoreService.Options.Compile
|
||||||
): void {
|
): void {
|
||||||
const sketchPath = FileUri.fsPath(options.sketch.uri);
|
const sketchPath = FileUri.fsPath(options.sketch.uri);
|
||||||
for (const uri of Object.keys(options.sourceOverride)) {
|
for (const uri of Object.keys(options.sourceOverride)) {
|
||||||
@ -422,18 +428,24 @@ type StreamingResponse =
|
|||||||
namespace StreamingResponse {
|
namespace StreamingResponse {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export function createOnDataHandler<R extends StreamingResponse>(
|
export function createOnDataHandler<R extends StreamingResponse>(
|
||||||
stderr: Uint8Array[],
|
options: StreamingResponse.Options<R>
|
||||||
onData: (out: Uint8Array, err: Uint8Array) => void,
|
|
||||||
onResponse?: (response: R) => void
|
|
||||||
): (response: R) => void {
|
): (response: R) => void {
|
||||||
return (response: R) => {
|
return (response: R) => {
|
||||||
const out = response.getOutStream_asU8();
|
const out = response.getOutStream_asU8();
|
||||||
const err = response.getErrStream_asU8();
|
const err = response.getErrStream_asU8();
|
||||||
stderr.push(err);
|
options.stderr.push(err);
|
||||||
onData(out, err);
|
options.onData(out, err);
|
||||||
if (onResponse) {
|
options.handlers?.forEach((handler) => handler(response));
|
||||||
onResponse(response);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export interface Options<R extends StreamingResponse> {
|
||||||
|
readonly stderr: Uint8Array[];
|
||||||
|
readonly onData: (out: Uint8Array, err: Uint8Array) => void;
|
||||||
|
/**
|
||||||
|
* Additional request handlers.
|
||||||
|
* For example, when tracing the progress of a task and
|
||||||
|
* collecting the output (out, err) and the `build_path` from the CLI.
|
||||||
|
*/
|
||||||
|
readonly handlers?: ((response: R) => void)[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
DownloadProgress,
|
DownloadProgress,
|
||||||
TaskProgress,
|
TaskProgress,
|
||||||
} from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
} from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
||||||
|
import { CompileResponse } from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb';
|
||||||
import {
|
import {
|
||||||
PlatformInstallResponse,
|
PlatformInstallResponse,
|
||||||
PlatformUninstallResponse,
|
PlatformUninstallResponse,
|
||||||
@ -21,6 +22,11 @@ import {
|
|||||||
LibraryUninstallResponse,
|
LibraryUninstallResponse,
|
||||||
ZipLibraryInstallResponse,
|
ZipLibraryInstallResponse,
|
||||||
} from './cli-protocol/cc/arduino/cli/commands/v1/lib_pb';
|
} from './cli-protocol/cc/arduino/cli/commands/v1/lib_pb';
|
||||||
|
import {
|
||||||
|
BurnBootloaderResponse,
|
||||||
|
UploadResponse,
|
||||||
|
UploadUsingProgrammerResponse,
|
||||||
|
} from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
||||||
|
|
||||||
type LibraryProgressResponse =
|
type LibraryProgressResponse =
|
||||||
| LibraryInstallResponse
|
| LibraryInstallResponse
|
||||||
@ -78,15 +84,62 @@ namespace IndexProgressResponse {
|
|||||||
return { download: response.getDownloadProgress() };
|
return { download: response.getDownloadProgress() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* These responses have neither `task` nor `progress` property but for the sake of completeness
|
||||||
|
* on typings (from the gRPC API) and UX, these responses represent an indefinite progress.
|
||||||
|
*/
|
||||||
|
type IndefiniteProgressResponse =
|
||||||
|
| UploadResponse
|
||||||
|
| UploadUsingProgrammerResponse
|
||||||
|
| BurnBootloaderResponse;
|
||||||
|
namespace IndefiniteProgressResponse {
|
||||||
|
export function is(
|
||||||
|
response: unknown
|
||||||
|
): response is IndefiniteProgressResponse {
|
||||||
|
return (
|
||||||
|
response instanceof UploadResponse ||
|
||||||
|
response instanceof UploadUsingProgrammerResponse ||
|
||||||
|
response instanceof BurnBootloaderResponse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type DefiniteProgressResponse = CompileResponse;
|
||||||
|
namespace DefiniteProgressResponse {
|
||||||
|
export function is(response: unknown): response is DefiniteProgressResponse {
|
||||||
|
return response instanceof CompileResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type CoreProgressResponse =
|
||||||
|
| DefiniteProgressResponse
|
||||||
|
| IndefiniteProgressResponse;
|
||||||
|
namespace CoreProgressResponse {
|
||||||
|
export function is(response: unknown): response is CoreProgressResponse {
|
||||||
|
return (
|
||||||
|
DefiniteProgressResponse.is(response) ||
|
||||||
|
IndefiniteProgressResponse.is(response)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function workUnit(response: CoreProgressResponse): UnitOfWork {
|
||||||
|
if (DefiniteProgressResponse.is(response)) {
|
||||||
|
return { task: response.getProgress() };
|
||||||
|
}
|
||||||
|
return UnitOfWork.Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type ProgressResponse =
|
export type ProgressResponse =
|
||||||
| LibraryProgressResponse
|
| LibraryProgressResponse
|
||||||
| PlatformProgressResponse
|
| PlatformProgressResponse
|
||||||
| IndexProgressResponse;
|
| IndexProgressResponse
|
||||||
|
| CoreProgressResponse;
|
||||||
|
|
||||||
interface UnitOfWork {
|
interface UnitOfWork {
|
||||||
task?: TaskProgress;
|
task?: TaskProgress;
|
||||||
download?: DownloadProgress;
|
download?: DownloadProgress;
|
||||||
}
|
}
|
||||||
|
namespace UnitOfWork {
|
||||||
|
export const Unknown: UnitOfWork = {};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It's solely a dev thing. Flip it to `true` if you want to debug the progress from the CLI responses.
|
* It's solely a dev thing. Flip it to `true` if you want to debug the progress from the CLI responses.
|
||||||
@ -115,14 +168,28 @@ export namespace ExecuteWithProgress {
|
|||||||
console.log(`Progress response [${uuid}]: ${json}`);
|
console.log(`Progress response [${uuid}]: ${json}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { task, download } = resolve(response);
|
const unitOfWork = resolve(response);
|
||||||
|
const { task, download } = unitOfWork;
|
||||||
if (!download && !task) {
|
if (!download && !task) {
|
||||||
console.warn(
|
// report a fake unknown progress.
|
||||||
"Implementation error. Neither 'download' nor 'task' is available."
|
if (unitOfWork === UnitOfWork.Unknown && progressId) {
|
||||||
);
|
if (progressId) {
|
||||||
// This is still an API error from the CLI, but IDE2 ignores it.
|
responseService.reportProgress?.({
|
||||||
// Technically, it does not cause an error, but could mess up the progress reporting.
|
progressId,
|
||||||
// See an example of an empty object `{}` repose here: https://github.com/arduino/arduino-ide/issues/906#issuecomment-1171145630.
|
message: '',
|
||||||
|
work: { done: Number.NaN, total: Number.NaN },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
// This is still an API error from the CLI, but IDE2 ignores it.
|
||||||
|
// Technically, it does not cause an error, but could mess up the progress reporting.
|
||||||
|
// See an example of an empty object `{}` repose here: https://github.com/arduino/arduino-ide/issues/906#issuecomment-1171145630.
|
||||||
|
console.warn(
|
||||||
|
"Implementation error. Neither 'download' nor 'task' is available."
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (task && download) {
|
if (task && download) {
|
||||||
@ -132,6 +199,7 @@ export namespace ExecuteWithProgress {
|
|||||||
}
|
}
|
||||||
if (task) {
|
if (task) {
|
||||||
const message = task.getName() || task.getMessage();
|
const message = task.getName() || task.getMessage();
|
||||||
|
const percent = task.getPercent();
|
||||||
if (message) {
|
if (message) {
|
||||||
if (progressId) {
|
if (progressId) {
|
||||||
responseService.reportProgress?.({
|
responseService.reportProgress?.({
|
||||||
@ -141,6 +209,14 @@ export namespace ExecuteWithProgress {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
responseService.appendToOutput?.({ chunk: `${message}\n` });
|
responseService.appendToOutput?.({ chunk: `${message}\n` });
|
||||||
|
} else if (percent) {
|
||||||
|
if (progressId) {
|
||||||
|
responseService.reportProgress?.({
|
||||||
|
progressId,
|
||||||
|
message,
|
||||||
|
work: { done: percent, total: 100 },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (download) {
|
} else if (download) {
|
||||||
if (download.getFile() && !localFile) {
|
if (download.getFile() && !localFile) {
|
||||||
@ -191,38 +267,38 @@ export namespace ExecuteWithProgress {
|
|||||||
return PlatformProgressResponse.workUnit(response);
|
return PlatformProgressResponse.workUnit(response);
|
||||||
} else if (IndexProgressResponse.is(response)) {
|
} else if (IndexProgressResponse.is(response)) {
|
||||||
return IndexProgressResponse.workUnit(response);
|
return IndexProgressResponse.workUnit(response);
|
||||||
|
} else if (CoreProgressResponse.is(response)) {
|
||||||
|
return CoreProgressResponse.workUnit(response);
|
||||||
}
|
}
|
||||||
console.warn('Unhandled gRPC response', response);
|
console.warn('Unhandled gRPC response', response);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
function toJson(response: ProgressResponse): string | undefined {
|
function toJson(response: ProgressResponse): string | undefined {
|
||||||
|
let object: Record<string, unknown> | undefined = undefined;
|
||||||
if (response instanceof LibraryInstallResponse) {
|
if (response instanceof LibraryInstallResponse) {
|
||||||
return JSON.stringify(LibraryInstallResponse.toObject(false, response));
|
object = LibraryInstallResponse.toObject(false, response);
|
||||||
} else if (response instanceof LibraryUninstallResponse) {
|
} else if (response instanceof LibraryUninstallResponse) {
|
||||||
return JSON.stringify(LibraryUninstallResponse.toObject(false, response));
|
object = LibraryUninstallResponse.toObject(false, response);
|
||||||
} else if (response instanceof ZipLibraryInstallResponse) {
|
} else if (response instanceof ZipLibraryInstallResponse) {
|
||||||
return JSON.stringify(
|
object = ZipLibraryInstallResponse.toObject(false, response);
|
||||||
ZipLibraryInstallResponse.toObject(false, response)
|
|
||||||
);
|
|
||||||
} else if (response instanceof PlatformInstallResponse) {
|
} else if (response instanceof PlatformInstallResponse) {
|
||||||
return JSON.stringify(PlatformInstallResponse.toObject(false, response));
|
object = PlatformInstallResponse.toObject(false, response);
|
||||||
} else if (response instanceof PlatformUninstallResponse) {
|
} else if (response instanceof PlatformUninstallResponse) {
|
||||||
return JSON.stringify(
|
object = PlatformUninstallResponse.toObject(false, response);
|
||||||
PlatformUninstallResponse.toObject(false, response)
|
|
||||||
);
|
|
||||||
} else if (response instanceof UpdateIndexResponse) {
|
} else if (response instanceof UpdateIndexResponse) {
|
||||||
return JSON.stringify(UpdateIndexResponse.toObject(false, response));
|
object = UpdateIndexResponse.toObject(false, response);
|
||||||
} else if (response instanceof UpdateLibrariesIndexResponse) {
|
} else if (response instanceof UpdateLibrariesIndexResponse) {
|
||||||
return JSON.stringify(
|
object = UpdateLibrariesIndexResponse.toObject(false, response);
|
||||||
UpdateLibrariesIndexResponse.toObject(false, response)
|
|
||||||
);
|
|
||||||
} else if (response instanceof UpdateCoreLibrariesIndexResponse) {
|
} else if (response instanceof UpdateCoreLibrariesIndexResponse) {
|
||||||
return JSON.stringify(
|
object = UpdateCoreLibrariesIndexResponse.toObject(false, response);
|
||||||
UpdateCoreLibrariesIndexResponse.toObject(false, response)
|
} else if (response instanceof CompileResponse) {
|
||||||
);
|
object = CompileResponse.toObject(false, response);
|
||||||
}
|
}
|
||||||
console.warn('Unhandled gRPC response', response);
|
if (!object) {
|
||||||
return undefined;
|
console.warn('Unhandled gRPC response', response);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return JSON.stringify(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
"boardsManager": "Boards Manager",
|
"boardsManager": "Boards Manager",
|
||||||
"bootloader": {
|
"bootloader": {
|
||||||
"burnBootloader": "Burn Bootloader",
|
"burnBootloader": "Burn Bootloader",
|
||||||
|
"burningBootloader": "Burning bootloader...",
|
||||||
"doneBurningBootloader": "Done burning bootloader."
|
"doneBurningBootloader": "Done burning bootloader."
|
||||||
},
|
},
|
||||||
"burnBootloader": {
|
"burnBootloader": {
|
||||||
@ -306,6 +307,7 @@
|
|||||||
"archiveSketch": "Archive Sketch",
|
"archiveSketch": "Archive Sketch",
|
||||||
"cantOpen": "A folder named \"{0}\" already exists. Can't open sketch.",
|
"cantOpen": "A folder named \"{0}\" already exists. Can't open sketch.",
|
||||||
"close": "Are you sure you want to close the sketch?",
|
"close": "Are you sure you want to close the sketch?",
|
||||||
|
"compile": "Compiling sketch...",
|
||||||
"configureAndUpload": "Configure And Upload",
|
"configureAndUpload": "Configure And Upload",
|
||||||
"createdArchive": "Created archive '{0}'.",
|
"createdArchive": "Created archive '{0}'.",
|
||||||
"doneCompiling": "Done compiling.",
|
"doneCompiling": "Done compiling.",
|
||||||
@ -327,6 +329,7 @@
|
|||||||
"titleSketchbook": "Sketchbook",
|
"titleSketchbook": "Sketchbook",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"uploadUsingProgrammer": "Upload Using Programmer",
|
"uploadUsingProgrammer": "Upload Using Programmer",
|
||||||
|
"uploading": "Uploading...",
|
||||||
"userFieldsNotFoundError": "Can't find user fields for connected board",
|
"userFieldsNotFoundError": "Can't find user fields for connected board",
|
||||||
"verify": "Verify",
|
"verify": "Verify",
|
||||||
"verifyOrCompile": "Verify/Compile"
|
"verifyOrCompile": "Verify/Compile"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user