From 7d04c7efb864bf02c136564aa9a8928a2919b7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 25 Jul 2019 15:50:25 +0200 Subject: [PATCH 1/4] Added language contribution for ino files --- .../src/browser/arduino-frontend-module.ts | 5 +- .../arduino-language-client-contribution.ts | 18 +++++++ .../src/node/arduino-backend-module.ts | 5 ++ .../arduino-language-server-contribution.ts | 48 +++++++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 arduino-ide-extension/src/browser/language/arduino-language-client-contribution.ts create mode 100644 arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index c11aca32..2e7e0d19 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -7,6 +7,8 @@ import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider'; import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application' import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser/textmate'; +import { LanguageClientContribution } from '@theia/languages/lib/browser'; +import { ArduinoLanguageClientContribution } from './language/arduino-language-client-contribution'; import { LibraryListWidget } from './library/library-list-widget'; import { ArduinoFrontendContribution, ArduinoAdvancedMode } from './arduino-frontend-contribution'; import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution'; @@ -78,8 +80,9 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un bind(ArduinoToolbarContribution).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution); - // `ino` TextMate grammar + // `ino` TextMate grammar and language client bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope(); + bind(LanguageClientContribution).to(ArduinoLanguageClientContribution).inSingletonScope(); // Library service bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope(); diff --git a/arduino-ide-extension/src/browser/language/arduino-language-client-contribution.ts b/arduino-ide-extension/src/browser/language/arduino-language-client-contribution.ts new file mode 100644 index 00000000..e3c1cecc --- /dev/null +++ b/arduino-ide-extension/src/browser/language/arduino-language-client-contribution.ts @@ -0,0 +1,18 @@ +import { injectable } from 'inversify'; +import { BaseLanguageClientContribution } from '@theia/languages/lib/browser'; + +@injectable() +export class ArduinoLanguageClientContribution extends BaseLanguageClientContribution { + + readonly id = 'ino' + readonly name = 'Arduino' + + protected get documentSelector(): string[] { + return ['ino']; + } + + protected get globPatterns() { + return ['**/*.ino'] + } + +} diff --git a/arduino-ide-extension/src/node/arduino-backend-module.ts b/arduino-ide-extension/src/node/arduino-backend-module.ts index daeb3878..f69c39f4 100644 --- a/arduino-ide-extension/src/node/arduino-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-backend-module.ts @@ -2,6 +2,8 @@ import { ContainerModule } from 'inversify'; import { ArduinoDaemon } from './arduino-daemon'; import { ILogger } from '@theia/core/lib/common/logger'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; +import { LanguageServerContribution } from '@theia/languages/lib/node'; +import { ArduinoLanguageServerContribution } from './language/arduino-language-server-contribution'; import { LibraryService, LibraryServicePath } from '../common/protocol/library-service'; import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service'; import { LibraryServiceImpl } from './library-service-impl'; @@ -40,6 +42,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoDaemon).toSelf().inSingletonScope(); bind(BackendApplicationContribution).toService(ArduinoDaemon); + // Language server + bind(LanguageServerContribution).to(ArduinoLanguageServerContribution).inSingletonScope(); + // Library service const libraryServiceConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => { bind(LibraryServiceImpl).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts new file mode 100644 index 00000000..802fe8d3 --- /dev/null +++ b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts @@ -0,0 +1,48 @@ +import * as which from 'which'; +import * as os from 'os'; +import { join, delimiter } from 'path'; +import { injectable } from 'inversify'; +import { BaseLanguageServerContribution, IConnection } from '@theia/languages/lib/node'; + +@injectable() +export class ArduinoLanguageServerContribution extends BaseLanguageServerContribution { + + readonly description = { + id: 'ino', + name: 'Arduino', + documentSelector: ['ino'], + fileEvents: ['**/*.ino'] + } + + get id() { + return this.description.id; + } + + get name() { + return this.description.name; + } + + async start(clientConnection: IConnection): Promise { + const clangd = await this.resolveExecutable('clangd') + const languageServer = await this.resolveExecutable('arduino-language-server') + // Add '-log' argument to enable logging to files + const args: string[] = ['-clangd', clangd] + console.log(`Starting language server ${languageServer} ${args.join(' ')}`) + const serverConnection = await this.createProcessStreamConnectionAsync(languageServer, args) + this.forward(clientConnection, serverConnection) + } + + protected resolveExecutable(name: string): Promise { + return new Promise((resolve, reject) => { + const path = `${process.env.PATH}${delimiter}${join(__dirname, '..', '..', '..', 'build')}`; + const suffix = os.platform() === 'win32' ? '.exe' : ''; + which(name + suffix, { path }, (err, execPath) => { + if (err) { + reject(err); + } else { + resolve(execPath); + } + }); + }); + } +} From c2675efea4b918c83d899e27efc57f0f55cb8a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 21 Aug 2019 14:33:22 +0200 Subject: [PATCH 2/4] Fixed z-index of main toolbar --- arduino-ide-extension/src/browser/style/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/arduino-ide-extension/src/browser/style/main.css b/arduino-ide-extension/src/browser/style/main.css index 6f898eb6..5cbb676f 100644 --- a/arduino-ide-extension/src/browser/style/main.css +++ b/arduino-ide-extension/src/browser/style/main.css @@ -74,6 +74,7 @@ .p-TabBar-toolbar.theia-arduino-toolbar { flex: 1; + z-index: 0; } #theia-top-panel .p-TabBar-toolbar.theia-arduino-toolbar.right { From 60bf58ac0f2befe4f4f4d8b3710258576c29b500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Tue, 27 Aug 2019 15:03:14 +0200 Subject: [PATCH 3/4] Added download script for arduino-language-server --- arduino-ide-extension/package.json | 3 +- arduino-ide-extension/scripts/download-cli.js | 66 +---------------- arduino-ide-extension/scripts/download-ls.js | 72 +++++++++++++++++++ arduino-ide-extension/scripts/downloader.js | 71 ++++++++++++++++++ 4 files changed, 148 insertions(+), 64 deletions(-) create mode 100755 arduino-ide-extension/scripts/download-ls.js create mode 100644 arduino-ide-extension/scripts/downloader.js diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 3850424b..5f33451c 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -33,9 +33,10 @@ "which": "^1.3.1" }, "scripts": { - "prepare": "yarn download-cli && yarn run clean && yarn run build", + "prepare": "yarn download-cli && yarn download-ls && yarn run clean && yarn run build", "clean": "rimraf lib", "download-cli": "node ./scripts/download-cli.js", + "download-ls": "node ./scripts/download-ls.js", "generate-protocol": "node ./scripts/generate-protocol.js", "lint": "tslint -c ./tslint.json --project ./tsconfig.json", "build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint", diff --git a/arduino-ide-extension/scripts/download-cli.js b/arduino-ide-extension/scripts/download-cli.js index 62506946..d37b1093 100755 --- a/arduino-ide-extension/scripts/download-cli.js +++ b/arduino-ide-extension/scripts/download-cli.js @@ -8,29 +8,14 @@ // [...] // redirecting to latest generated builds by replacing latest with the latest available build date, using the format YYYYMMDD (i.e for 2019/Aug/06 latest is replaced with 20190806 -(async () => { +(() => { const DEFAULT_VERSION = 'latest'; // require('moment')().format('YYYYMMDD'); const os = require('os'); - const fs = require('fs'); const path = require('path'); const shell = require('shelljs'); - const download = require('download'); - const decompress = require('decompress'); - const unzip = require('decompress-unzip'); - const untargz = require('decompress-targz'); - - process.on('unhandledRejection', (reason, _) => { - shell.echo(String(reason)); - shell.exit(1); - throw reason; - }); - process.on('uncaughtException', error => { - shell.echo(String(error)); - shell.exit(1); - throw error; - }); + const downloader = require('./downloader'); const yargs = require('yargs') .option('cli-version', { @@ -50,23 +35,8 @@ const { platform, arch } = process; const build = path.join(__dirname, '..', 'build'); - const downloads = path.join(__dirname, '..', 'downloads'); const cli = path.join(build, `arduino-cli${os.platform() === 'win32' ? '.exe' : ''}`); - if (fs.existsSync(cli) && !force) { - shell.echo(`The 'arduino-cli' already exists at ${cli}. Skipping download.`); - shell.exit(0); - } - if (!fs.existsSync(build)) { - if (shell.mkdir('-p', build).code !== 0) { - shell.echo('Could not create new directory.'); - shell.exit(1); - } - } - if (shell.rm('-rf', cli, downloads).code !== 0) { - shell.exit(1); - } - const suffix = (() => { switch (platform) { case 'darwin': return 'macOS_64bit.tar.gz'; @@ -87,36 +57,6 @@ } const url = `https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-${version}_${suffix}`; - shell.echo(`>>> Downloading 'arduino-cli' from '${url}'...`); - const data = await download(url); - shell.echo(`<<< Download succeeded.`); - shell.echo('>>> Decompressing CLI...'); - const files = await decompress(data, downloads, { - plugins: [ - unzip(), - untargz() - ] - }); - if (files.length === 0) { - shell.echo('Error ocurred when decompressing the CLI.'); - shell.exit(1); - } - const cliIndex = files.findIndex(f => f.path.startsWith('arduino-cli')); - if (cliIndex === -1) { - shell.echo('The downloaded artifact does not contains the CLI.'); - shell.exit(1); - } - shell.echo('<<< Decompressing succeeded.'); - - if (shell.mv('-f', path.join(downloads, files[cliIndex].path), cli).code !== 0) { - shell.echo(`Could not move file to ${cli}.`); - shell.exit(1); - } - if (!fs.existsSync(cli)) { - shell.echo(`Could not find CLI at ${cli}.`); - shell.exit(1); - } else { - shell.echo('Done.'); - } + downloader.download(url, cli, 'arduino-cli', force); })(); \ No newline at end of file diff --git a/arduino-ide-extension/scripts/download-ls.js b/arduino-ide-extension/scripts/download-ls.js new file mode 100755 index 00000000..930841f4 --- /dev/null +++ b/arduino-ide-extension/scripts/download-ls.js @@ -0,0 +1,72 @@ +// @ts-check +// The links to the downloads as of today (28.08.2019) are the following: +// - https://downloads.arduino.cc/arduino-language-server/nightly/arduino-language-server_${SUFFIX} +// - https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${VERSION}_${SUFFIX} + +(() => { + + const DEFAULT_ALS_VERSION = 'nightly'; + const DEFAULT_CLANGD_VERSION = '8.0.1'; + + const os = require('os'); + const path = require('path'); + const shell = require('shelljs'); + const downloader = require('./downloader'); + + const yargs = require('yargs') + .option('ls-version', { + alias: 'lv', + default: DEFAULT_ALS_VERSION, + choices: ['nightly'], + describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_ALS_VERSION}.` + }) + .option('clangd-version', { + alias: 'cv', + default: DEFAULT_CLANGD_VERSION, + choices: ['8.0.1'], + describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.` + }) + .option('force-download', { + alias: 'fd', + default: false, + describe: `If set, this script force downloads the 'arduino-language-server' even if it already exists on the file system.` + }) + .version(false).parse(); + + const alsVersion = yargs['ls-version']; + const clangdVersion = yargs['clangd-version'] + const force = yargs['force-download']; + const { platform, arch } = process; + + const build = path.join(__dirname, '..', 'build'); + const als = path.join(build, `arduino-language-server${os.platform() === 'win32' ? '.exe' : ''}`); + const clangd = path.join(build, `clangd${os.platform() === 'win32' ? '.exe' : ''}`); + + let alsSuffix, clangdSuffix; + switch (platform) { + case 'darwin': + alsSuffix = 'Darwin_amd64.zip'; + clangdSuffix = 'macos.zip'; + break; + case 'win32': + alsSuffix = 'Windows_NT_amd64.zip'; + clangdSuffix = 'windows.zip'; + break; + case 'linux': + alsSuffix = 'Linux_amd64.zip'; + break; + } + if (!alsSuffix) { + shell.echo(`The arduino-language-server is not available for ${platform} ${arch}.`); + shell.exit(1); + } + + const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${alsVersion === 'nightly' ? 'nightly/arduino-language-server' : 'arduino-language-server_' + alsVersion}_${alsSuffix}`; + downloader.download(alsUrl, als, 'arduino-language-server', force); + + if (clangdSuffix) { + const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${clangdVersion}_${clangdSuffix}`; + downloader.download(clangdUrl, clangd, 'clangd', force); + } + +})(); \ No newline at end of file diff --git a/arduino-ide-extension/scripts/downloader.js b/arduino-ide-extension/scripts/downloader.js new file mode 100644 index 00000000..929404c9 --- /dev/null +++ b/arduino-ide-extension/scripts/downloader.js @@ -0,0 +1,71 @@ +const fs = require('fs'); +const path = require('path'); +const shell = require('shelljs'); +const download = require('download'); +const decompress = require('decompress'); +const unzip = require('decompress-unzip'); +const untargz = require('decompress-targz'); + +process.on('unhandledRejection', (reason, _) => { + shell.echo(String(reason)); + shell.exit(1); + throw reason; +}); +process.on('uncaughtException', error => { + shell.echo(String(error)); + shell.exit(1); + throw error; +}); + +exports.download = async (url, targetFile, filePrefix, force) => { + const { platform, arch } = process; + + if (fs.existsSync(targetFile) && !force) { + shell.echo(`Skipping download because file already exists: ${targetFile}`); + return; + } + if (!fs.existsSync(path.dirname(targetFile))) { + if (shell.mkdir('-p', path.dirname(targetFile)).code !== 0) { + shell.echo('Could not create new directory.'); + shell.exit(1); + } + } + + const downloads = path.join(__dirname, '..', 'downloads'); + if (shell.rm('-rf', targetFile, downloads).code !== 0) { + shell.exit(1); + } + + shell.echo(`>>> Downloading from '${url}'...`); + const data = await download(url); + shell.echo(`<<< Download succeeded.`); + + shell.echo('>>> Decompressing...'); + const files = await decompress(data, downloads, { + plugins: [ + unzip(), + untargz() + ] + }); + if (files.length === 0) { + shell.echo('Error ocurred while decompressing the archive.'); + shell.exit(1); + } + const fileIndex = files.findIndex(f => f.path.startsWith(filePrefix)); + if (fileIndex === -1) { + shell.echo(`The downloaded artifact does not contain any file with prefix ${filePrefix}.`); + shell.exit(1); + } + shell.echo('<<< Decompressing succeeded.'); + + if (shell.mv('-f', path.join(downloads, files[fileIndex].path), targetFile).code !== 0) { + shell.echo(`Could not move file to target path: ${targetFile}`); + shell.exit(1); + } + if (!fs.existsSync(targetFile)) { + shell.echo(`Could not find file: ${targetFile}`); + shell.exit(1); + } else { + shell.echo(`Done: ${targetFile}`); + } +} From ec6b5ed3f37e7d68dd62865a62650fcf3ff5e19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Tue, 27 Aug 2019 16:25:31 +0200 Subject: [PATCH 4/4] Added command line argument for specifying cli path --- .../arduino-language-server-contribution.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts index 802fe8d3..1e6d44e9 100644 --- a/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts +++ b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts @@ -23,13 +23,14 @@ export class ArduinoLanguageServerContribution extends BaseLanguageServerContrib } async start(clientConnection: IConnection): Promise { - const clangd = await this.resolveExecutable('clangd') - const languageServer = await this.resolveExecutable('arduino-language-server') + const clangd = await this.resolveExecutable('clangd'); + const languageServer = await this.resolveExecutable('arduino-language-server'); + const cli = await this.resolveExecutable('arduino-cli'); // Add '-log' argument to enable logging to files - const args: string[] = ['-clangd', clangd] - console.log(`Starting language server ${languageServer} ${args.join(' ')}`) - const serverConnection = await this.createProcessStreamConnectionAsync(languageServer, args) - this.forward(clientConnection, serverConnection) + const args: string[] = ['-clangd', clangd, '-cli', cli]; + console.log(`Starting language server ${languageServer} ${args.join(' ')}`); + const serverConnection = await this.createProcessStreamConnectionAsync(languageServer, args); + this.forward(clientConnection, serverConnection); } protected resolveExecutable(name: string): Promise {