Skip to content

Commit 8da7359

Browse files
author
Akos Kitta
committed
Can start LS on a skecth.
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
1 parent 57607d0 commit 8da7359

File tree

2 files changed

+137
-27
lines changed

2 files changed

+137
-27
lines changed

package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@
7474
"theme": "light"
7575
},
7676
"activationEvents": [
77+
"onStartupFinished",
7778
"onLanguage:ino",
78-
"onCommand:arduino.ide2Path",
7979
"onCommand:arduino.debug.start",
8080
"onCommand:arduino.languageserver.start",
8181
"onCommand:arduino.languageserver.stop",
@@ -133,11 +133,6 @@
133133
"command": "arduino.debug.start",
134134
"title": "Start Debug",
135135
"category": "Arduino"
136-
},
137-
{
138-
"command": "arduino.ide2Path",
139-
"title": "Print IDE2 Path",
140-
"category": "Arduino"
141136
}
142137
],
143138
"configuration": {

src/extension.ts

Lines changed: 136 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import * as cp from 'child_process';
12
import * as path from 'path';
2-
import { promises as fs } from 'fs';
3+
import * as os from 'os';
4+
import { promises as fs, constants } from 'fs';
35
import { spawnSync } from 'child_process';
46
import deepEqual from 'deep-equal';
57
import WebRequest from 'web-request';
@@ -9,11 +11,52 @@ import vscode, { ExtensionContext } from 'vscode';
911
import { LanguageClient, CloseAction, ErrorAction, InitializeError, Message, RevealOutputChannelOn } from 'vscode-languageclient';
1012
import { DidCompleteBuildNotification, DidCompleteBuildParams } from './protocol';
1113

12-
interface LanguageServerConfig {
14+
interface LanguageServerExecutables {
1315
readonly lsPath: string;
14-
readonly cliDaemonAddr: string;
15-
readonly cliDaemonInstance: string;
1616
readonly clangdPath: string;
17+
readonly cliPath: string;
18+
}
19+
namespace LanguageServerExecutables {
20+
export function fromDir(dirPath: string): LanguageServerExecutables {
21+
const appRootPath = fromAppRootPath();
22+
return appendExeOnWindows({
23+
cliPath: path.join(dirPath, appRootPath, 'arduino-cli'),
24+
lsPath: path.join(dirPath, appRootPath, 'arduino-language-server'),
25+
clangdPath: path.join(dirPath, appRootPath, 'clangd'),
26+
});
27+
}
28+
export async function validate(executables: LanguageServerExecutables): Promise<void> {
29+
await Promise.all(Object.values(executables).map(canExecute));
30+
}
31+
export async function canExecute(pathToExecutable: string): Promise<void> {
32+
return fs.access(pathToExecutable, constants.X_OK);
33+
}
34+
function appendExeOnWindows(executables: LanguageServerExecutables): LanguageServerExecutables {
35+
if (process.platform === 'win32') {
36+
const exe = '.exe';
37+
return {
38+
cliPath: executables.cliPath + exe,
39+
lsPath: executables.lsPath + exe,
40+
clangdPath: executables.clangdPath + exe,
41+
};
42+
}
43+
return executables;
44+
}
45+
function fromAppRootPath(): string {
46+
const defaultPath = path.join('app', 'node_modules', 'arduino-ide-extension', 'build');
47+
switch (process.platform) {
48+
case 'win32':
49+
case 'linux':
50+
return path.join('resources', defaultPath);
51+
case 'darwin':
52+
return path.join('Contents', 'Resources', defaultPath);
53+
default:
54+
throw new Error(`Unsupported platform: ${process.platform}`);
55+
}
56+
}
57+
}
58+
59+
interface LanguageServerConfig {
1760
readonly board: {
1861
readonly fqbn: string;
1962
readonly name?: string;
@@ -67,28 +110,55 @@ const languageServerStartMutex = new Mutex();
67110
export let languageServerIsRunning = false; // TODO: use later for `start`, `stop`, and `restart` language server.
68111

69112
let ide2Path: string | undefined;
113+
let executables: LanguageServerExecutables | undefined;
114+
115+
function useIde2Path(ide2PathToUse: string | undefined = vscode.workspace.getConfiguration('arduinoTools').get('ide2Path')): string | undefined {
116+
ide2Path = ide2PathToUse;
117+
executables = findExecutables();
118+
if (executables) {
119+
vscode.window.showInformationMessage(`Executables: ${JSON.stringify(executables)}`);
120+
}
121+
return ide2Path;
122+
}
123+
function findExecutables(): LanguageServerExecutables | undefined {
124+
if (!ide2Path) {
125+
return undefined;
126+
}
127+
return LanguageServerExecutables.fromDir(ide2Path);
128+
}
129+
130+
interface Platform {
131+
readonly boards: Board[];
132+
}
133+
interface Board {
134+
readonly name: string;
135+
readonly fqbn?: string;
136+
}
137+
namespace Board {
138+
export function installed(board: Board): board is Board & { fqbn: string } {
139+
return !!board.fqbn;
140+
}
141+
}
70142

71143
export function activate(context: ExtensionContext) {
72-
ide2Path = vscode.workspace.getConfiguration('vscode-arduino-tools').get('arduinoTools.ide2Path');
144+
useIde2Path();
73145
vscode.window.showInformationMessage('ide2Path: ' + ide2Path);
74146
vscode.workspace.onDidChangeConfiguration(event => {
75147
if (event.affectsConfiguration('arduinoTools.ide2Path')) {
76-
ide2Path = vscode.workspace.getConfiguration('vscode-arduino-tools').get('arduinoTools.ide2Path');
77-
vscode.window.showInformationMessage('ide2Path: ' + ide2Path);
148+
useIde2Path();
78149
}
79150
});
80-
console.log('hello');
81151
context.subscriptions.push(
82-
vscode.commands.registerCommand('arduino.ide2Path', () => {
83-
ide2Path = vscode.workspace.getConfiguration('vscode-arduino-tools').get('arduinoTools.ide2Path');
84-
vscode.window.showInformationMessage('ide2Path: ' + ide2Path);
85-
}),
86-
vscode.commands.registerCommand('arduino.languageserver.start', async (config: LanguageServerConfig) => {
152+
vscode.commands.registerCommand('arduino.languageserver.start', async () => {
87153
const unlock = await languageServerStartMutex.acquire();
88154
try {
89-
const started = await startLanguageServer(context, config);
90-
languageServerIsRunning = started;
91-
return languageServerIsRunning ? config.board.fqbn : undefined;
155+
const fqbn = await selectFqbn();
156+
if (fqbn) {
157+
const started = await startLanguageServer(context, { board: { fqbn } });
158+
languageServerIsRunning = started;
159+
return languageServerIsRunning ? fqbn : undefined;
160+
}
161+
return false;
92162
} catch (err) {
93163
console.error('Failed to start the language server.', err);
94164
languageServerIsRunning = false;
@@ -118,10 +188,51 @@ export function activate(context: ExtensionContext) {
118188
} else {
119189
vscode.window.showWarningMessage('Language server is not running.');
120190
}
121-
})
191+
}),
122192
);
123193
}
124194

195+
async function selectFqbn(): Promise<string | undefined> {
196+
if (executables) {
197+
const boards = await installedBoards();
198+
const fqbn = await vscode.window.showQuickPick(boards.map(({fqbn}) => fqbn));
199+
return fqbn;
200+
}
201+
return undefined;
202+
}
203+
async function coreList(): Promise<Platform[]> {
204+
const raw = await cliExec(['core', 'list', '--format', 'json']);
205+
return JSON.parse(raw) as Platform[];
206+
}
207+
async function installedBoards(): Promise<(Board & {fqbn: string})[]> {
208+
const platforms = await coreList();
209+
return platforms.map(({boards}) => boards).reduce((acc, curr) => {
210+
acc.push(...curr);
211+
return acc;
212+
}, [] as Board[]).filter(Board.installed);
213+
}
214+
215+
async function cliExec(args: string[] = []): Promise<string> {
216+
if (!executables) {
217+
throw new Error("Could not find the Arduino executables. Did you set the 'ide2Path' correctly?");
218+
}
219+
const out: Buffer[] = [];
220+
const err: Buffer[] = [];
221+
return new Promise((resolve, reject) => {
222+
const child = cp.spawn(`"${executables?.cliPath}"`, args, { shell: true });
223+
child.stdout.on('data', (data) => out.push(data));
224+
child.stderr.on('data', (data) => err.push(data));
225+
child.on('error', reject);
226+
child.on('exit', (code) => {
227+
if (code === 0) {
228+
return resolve(Buffer.concat(out).toString('utf-8'));
229+
} else {
230+
return reject(Buffer.concat(err).toString('utf-8'));
231+
}
232+
});
233+
});
234+
};
235+
125236
async function startDebug(_: ExtensionContext, config: DebugConfig): Promise<boolean> {
126237
let info: DebugInfo | undefined = undefined;
127238
let rawStdout: string | undefined = undefined;
@@ -200,9 +311,13 @@ async function stopLanguageServer(context: ExtensionContext): Promise<void> {
200311

201312
async function startLanguageServer(context: ExtensionContext, config: LanguageServerConfig): Promise<boolean> {
202313
await stopLanguageServer(context);
314+
if (!executables) {
315+
vscode.window.showErrorMessage("Failed to start the language server. Could not find the Arduino executables. Did you set the 'ide2Path' correctly?");
316+
return false;
317+
}
203318
if (!languageClient || !deepEqual(latestConfig, config)) {
204319
latestConfig = config;
205-
languageClient = await buildLanguageClient(config);
320+
languageClient = await buildLanguageClient(Object.assign(config, executables));
206321
crashCount = 0;
207322
}
208323

@@ -212,9 +327,9 @@ async function startLanguageServer(context: ExtensionContext, config: LanguageSe
212327
return true;
213328
}
214329

215-
async function buildLanguageClient(config: LanguageServerConfig): Promise<LanguageClient> {
216-
const { lsPath: command, clangdPath, cliDaemonAddr, cliDaemonInstance, board, flags, env, log } = config;
217-
const args = ['-clangd', clangdPath, '-cli-daemon-addr', cliDaemonAddr, '-cli-daemon-instance', cliDaemonInstance, '-fqbn', board.fqbn, '-skip-libraries-discovery-on-rebuild'];
330+
async function buildLanguageClient(config: LanguageServerConfig & LanguageServerExecutables): Promise<LanguageClient> {
331+
const { lsPath: command, clangdPath, board, flags, env, log } = config;
332+
const args = ['-cli', config.cliPath, '-cli-config', path.join(os.homedir(), '.arduinoIDE/arduino-cli.yaml'), '-clangd', clangdPath, '-fqbn', board.fqbn ?? 'arduino:avr:uno', '-skip-libraries-discovery-on-rebuild'];
218333
if (board.name) {
219334
args.push('-board-name', board.name);
220335
}

0 commit comments

Comments
 (0)