Merge pull request #59 from bcmi-labs/ino-language

Added language support for ino files
This commit is contained in:
Miro Spönemann 2019-09-09 10:28:40 +02:00 committed by GitHub
commit b1c69aef9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 225 additions and 65 deletions

View File

@ -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",

View File

@ -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);
})();

View File

@ -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);
}
})();

View File

@ -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}`);
}
}

View File

@ -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();

View File

@ -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']
}
}

View File

@ -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 {

View File

@ -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();

View File

@ -0,0 +1,49 @@
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<void> {
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, '-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<string> {
return new Promise<string>((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);
}
});
});
}
}