mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-04-19 12:57:17 +00:00
feat: expose Arduino state to VS Code extensions
- Update a shared state on fqbn, port, sketch path, and etc. changes. VS Code extensions can access it and listen on changes. - Force VISX activation order: API VSIX starts first. Signed-off-by: dankeboy36 <dankeboy36@gmail.com>
This commit is contained in:
parent
c66b720509
commit
42d017e876
@ -104,7 +104,8 @@
|
||||
"temp": "^0.9.1",
|
||||
"temp-dir": "^2.0.0",
|
||||
"tree-kill": "^1.2.1",
|
||||
"util": "^0.12.5"
|
||||
"util": "^0.12.5",
|
||||
"vscode-arduino-api": "^0.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/rest": "^18.12.0",
|
||||
|
@ -354,6 +354,7 @@ import { FileResourceResolver as TheiaFileResourceResolver } from '@theia/filesy
|
||||
import { StylingParticipant } from '@theia/core/lib/browser/styling-service';
|
||||
import { MonacoEditorMenuContribution } from './theia/monaco/monaco-menu';
|
||||
import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } from '@theia/monaco/lib/browser/monaco-menu';
|
||||
import { UpdateArduinoState } from './contributions/update-arduino-state';
|
||||
|
||||
// Hack to fix copy/cut/paste issue after electron version update in Theia.
|
||||
// https://github.com/eclipse-theia/theia/issues/12487
|
||||
@ -747,6 +748,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, Account);
|
||||
Contribution.configure(bind, CloudSketchbookContribution);
|
||||
Contribution.configure(bind, CreateCloudCopy);
|
||||
Contribution.configure(bind, UpdateArduinoState);
|
||||
|
||||
bindContributionProvider(bind, StartupTaskProvider);
|
||||
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
|
||||
|
@ -0,0 +1,179 @@
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import type { ArduinoState } from 'vscode-arduino-api';
|
||||
import {
|
||||
BoardsService,
|
||||
CompileSummary,
|
||||
Port,
|
||||
isCompileSummary,
|
||||
} from '../../common/protocol';
|
||||
import {
|
||||
toApiBoardDetails,
|
||||
toApiCompileSummary,
|
||||
toApiPort,
|
||||
} from '../../common/protocol/arduino-context-mapper';
|
||||
import type { BoardsConfig } from '../boards/boards-config';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||
import { SketchContribution } from './contribution';
|
||||
|
||||
interface UpdateStateParams<T extends ArduinoState> {
|
||||
readonly key: keyof T;
|
||||
readonly value: T[keyof T];
|
||||
}
|
||||
|
||||
/**
|
||||
* Contribution for updating the Arduino state, such as the FQBN, selected port, and sketch path changes via commands, so other VS Code extensions can access it.
|
||||
* See [`vscode-arduino-api`](https://github.com/dankeboy36/vscode-arduino-api#api) for more details.
|
||||
*/
|
||||
@injectable()
|
||||
export class UpdateArduinoState extends SketchContribution {
|
||||
@inject(BoardsService)
|
||||
private readonly boardsService: BoardsService;
|
||||
@inject(BoardsServiceProvider)
|
||||
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
@inject(BoardsDataStore)
|
||||
private readonly boardsDataStore: BoardsDataStore;
|
||||
@inject(HostedPluginSupport)
|
||||
private readonly hostedPluginSupport: HostedPluginSupport;
|
||||
|
||||
private readonly toDispose = new DisposableCollection();
|
||||
|
||||
override onStart(): void {
|
||||
this.toDispose.pushAll([
|
||||
this.boardsServiceProvider.onBoardsConfigChanged((config) =>
|
||||
this.updateBoardsConfig(config)
|
||||
),
|
||||
this.sketchServiceClient.onCurrentSketchDidChange((sketch) =>
|
||||
this.updateSketchPath(sketch)
|
||||
),
|
||||
this.configService.onDidChangeDataDirUri((dataDirUri) =>
|
||||
this.updateDataDirPath(dataDirUri)
|
||||
),
|
||||
this.configService.onDidChangeSketchDirUri((userDirUri) =>
|
||||
this.updateUserDirPath(userDirUri)
|
||||
),
|
||||
this.commandService.onDidExecuteCommand(({ commandId, args }) => {
|
||||
if (
|
||||
commandId === 'arduino.languageserver.notifyBuildDidComplete' &&
|
||||
isCompileSummary(args[0])
|
||||
) {
|
||||
this.updateCompileSummary(args[0]);
|
||||
}
|
||||
}),
|
||||
this.boardsDataStore.onChanged((fqbn) => {
|
||||
const selectedFqbn =
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn;
|
||||
if (selectedFqbn && fqbn.includes(selectedFqbn)) {
|
||||
this.updateBoardDetails(selectedFqbn);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this.boardsServiceProvider.reconciled.then(() => {
|
||||
this.updateBoardsConfig(this.boardsServiceProvider.boardsConfig);
|
||||
});
|
||||
this.updateSketchPath(this.sketchServiceClient.tryGetCurrentSketch());
|
||||
this.updateUserDirPath(this.configService.tryGetSketchDirUri());
|
||||
this.updateDataDirPath(this.configService.tryGetDataDirUri());
|
||||
}
|
||||
|
||||
onStop(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
private async updateSketchPath(
|
||||
sketch: CurrentSketch | undefined
|
||||
): Promise<void> {
|
||||
const sketchPath = CurrentSketch.isValid(sketch)
|
||||
? new URI(sketch.uri).path.fsPath()
|
||||
: undefined;
|
||||
return this.updateState({ key: 'sketchPath', value: sketchPath });
|
||||
}
|
||||
|
||||
private async updateCompileSummary(
|
||||
compileSummary: CompileSummary
|
||||
): Promise<void> {
|
||||
const apiCompileSummary = toApiCompileSummary(compileSummary);
|
||||
return this.updateState({
|
||||
key: 'compileSummary',
|
||||
value: apiCompileSummary,
|
||||
});
|
||||
}
|
||||
|
||||
private async updateBoardsConfig(
|
||||
boardsConfig: BoardsConfig.Config
|
||||
): Promise<void> {
|
||||
const fqbn = boardsConfig.selectedBoard?.fqbn;
|
||||
const port = boardsConfig.selectedPort;
|
||||
await this.updateFqbn(fqbn);
|
||||
await this.updateBoardDetails(fqbn);
|
||||
await this.updatePort(port);
|
||||
}
|
||||
|
||||
private async updateFqbn(fqbn: string | undefined): Promise<void> {
|
||||
await this.updateState({ key: 'fqbn', value: fqbn });
|
||||
}
|
||||
|
||||
private async updateBoardDetails(fqbn: string | undefined): Promise<void> {
|
||||
const unset = () =>
|
||||
this.updateState({ key: 'boardDetails', value: undefined });
|
||||
if (!fqbn) {
|
||||
return unset();
|
||||
}
|
||||
const [details, persistedData] = await Promise.all([
|
||||
this.boardsService.getBoardDetails({ fqbn }),
|
||||
this.boardsDataStore.getData(fqbn),
|
||||
]);
|
||||
if (!details) {
|
||||
return unset();
|
||||
}
|
||||
const apiBoardDetails = toApiBoardDetails({
|
||||
...details,
|
||||
configOptions:
|
||||
BoardsDataStore.Data.EMPTY === persistedData
|
||||
? details.configOptions
|
||||
: persistedData.configOptions.slice(),
|
||||
});
|
||||
return this.updateState({
|
||||
key: 'boardDetails',
|
||||
value: apiBoardDetails,
|
||||
});
|
||||
}
|
||||
|
||||
private async updatePort(port: Port | undefined): Promise<void> {
|
||||
const apiPort = port && toApiPort(port);
|
||||
return this.updateState({ key: 'port', value: apiPort });
|
||||
}
|
||||
|
||||
private async updateUserDirPath(userDirUri: URI | undefined): Promise<void> {
|
||||
const userDirPath = userDirUri?.path.fsPath();
|
||||
return this.updateState({
|
||||
key: 'userDirPath',
|
||||
value: userDirPath,
|
||||
});
|
||||
}
|
||||
|
||||
private async updateDataDirPath(dataDirUri: URI | undefined): Promise<void> {
|
||||
const dataDirPath = dataDirUri?.path.fsPath();
|
||||
return this.updateState({
|
||||
key: 'dataDirPath',
|
||||
value: dataDirPath,
|
||||
});
|
||||
}
|
||||
|
||||
private async updateState<T extends ArduinoState>(
|
||||
params: UpdateStateParams<T>
|
||||
): Promise<void> {
|
||||
await this.hostedPluginSupport.didStart;
|
||||
return this.commandService.executeCommand(
|
||||
'arduinoAPI.updateState',
|
||||
params
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
import { Emitter, Event, JsonRpcProxy } from '@theia/core';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { injectable, interfaces } from '@theia/core/shared/inversify';
|
||||
import { HostedPluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol';
|
||||
import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import {
|
||||
PluginContributions,
|
||||
HostedPluginSupport as TheiaHostedPluginSupport,
|
||||
} from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
|
||||
@injectable()
|
||||
export class HostedPluginSupport extends TheiaHostedPluginSupport {
|
||||
@ -10,7 +13,7 @@ export class HostedPluginSupport extends TheiaHostedPluginSupport {
|
||||
|
||||
override onStart(container: interfaces.Container): void {
|
||||
super.onStart(container);
|
||||
this.hostedPluginServer.onDidCloseConnection(() =>
|
||||
this['server'].onDidCloseConnection(() =>
|
||||
this.onDidCloseConnectionEmitter.fire()
|
||||
);
|
||||
}
|
||||
@ -28,8 +31,38 @@ export class HostedPluginSupport extends TheiaHostedPluginSupport {
|
||||
return this.onDidCloseConnectionEmitter.event;
|
||||
}
|
||||
|
||||
private get hostedPluginServer(): JsonRpcProxy<HostedPluginServer> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (this as any).server;
|
||||
protected override startPlugins(
|
||||
contributionsByHost: Map<string, PluginContributions[]>,
|
||||
toDisconnect: DisposableCollection
|
||||
): Promise<void> {
|
||||
reorderPlugins(contributionsByHost);
|
||||
return super.startPlugins(contributionsByHost, toDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the `vscode-arduino-ide` API to activate before any Arduino IDE tool VSIX.
|
||||
*
|
||||
* Arduino IDE tool VISXs are not forced to declare the `vscode-arduino-api` as a `extensionDependencies`,
|
||||
* but the API must activate before any tools. This in place sorting helps to bypass Theia's plugin resolution
|
||||
* without forcing tools developers to add `vscode-arduino-api` to the `extensionDependencies`.
|
||||
*/
|
||||
function reorderPlugins(
|
||||
contributionsByHost: Map<string, PluginContributions[]>
|
||||
): void {
|
||||
for (const [, contributions] of contributionsByHost) {
|
||||
const apiPluginIndex = contributions.findIndex(isArduinoAPI);
|
||||
if (apiPluginIndex >= 0) {
|
||||
const apiPlugin = contributions[apiPluginIndex];
|
||||
contributions.splice(apiPluginIndex, 1);
|
||||
contributions.unshift(apiPlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isArduinoAPI(pluginContribution: PluginContributions): boolean {
|
||||
return (
|
||||
pluginContribution.plugin.metadata.model.id ===
|
||||
'dankeboy36.vscode-arduino-api'
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,126 @@
|
||||
import type {
|
||||
Port as APIPort,
|
||||
BoardDetails as ApiBoardDetails,
|
||||
BuildProperties as ApiBuildProperties,
|
||||
CompileSummary as ApiCompileSummary,
|
||||
ConfigOption as ApiConfigOption,
|
||||
ConfigValue as ApiConfigValue,
|
||||
Tool as ApiTool,
|
||||
} from 'vscode-arduino-api';
|
||||
import type {
|
||||
BoardDetails,
|
||||
CompileSummary,
|
||||
ConfigOption,
|
||||
ConfigValue,
|
||||
Port,
|
||||
Tool,
|
||||
} from '../protocol';
|
||||
|
||||
export function toApiCompileSummary(
|
||||
compileSummary: CompileSummary
|
||||
): ApiCompileSummary {
|
||||
const {
|
||||
buildPath,
|
||||
buildProperties,
|
||||
boardPlatform,
|
||||
buildPlatform,
|
||||
executableSectionsSize,
|
||||
usedLibraries,
|
||||
} = compileSummary;
|
||||
return {
|
||||
buildPath,
|
||||
buildProperties: toApiBuildProperties(buildProperties),
|
||||
executableSectionsSize: executableSectionsSize,
|
||||
boardPlatform,
|
||||
buildPlatform,
|
||||
usedLibraries,
|
||||
};
|
||||
}
|
||||
|
||||
export function toApiPort(port: Port): APIPort | undefined {
|
||||
const {
|
||||
hardwareId = '',
|
||||
properties = {},
|
||||
address,
|
||||
protocol,
|
||||
protocolLabel,
|
||||
addressLabel: label,
|
||||
} = port;
|
||||
return {
|
||||
label,
|
||||
address,
|
||||
hardwareId,
|
||||
properties,
|
||||
protocol,
|
||||
protocolLabel,
|
||||
};
|
||||
}
|
||||
|
||||
export function toApiBoardDetails(boardDetails: BoardDetails): ApiBoardDetails {
|
||||
const { fqbn, programmers, configOptions, requiredTools } = boardDetails;
|
||||
return {
|
||||
buildProperties: toApiBuildProperties(boardDetails.buildProperties),
|
||||
configOptions: configOptions.map(toApiConfigOption),
|
||||
fqbn,
|
||||
programmers,
|
||||
toolsDependencies: requiredTools.map(toApiTool),
|
||||
};
|
||||
}
|
||||
|
||||
function toApiConfigOption(configOption: ConfigOption): ApiConfigOption {
|
||||
const { label, values, option } = configOption;
|
||||
return {
|
||||
optionLabel: label,
|
||||
option,
|
||||
values: values.map(toApiConfigValue),
|
||||
};
|
||||
}
|
||||
|
||||
function toApiConfigValue(configValue: ConfigValue): ApiConfigValue {
|
||||
const { label, selected, value } = configValue;
|
||||
return {
|
||||
selected,
|
||||
value,
|
||||
valueLabel: label,
|
||||
};
|
||||
}
|
||||
|
||||
function toApiTool(toolDependency: Tool): ApiTool {
|
||||
const { name, packager, version } = toolDependency;
|
||||
return {
|
||||
name,
|
||||
packager,
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
const propertySep = '=';
|
||||
|
||||
function parseProperty(
|
||||
property: string
|
||||
): [key: string, value: string] | undefined {
|
||||
const segments = property.split(propertySep);
|
||||
if (segments.length < 2) {
|
||||
console.warn(`Could not parse build property: ${property}.`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [key, ...rest] = segments;
|
||||
if (!key) {
|
||||
console.warn(`Could not determine property key from raw: ${property}.`);
|
||||
return undefined;
|
||||
}
|
||||
const value = rest.join(propertySep);
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
export function toApiBuildProperties(properties: string[]): ApiBuildProperties {
|
||||
return properties.reduce((acc, curr) => {
|
||||
const entry = parseProperty(curr);
|
||||
if (entry) {
|
||||
const [key, value] = entry;
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, <Record<string, string>>{});
|
||||
}
|
@ -441,6 +441,7 @@ export interface BoardDetails {
|
||||
readonly debuggingSupported: boolean;
|
||||
readonly VID: string;
|
||||
readonly PID: string;
|
||||
readonly buildProperties: string[];
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
|
@ -5,13 +5,11 @@ import type {
|
||||
Range,
|
||||
Position,
|
||||
} from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import type {
|
||||
BoardUserField,
|
||||
Port,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import type { BoardUserField, Port, Installable } from '../../common/protocol/';
|
||||
import type { Programmer } from './boards-service';
|
||||
import type { Sketch } from './sketches-service';
|
||||
import { IndexUpdateSummary } from './notification-service';
|
||||
import type { CompileSummary as ApiCompileSummary } from 'vscode-arduino-api';
|
||||
|
||||
export const CompilerWarningLiterals = [
|
||||
'None',
|
||||
@ -19,7 +17,7 @@ export const CompilerWarningLiterals = [
|
||||
'More',
|
||||
'All',
|
||||
] as const;
|
||||
export type CompilerWarnings = typeof CompilerWarningLiterals[number];
|
||||
export type CompilerWarnings = (typeof CompilerWarningLiterals)[number];
|
||||
export namespace CompilerWarnings {
|
||||
export function labelOf(warning: CompilerWarnings): string {
|
||||
return CompilerWarningLabels[warning];
|
||||
@ -103,6 +101,53 @@ export namespace CoreError {
|
||||
}
|
||||
}
|
||||
|
||||
export interface InstalledPlatformReference {
|
||||
readonly id: string;
|
||||
readonly version: Installable.Version;
|
||||
/**
|
||||
* Absolute filesystem path.
|
||||
*/
|
||||
readonly installDir: string;
|
||||
readonly packageUrl: string;
|
||||
}
|
||||
|
||||
export interface ExecutableSectionSize {
|
||||
readonly name: string;
|
||||
readonly size: number;
|
||||
readonly maxSize: number;
|
||||
}
|
||||
|
||||
export interface CompileSummary {
|
||||
readonly buildPath: string;
|
||||
/**
|
||||
* To be compatible with the `vscode-arduino-tools` API.
|
||||
* @deprecated Use `buildPath` instead. Use Theia or VS Code URI to convert to an URI string on the client side.
|
||||
*/
|
||||
readonly buildOutputUri: string;
|
||||
readonly usedLibraries: ApiCompileSummary['usedLibraries'];
|
||||
readonly executableSectionsSize: ExecutableSectionSize[];
|
||||
readonly boardPlatform?: InstalledPlatformReference | undefined;
|
||||
readonly buildPlatform?: InstalledPlatformReference | undefined;
|
||||
readonly buildProperties: string[];
|
||||
}
|
||||
|
||||
export function isCompileSummary(arg: unknown): arg is CompileSummary {
|
||||
return (
|
||||
Boolean(arg) &&
|
||||
typeof arg === 'object' &&
|
||||
(<CompileSummary>arg).buildPath !== undefined &&
|
||||
typeof (<CompileSummary>arg).buildPath === 'string' &&
|
||||
(<CompileSummary>arg).buildOutputUri !== undefined &&
|
||||
typeof (<CompileSummary>arg).buildOutputUri === 'string' &&
|
||||
(<CompileSummary>arg).executableSectionsSize !== undefined &&
|
||||
Array.isArray((<CompileSummary>arg).executableSectionsSize) &&
|
||||
(<CompileSummary>arg).usedLibraries !== undefined &&
|
||||
Array.isArray((<CompileSummary>arg).usedLibraries) &&
|
||||
(<CompileSummary>arg).buildProperties !== undefined &&
|
||||
Array.isArray((<CompileSummary>arg).buildProperties)
|
||||
);
|
||||
}
|
||||
|
||||
export const CoreServicePath = '/services/core-service';
|
||||
export const CoreService = Symbol('CoreService');
|
||||
export interface CoreService {
|
||||
@ -132,7 +177,7 @@ export interface CoreService {
|
||||
}
|
||||
|
||||
export const IndexTypeLiterals = ['platform', 'library'] as const;
|
||||
export type IndexType = typeof IndexTypeLiterals[number];
|
||||
export type IndexType = (typeof IndexTypeLiterals)[number];
|
||||
export namespace IndexType {
|
||||
export function is(arg: unknown): arg is IndexType {
|
||||
return (
|
||||
|
@ -155,6 +155,7 @@ export class BoardsServiceImpl
|
||||
VID = prop.get('vid') || '';
|
||||
PID = prop.get('pid') || '';
|
||||
}
|
||||
const buildProperties = detailsResp.getBuildPropertiesList();
|
||||
|
||||
return {
|
||||
fqbn,
|
||||
@ -164,6 +165,7 @@ export class BoardsServiceImpl
|
||||
debuggingSupported,
|
||||
VID,
|
||||
PID,
|
||||
buildProperties
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
CompilerWarnings,
|
||||
CoreService,
|
||||
CoreError,
|
||||
CompileSummary,
|
||||
isCompileSummary,
|
||||
} from '../common/protocol/core-service';
|
||||
import {
|
||||
CompileRequest,
|
||||
@ -35,12 +37,15 @@ import { firstToUpperCase, notEmpty } from '../common/utils';
|
||||
import { ServiceError } from './service-error';
|
||||
import { ExecuteWithProgress, ProgressResponse } from './grpc-progressible';
|
||||
import { BoardDiscovery } from './board-discovery';
|
||||
import { Mutable } from '@theia/core/lib/common/types';
|
||||
|
||||
namespace Uploadable {
|
||||
export type Request = UploadRequest | UploadUsingProgrammerRequest;
|
||||
export type Response = UploadResponse | UploadUsingProgrammerResponse;
|
||||
}
|
||||
|
||||
type CompileSummaryFragment = Partial<Mutable<CompileSummary>>;
|
||||
|
||||
@injectable()
|
||||
export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
@inject(ResponseService)
|
||||
@ -58,23 +63,13 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
async compile(options: CoreService.Options.Compile): Promise<void> {
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
let buildPath: string | undefined = undefined;
|
||||
const compileSummary = <CompileSummaryFragment>{};
|
||||
const progressHandler = this.createProgressHandler(options);
|
||||
const buildPathHandler = (response: CompileResponse) => {
|
||||
const currentBuildPath = response.getBuildPath();
|
||||
if (currentBuildPath) {
|
||||
buildPath = currentBuildPath;
|
||||
} else {
|
||||
if (!!buildPath && currentBuildPath !== buildPath) {
|
||||
throw new Error(
|
||||
`The CLI has already provided a build path: <${buildPath}>, and IDE received a new build path value: <${currentBuildPath}>.`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
const compileSummaryHandler = (response: CompileResponse) =>
|
||||
updateCompileSummary(compileSummary, response);
|
||||
const handler = this.createOnDataHandler<CompileResponse>(
|
||||
progressHandler,
|
||||
buildPathHandler
|
||||
compileSummaryHandler
|
||||
);
|
||||
const request = this.compileRequest(options, instance);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
@ -111,31 +106,35 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
.on('end', resolve);
|
||||
}).finally(() => {
|
||||
handler.dispose();
|
||||
if (!buildPath) {
|
||||
if (!isCompileSummary(compileSummary)) {
|
||||
console.error(
|
||||
`Have not received the build path from the CLI while running the compilation.`
|
||||
`Have not received the full compile summary from the CLI while running the compilation. ${JSON.stringify(
|
||||
compileSummary
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
this.fireBuildDidComplete(FileUri.create(buildPath).toString());
|
||||
this.fireBuildDidComplete(compileSummary);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This executes on the frontend, the VS Code extension receives it, and sends an `ino/buildDidComplete` notification to the language server.
|
||||
private fireBuildDidComplete(buildOutputUri: string): void {
|
||||
private fireBuildDidComplete(compileSummary: CompileSummary): void {
|
||||
const params = {
|
||||
buildOutputUri,
|
||||
...compileSummary,
|
||||
};
|
||||
console.info(
|
||||
`Executing 'arduino.languageserver.notifyBuildDidComplete' with ${JSON.stringify(
|
||||
params
|
||||
params.buildOutputUri
|
||||
)}`
|
||||
);
|
||||
this.commandService
|
||||
.executeCommand('arduino.languageserver.notifyBuildDidComplete', params)
|
||||
.catch((err) =>
|
||||
console.error(
|
||||
`Unexpected error when firing event on build did complete. ${buildOutputUri}`,
|
||||
`Unexpected error when firing event on build did complete. ${JSON.stringify(
|
||||
params.buildOutputUri
|
||||
)}`,
|
||||
err
|
||||
)
|
||||
);
|
||||
@ -465,3 +464,62 @@ namespace StreamingResponse {
|
||||
readonly handlers?: ((response: R) => void)[];
|
||||
}
|
||||
}
|
||||
|
||||
function updateCompileSummary(
|
||||
compileSummary: CompileSummaryFragment,
|
||||
response: CompileResponse
|
||||
): CompileSummaryFragment {
|
||||
const buildPath = response.getBuildPath();
|
||||
if (buildPath) {
|
||||
compileSummary.buildPath = buildPath;
|
||||
compileSummary.buildOutputUri = FileUri.create(buildPath).toString();
|
||||
}
|
||||
const executableSectionsSize = response.getExecutableSectionsSizeList();
|
||||
if (executableSectionsSize) {
|
||||
compileSummary.executableSectionsSize = executableSectionsSize.map((item) =>
|
||||
item.toObject(false)
|
||||
);
|
||||
}
|
||||
const usedLibraries = response.getUsedLibrariesList();
|
||||
if (usedLibraries) {
|
||||
compileSummary.usedLibraries = usedLibraries.map((item) => {
|
||||
const object = item.toObject(false);
|
||||
const library = {
|
||||
...object,
|
||||
architectures: object.architecturesList,
|
||||
types: object.typesList,
|
||||
examples: object.examplesList,
|
||||
providesIncludes: object.providesIncludesList,
|
||||
properties: object.propertiesMap.reduce((acc, [key, value]) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {} as Record<string, string>),
|
||||
compatibleWith: object.compatibleWithMap.reduce((acc, [key, value]) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
}, {} as Record<string, boolean>),
|
||||
} as const;
|
||||
const mutable = <Partial<Mutable<typeof library>>>library;
|
||||
delete mutable.architecturesList;
|
||||
delete mutable.typesList;
|
||||
delete mutable.examplesList;
|
||||
delete mutable.providesIncludesList;
|
||||
delete mutable.propertiesMap;
|
||||
delete mutable.compatibleWithMap;
|
||||
return library;
|
||||
});
|
||||
}
|
||||
const boardPlatform = response.getBoardPlatform();
|
||||
if (boardPlatform) {
|
||||
compileSummary.buildPlatform = boardPlatform.toObject(false);
|
||||
}
|
||||
const buildPlatform = response.getBuildPlatform();
|
||||
if (buildPlatform) {
|
||||
compileSummary.buildPlatform = buildPlatform.toObject(false);
|
||||
}
|
||||
const buildProperties = response.getBuildPropertiesList();
|
||||
if (buildProperties) {
|
||||
compileSummary.buildProperties = buildProperties.slice();
|
||||
}
|
||||
return compileSummary;
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { expect } from 'chai';
|
||||
import { toApiBuildProperties } from '../../common/protocol/arduino-context-mapper';
|
||||
|
||||
describe('arduino-context-mapper', () => {
|
||||
describe('toApiBuildProperties', () => {
|
||||
it('should parse an array of build properties string into a record', () => {
|
||||
const expected = {
|
||||
foo: 'alma',
|
||||
bar: '36',
|
||||
baz: 'false',
|
||||
};
|
||||
const actual = toApiBuildProperties(['foo=alma', 'bar=36', 'baz=false']);
|
||||
expect(actual).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should not skip build property key with empty value', () => {
|
||||
const expected = {
|
||||
foo: '',
|
||||
};
|
||||
const actual = toApiBuildProperties(['foo=']);
|
||||
expect(actual).to.be.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('should skip invalid entries', () => {
|
||||
const expected = {
|
||||
foo: 'alma',
|
||||
bar: '36',
|
||||
baz: '-DARDUINO_USB_CDC_ON_BOOT=0',
|
||||
};
|
||||
const actual = toApiBuildProperties([
|
||||
'foo=alma',
|
||||
'invalid',
|
||||
'=invalid2',
|
||||
'=invalid3=',
|
||||
'=',
|
||||
'==',
|
||||
'bar=36',
|
||||
'baz=-DARDUINO_USB_CDC_ON_BOOT=0',
|
||||
]);
|
||||
expect(actual).to.be.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
@ -9,6 +9,7 @@ import {
|
||||
BoardsService,
|
||||
CoreService,
|
||||
SketchesService,
|
||||
isCompileSummary,
|
||||
} from '../../common/protocol';
|
||||
import { createBaseContainer, startDaemon } from './test-bindings';
|
||||
|
||||
@ -31,7 +32,7 @@ describe('core-service-impl', () => {
|
||||
afterEach(() => toDispose.dispose());
|
||||
|
||||
describe('compile', () => {
|
||||
it('should execute a command with the build path', async function () {
|
||||
it('should execute a command with the compile summary, including the build path', async function () {
|
||||
this.timeout(testTimeout);
|
||||
const coreService = container.get<CoreService>(CoreService);
|
||||
const sketchesService = container.get<SketchesService>(SketchesService);
|
||||
@ -56,7 +57,7 @@ describe('core-service-impl', () => {
|
||||
const [, args] = executedBuildDidCompleteCommands[0];
|
||||
expect(args.length).to.be.equal(1);
|
||||
const arg = args[0];
|
||||
expect(typeof arg).to.be.equal('object');
|
||||
expect(isCompileSummary(arg)).to.be.true;
|
||||
expect('buildOutputUri' in arg).to.be.true;
|
||||
expect(arg.buildOutputUri).to.be.not.undefined;
|
||||
|
||||
|
@ -70,6 +70,7 @@
|
||||
"theiaPluginsDir": "plugins",
|
||||
"theiaPlugins": {
|
||||
"vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix",
|
||||
"vscode-arduino-api": "https://github.com/dankeboy36/vscode-arduino-api/releases/download/0.1.2/vscode-arduino-api-0.1.2.vsix",
|
||||
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.8.vsix",
|
||||
"vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix",
|
||||
"vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
|
||||
|
41
yarn.lock
41
yarn.lock
@ -3209,6 +3209,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.5.tgz#b1d2f772142a301538fae9bdf9cf15b9f2573a29"
|
||||
integrity sha512-hKB88y3YHL8oPOs/CNlaXtjWn93+Bs48sDQR37ZUqG2tLeCS7EA1cmnkKsuQsub9OKEB/y/Rw9zqJqqNSbqVlQ==
|
||||
|
||||
"@types/vscode@^1.78.0":
|
||||
version "1.78.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.78.0.tgz#b5600abce8855cf21fb32d0857bcd084b1f83069"
|
||||
integrity sha512-LJZIJpPvKJ0HVQDqfOy6W4sNKUBBwyDu1Bs8chHBZOe9MNuKTJtidgZ2bqjhmmWpUb0TIIqv47BFUcVmAsgaVA==
|
||||
|
||||
"@types/which@^1.3.1":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
|
||||
@ -3991,6 +3996,14 @@ arduino-serial-plotter-webapp@0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.2.0.tgz#90d61ad7ed1452f70fd226ff25eccb36c1ab1a4f"
|
||||
integrity sha512-AxQIsKr6Mf8K1c3kj+ojjFvE9Vz8cUqJqRink6/myp/ranEGwsQQ83hziktkPKZvBQshqrMH8nzoGIY2Z3A2OA==
|
||||
|
||||
ardunno-cli@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ardunno-cli/-/ardunno-cli-0.1.2.tgz#145d998231b34b33bf70f7fc6e5be6497191f708"
|
||||
integrity sha512-8PTBMDS2ofe2LJZZKHw/MgfXgDwpiImXJcBeqeZ6lcTSDqQNMJpEIjcCdPcxbsQbJXRRfZZ4nn6G/gXwEuJPpw==
|
||||
dependencies:
|
||||
nice-grpc-common "^2.0.2"
|
||||
protobufjs "^7.2.3"
|
||||
|
||||
are-we-there-yet@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
|
||||
@ -10738,6 +10751,13 @@ nested-error-stacks@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5"
|
||||
integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==
|
||||
|
||||
nice-grpc-common@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/nice-grpc-common/-/nice-grpc-common-2.0.2.tgz#e6aeebb2bd19d87114b351e291e30d79dd38acf7"
|
||||
integrity sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==
|
||||
dependencies:
|
||||
ts-error "^1.0.6"
|
||||
|
||||
nice-try@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
@ -12184,7 +12204,7 @@ proto-list@~1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||
integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
|
||||
|
||||
protobufjs@^7.0.0:
|
||||
protobufjs@^7.0.0, protobufjs@^7.2.3:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.3.tgz#01af019e40d9c6133c49acbb3ff9e30f4f0f70b2"
|
||||
integrity sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==
|
||||
@ -13083,6 +13103,11 @@ safe-regex@^1.1.0:
|
||||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
safe-stable-stringify@^2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
|
||||
integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
@ -14350,6 +14375,11 @@ trough@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876"
|
||||
integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==
|
||||
|
||||
ts-error@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/ts-error/-/ts-error-1.0.6.tgz#277496f2a28de6c184cfce8dfd5cdd03a4e6b0fc"
|
||||
integrity sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==
|
||||
|
||||
ts-md5@^1.2.2:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-md5/-/ts-md5-1.3.1.tgz#f5b860c0d5241dd9bb4e909dd73991166403f511"
|
||||
@ -14947,6 +14977,15 @@ vinyl@^2.2.1:
|
||||
remove-trailing-separator "^1.0.1"
|
||||
replace-ext "^1.0.0"
|
||||
|
||||
vscode-arduino-api@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-arduino-api/-/vscode-arduino-api-0.1.2.tgz#11d294fd72c36bbea1ccacd101f16c11df490b77"
|
||||
integrity sha512-FxZllcBIUKxYMiakCSOZ2VSaxscQACxzo0tI5xu8HrbDBU5yvl4zvBzwss4PIYvBG0oZeSKDf950i37Qn7dcmA==
|
||||
dependencies:
|
||||
"@types/vscode" "^1.78.0"
|
||||
ardunno-cli "^0.1.2"
|
||||
safe-stable-stringify "^2.4.3"
|
||||
|
||||
vscode-jsonrpc@8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz#cb9989c65e219e18533cc38e767611272d274c94"
|
||||
|
Loading…
x
Reference in New Issue
Block a user