diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 2cd904e14..fb7e750a7 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -155,7 +155,7 @@ ], "arduino": { "cli": { - "version": "0.24.0" + "version": "0.25.0-rc1" }, "fwuploader": { "version": "2.2.0" @@ -164,7 +164,7 @@ "version": "14.0.0" }, "languageServer": { - "version": "0.6.0" + "version": "0.7.1" } } } diff --git a/arduino-ide-extension/scripts/download-cli.js b/arduino-ide-extension/scripts/download-cli.js index 4861f21aa..4121bbcb5 100755 --- a/arduino-ide-extension/scripts/download-cli.js +++ b/arduino-ide-extension/scripts/download-cli.js @@ -1,141 +1,87 @@ // @ts-check (async () => { + const path = require('path'); + const shell = require('shelljs'); + const semver = require('semver'); + const moment = require('moment'); + const downloader = require('./downloader'); + const { goBuildFromGit } = require('./utils'); + + const version = (() => { + const pkg = require(path.join(__dirname, '..', 'package.json')); + if (!pkg) { + return undefined; + } - const fs = require('fs'); - const path = require('path'); - const temp = require('temp'); - const shell = require('shelljs'); - const semver = require('semver'); - const moment = require('moment'); - const downloader = require('./downloader'); - - const version = (() => { - const pkg = require(path.join(__dirname, '..', 'package.json')); - if (!pkg) { - return undefined; - } + const { arduino } = pkg; + if (!arduino) { + return undefined; + } - const { arduino } = pkg; - if (!arduino) { - return undefined; - } + const { cli } = arduino; + if (!cli) { + return undefined; + } - const { cli } = arduino; - if (!cli) { - return undefined; + const { version } = cli; + return version; + })(); + + if (!version) { + shell.echo(`Could not retrieve CLI version info from the 'package.json'.`); + shell.exit(1); + } + + const { platform, arch } = process; + const buildFolder = path.join(__dirname, '..', 'build'); + const cliName = `arduino-cli${platform === 'win32' ? '.exe' : ''}`; + const destinationPath = path.join(buildFolder, cliName); + + if (typeof version === 'string') { + const suffix = (() => { + switch (platform) { + case 'darwin': + return 'macOS_64bit.tar.gz'; + case 'win32': + return 'Windows_64bit.zip'; + case 'linux': { + switch (arch) { + case 'arm': + return 'Linux_ARMv7.tar.gz'; + case 'arm64': + return 'Linux_ARM64.tar.gz'; + case 'x64': + return 'Linux_64bit.tar.gz'; + default: + return undefined; + } } - - const { version } = cli; - return version; + default: + return undefined; + } })(); - - if (!version) { - shell.echo(`Could not retrieve CLI version info from the 'package.json'.`); - shell.exit(1); + if (!suffix) { + shell.echo(`The CLI is not available for ${platform} ${arch}.`); + shell.exit(1); } - - const { platform, arch } = process; - const buildFolder = path.join(__dirname, '..', 'build'); - const cliName = `arduino-cli${platform === 'win32' ? '.exe' : ''}`; - const destinationPath = path.join(buildFolder, cliName); - - if (typeof version === 'string') { - const suffix = (() => { - switch (platform) { - case 'darwin': return 'macOS_64bit.tar.gz'; - case 'win32': return 'Windows_64bit.zip'; - case 'linux': { - switch (arch) { - case 'arm': return 'Linux_ARMv7.tar.gz'; - case 'arm64': return 'Linux_ARM64.tar.gz'; - case 'x64': return 'Linux_64bit.tar.gz'; - default: return undefined; - } - } - default: return undefined; - } - })(); - if (!suffix) { - shell.echo(`The CLI is not available for ${platform} ${arch}.`); - shell.exit(1); - } - if (semver.valid(version)) { - const url = `https://downloads.arduino.cc/arduino-cli/arduino-cli_${version}_${suffix}`; - shell.echo(`📦 Identified released version of the CLI. Downloading version ${version} from '${url}'`); - await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli'); - } else if (moment(version, 'YYYYMMDD', true).isValid()) { - const url = `https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-${version}_${suffix}`; - shell.echo(`🌙 Identified nightly version of the CLI. Downloading version ${version} from '${url}'`); - await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli'); - } else { - shell.echo(`🔥 Could not interpret 'version': ${version}`); - shell.exit(1); - } + if (semver.valid(version)) { + const url = `https://downloads.arduino.cc/arduino-cli/arduino-cli_${version}_${suffix}`; + shell.echo( + `📦 Identified released version of the CLI. Downloading version ${version} from '${url}'` + ); + await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli'); + } else if (moment(version, 'YYYYMMDD', true).isValid()) { + const url = `https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-${version}_${suffix}`; + shell.echo( + `🌙 Identified nightly version of the CLI. Downloading version ${version} from '${url}'` + ); + await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli'); } else { - - // We assume an object with `owner`, `repo`, commitish?` properties. - const { owner, repo, commitish } = version; - if (!owner) { - shell.echo(`Could not retrieve 'owner' from ${JSON.stringify(version)}`); - shell.exit(1); - } - if (!repo) { - shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`); - shell.exit(1); - } - const url = `https://github.com/${owner}/${repo}.git`; - shell.echo(`Building CLI from ${url}. Commitish: ${commitish ? commitish : 'HEAD'}`); - - if (fs.existsSync(destinationPath)) { - shell.echo(`Skipping the CLI build because it already exists: ${destinationPath}`); - return; - } - - if (shell.mkdir('-p', buildFolder).code !== 0) { - shell.echo('Could not create build folder.'); - shell.exit(1); - } - - const tempRepoPath = temp.mkdirSync(); - shell.echo(`>>> Cloning CLI source to ${tempRepoPath}...`); - if (shell.exec(`git clone ${url} ${tempRepoPath}`).code !== 0) { - shell.exit(1); - } - shell.echo('<<< Cloned CLI repo.') - - if (commitish) { - shell.echo(`>>> Checking out ${commitish}...`); - if (shell.exec(`git -C ${tempRepoPath} checkout ${commitish}`).code !== 0) { - shell.exit(1); - } - shell.echo(`<<< Checked out ${commitish}.`); - } - - shell.echo(`>>> Building the CLI...`); - if (shell.exec('go build', { cwd: tempRepoPath }).code !== 0) { - shell.exit(1); - } - shell.echo('<<< CLI build done.') - - if (!fs.existsSync(path.join(tempRepoPath, cliName))) { - shell.echo(`Could not find the CLI at ${path.join(tempRepoPath, cliName)}.`); - shell.exit(1); - } - - const builtCliPath = path.join(tempRepoPath, cliName); - shell.echo(`>>> Copying CLI from ${builtCliPath} to ${destinationPath}...`); - if (shell.cp(builtCliPath, destinationPath).code !== 0) { - shell.exit(1); - } - shell.echo(`<<< Copied the CLI.`); - - shell.echo('<<< Verifying CLI...'); - if (!fs.existsSync(destinationPath)) { - shell.exit(1); - } - shell.echo('>>> Verified CLI.'); - + shell.echo(`🔥 Could not interpret 'version': ${version}`); + shell.exit(1); } - + } else { + goBuildFromGit(version, destinationPath, 'CLI'); + } })(); diff --git a/arduino-ide-extension/scripts/download-ls.js b/arduino-ide-extension/scripts/download-ls.js index 81b932950..a67c65929 100755 --- a/arduino-ide-extension/scripts/download-ls.js +++ b/arduino-ide-extension/scripts/download-ls.js @@ -7,22 +7,23 @@ const path = require('path'); const shell = require('shelljs'); const downloader = require('./downloader'); + const { goBuildFromGit } = require('./utils'); - const [DEFAULT_ALS_VERSION, DEFAULT_CLANGD_VERSION] = (() => { + const [DEFAULT_LS_VERSION, DEFAULT_CLANGD_VERSION] = (() => { const pkg = require(path.join(__dirname, '..', 'package.json')); - if (!pkg) return undefined; + if (!pkg) return [undefined, undefined]; const { arduino } = pkg; - if (!arduino) return undefined; + if (!arduino) return [undefined, undefined]; const { languageServer, clangd } = arduino; - if (!languageServer) return undefined; - if (!clangd) return undefined; + if (!languageServer) return [undefined, undefined]; + if (!clangd) return [undefined, undefined]; return [languageServer.version, clangd.version]; })(); - if (!DEFAULT_ALS_VERSION) { + if (!DEFAULT_LS_VERSION) { shell.echo( `Could not retrieve Arduino Language Server version info from the 'package.json'.` ); @@ -39,8 +40,8 @@ const yargs = require('yargs') .option('ls-version', { alias: 'lv', - default: DEFAULT_ALS_VERSION, - describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_ALS_VERSION}.`, + default: DEFAULT_LS_VERSION, + describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_LS_VERSION}.`, }) .option('clangd-version', { alias: 'cv', @@ -56,7 +57,7 @@ .version(false) .parse(); - const alsVersion = yargs['ls-version']; + const lsVersion = yargs['ls-version']; const clangdVersion = yargs['clangd-version']; const force = yargs['force-download']; const { platform, arch } = process; @@ -87,6 +88,8 @@ lsSuffix = 'Windows_64bit.zip'; clangdSuffix = 'Windows_64bit'; break; + default: + throw new Error(`Unsupported platform/arch: ${platformArch}.`); } if (!lsSuffix || !clangdSuffix) { shell.echo( @@ -95,12 +98,16 @@ shell.exit(1); } - const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${ - alsVersion === 'nightly' - ? 'nightly/arduino-language-server' - : 'arduino-language-server_' + alsVersion - }_${lsSuffix}`; - downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force); + if (typeof lsVersion === 'string') { + const lsUrl = `https://downloads.arduino.cc/arduino-language-server/${ + lsVersion === 'nightly' + ? 'nightly/arduino-language-server' + : 'arduino-language-server_' + lsVersion + }_${lsSuffix}`; + downloader.downloadUnzipAll(lsUrl, build, lsExecutablePath, force); + } else { + goBuildFromGit(lsVersion, lsExecutablePath, 'language-server'); + } const clangdUrl = `https://downloads.arduino.cc/tools/clangd_${clangdVersion}_${clangdSuffix}.tar.bz2`; downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, { diff --git a/arduino-ide-extension/scripts/downloader.js b/arduino-ide-extension/scripts/downloader.js index 775aa0435..41041cd2f 100644 --- a/arduino-ide-extension/scripts/downloader.js +++ b/arduino-ide-extension/scripts/downloader.js @@ -86,6 +86,7 @@ exports.downloadUnzipFile = async ( * @param targetDir {string} Directory into which to decompress the archive * @param targetFile {string} Path to the main file expected after decompressing * @param force {boolean} Whether to download even if the target file exists + * @param decompressOptions {import('decompress').DecompressOptions} */ exports.downloadUnzipAll = async ( url, diff --git a/arduino-ide-extension/scripts/utils.js b/arduino-ide-extension/scripts/utils.js new file mode 100644 index 000000000..42e6114cc --- /dev/null +++ b/arduino-ide-extension/scripts/utils.js @@ -0,0 +1,92 @@ +/** + * Clones something from GitHub and builds it with `Golang`. + * + * @param version {object} the version object. + * @param destinationPath {string} the absolute path of the output binary. For example, `C:\\folder\\arduino-cli.exe` or `/path/to/arduino-language-server` + * @param taskName {string} for the CLI logging . Can be `'CLI'` or `'language-server'`, etc. + */ +exports.goBuildFromGit = (version, destinationPath, taskName) => { + const fs = require('fs'); + const path = require('path'); + const temp = require('temp'); + const shell = require('shelljs'); + + // We assume an object with `owner`, `repo`, commitish?` properties. + if (typeof version !== 'object') { + shell.echo( + `Expected a \`{ owner, repo, commitish }\` object. Got <${version}> instead.` + ); + } + const { owner, repo, commitish } = version; + if (!owner) { + shell.echo(`Could not retrieve 'owner' from ${JSON.stringify(version)}`); + shell.exit(1); + } + if (!repo) { + shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`); + shell.exit(1); + } + const url = `https://github.com/${owner}/${repo}.git`; + shell.echo( + `Building ${taskName} from ${url}. Commitish: ${ + commitish ? commitish : 'HEAD' + }` + ); + + if (fs.existsSync(destinationPath)) { + shell.echo( + `Skipping the ${taskName} build because it already exists: ${destinationPath}` + ); + return; + } + + const buildFolder = path.join(__dirname, '..', 'build'); + if (shell.mkdir('-p', buildFolder).code !== 0) { + shell.echo('Could not create build folder.'); + shell.exit(1); + } + + const tempRepoPath = temp.mkdirSync(); + shell.echo(`>>> Cloning ${taskName} source to ${tempRepoPath}...`); + if (shell.exec(`git clone ${url} ${tempRepoPath}`).code !== 0) { + shell.exit(1); + } + shell.echo(`<<< Cloned ${taskName} repo.`); + + if (commitish) { + shell.echo(`>>> Checking out ${commitish}...`); + if (shell.exec(`git -C ${tempRepoPath} checkout ${commitish}`).code !== 0) { + shell.exit(1); + } + shell.echo(`<<< Checked out ${commitish}.`); + } + + shell.echo(`>>> Building the ${taskName}...`); + if (shell.exec('go build', { cwd: tempRepoPath }).code !== 0) { + shell.exit(1); + } + shell.echo(`<<< Done ${taskName} build.`); + + const binName = path.basename(destinationPath); + if (!fs.existsSync(path.join(tempRepoPath, binName))) { + shell.echo( + `Could not find the ${taskName} at ${path.join(tempRepoPath, binName)}.` + ); + shell.exit(1); + } + + const binPath = path.join(tempRepoPath, binName); + shell.echo( + `>>> Copying ${taskName} from ${binPath} to ${destinationPath}...` + ); + if (shell.cp(binPath, destinationPath).code !== 0) { + shell.exit(1); + } + shell.echo(`<<< Copied the ${taskName}.`); + + shell.echo(`<<< Verifying ${taskName}...`); + if (!fs.existsSync(destinationPath)) { + shell.exit(1); + } + shell.echo(`>>> Verified ${taskName}.`); +}; diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index a1d92bf6f..5722e3c62 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -277,11 +277,14 @@ export class ArduinoFrontendContribution ); }); - const start = async ({ selectedBoard }: BoardsConfig.Config) => { + const start = async ( + { selectedBoard }: BoardsConfig.Config, + forceStart = false + ) => { if (selectedBoard) { const { name, fqbn } = selectedBoard; if (fqbn) { - this.startLanguageServer(fqbn, name); + this.startLanguageServer(fqbn, name, forceStart); } } }; @@ -296,7 +299,8 @@ export class ArduinoFrontendContribution if (event.newValue !== event.oldValue) { switch (event.preferenceName) { case 'arduino.language.log': - start(this.boardsServiceClientImpl.boardsConfig); + case 'arduino.language.realTimeDiagnostics': + start(this.boardsServiceClientImpl.boardsConfig, true); break; case 'arduino.window.zoomLevel': if (typeof event.newValue === 'number') { @@ -344,7 +348,8 @@ export class ArduinoFrontendContribution protected languageServerStartMutex = new Mutex(); protected async startLanguageServer( fqbn: string, - name: string | undefined + name: string | undefined, + forceStart = false ): Promise { const port = await this.daemon.tryGetPort(); if (!port) { @@ -378,12 +383,15 @@ export class ArduinoFrontendContribution } return; } - if (fqbn === this.languageServerFqbn) { + if (!forceStart && fqbn === this.languageServerFqbn) { // NOOP return; } this.logger.info(`Starting language server: ${fqbn}`); const log = this.arduinoPreferences.get('arduino.language.log'); + const realTimeDiagnostics = this.arduinoPreferences.get( + 'arduino.language.realTimeDiagnostics' + ); let currentSketchPath: string | undefined = undefined; if (log) { const currentSketch = await this.sketchServiceClient.currentSketch(); @@ -414,6 +422,7 @@ export class ArduinoFrontendContribution clangdPath, log: currentSketchPath ? currentSketchPath : log, cliDaemonInstance: '1', + realTimeDiagnostics, board: { fqbn, name: name ? `"${name}"` : undefined, diff --git a/arduino-ide-extension/src/browser/arduino-preferences.ts b/arduino-ide-extension/src/browser/arduino-preferences.ts index 0caf22837..398a4f2c2 100644 --- a/arduino-ide-extension/src/browser/arduino-preferences.ts +++ b/arduino-ide-extension/src/browser/arduino-preferences.ts @@ -51,6 +51,14 @@ export const ArduinoConfigSchema: PreferenceSchema = { ), default: false, }, + 'arduino.language.realTimeDiagnostics': { + type: 'boolean', + description: nls.localize( + 'arduino/preferences/language.realTimeDiagnostics', + "If true, the language server provides real-time diagnostics when typing in the editor. It's false by default." + ), + default: false, + }, 'arduino.compile.verbose': { type: 'boolean', description: nls.localize( @@ -238,6 +246,7 @@ export const ArduinoConfigSchema: PreferenceSchema = { export interface ArduinoConfiguration { 'arduino.language.log': boolean; + 'arduino.language.realTimeDiagnostics': boolean; 'arduino.compile.verbose': boolean; 'arduino.compile.experimental': boolean; 'arduino.compile.revealRange': ErrorRevealStrategy; diff --git a/arduino-ide-extension/src/browser/contributions/compiler-errors.ts b/arduino-ide-extension/src/browser/contributions/compiler-errors.ts index 728341110..0356d40c8 100644 --- a/arduino-ide-extension/src/browser/contributions/compiler-errors.ts +++ b/arduino-ide-extension/src/browser/contributions/compiler-errors.ts @@ -275,7 +275,7 @@ export class CompilerErrors } private async handleCompilerErrorsDidChange( - errors: CoreError.Compiler[] + errors: CoreError.ErrorLocation[] ): Promise { this.toDisposeOnCompilerErrorDidChange.dispose(); const compilerErrorsPerResource = this.groupByResource( @@ -312,8 +312,8 @@ export class CompilerErrors } private async filter( - errors: CoreError.Compiler[] - ): Promise { + errors: CoreError.ErrorLocation[] + ): Promise { if (!errors.length) { return []; } @@ -326,7 +326,7 @@ export class CompilerErrors } private async decorateEditors( - errors: Map + errors: Map ): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> { const composite = await Promise.all( [...errors.entries()].map(([uri, errors]) => @@ -346,7 +346,7 @@ export class CompilerErrors private async decorateEditor( uri: string, - errors: CoreError.Compiler[] + errors: CoreError.ErrorLocation[] ): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> { const editor = await this.editorManager.getByUri(new URI(uri)); if (!editor) { @@ -523,7 +523,7 @@ export class CompilerErrors } private async trackEditors( - errors: Map, + errors: Map, ...track: ((editor: EditorWidget) => Disposable)[] ): Promise { return new DisposableCollection( @@ -605,8 +605,8 @@ export class CompilerErrors } private groupByResource( - errors: CoreError.Compiler[] - ): Map { + errors: CoreError.ErrorLocation[] + ): Map { return errors.reduce((acc, curr) => { const { location: { uri }, @@ -618,7 +618,7 @@ export class CompilerErrors } errors.push(curr); return acc; - }, new Map()); + }, new Map()); } private monacoEditor(widget: EditorWidget): MonacoEditor | undefined; diff --git a/arduino-ide-extension/src/browser/contributions/core-error-handler.ts b/arduino-ide-extension/src/browser/contributions/core-error-handler.ts index 9eec07cd6..82aba4c00 100644 --- a/arduino-ide-extension/src/browser/contributions/core-error-handler.ts +++ b/arduino-ide-extension/src/browser/contributions/core-error-handler.ts @@ -4,29 +4,29 @@ import { CoreError } from '../../common/protocol/core-service'; @injectable() export class CoreErrorHandler { - private readonly compilerErrors: CoreError.Compiler[] = []; + private readonly errors: CoreError.ErrorLocation[] = []; private readonly compilerErrorsDidChangeEmitter = new Emitter< - CoreError.Compiler[] + CoreError.ErrorLocation[] >(); tryHandle(error: unknown): void { if (CoreError.is(error)) { - this.compilerErrors.length = 0; - this.compilerErrors.push(...error.data.filter(CoreError.Compiler.is)); + this.errors.length = 0; + this.errors.push(...error.data); this.fireCompilerErrorsDidChange(); } } reset(): void { - this.compilerErrors.length = 0; + this.errors.length = 0; this.fireCompilerErrorsDidChange(); } - get onCompilerErrorsDidChange(): Event { + get onCompilerErrorsDidChange(): Event { return this.compilerErrorsDidChangeEmitter.event; } private fireCompilerErrorsDidChange(): void { - this.compilerErrorsDidChangeEmitter.fire(this.compilerErrors.slice()); + this.compilerErrorsDidChangeEmitter.fire(this.errors.slice()); } } diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts index debf0b9fb..f720a7d61 100644 --- a/arduino-ide-extension/src/common/protocol/core-service.ts +++ b/arduino-ide-extension/src/common/protocol/core-service.ts @@ -5,7 +5,6 @@ import type { BoardUserField, Port, } from '../../common/protocol/boards-service'; -import type { ErrorInfo as CliErrorInfo } from '../../node/cli-error-parser'; import type { Programmer } from './boards-service'; import type { Sketch } from './sketches-service'; @@ -17,16 +16,10 @@ export const CompilerWarningLiterals = [ ] as const; export type CompilerWarnings = typeof CompilerWarningLiterals[number]; export namespace CoreError { - export type ErrorInfo = CliErrorInfo; - export interface Compiler extends ErrorInfo { + export interface ErrorLocation { readonly message: string; readonly location: Location; - } - export namespace Compiler { - export function is(error: ErrorInfo): error is Compiler { - const { message, location } = error; - return !!message && !!location; - } + readonly details?: string; } export const Codes = { Verify: 4001, @@ -42,7 +35,7 @@ export namespace CoreError { export const BurnBootloaderFailed = create(Codes.BurnBootloader); export function is( error: unknown - ): error is ApplicationError { + ): error is ApplicationError { return ( error instanceof Error && ApplicationError.is(error) && @@ -51,10 +44,10 @@ export namespace CoreError { } function create( code: number - ): ApplicationError.Constructor { + ): ApplicationError.Constructor { return ApplicationError.declare( code, - (message: string, data: ErrorInfo[]) => { + (message: string, data: ErrorLocation[]) => { return { data, message, diff --git a/arduino-ide-extension/src/node/cli-error-parser.ts b/arduino-ide-extension/src/node/cli-error-parser.ts index b002742c6..0f72ea71c 100644 --- a/arduino-ide-extension/src/node/cli-error-parser.ts +++ b/arduino-ide-extension/src/node/cli-error-parser.ts @@ -1,24 +1,19 @@ -import { notEmpty } from '@theia/core'; +import { notEmpty } from '@theia/core/lib/common/objects'; import { nls } from '@theia/core/lib/common/nls'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { - Location, Range, Position, } from '@theia/core/shared/vscode-languageserver-protocol'; -import { Sketch } from '../common/protocol'; +import type { CoreError } from '../common/protocol'; +import { Sketch } from '../common/protocol/sketches-service'; -export interface ErrorInfo { - readonly message?: string; - readonly location?: Location; - readonly details?: string; -} export interface ErrorSource { readonly content: string | ReadonlyArray; readonly sketch?: Sketch; } -export function tryParseError(source: ErrorSource): ErrorInfo[] { +export function tryParseError(source: ErrorSource): CoreError.ErrorLocation[] { const { content, sketch } = source; const err = typeof content === 'string' @@ -28,7 +23,7 @@ export function tryParseError(source: ErrorSource): ErrorInfo[] { return tryParse(err) .map(remapErrorMessages) .filter(isLocationInSketch(sketch)) - .map(errorInfo()); + .map(toErrorInfo); } return []; } @@ -50,9 +45,7 @@ namespace ParseResult { } } -function isLocationInSketch( - sketch: Sketch -): (value: ParseResult, index: number, array: ParseResult[]) => unknown { +function isLocationInSketch(sketch: Sketch): (result: ParseResult) => boolean { return (result) => { const uri = FileUri.create(result.path).toString(); if (!Sketch.isInSketch(uri, sketch)) { @@ -65,15 +58,21 @@ function isLocationInSketch( }; } -function errorInfo(): (value: ParseResult) => ErrorInfo { - return ({ error, message, path, line, column }) => ({ +function toErrorInfo({ + error, + message, + path, + line, + column, +}: ParseResult): CoreError.ErrorLocation { + return { message: error, details: message, location: { uri: FileUri.create(path).toString(), range: range(line, column), }, - }); + }; } function range(line: number, column?: number): Range { diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts index db947e130..34c8ec082 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.d.ts @@ -95,6 +95,9 @@ export class CompileRequest extends jspb.Message { getEncryptKey(): string; setEncryptKey(value: string): CompileRequest; + getSkipLibrariesDiscovery(): boolean; + setSkipLibrariesDiscovery(value: boolean): CompileRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CompileRequest.AsObject; @@ -133,6 +136,7 @@ export namespace CompileRequest { keysKeychain: string, signKey: string, encryptKey: string, + skipLibrariesDiscovery: boolean, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js index 69aafe386..c2bae211d 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/compile_pb.js @@ -149,7 +149,8 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.toObject = function(includeInsta libraryList: (f = jspb.Message.getRepeatedField(msg, 24)) == null ? undefined : f, keysKeychain: jspb.Message.getFieldWithDefault(msg, 25, ""), signKey: jspb.Message.getFieldWithDefault(msg, 26, ""), - encryptKey: jspb.Message.getFieldWithDefault(msg, 27, "") + encryptKey: jspb.Message.getFieldWithDefault(msg, 27, ""), + skipLibrariesDiscovery: jspb.Message.getBooleanFieldWithDefault(msg, 28, false) }; if (includeInstance) { @@ -286,6 +287,10 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.deserializeBinaryFromReader = fu var value = /** @type {string} */ (reader.readString()); msg.setEncryptKey(value); break; + case 28: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setSkipLibrariesDiscovery(value); + break; default: reader.skipField(); break; @@ -482,6 +487,13 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.serializeBinaryToWriter = functi f ); } + f = message.getSkipLibrariesDiscovery(); + if (f) { + writer.writeBool( + 28, + f + ); + } }; @@ -1016,6 +1028,24 @@ proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setEncryptKey = functi }; +/** + * optional bool skip_libraries_discovery = 28; + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.getSkipLibrariesDiscovery = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 28, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.cc.arduino.cli.commands.v1.CompileRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.CompileRequest.prototype.setSkipLibrariesDiscovery = function(value) { + return jspb.Message.setProto3BooleanField(this, 28, value); +}; + + /** * List of repeated fields within this message type. diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts index cec05ce33..30792fd12 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.d.ts @@ -26,6 +26,9 @@ export class PlatformInstallRequest extends jspb.Message { getSkipPostInstall(): boolean; setSkipPostInstall(value: boolean): PlatformInstallRequest; + getNoOverwrite(): boolean; + setNoOverwrite(value: boolean): PlatformInstallRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): PlatformInstallRequest.AsObject; @@ -44,6 +47,7 @@ export namespace PlatformInstallRequest { architecture: string, version: string, skipPostInstall: boolean, + noOverwrite: boolean, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.js index 6db0ad1b4..093656172 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/core_pb.js @@ -339,7 +339,8 @@ proto.cc.arduino.cli.commands.v1.PlatformInstallRequest.toObject = function(incl platformPackage: jspb.Message.getFieldWithDefault(msg, 2, ""), architecture: jspb.Message.getFieldWithDefault(msg, 3, ""), version: jspb.Message.getFieldWithDefault(msg, 4, ""), - skipPostInstall: jspb.Message.getBooleanFieldWithDefault(msg, 5, false) + skipPostInstall: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), + noOverwrite: jspb.Message.getBooleanFieldWithDefault(msg, 6, false) }; if (includeInstance) { @@ -397,6 +398,10 @@ proto.cc.arduino.cli.commands.v1.PlatformInstallRequest.deserializeBinaryFromRea var value = /** @type {boolean} */ (reader.readBool()); msg.setSkipPostInstall(value); break; + case 6: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setNoOverwrite(value); + break; default: reader.skipField(); break; @@ -462,6 +467,13 @@ proto.cc.arduino.cli.commands.v1.PlatformInstallRequest.serializeBinaryToWriter f ); } + f = message.getNoOverwrite(); + if (f) { + writer.writeBool( + 6, + f + ); + } }; @@ -574,6 +586,24 @@ proto.cc.arduino.cli.commands.v1.PlatformInstallRequest.prototype.setSkipPostIns }; +/** + * optional bool no_overwrite = 6; + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.PlatformInstallRequest.prototype.getNoOverwrite = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 6, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.cc.arduino.cli.commands.v1.PlatformInstallRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.PlatformInstallRequest.prototype.setNoOverwrite = function(value) { + return jspb.Message.setProto3BooleanField(this, 6, value); +}; + + diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts index c3e5155fd..d8f4dc9cd 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts @@ -79,6 +79,9 @@ export class LibraryInstallRequest extends jspb.Message { getNoDeps(): boolean; setNoDeps(value: boolean): LibraryInstallRequest; + getNoOverwrite(): boolean; + setNoOverwrite(value: boolean): LibraryInstallRequest; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): LibraryInstallRequest.AsObject; @@ -96,6 +99,7 @@ export namespace LibraryInstallRequest { name: string, version: string, noDeps: boolean, + noOverwrite: boolean, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js index 44f24309b..be2398106 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js @@ -967,7 +967,8 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.toObject = function(inclu instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f), name: jspb.Message.getFieldWithDefault(msg, 2, ""), version: jspb.Message.getFieldWithDefault(msg, 3, ""), - noDeps: jspb.Message.getBooleanFieldWithDefault(msg, 4, false) + noDeps: jspb.Message.getBooleanFieldWithDefault(msg, 4, false), + noOverwrite: jspb.Message.getBooleanFieldWithDefault(msg, 5, false) }; if (includeInstance) { @@ -1021,6 +1022,10 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.deserializeBinaryFromRead var value = /** @type {boolean} */ (reader.readBool()); msg.setNoDeps(value); break; + case 5: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setNoOverwrite(value); + break; default: reader.skipField(); break; @@ -1079,6 +1084,13 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.serializeBinaryToWriter = f ); } + f = message.getNoOverwrite(); + if (f) { + writer.writeBool( + 5, + f + ); + } }; @@ -1173,6 +1185,24 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.prototype.setNoDeps = fun }; +/** + * optional bool no_overwrite = 5; + * @return {boolean} + */ +proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.prototype.getNoOverwrite = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 5, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.cc.arduino.cli.commands.v1.LibraryInstallRequest} returns this + */ +proto.cc.arduino.cli.commands.v1.LibraryInstallRequest.prototype.setNoOverwrite = function(value) { + return jspb.Message.setProto3BooleanField(this, 5, value); +}; + + diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index a1dc98b26..afe7f2099 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -26,7 +26,7 @@ import { ResponseService } from '../common/protocol/response-service'; import { Board, OutputMessage, Port, Status } from '../common/protocol'; import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; import { Port as GrpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb'; -import { ApplicationError, Disposable, nls } from '@theia/core'; +import { ApplicationError, CommandService, Disposable, nls } from '@theia/core'; import { MonitorManager } from './monitor-manager'; import { AutoFlushingBuffer } from './utils/buffers'; import { tryParseError } from './cli-error-parser'; @@ -42,6 +42,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { @inject(MonitorManager) private readonly monitorManager: MonitorManager; + @inject(CommandService) + private readonly commandService: CommandService; + async compile( options: CoreService.Compile.Options & { exportBinaries?: boolean; @@ -50,7 +53,19 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { ): Promise { const coreClient = await this.coreClient; const { client, instance } = coreClient; - const handler = this.createOnDataHandler(); + let buildPath: string | undefined = undefined; + const handler = this.createOnDataHandler((response) => { + const currentBuildPath = response.getBuildPath(); + if (!buildPath && currentBuildPath) { + buildPath = currentBuildPath; + } else { + if (!!currentBuildPath && currentBuildPath !== buildPath) { + throw new Error( + `The CLI has already provided a build path: <${buildPath}>, and there is a new build path value: <${currentBuildPath}>.` + ); + } + } + }); const request = this.compileRequest(options, instance); return new Promise((resolve, reject) => { client @@ -84,7 +99,36 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { } }) .on('end', resolve); - }).finally(() => handler.dispose()); + }).finally(() => { + handler.dispose(); + if (!buildPath) { + console.error( + `Have not received the build path from the CLI while running the compilation.` + ); + } else { + this.fireBuildDidComplete(FileUri.create(buildPath).toString()); + } + }); + } + + // 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 { + const params = { + buildOutputUri, + }; + console.info( + `Executing 'arduino.languageserver.notifyBuildDidComplete' with ${JSON.stringify( + params + )}` + ); + this.commandService + .executeCommand('arduino.languageserver.notifyBuildDidComplete', params) + .catch((err) => + console.error( + `Unexpected error when firing event on build did complete. ${buildOutputUri}`, + err + ) + ); } private compileRequest( @@ -124,8 +168,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { options, () => new UploadRequest(), (client, req) => client.upload(req), - (message: string, info: CoreError.ErrorInfo[]) => - CoreError.UploadFailed(message, info), + (message: string, locations: CoreError.ErrorLocation[]) => + CoreError.UploadFailed(message, locations), 'upload' ); } @@ -137,8 +181,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { options, () => new UploadUsingProgrammerRequest(), (client, req) => client.uploadUsingProgrammer(req), - (message: string, info: CoreError.ErrorInfo[]) => - CoreError.UploadUsingProgrammerFailed(message, info), + (message: string, locations: CoreError.ErrorLocation[]) => + CoreError.UploadUsingProgrammerFailed(message, locations), 'upload using programmer' ); } @@ -152,8 +196,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { ) => ClientReadableStream, errorHandler: ( message: string, - info: CoreError.ErrorInfo[] - ) => ApplicationError, + locations: CoreError.ErrorLocation[] + ) => ApplicationError, task: string ): Promise { await this.compile(Object.assign(options, { exportBinaries: false })); @@ -285,7 +329,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { return request; } - private createOnDataHandler(): Disposable & { + private createOnDataHandler( + onResponse?: (response: R) => void + ): Disposable & { stderr: Buffer[]; onData: (response: R) => void; } { @@ -297,10 +343,14 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { } }); }); - const onData = StreamingResponse.createOnDataHandler(stderr, (out, err) => { - buffer.addChunk(out); - buffer.addChunk(err, OutputMessage.Severity.Error); - }); + const onData = StreamingResponse.createOnDataHandler( + stderr, + (out, err) => { + buffer.addChunk(out); + buffer.addChunk(err, OutputMessage.Severity.Error); + }, + onResponse + ); return { dispose: () => buffer.dispose(), stderr, @@ -369,13 +419,17 @@ namespace StreamingResponse { // eslint-disable-next-line @typescript-eslint/no-explicit-any export function createOnDataHandler( stderr: Uint8Array[], - onData: (out: Uint8Array, err: Uint8Array) => void + onData: (out: Uint8Array, err: Uint8Array) => void, + onResponse?: (response: R) => void ): (response: R) => void { return (response: R) => { const out = response.getOutStream_asU8(); const err = response.getErrStream_asU8(); stderr.push(err); onData(out, err); + if (onResponse) { + onResponse(response); + } }; } } diff --git a/electron/build/template-package.json b/electron/build/template-package.json index caa8087cd..0b4d9e257 100644 --- a/electron/build/template-package.json +++ b/electron/build/template-package.json @@ -141,7 +141,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-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.2.vsix", + "vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.4.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", "cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix", diff --git a/i18n/en.json b/i18n/en.json index 3dcee3556..75ff98284 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -269,6 +269,7 @@ "invalid.sketchbook.location": "Invalid sketchbook location: {0}", "invalid.theme": "Invalid theme.", "language.log": "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.", + "language.realTimeDiagnostics": "If true, the language server provides real-time diagnostics when typing in the editor. It's false by default.", "manualProxy": "Manual proxy configuration", "network": "Network", "newSketchbookLocation": "Select new sketchbook location", diff --git a/package.json b/package.json index 2c03dcccc..f3dfbd1d1 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,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-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.2.vsix", + "vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.4.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", "cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix",