mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-10 12:56:32 +00:00
IDE2 falls back to a new sketch if the opening fails. Closes #1089 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
fe31d15b9f
commit
8ad10b5adf
@ -1,21 +0,0 @@
|
|||||||
import { Command } from '@theia/core/lib/common/command';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated all these commands should go under contributions and have their command, menu, keybinding, and toolbar contributions.
|
|
||||||
*/
|
|
||||||
export namespace ArduinoCommands {
|
|
||||||
export const TOGGLE_COMPILE_FOR_DEBUG: Command = {
|
|
||||||
id: 'arduino-toggle-compile-for-debug',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unlike `OPEN_SKETCH`, it opens all files from a sketch folder. (ino, cpp, etc...)
|
|
||||||
*/
|
|
||||||
export const OPEN_SKETCH_FILES: Command = {
|
|
||||||
id: 'arduino-open-sketch-files',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OPEN_BOARDS_DIALOG: Command = {
|
|
||||||
id: 'arduino-open-boards-dialog',
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,36 +1,22 @@
|
|||||||
|
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
||||||
import {
|
import {
|
||||||
inject,
|
inject,
|
||||||
injectable,
|
injectable,
|
||||||
postConstruct,
|
postConstruct,
|
||||||
} from '@theia/core/shared/inversify';
|
} from '@theia/core/shared/inversify';
|
||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
import { SketchesService } from '../common/protocol';
|
||||||
import {
|
|
||||||
BoardsService,
|
|
||||||
SketchesService,
|
|
||||||
ExecutableService,
|
|
||||||
Sketch,
|
|
||||||
ArduinoDaemon,
|
|
||||||
} from '../common/protocol';
|
|
||||||
import { Mutex } from 'async-mutex';
|
|
||||||
import {
|
import {
|
||||||
MAIN_MENU_BAR,
|
MAIN_MENU_BAR,
|
||||||
MenuContribution,
|
MenuContribution,
|
||||||
MenuModelRegistry,
|
MenuModelRegistry,
|
||||||
ILogger,
|
|
||||||
DisposableCollection,
|
|
||||||
} from '@theia/core';
|
} from '@theia/core';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
FrontendApplication,
|
FrontendApplication,
|
||||||
FrontendApplicationContribution,
|
FrontendApplicationContribution,
|
||||||
LocalStorageService,
|
|
||||||
OnWillStopAction,
|
OnWillStopAction,
|
||||||
SaveableWidget,
|
|
||||||
StatusBar,
|
|
||||||
StatusBarAlignment,
|
|
||||||
} from '@theia/core/lib/browser';
|
} from '@theia/core/lib/browser';
|
||||||
import { nls } from '@theia/core/lib/common';
|
|
||||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||||
@ -38,45 +24,28 @@ import {
|
|||||||
TabBarToolbarContribution,
|
TabBarToolbarContribution,
|
||||||
TabBarToolbarRegistry,
|
TabBarToolbarRegistry,
|
||||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||||
|
import { nls } from '@theia/core/lib/common';
|
||||||
import {
|
import {
|
||||||
CommandContribution,
|
CommandContribution,
|
||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
} from '@theia/core/lib/common/command';
|
} from '@theia/core/lib/common/command';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import { EditorCommands, EditorMainMenu } from '@theia/editor/lib/browser';
|
||||||
import {
|
|
||||||
EditorCommands,
|
|
||||||
EditorMainMenu,
|
|
||||||
EditorManager,
|
|
||||||
EditorOpenerOptions,
|
|
||||||
} from '@theia/editor/lib/browser';
|
|
||||||
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
||||||
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
||||||
import { FileChangeType } from '@theia/filesystem/lib/browser';
|
|
||||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
|
||||||
import { ArduinoCommands } from './arduino-commands';
|
|
||||||
import { BoardsConfig } from './boards/boards-config';
|
|
||||||
import { BoardsConfigDialog } from './boards/boards-config-dialog';
|
|
||||||
import { BoardsServiceProvider } from './boards/boards-service-provider';
|
|
||||||
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
|
||||||
import { EditorMode } from './editor-mode';
|
|
||||||
import { ArduinoMenus } from './menu/arduino-menus';
|
|
||||||
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
|
|
||||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
|
||||||
import { ArduinoPreferences } from './arduino-preferences';
|
|
||||||
import {
|
import {
|
||||||
CurrentSketch,
|
CurrentSketch,
|
||||||
SketchesServiceClientImpl,
|
SketchesServiceClientImpl,
|
||||||
} from '../common/protocol/sketches-service-client-impl';
|
} from '../common/protocol/sketches-service-client-impl';
|
||||||
|
import { ArduinoPreferences } from './arduino-preferences';
|
||||||
|
import { BoardsServiceProvider } from './boards/boards-service-provider';
|
||||||
|
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
||||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||||
import { IDEUpdaterDialog } from './dialogs/ide-updater/ide-updater-dialog';
|
import { ArduinoMenus } from './menu/arduino-menus';
|
||||||
import { IDEUpdater } from '../common/protocol/ide-updater';
|
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
|
||||||
import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
|
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||||
import { HostedPluginEvents } from './hosted-plugin-events';
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
|
||||||
export const SKIP_IDE_VERSION = 'skipIDEVersion';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ArduinoFrontendContribution
|
export class ArduinoFrontendContribution
|
||||||
@ -87,45 +56,18 @@ export class ArduinoFrontendContribution
|
|||||||
MenuContribution,
|
MenuContribution,
|
||||||
ColorContribution
|
ColorContribution
|
||||||
{
|
{
|
||||||
@inject(ILogger)
|
|
||||||
private readonly logger: ILogger;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
@inject(MessageService)
|
||||||
private readonly messageService: MessageService;
|
private readonly messageService: MessageService;
|
||||||
|
|
||||||
@inject(BoardsService)
|
|
||||||
private readonly boardsService: BoardsService;
|
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
@inject(BoardsServiceProvider)
|
||||||
private readonly boardsServiceClientImpl: BoardsServiceProvider;
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
@inject(EditorManager)
|
|
||||||
private readonly editorManager: EditorManager;
|
|
||||||
|
|
||||||
@inject(FileService)
|
|
||||||
private readonly fileService: FileService;
|
|
||||||
|
|
||||||
@inject(SketchesService)
|
@inject(SketchesService)
|
||||||
private readonly sketchService: SketchesService;
|
private readonly sketchService: SketchesService;
|
||||||
|
|
||||||
@inject(BoardsConfigDialog)
|
|
||||||
private readonly boardsConfigDialog: BoardsConfigDialog;
|
|
||||||
|
|
||||||
@inject(CommandRegistry)
|
@inject(CommandRegistry)
|
||||||
private readonly commandRegistry: CommandRegistry;
|
private readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
@inject(StatusBar)
|
|
||||||
private readonly statusBar: StatusBar;
|
|
||||||
|
|
||||||
@inject(EditorMode)
|
|
||||||
private readonly editorMode: EditorMode;
|
|
||||||
|
|
||||||
@inject(HostedPluginEvents)
|
|
||||||
private readonly hostedPluginEvents: HostedPluginEvents;
|
|
||||||
|
|
||||||
@inject(ExecutableService)
|
|
||||||
private readonly executableService: ExecutableService;
|
|
||||||
|
|
||||||
@inject(ArduinoPreferences)
|
@inject(ArduinoPreferences)
|
||||||
private readonly arduinoPreferences: ArduinoPreferences;
|
private readonly arduinoPreferences: ArduinoPreferences;
|
||||||
|
|
||||||
@ -135,26 +77,6 @@ export class ArduinoFrontendContribution
|
|||||||
@inject(FrontendApplicationStateService)
|
@inject(FrontendApplicationStateService)
|
||||||
private readonly appStateService: FrontendApplicationStateService;
|
private readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
@inject(LocalStorageService)
|
|
||||||
private readonly localStorageService: LocalStorageService;
|
|
||||||
|
|
||||||
@inject(FileSystemFrontendContribution)
|
|
||||||
private readonly fileSystemFrontendContribution: FileSystemFrontendContribution;
|
|
||||||
|
|
||||||
@inject(IDEUpdater)
|
|
||||||
private readonly updater: IDEUpdater;
|
|
||||||
|
|
||||||
@inject(IDEUpdaterDialog)
|
|
||||||
private readonly updaterDialog: IDEUpdaterDialog;
|
|
||||||
|
|
||||||
@inject(ArduinoDaemon)
|
|
||||||
private readonly daemon: ArduinoDaemon;
|
|
||||||
|
|
||||||
protected invalidConfigPopup:
|
|
||||||
| Promise<void | 'No' | 'Yes' | undefined>
|
|
||||||
| undefined;
|
|
||||||
protected toDisposeOnStop = new DisposableCollection();
|
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected async init(): Promise<void> {
|
protected async init(): Promise<void> {
|
||||||
if (!window.navigator.onLine) {
|
if (!window.navigator.onLine) {
|
||||||
@ -166,250 +88,32 @@ export class ArduinoFrontendContribution
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const updateStatusBar = ({
|
|
||||||
selectedBoard,
|
|
||||||
selectedPort,
|
|
||||||
}: BoardsConfig.Config) => {
|
|
||||||
this.statusBar.setElement('arduino-selected-board', {
|
|
||||||
alignment: StatusBarAlignment.RIGHT,
|
|
||||||
text: selectedBoard
|
|
||||||
? `$(microchip) ${selectedBoard.name}`
|
|
||||||
: `$(close) ${nls.localize(
|
|
||||||
'arduino/common/noBoardSelected',
|
|
||||||
'No board selected'
|
|
||||||
)}`,
|
|
||||||
className: 'arduino-selected-board',
|
|
||||||
});
|
|
||||||
if (selectedBoard) {
|
|
||||||
this.statusBar.setElement('arduino-selected-port', {
|
|
||||||
alignment: StatusBarAlignment.RIGHT,
|
|
||||||
text: selectedPort
|
|
||||||
? nls.localize(
|
|
||||||
'arduino/common/selectedOn',
|
|
||||||
'on {0}',
|
|
||||||
selectedPort.address
|
|
||||||
)
|
|
||||||
: nls.localize('arduino/common/notConnected', '[not connected]'),
|
|
||||||
className: 'arduino-selected-port',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.boardsServiceClientImpl.onBoardsConfigChanged(updateStatusBar);
|
|
||||||
updateStatusBar(this.boardsServiceClientImpl.boardsConfig);
|
|
||||||
this.appStateService.reachedState('ready').then(async () => {
|
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
|
||||||
if (
|
|
||||||
CurrentSketch.isValid(sketch) &&
|
|
||||||
!(await this.sketchService.isTemp(sketch))
|
|
||||||
) {
|
|
||||||
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
|
|
||||||
this.toDisposeOnStop.push(
|
|
||||||
this.fileService.onDidFilesChange(async (event) => {
|
|
||||||
for (const { type, resource } of event.changes) {
|
|
||||||
if (
|
|
||||||
type === FileChangeType.ADDED &&
|
|
||||||
resource.parent.toString() === sketch.uri
|
|
||||||
) {
|
|
||||||
const reloadedSketch = await this.sketchService.loadSketch(
|
|
||||||
sketch.uri
|
|
||||||
);
|
|
||||||
if (Sketch.isInSketch(resource, reloadedSketch)) {
|
|
||||||
this.ensureOpened(resource.toString(), true, {
|
|
||||||
mode: 'open',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onStart(app: FrontendApplication): Promise<void> {
|
async onStart(app: FrontendApplication): Promise<void> {
|
||||||
this.updater
|
|
||||||
.init(
|
|
||||||
this.arduinoPreferences.get('arduino.ide.updateChannel'),
|
|
||||||
this.arduinoPreferences.get('arduino.ide.updateBaseUrl')
|
|
||||||
)
|
|
||||||
.then(() => this.updater.checkForUpdates(true))
|
|
||||||
.then(async (updateInfo) => {
|
|
||||||
if (!updateInfo) return;
|
|
||||||
const versionToSkip = await this.localStorageService.getData<string>(
|
|
||||||
SKIP_IDE_VERSION
|
|
||||||
);
|
|
||||||
if (versionToSkip === updateInfo.version) return;
|
|
||||||
this.updaterDialog.open(updateInfo);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
this.messageService.error(
|
|
||||||
nls.localize(
|
|
||||||
'arduino/ide-updater/errorCheckingForUpdates',
|
|
||||||
'Error while checking for Arduino IDE updates.\n{0}',
|
|
||||||
e.message
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const start = async (
|
|
||||||
{ selectedBoard }: BoardsConfig.Config,
|
|
||||||
forceStart = false
|
|
||||||
) => {
|
|
||||||
if (selectedBoard) {
|
|
||||||
const { name, fqbn } = selectedBoard;
|
|
||||||
if (fqbn) {
|
|
||||||
this.startLanguageServer(fqbn, name, forceStart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.boardsServiceClientImpl.onBoardsConfigChanged(start);
|
|
||||||
this.hostedPluginEvents.onPluginsDidStart(() =>
|
|
||||||
start(this.boardsServiceClientImpl.boardsConfig)
|
|
||||||
);
|
|
||||||
this.hostedPluginEvents.onPluginsWillUnload(
|
|
||||||
() => (this.languageServerFqbn = undefined)
|
|
||||||
);
|
|
||||||
this.arduinoPreferences.onPreferenceChanged((event) => {
|
this.arduinoPreferences.onPreferenceChanged((event) => {
|
||||||
if (event.newValue !== event.oldValue) {
|
if (event.newValue !== event.oldValue) {
|
||||||
switch (event.preferenceName) {
|
switch (event.preferenceName) {
|
||||||
case 'arduino.language.log':
|
|
||||||
case 'arduino.language.realTimeDiagnostics':
|
|
||||||
start(this.boardsServiceClientImpl.boardsConfig, true);
|
|
||||||
break;
|
|
||||||
case 'arduino.window.zoomLevel':
|
case 'arduino.window.zoomLevel':
|
||||||
if (typeof event.newValue === 'number') {
|
if (typeof event.newValue === 'number') {
|
||||||
const webContents = remote.getCurrentWebContents();
|
const webContents = remote.getCurrentWebContents();
|
||||||
webContents.setZoomLevel(event.newValue || 0);
|
webContents.setZoomLevel(event.newValue || 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'arduino.ide.updateChannel':
|
|
||||||
case 'arduino.ide.updateBaseUrl':
|
|
||||||
this.updater.init(
|
|
||||||
this.arduinoPreferences.get('arduino.ide.updateChannel'),
|
|
||||||
this.arduinoPreferences.get('arduino.ide.updateBaseUrl')
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.arduinoPreferences.ready.then(() => {
|
this.appStateService.reachedState('initialized_layout').then(() =>
|
||||||
const webContents = remote.getCurrentWebContents();
|
this.arduinoPreferences.ready.then(() => {
|
||||||
const zoomLevel = this.arduinoPreferences.get('arduino.window.zoomLevel');
|
const webContents = remote.getCurrentWebContents();
|
||||||
webContents.setZoomLevel(zoomLevel);
|
const zoomLevel = this.arduinoPreferences.get(
|
||||||
});
|
'arduino.window.zoomLevel'
|
||||||
|
|
||||||
app.shell.leftPanelHandler.removeBottomMenu('settings-menu');
|
|
||||||
|
|
||||||
this.fileSystemFrontendContribution.onDidChangeEditorFile(
|
|
||||||
({ type, editor }) => {
|
|
||||||
if (type === FileChangeType.DELETED) {
|
|
||||||
const editorWidget = editor;
|
|
||||||
if (SaveableWidget.is(editorWidget)) {
|
|
||||||
editorWidget.closeWithoutSaving();
|
|
||||||
} else {
|
|
||||||
editorWidget.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onStop(): void {
|
|
||||||
this.toDisposeOnStop.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected languageServerFqbn?: string;
|
|
||||||
protected languageServerStartMutex = new Mutex();
|
|
||||||
protected async startLanguageServer(
|
|
||||||
fqbn: string,
|
|
||||||
name: string | undefined,
|
|
||||||
forceStart = false
|
|
||||||
): Promise<void> {
|
|
||||||
const port = await this.daemon.tryGetPort();
|
|
||||||
if (!port) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const release = await this.languageServerStartMutex.acquire();
|
|
||||||
try {
|
|
||||||
await this.hostedPluginEvents.didStart;
|
|
||||||
const details = await this.boardsService.getBoardDetails({ fqbn });
|
|
||||||
if (!details) {
|
|
||||||
// Core is not installed for the selected board.
|
|
||||||
console.info(
|
|
||||||
`Could not start language server for ${fqbn}. The core is not installed for the board.`
|
|
||||||
);
|
);
|
||||||
if (this.languageServerFqbn) {
|
webContents.setZoomLevel(zoomLevel);
|
||||||
try {
|
})
|
||||||
await this.commandRegistry.executeCommand(
|
);
|
||||||
'arduino.languageserver.stop'
|
// Removes the _Settings_ (cog) icon from the left sidebar
|
||||||
);
|
app.shell.leftPanelHandler.removeBottomMenu('settings-menu');
|
||||||
console.info(
|
|
||||||
`Stopped language server process for ${this.languageServerFqbn}.`
|
|
||||||
);
|
|
||||||
this.languageServerFqbn = undefined;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(
|
|
||||||
`Failed to start language server process for ${this.languageServerFqbn}`,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
if (CurrentSketch.isValid(currentSketch)) {
|
|
||||||
currentSketchPath = await this.fileService.fsPath(
|
|
||||||
new URI(currentSketch.uri)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { clangdUri, lsUri } = await this.executableService.list();
|
|
||||||
const [clangdPath, lsPath] = await Promise.all([
|
|
||||||
this.fileService.fsPath(new URI(clangdUri)),
|
|
||||||
this.fileService.fsPath(new URI(lsUri)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.languageServerFqbn = await Promise.race([
|
|
||||||
new Promise<undefined>((_, reject) =>
|
|
||||||
setTimeout(
|
|
||||||
() => reject(new Error(`Timeout after ${20_000} ms.`)),
|
|
||||||
20_000
|
|
||||||
)
|
|
||||||
),
|
|
||||||
this.commandRegistry.executeCommand<string>(
|
|
||||||
'arduino.languageserver.start',
|
|
||||||
{
|
|
||||||
lsPath,
|
|
||||||
cliDaemonAddr: `localhost:${port}`,
|
|
||||||
clangdPath,
|
|
||||||
log: currentSketchPath ? currentSketchPath : log,
|
|
||||||
cliDaemonInstance: '1',
|
|
||||||
realTimeDiagnostics,
|
|
||||||
board: {
|
|
||||||
fqbn,
|
|
||||||
name: name ? `"${name}"` : undefined,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Failed to start language server for ${fqbn}`, e);
|
|
||||||
this.languageServerFqbn = undefined;
|
|
||||||
} finally {
|
|
||||||
release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||||
@ -419,7 +123,7 @@ export class ArduinoFrontendContribution
|
|||||||
<BoardsToolBarItem
|
<BoardsToolBarItem
|
||||||
key="boardsToolbarItem"
|
key="boardsToolbarItem"
|
||||||
commands={this.commandRegistry}
|
commands={this.commandRegistry}
|
||||||
boardsServiceClient={this.boardsServiceClientImpl}
|
boardsServiceProvider={this.boardsServiceProvider}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
isVisible: (widget) =>
|
isVisible: (widget) =>
|
||||||
@ -434,24 +138,6 @@ export class ArduinoFrontendContribution
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, {
|
|
||||||
execute: () => this.editorMode.toggleCompileForDebug(),
|
|
||||||
isToggled: () => this.editorMode.compileForDebug,
|
|
||||||
});
|
|
||||||
registry.registerCommand(ArduinoCommands.OPEN_SKETCH_FILES, {
|
|
||||||
execute: async (uri: URI) => {
|
|
||||||
this.openSketchFiles(uri);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, {
|
|
||||||
execute: async (query?: string | undefined) => {
|
|
||||||
const boardsConfig = await this.boardsConfigDialog.open(query);
|
|
||||||
if (boardsConfig) {
|
|
||||||
this.boardsServiceClientImpl.boardsConfig = boardsConfig;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const command of [
|
for (const command of [
|
||||||
EditorCommands.SPLIT_EDITOR_DOWN,
|
EditorCommands.SPLIT_EDITOR_DOWN,
|
||||||
EditorCommands.SPLIT_EDITOR_LEFT,
|
EditorCommands.SPLIT_EDITOR_LEFT,
|
||||||
@ -484,70 +170,6 @@ export class ArduinoFrontendContribution
|
|||||||
ArduinoMenus.TOOLS,
|
ArduinoMenus.TOOLS,
|
||||||
nls.localize('arduino/menu/tools', 'Tools')
|
nls.localize('arduino/menu/tools', 'Tools')
|
||||||
);
|
);
|
||||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
|
||||||
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
|
|
||||||
label: nls.localize(
|
|
||||||
'arduino/debug/optimizeForDebugging',
|
|
||||||
'Optimize for Debugging'
|
|
||||||
),
|
|
||||||
order: '5',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async openSketchFiles(uri: URI): Promise<void> {
|
|
||||||
try {
|
|
||||||
const sketch = await this.sketchService.loadSketch(uri.toString());
|
|
||||||
const { mainFileUri, rootFolderFileUris } = sketch;
|
|
||||||
for (const uri of [mainFileUri, ...rootFolderFileUris]) {
|
|
||||||
await this.ensureOpened(uri);
|
|
||||||
}
|
|
||||||
if (mainFileUri.endsWith('.pde')) {
|
|
||||||
const message = nls.localize(
|
|
||||||
'arduino/common/oldFormat',
|
|
||||||
"The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?",
|
|
||||||
sketch.name
|
|
||||||
);
|
|
||||||
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
|
||||||
this.messageService
|
|
||||||
.info(message, nls.localize('arduino/common/later', 'Later'), yes)
|
|
||||||
.then(async (answer) => {
|
|
||||||
if (answer === yes) {
|
|
||||||
this.commandRegistry.executeCommand(
|
|
||||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
|
||||||
{
|
|
||||||
execOnlyIfTemp: false,
|
|
||||||
openAfterMove: true,
|
|
||||||
wipeOriginal: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
const message = e instanceof Error ? e.message : JSON.stringify(e);
|
|
||||||
this.messageService.error(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async ensureOpened(
|
|
||||||
uri: string,
|
|
||||||
forceOpen = false,
|
|
||||||
options?: EditorOpenerOptions | undefined
|
|
||||||
): Promise<unknown> {
|
|
||||||
const widget = this.editorManager.all.find(
|
|
||||||
(widget) => widget.editor.uri.toString() === uri
|
|
||||||
);
|
|
||||||
if (!widget || forceOpen) {
|
|
||||||
return this.editorManager.open(
|
|
||||||
new URI(uri),
|
|
||||||
options ?? {
|
|
||||||
mode: 'reveal',
|
|
||||||
preview: false,
|
|
||||||
counter: 0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerColors(colors: ColorRegistry): void {
|
registerColors(colors: ColorRegistry): void {
|
||||||
@ -699,6 +321,7 @@ export class ArduinoFrontendContribution
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: should be handled by `Close` contribution. https://github.com/arduino/arduino-ide/issues/1016
|
||||||
onWillStop(): OnWillStopAction {
|
onWillStop(): OnWillStopAction {
|
||||||
return {
|
return {
|
||||||
reason: 'temp-sketch',
|
reason: 'temp-sketch',
|
||||||
|
@ -80,7 +80,6 @@ import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browse
|
|||||||
import { ProblemManager } from './theia/markers/problem-manager';
|
import { ProblemManager } from './theia/markers/problem-manager';
|
||||||
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
||||||
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
|
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
|
||||||
import { EditorMode } from './editor-mode';
|
|
||||||
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
||||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||||
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||||
@ -301,10 +300,16 @@ import { CoreErrorHandler } from './contributions/core-error-handler';
|
|||||||
import { CompilerErrors } from './contributions/compiler-errors';
|
import { CompilerErrors } from './contributions/compiler-errors';
|
||||||
import { WidgetManager } from './theia/core/widget-manager';
|
import { WidgetManager } from './theia/core/widget-manager';
|
||||||
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
|
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
|
||||||
import { StartupTask } from './widgets/sketchbook/startup-task';
|
import { StartupTasks } from './widgets/sketchbook/startup-task';
|
||||||
import { IndexesUpdateProgress } from './contributions/indexes-update-progress';
|
import { IndexesUpdateProgress } from './contributions/indexes-update-progress';
|
||||||
import { Daemon } from './contributions/daemon';
|
import { Daemon } from './contributions/daemon';
|
||||||
import { FirstStartupInstaller } from './contributions/first-startup-installer';
|
import { FirstStartupInstaller } from './contributions/first-startup-installer';
|
||||||
|
import { OpenSketchFiles } from './contributions/open-sketch-files';
|
||||||
|
import { InoLanguage } from './contributions/ino-language';
|
||||||
|
import { SelectedBoard } from './contributions/selected-board';
|
||||||
|
import { CheckForUpdates } from './contributions/check-for-updates';
|
||||||
|
import { OpenBoardsConfig } from './contributions/open-boards-config';
|
||||||
|
import { SketchFilesTracker } from './contributions/sketch-files-tracker';
|
||||||
|
|
||||||
MonacoThemingService.register({
|
MonacoThemingService.register({
|
||||||
id: 'arduino-theme',
|
id: 'arduino-theme',
|
||||||
@ -486,10 +491,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
WorkspaceVariableContribution
|
WorkspaceVariableContribution
|
||||||
);
|
);
|
||||||
|
|
||||||
// Customizing default Theia layout based on the editor mode: `pro-mode` or `classic`.
|
|
||||||
bind(EditorMode).toSelf().inSingletonScope();
|
|
||||||
bind(FrontendApplicationContribution).toService(EditorMode);
|
|
||||||
|
|
||||||
bind(SurveyNotificationService)
|
bind(SurveyNotificationService)
|
||||||
.toDynamicValue((context) => {
|
.toDynamicValue((context) => {
|
||||||
return ElectronIpcConnectionProvider.createProxy(
|
return ElectronIpcConnectionProvider.createProxy(
|
||||||
@ -697,10 +698,16 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
Contribution.configure(bind, PlotterFrontendContribution);
|
Contribution.configure(bind, PlotterFrontendContribution);
|
||||||
Contribution.configure(bind, Format);
|
Contribution.configure(bind, Format);
|
||||||
Contribution.configure(bind, CompilerErrors);
|
Contribution.configure(bind, CompilerErrors);
|
||||||
Contribution.configure(bind, StartupTask);
|
Contribution.configure(bind, StartupTasks);
|
||||||
Contribution.configure(bind, IndexesUpdateProgress);
|
Contribution.configure(bind, IndexesUpdateProgress);
|
||||||
Contribution.configure(bind, Daemon);
|
Contribution.configure(bind, Daemon);
|
||||||
Contribution.configure(bind, FirstStartupInstaller);
|
Contribution.configure(bind, FirstStartupInstaller);
|
||||||
|
Contribution.configure(bind, OpenSketchFiles);
|
||||||
|
Contribution.configure(bind, InoLanguage);
|
||||||
|
Contribution.configure(bind, SelectedBoard);
|
||||||
|
Contribution.configure(bind, CheckForUpdates);
|
||||||
|
Contribution.configure(bind, OpenBoardsConfig);
|
||||||
|
Contribution.configure(bind, SketchFilesTracker);
|
||||||
|
|
||||||
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
||||||
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
||||||
|
@ -92,6 +92,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
|
|||||||
),
|
),
|
||||||
default: 'None',
|
default: 'None',
|
||||||
},
|
},
|
||||||
|
'arduino.compile.optimizeForDebug': {
|
||||||
|
type: 'boolean',
|
||||||
|
description: nls.localize(
|
||||||
|
'arduino/preferences/compile.optimizeForDebug',
|
||||||
|
"Optimize compile output for debug, not for release. It's 'false' by default."
|
||||||
|
),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
'arduino.upload.verbose': {
|
'arduino.upload.verbose': {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: nls.localize(
|
description: nls.localize(
|
||||||
@ -185,10 +193,10 @@ export const ArduinoConfigSchema: PreferenceSchema = {
|
|||||||
),
|
),
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
'arduino.cloud.sketchSyncEnpoint': {
|
'arduino.cloud.sketchSyncEndpoint': {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: nls.localize(
|
description: nls.localize(
|
||||||
'arduino/preferences/cloud.sketchSyncEnpoint',
|
'arduino/preferences/cloud.sketchSyncEndpoint',
|
||||||
'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.'
|
'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.'
|
||||||
),
|
),
|
||||||
default: 'https://api2.arduino.cc/create',
|
default: 'https://api2.arduino.cc/create',
|
||||||
@ -251,6 +259,7 @@ export interface ArduinoConfiguration {
|
|||||||
'arduino.compile.experimental': boolean;
|
'arduino.compile.experimental': boolean;
|
||||||
'arduino.compile.revealRange': ErrorRevealStrategy;
|
'arduino.compile.revealRange': ErrorRevealStrategy;
|
||||||
'arduino.compile.warnings': CompilerWarnings;
|
'arduino.compile.warnings': CompilerWarnings;
|
||||||
|
'arduino.compile.optimizeForDebug': boolean;
|
||||||
'arduino.upload.verbose': boolean;
|
'arduino.upload.verbose': boolean;
|
||||||
'arduino.upload.verify': boolean;
|
'arduino.upload.verify': boolean;
|
||||||
'arduino.window.autoScale': boolean;
|
'arduino.window.autoScale': boolean;
|
||||||
@ -263,7 +272,7 @@ export interface ArduinoConfiguration {
|
|||||||
'arduino.cloud.pull.warn': boolean;
|
'arduino.cloud.pull.warn': boolean;
|
||||||
'arduino.cloud.push.warn': boolean;
|
'arduino.cloud.push.warn': boolean;
|
||||||
'arduino.cloud.pushpublic.warn': boolean;
|
'arduino.cloud.pushpublic.warn': boolean;
|
||||||
'arduino.cloud.sketchSyncEnpoint': string;
|
'arduino.cloud.sketchSyncEndpoint': string;
|
||||||
'arduino.auth.clientID': string;
|
'arduino.auth.clientID': string;
|
||||||
'arduino.auth.domain': string;
|
'arduino.auth.domain': string;
|
||||||
'arduino.auth.audience': string;
|
'arduino.auth.audience': string;
|
||||||
|
@ -17,10 +17,10 @@ import {
|
|||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
import { naturalCompare } from '../../common/utils';
|
import { naturalCompare } from '../../common/utils';
|
||||||
import { NotificationCenter } from '../notification-center';
|
import { NotificationCenter } from '../notification-center';
|
||||||
import { ArduinoCommands } from '../arduino-commands';
|
|
||||||
import { StorageWrapper } from '../storage-wrapper';
|
import { StorageWrapper } from '../storage-wrapper';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||||
@ -39,6 +39,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
@inject(NotificationCenter)
|
@inject(NotificationCenter)
|
||||||
protected notificationCenter: NotificationCenter;
|
protected notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
@inject(FrontendApplicationStateService)
|
||||||
|
private readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
protected readonly onBoardsConfigChangedEmitter =
|
protected readonly onBoardsConfigChangedEmitter =
|
||||||
new Emitter<BoardsConfig.Config>();
|
new Emitter<BoardsConfig.Config>();
|
||||||
protected readonly onAvailableBoardsChangedEmitter = new Emitter<
|
protected readonly onAvailableBoardsChangedEmitter = new Emitter<
|
||||||
@ -87,11 +90,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
this.notifyPlatformUninstalled.bind(this)
|
this.notifyPlatformUninstalled.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
Promise.all([
|
this.appStateService.reachedState('ready').then(async () => {
|
||||||
this.boardsService.getAttachedBoards(),
|
const [attachedBoards, availablePorts] = await Promise.all([
|
||||||
this.boardsService.getAvailablePorts(),
|
this.boardsService.getAttachedBoards(),
|
||||||
this.loadState(),
|
this.boardsService.getAvailablePorts(),
|
||||||
]).then(async ([attachedBoards, availablePorts]) => {
|
this.loadState(),
|
||||||
|
]);
|
||||||
this._attachedBoards = attachedBoards;
|
this._attachedBoards = attachedBoards;
|
||||||
this._availablePorts = availablePorts;
|
this._availablePorts = availablePorts;
|
||||||
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
|
||||||
@ -166,7 +170,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
|||||||
.then(async (answer) => {
|
.then(async (answer) => {
|
||||||
if (answer === yes) {
|
if (answer === yes) {
|
||||||
this.commandService.executeCommand(
|
this.commandService.executeCommand(
|
||||||
ArduinoCommands.OPEN_BOARDS_DIALOG.id,
|
'arduino-open-boards-dialog',
|
||||||
selectedBoard.name
|
selectedBoard.name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import * as ReactDOM from '@theia/core/shared/react-dom';
|
|||||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
import { Port } from '../../common/protocol';
|
import { Port } from '../../common/protocol';
|
||||||
import { ArduinoCommands } from '../arduino-commands';
|
import { OpenBoardsConfig } from '../contributions/open-boards-config';
|
||||||
import {
|
import {
|
||||||
BoardsServiceProvider,
|
BoardsServiceProvider,
|
||||||
AvailableBoard,
|
AvailableBoard,
|
||||||
@ -155,7 +155,7 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
constructor(props: BoardsToolBarItem.Props) {
|
constructor(props: BoardsToolBarItem.Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { availableBoards } = props.boardsServiceClient;
|
const { availableBoards } = props.boardsServiceProvider;
|
||||||
this.state = {
|
this.state = {
|
||||||
availableBoards,
|
availableBoards,
|
||||||
coords: 'hidden',
|
coords: 'hidden',
|
||||||
@ -167,8 +167,8 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override componentDidMount(): void {
|
override componentDidMount(): void {
|
||||||
this.props.boardsServiceClient.onAvailableBoardsChanged((availableBoards) =>
|
this.props.boardsServiceProvider.onAvailableBoardsChanged(
|
||||||
this.setState({ availableBoards })
|
(availableBoards) => this.setState({ availableBoards })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
this.toDispose.dispose();
|
this.toDispose.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly show = (event: React.MouseEvent<HTMLElement>) => {
|
protected readonly show = (event: React.MouseEvent<HTMLElement>): void => {
|
||||||
const { currentTarget: element } = event;
|
const { currentTarget: element } = event;
|
||||||
if (element instanceof HTMLElement) {
|
if (element instanceof HTMLElement) {
|
||||||
if (this.state.coords === 'hidden') {
|
if (this.state.coords === 'hidden') {
|
||||||
@ -212,7 +212,7 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
const protocolIcon = isConnected
|
const protocolIcon = isConnected
|
||||||
? iconNameFromProtocol(selectedBoard?.port?.protocol || '')
|
? iconNameFromProtocol(selectedBoard?.port?.protocol || '')
|
||||||
: null;
|
: null;
|
||||||
const procolIconClassNames = classNames(
|
const protocolIconClassNames = classNames(
|
||||||
'arduino-boards-toolbar-item--protocol',
|
'arduino-boards-toolbar-item--protocol',
|
||||||
'fa',
|
'fa',
|
||||||
protocolIcon
|
protocolIcon
|
||||||
@ -225,7 +225,7 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
title={selectedPortLabel}
|
title={selectedPortLabel}
|
||||||
onClick={this.show}
|
onClick={this.show}
|
||||||
>
|
>
|
||||||
{protocolIcon && <div className={procolIconClassNames} />}
|
{protocolIcon && <div className={protocolIconClassNames} />}
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'arduino-boards-toolbar-item--label',
|
'arduino-boards-toolbar-item--label',
|
||||||
@ -245,12 +245,12 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
...board,
|
...board,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (board.state === AvailableBoard.State.incomplete) {
|
if (board.state === AvailableBoard.State.incomplete) {
|
||||||
this.props.boardsServiceClient.boardsConfig = {
|
this.props.boardsServiceProvider.boardsConfig = {
|
||||||
selectedPort: board.port,
|
selectedPort: board.port,
|
||||||
};
|
};
|
||||||
this.openDialog();
|
this.openDialog();
|
||||||
} else {
|
} else {
|
||||||
this.props.boardsServiceClient.boardsConfig = {
|
this.props.boardsServiceProvider.boardsConfig = {
|
||||||
selectedBoard: board,
|
selectedBoard: board,
|
||||||
selectedPort: board.port,
|
selectedPort: board.port,
|
||||||
};
|
};
|
||||||
@ -264,13 +264,15 @@ export class BoardsToolBarItem extends React.Component<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected openDialog = () => {
|
protected openDialog = (): void => {
|
||||||
this.props.commands.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id);
|
this.props.commands.executeCommand(
|
||||||
|
OpenBoardsConfig.Commands.OPEN_DIALOG.id
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export namespace BoardsToolBarItem {
|
export namespace BoardsToolBarItem {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly boardsServiceClient: BoardsServiceProvider;
|
readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
readonly commands: CommandRegistry;
|
readonly commands: CommandRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import {
|
||||||
|
IDEUpdater,
|
||||||
|
SKIP_IDE_VERSION,
|
||||||
|
} from '../../common/protocol/ide-updater';
|
||||||
|
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
|
||||||
|
import { Contribution } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class CheckForUpdates extends Contribution {
|
||||||
|
@inject(IDEUpdater)
|
||||||
|
private readonly updater: IDEUpdater;
|
||||||
|
|
||||||
|
@inject(IDEUpdaterDialog)
|
||||||
|
private readonly updaterDialog: IDEUpdaterDialog;
|
||||||
|
|
||||||
|
@inject(LocalStorageService)
|
||||||
|
private readonly localStorage: LocalStorageService;
|
||||||
|
|
||||||
|
override onStart(): void {
|
||||||
|
this.preferences.onPreferenceChanged(
|
||||||
|
({ preferenceName, newValue, oldValue }) => {
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
switch (preferenceName) {
|
||||||
|
case 'arduino.ide.updateChannel':
|
||||||
|
case 'arduino.ide.updateBaseUrl':
|
||||||
|
this.updater.init(
|
||||||
|
this.preferences.get('arduino.ide.updateChannel'),
|
||||||
|
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override onReady(): void {
|
||||||
|
this.updater
|
||||||
|
.init(
|
||||||
|
this.preferences.get('arduino.ide.updateChannel'),
|
||||||
|
this.preferences.get('arduino.ide.updateBaseUrl')
|
||||||
|
)
|
||||||
|
.then(() => this.updater.checkForUpdates(true))
|
||||||
|
.then(async (updateInfo) => {
|
||||||
|
if (!updateInfo) return;
|
||||||
|
const versionToSkip = await this.localStorage.getData<string>(
|
||||||
|
SKIP_IDE_VERSION
|
||||||
|
);
|
||||||
|
if (versionToSkip === updateInfo.version) return;
|
||||||
|
this.updaterDialog.open(updateInfo);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.messageService.error(
|
||||||
|
nls.localize(
|
||||||
|
'arduino/ide-updater/errorCheckingForUpdates',
|
||||||
|
'Error while checking for Arduino IDE updates.\n{0}',
|
||||||
|
e.message
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -29,10 +29,7 @@ import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
|||||||
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
|
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
|
||||||
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
|
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
|
||||||
import { CoreError } from '../../common/protocol/core-service';
|
import { CoreError } from '../../common/protocol/core-service';
|
||||||
import {
|
import { ErrorRevealStrategy } from '../arduino-preferences';
|
||||||
ArduinoPreferences,
|
|
||||||
ErrorRevealStrategy,
|
|
||||||
} from '../arduino-preferences';
|
|
||||||
import { InoSelector } from '../ino-selectors';
|
import { InoSelector } from '../ino-selectors';
|
||||||
import { fullRange } from '../utils/monaco';
|
import { fullRange } from '../utils/monaco';
|
||||||
import { Contribution } from './contribution';
|
import { Contribution } from './contribution';
|
||||||
@ -127,9 +124,6 @@ export class CompilerErrors
|
|||||||
@inject(CoreErrorHandler)
|
@inject(CoreErrorHandler)
|
||||||
private readonly coreErrorHandler: CoreErrorHandler;
|
private readonly coreErrorHandler: CoreErrorHandler;
|
||||||
|
|
||||||
@inject(ArduinoPreferences)
|
|
||||||
private readonly preferences: ArduinoPreferences;
|
|
||||||
|
|
||||||
private readonly errors: ErrorDecoration[] = [];
|
private readonly errors: ErrorDecoration[] = [];
|
||||||
private readonly onDidChangeEmitter = new monaco.Emitter<this>();
|
private readonly onDidChangeEmitter = new monaco.Emitter<this>();
|
||||||
private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
|
private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
|
||||||
|
@ -37,7 +37,6 @@ import {
|
|||||||
CommandContribution,
|
CommandContribution,
|
||||||
CommandService,
|
CommandService,
|
||||||
} from '@theia/core/lib/common/command';
|
} from '@theia/core/lib/common/command';
|
||||||
import { EditorMode } from '../editor-mode';
|
|
||||||
import { SettingsService } from '../dialogs/settings/settings';
|
import { SettingsService } from '../dialogs/settings/settings';
|
||||||
import {
|
import {
|
||||||
CurrentSketch,
|
CurrentSketch,
|
||||||
@ -90,15 +89,15 @@ export abstract class Contribution
|
|||||||
@inject(WorkspaceService)
|
@inject(WorkspaceService)
|
||||||
protected readonly workspaceService: WorkspaceService;
|
protected readonly workspaceService: WorkspaceService;
|
||||||
|
|
||||||
@inject(EditorMode)
|
|
||||||
protected readonly editorMode: EditorMode;
|
|
||||||
|
|
||||||
@inject(LabelProvider)
|
@inject(LabelProvider)
|
||||||
protected readonly labelProvider: LabelProvider;
|
protected readonly labelProvider: LabelProvider;
|
||||||
|
|
||||||
@inject(SettingsService)
|
@inject(SettingsService)
|
||||||
protected readonly settingsService: SettingsService;
|
protected readonly settingsService: SettingsService;
|
||||||
|
|
||||||
|
@inject(ArduinoPreferences)
|
||||||
|
protected readonly preferences: ArduinoPreferences;
|
||||||
|
|
||||||
@inject(FrontendApplicationStateService)
|
@inject(FrontendApplicationStateService)
|
||||||
protected readonly appStateService: FrontendApplicationStateService;
|
protected readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
@ -146,9 +145,6 @@ export abstract class SketchContribution extends Contribution {
|
|||||||
@inject(SketchesServiceClientImpl)
|
@inject(SketchesServiceClientImpl)
|
||||||
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||||
|
|
||||||
@inject(ArduinoPreferences)
|
|
||||||
protected readonly preferences: ArduinoPreferences;
|
|
||||||
|
|
||||||
@inject(EditorManager)
|
@inject(EditorManager)
|
||||||
protected readonly editorManager: EditorManager;
|
protected readonly editorManager: EditorManager;
|
||||||
|
|
||||||
|
@ -12,46 +12,54 @@ import {
|
|||||||
SketchContribution,
|
SketchContribution,
|
||||||
TabBarToolbarRegistry,
|
TabBarToolbarRegistry,
|
||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { MaybePromise, nls } from '@theia/core/lib/common';
|
import { MaybePromise, MenuModelRegistry, nls } from '@theia/core/lib/common';
|
||||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import {
|
||||||
|
PreferenceScope,
|
||||||
|
PreferenceService,
|
||||||
|
} from '@theia/core/lib/browser/preferences/preference-service';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class Debug extends SketchContribution {
|
export class Debug extends SketchContribution {
|
||||||
@inject(HostedPluginSupport)
|
@inject(HostedPluginSupport)
|
||||||
protected hostedPluginSupport: HostedPluginSupport;
|
private readonly hostedPluginSupport: HostedPluginSupport;
|
||||||
|
|
||||||
@inject(NotificationCenter)
|
@inject(NotificationCenter)
|
||||||
protected readonly notificationCenter: NotificationCenter;
|
private readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
@inject(ExecutableService)
|
@inject(ExecutableService)
|
||||||
protected readonly executableService: ExecutableService;
|
private readonly executableService: ExecutableService;
|
||||||
|
|
||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardService: BoardsService;
|
private readonly boardService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
@inject(BoardsServiceProvider)
|
||||||
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
@inject(PreferenceService)
|
||||||
|
private readonly preferenceService: PreferenceService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled.
|
* If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled.
|
||||||
*/
|
*/
|
||||||
protected _disabledMessages?: string = nls.localize(
|
private _disabledMessages?: string = nls.localize(
|
||||||
'arduino/common/noBoardSelected',
|
'arduino/common/noBoardSelected',
|
||||||
'No board selected'
|
'No board selected'
|
||||||
); // Initial pessimism.
|
); // Initial pessimism.
|
||||||
protected disabledMessageDidChangeEmitter = new Emitter<string | undefined>();
|
private disabledMessageDidChangeEmitter = new Emitter<string | undefined>();
|
||||||
protected onDisabledMessageDidChange =
|
private onDisabledMessageDidChange =
|
||||||
this.disabledMessageDidChangeEmitter.event;
|
this.disabledMessageDidChangeEmitter.event;
|
||||||
|
|
||||||
protected get disabledMessage(): string | undefined {
|
private get disabledMessage(): string | undefined {
|
||||||
return this._disabledMessages;
|
return this._disabledMessages;
|
||||||
}
|
}
|
||||||
protected set disabledMessage(message: string | undefined) {
|
private set disabledMessage(message: string | undefined) {
|
||||||
this._disabledMessages = message;
|
this._disabledMessages = message;
|
||||||
this.disabledMessageDidChangeEmitter.fire(this._disabledMessages);
|
this.disabledMessageDidChangeEmitter.fire(this._disabledMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly debugToolbarItem = {
|
private readonly debugToolbarItem = {
|
||||||
id: Debug.Commands.START_DEBUGGING.id,
|
id: Debug.Commands.START_DEBUGGING.id,
|
||||||
command: Debug.Commands.START_DEBUGGING.id,
|
command: Debug.Commands.START_DEBUGGING.id,
|
||||||
tooltip: `${
|
tooltip: `${
|
||||||
@ -98,12 +106,24 @@ export class Debug extends SketchContribution {
|
|||||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isEnabled: () => !this.disabledMessage,
|
isEnabled: () => !this.disabledMessage,
|
||||||
});
|
});
|
||||||
|
registry.registerCommand(Debug.Commands.OPTIMIZE_FOR_DEBUG, {
|
||||||
|
execute: () => this.toggleOptimizeForDebug(),
|
||||||
|
isToggled: () => this.isOptimizeForDebug(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
override registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||||
registry.registerItem(this.debugToolbarItem);
|
registry.registerItem(this.debugToolbarItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||||
|
commandId: Debug.Commands.OPTIMIZE_FOR_DEBUG.id,
|
||||||
|
label: Debug.Commands.OPTIMIZE_FOR_DEBUG.label,
|
||||||
|
order: '5',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async refreshState(
|
private async refreshState(
|
||||||
board: Board | undefined = this.boardsServiceProvider.boardsConfig
|
board: Board | undefined = this.boardsServiceProvider.boardsConfig
|
||||||
.selectedBoard
|
.selectedBoard
|
||||||
@ -145,7 +165,7 @@ export class Debug extends SketchContribution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async startDebug(
|
private async startDebug(
|
||||||
board: Board | undefined = this.boardsServiceProvider.boardsConfig
|
board: Board | undefined = this.boardsServiceProvider.boardsConfig
|
||||||
.selectedBoard
|
.selectedBoard
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@ -183,8 +203,19 @@ export class Debug extends SketchContribution {
|
|||||||
};
|
};
|
||||||
return this.commandService.executeCommand('arduino.debug.start', config);
|
return this.commandService.executeCommand('arduino.debug.start', config);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
private isOptimizeForDebug(): boolean {
|
||||||
|
return this.preferences.get('arduino.compile.optimizeForDebug');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async toggleOptimizeForDebug(): Promise<void> {
|
||||||
|
return this.preferenceService.set(
|
||||||
|
'arduino.compile.optimizeForDebug',
|
||||||
|
!this.isOptimizeForDebug(),
|
||||||
|
PreferenceScope.User
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
export namespace Debug {
|
export namespace Debug {
|
||||||
export namespace Commands {
|
export namespace Commands {
|
||||||
export const START_DEBUGGING = Command.toLocalizedCommand(
|
export const START_DEBUGGING = Command.toLocalizedCommand(
|
||||||
@ -195,5 +226,13 @@ export namespace Debug {
|
|||||||
},
|
},
|
||||||
'vscode/debug.contribution/startDebuggingHelp'
|
'vscode/debug.contribution/startDebuggingHelp'
|
||||||
);
|
);
|
||||||
|
export const OPTIMIZE_FOR_DEBUG = Command.toLocalizedCommand(
|
||||||
|
{
|
||||||
|
id: 'arduino-optimize-for-debug',
|
||||||
|
label: 'Optimize for Debugging',
|
||||||
|
category: 'Arduino',
|
||||||
|
},
|
||||||
|
'arduino/debug/optimizeForDebugging'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||||
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
|
|
||||||
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
||||||
import {
|
import {
|
||||||
Contribution,
|
Contribution,
|
||||||
@ -20,13 +19,10 @@ import type { StandaloneCodeEditor } from '@theia/monaco-editor-core/esm/vs/edit
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class EditContributions extends Contribution {
|
export class EditContributions extends Contribution {
|
||||||
@inject(MonacoEditorService)
|
@inject(MonacoEditorService)
|
||||||
protected readonly codeEditorService: MonacoEditorService;
|
private readonly codeEditorService: MonacoEditorService;
|
||||||
|
|
||||||
@inject(ClipboardService)
|
@inject(ClipboardService)
|
||||||
protected readonly clipboardService: ClipboardService;
|
private readonly clipboardService: ClipboardService;
|
||||||
|
|
||||||
@inject(PreferenceService)
|
|
||||||
protected readonly preferences: PreferenceService;
|
|
||||||
|
|
||||||
override registerCommands(registry: CommandRegistry): void {
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
registry.registerCommand(EditContributions.Commands.GO_TO_LINE, {
|
registry.registerCommand(EditContributions.Commands.GO_TO_LINE, {
|
||||||
|
154
arduino-ide-extension/src/browser/contributions/ino-language.ts
Normal file
154
arduino-ide-extension/src/browser/contributions/ino-language.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { Mutex } from 'async-mutex';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import {
|
||||||
|
ArduinoDaemon,
|
||||||
|
BoardsService,
|
||||||
|
ExecutableService,
|
||||||
|
} from '../../common/protocol';
|
||||||
|
import { HostedPluginEvents } from '../hosted-plugin-events';
|
||||||
|
import { SketchContribution, URI } from './contribution';
|
||||||
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import { BoardsConfig } from '../boards/boards-config';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class InoLanguage extends SketchContribution {
|
||||||
|
@inject(HostedPluginEvents)
|
||||||
|
private readonly hostedPluginEvents: HostedPluginEvents;
|
||||||
|
|
||||||
|
@inject(ExecutableService)
|
||||||
|
private readonly executableService: ExecutableService;
|
||||||
|
|
||||||
|
@inject(ArduinoDaemon)
|
||||||
|
private readonly daemon: ArduinoDaemon;
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
private readonly boardsService: BoardsService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
private languageServerFqbn?: string;
|
||||||
|
private languageServerStartMutex = new Mutex();
|
||||||
|
|
||||||
|
override onReady(): void {
|
||||||
|
const start = (
|
||||||
|
{ selectedBoard }: BoardsConfig.Config,
|
||||||
|
forceStart = false
|
||||||
|
) => {
|
||||||
|
if (selectedBoard) {
|
||||||
|
const { name, fqbn } = selectedBoard;
|
||||||
|
if (fqbn) {
|
||||||
|
this.startLanguageServer(fqbn, name, forceStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.boardsServiceProvider.onBoardsConfigChanged(start);
|
||||||
|
this.hostedPluginEvents.onPluginsDidStart(() =>
|
||||||
|
start(this.boardsServiceProvider.boardsConfig)
|
||||||
|
);
|
||||||
|
this.hostedPluginEvents.onPluginsWillUnload(
|
||||||
|
() => (this.languageServerFqbn = undefined)
|
||||||
|
);
|
||||||
|
this.preferences.onPreferenceChanged(
|
||||||
|
({ preferenceName, oldValue, newValue }) => {
|
||||||
|
if (oldValue !== newValue) {
|
||||||
|
switch (preferenceName) {
|
||||||
|
case 'arduino.language.log':
|
||||||
|
case 'arduino.language.realTimeDiagnostics':
|
||||||
|
start(this.boardsServiceProvider.boardsConfig, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
start(this.boardsServiceProvider.boardsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startLanguageServer(
|
||||||
|
fqbn: string,
|
||||||
|
name: string | undefined,
|
||||||
|
forceStart = false
|
||||||
|
): Promise<void> {
|
||||||
|
const port = await this.daemon.tryGetPort();
|
||||||
|
if (!port) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const release = await this.languageServerStartMutex.acquire();
|
||||||
|
try {
|
||||||
|
await this.hostedPluginEvents.didStart;
|
||||||
|
const details = await this.boardsService.getBoardDetails({ fqbn });
|
||||||
|
if (!details) {
|
||||||
|
// Core is not installed for the selected board.
|
||||||
|
console.info(
|
||||||
|
`Could not start language server for ${fqbn}. The core is not installed for the board.`
|
||||||
|
);
|
||||||
|
if (this.languageServerFqbn) {
|
||||||
|
try {
|
||||||
|
await this.commandService.executeCommand(
|
||||||
|
'arduino.languageserver.stop'
|
||||||
|
);
|
||||||
|
console.info(
|
||||||
|
`Stopped language server process for ${this.languageServerFqbn}.`
|
||||||
|
);
|
||||||
|
this.languageServerFqbn = undefined;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(
|
||||||
|
`Failed to start language server process for ${this.languageServerFqbn}`,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!forceStart && fqbn === this.languageServerFqbn) {
|
||||||
|
// NOOP
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.logger.info(`Starting language server: ${fqbn}`);
|
||||||
|
const log = this.preferences.get('arduino.language.log');
|
||||||
|
let currentSketchPath: string | undefined = undefined;
|
||||||
|
if (log) {
|
||||||
|
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
if (CurrentSketch.isValid(currentSketch)) {
|
||||||
|
currentSketchPath = await this.fileService.fsPath(
|
||||||
|
new URI(currentSketch.uri)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { clangdUri, lsUri } = await this.executableService.list();
|
||||||
|
const [clangdPath, lsPath] = await Promise.all([
|
||||||
|
this.fileService.fsPath(new URI(clangdUri)),
|
||||||
|
this.fileService.fsPath(new URI(lsUri)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.languageServerFqbn = await Promise.race([
|
||||||
|
new Promise<undefined>((_, reject) =>
|
||||||
|
setTimeout(
|
||||||
|
() => reject(new Error(`Timeout after ${20_000} ms.`)),
|
||||||
|
20_000
|
||||||
|
)
|
||||||
|
),
|
||||||
|
this.commandService.executeCommand<string>(
|
||||||
|
'arduino.languageserver.start',
|
||||||
|
{
|
||||||
|
lsPath,
|
||||||
|
cliDaemonAddr: `localhost:${port}`,
|
||||||
|
clangdPath,
|
||||||
|
log: currentSketchPath ? currentSketchPath : log,
|
||||||
|
cliDaemonInstance: '1',
|
||||||
|
board: {
|
||||||
|
fqbn,
|
||||||
|
name: name ? `"${name}"` : undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Failed to start language server for ${fqbn}`, e);
|
||||||
|
this.languageServerFqbn = undefined;
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
import { CommandRegistry } from '@theia/core';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { BoardsConfigDialog } from '../boards/boards-config-dialog';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { Contribution, Command } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class OpenBoardsConfig extends Contribution {
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
@inject(BoardsConfigDialog)
|
||||||
|
private readonly boardsConfigDialog: BoardsConfigDialog;
|
||||||
|
|
||||||
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(OpenBoardsConfig.Commands.OPEN_DIALOG, {
|
||||||
|
execute: async (query?: string | undefined) => {
|
||||||
|
const boardsConfig = await this.boardsConfigDialog.open(query);
|
||||||
|
if (boardsConfig) {
|
||||||
|
this.boardsServiceProvider.boardsConfig = boardsConfig;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export namespace OpenBoardsConfig {
|
||||||
|
export namespace Commands {
|
||||||
|
export const OPEN_DIALOG: Command = {
|
||||||
|
id: 'arduino-open-boards-dialog',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
|
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
||||||
|
import { SketchesError } from '../../common/protocol';
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandRegistry,
|
||||||
|
SketchContribution,
|
||||||
|
URI,
|
||||||
|
} from './contribution';
|
||||||
|
import { SaveAsSketch } from './save-as-sketch';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class OpenSketchFiles extends SketchContribution {
|
||||||
|
override registerCommands(registry: CommandRegistry): void {
|
||||||
|
registry.registerCommand(OpenSketchFiles.Commands.OPEN_SKETCH_FILES, {
|
||||||
|
execute: (uri: URI) => this.openSketchFiles(uri),
|
||||||
|
});
|
||||||
|
registry.registerCommand(OpenSketchFiles.Commands.ENSURE_OPENED, {
|
||||||
|
execute: (
|
||||||
|
uri: string,
|
||||||
|
forceOpen?: boolean,
|
||||||
|
options?: EditorOpenerOptions
|
||||||
|
) => {
|
||||||
|
this.ensureOpened(uri, forceOpen, options);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async openSketchFiles(uri: URI): Promise<void> {
|
||||||
|
try {
|
||||||
|
const sketch = await this.sketchService.loadSketch(uri.toString());
|
||||||
|
const { mainFileUri, rootFolderFileUris } = sketch;
|
||||||
|
for (const uri of [mainFileUri, ...rootFolderFileUris]) {
|
||||||
|
await this.ensureOpened(uri);
|
||||||
|
}
|
||||||
|
if (mainFileUri.endsWith('.pde')) {
|
||||||
|
const message = nls.localize(
|
||||||
|
'arduino/common/oldFormat',
|
||||||
|
"The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?",
|
||||||
|
sketch.name
|
||||||
|
);
|
||||||
|
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
|
||||||
|
this.messageService
|
||||||
|
.info(message, nls.localize('arduino/common/later', 'Later'), yes)
|
||||||
|
.then(async (answer) => {
|
||||||
|
if (answer === yes) {
|
||||||
|
this.commandService.executeCommand(
|
||||||
|
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||||
|
{
|
||||||
|
execOnlyIfTemp: false,
|
||||||
|
openAfterMove: true,
|
||||||
|
wipeOriginal: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (SketchesError.NotFound.is(err)) {
|
||||||
|
this.openFallbackSketch();
|
||||||
|
} else {
|
||||||
|
console.error(err);
|
||||||
|
const message =
|
||||||
|
err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: typeof err === 'string'
|
||||||
|
? err
|
||||||
|
: String(err);
|
||||||
|
this.messageService.error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async openFallbackSketch(): Promise<void> {
|
||||||
|
const sketch = await this.sketchService.createNewSketch();
|
||||||
|
this.workspaceService.open(new URI(sketch.uri), { preserveWindow: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureOpened(
|
||||||
|
uri: string,
|
||||||
|
forceOpen = false,
|
||||||
|
options?: EditorOpenerOptions
|
||||||
|
): Promise<unknown> {
|
||||||
|
const widget = this.editorManager.all.find(
|
||||||
|
(widget) => widget.editor.uri.toString() === uri
|
||||||
|
);
|
||||||
|
if (!widget || forceOpen) {
|
||||||
|
return this.editorManager.open(
|
||||||
|
new URI(uri),
|
||||||
|
options ?? {
|
||||||
|
mode: 'reveal',
|
||||||
|
preview: false,
|
||||||
|
counter: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export namespace OpenSketchFiles {
|
||||||
|
export namespace Commands {
|
||||||
|
export const OPEN_SKETCH_FILES: Command = {
|
||||||
|
id: 'arduino-open-sketch-files',
|
||||||
|
};
|
||||||
|
export const ENSURE_OPENED: Command = {
|
||||||
|
id: 'arduino-ensure-opened',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
StatusBar,
|
||||||
|
StatusBarAlignment,
|
||||||
|
} from '@theia/core/lib/browser/status-bar/status-bar';
|
||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { BoardsConfig } from '../boards/boards-config';
|
||||||
|
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||||
|
import { Contribution } from './contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SelectedBoard extends Contribution {
|
||||||
|
@inject(StatusBar)
|
||||||
|
private readonly statusBar: StatusBar;
|
||||||
|
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
|
||||||
|
override onStart(): void {
|
||||||
|
this.boardsServiceProvider.onBoardsConfigChanged((config) =>
|
||||||
|
this.update(config)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override onReady(): void {
|
||||||
|
this.update(this.boardsServiceProvider.boardsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private update({ selectedBoard, selectedPort }: BoardsConfig.Config): void {
|
||||||
|
this.statusBar.setElement('arduino-selected-board', {
|
||||||
|
alignment: StatusBarAlignment.RIGHT,
|
||||||
|
text: selectedBoard
|
||||||
|
? `$(microchip) ${selectedBoard.name}`
|
||||||
|
: `$(close) ${nls.localize(
|
||||||
|
'arduino/common/noBoardSelected',
|
||||||
|
'No board selected'
|
||||||
|
)}`,
|
||||||
|
className: 'arduino-selected-board',
|
||||||
|
});
|
||||||
|
if (selectedBoard) {
|
||||||
|
this.statusBar.setElement('arduino-selected-port', {
|
||||||
|
alignment: StatusBarAlignment.RIGHT,
|
||||||
|
text: selectedPort
|
||||||
|
? nls.localize(
|
||||||
|
'arduino/common/selectedOn',
|
||||||
|
'on {0}',
|
||||||
|
selectedPort.address
|
||||||
|
)
|
||||||
|
: nls.localize('arduino/common/notConnected', '[not connected]'),
|
||||||
|
className: 'arduino-selected-port',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
import { SaveableWidget } from '@theia/core/lib/browser/saveable';
|
||||||
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
|
||||||
|
import { FileChangeType } from '@theia/filesystem/lib/common/files';
|
||||||
|
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||||
|
import { Sketch, SketchContribution, URI } from './contribution';
|
||||||
|
import { OpenSketchFiles } from './open-sketch-files';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SketchFilesTracker extends SketchContribution {
|
||||||
|
@inject(FileSystemFrontendContribution)
|
||||||
|
private readonly fileSystemFrontendContribution: FileSystemFrontendContribution;
|
||||||
|
private readonly toDisposeOnStop = new DisposableCollection();
|
||||||
|
|
||||||
|
override onStart(): void {
|
||||||
|
this.fileSystemFrontendContribution.onDidChangeEditorFile(
|
||||||
|
({ type, editor }) => {
|
||||||
|
if (type === FileChangeType.DELETED) {
|
||||||
|
const editorWidget = editor;
|
||||||
|
if (SaveableWidget.is(editorWidget)) {
|
||||||
|
editorWidget.closeWithoutSaving();
|
||||||
|
} else {
|
||||||
|
editorWidget.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override onReady(): void {
|
||||||
|
this.sketchServiceClient.currentSketch().then(async (sketch) => {
|
||||||
|
if (
|
||||||
|
CurrentSketch.isValid(sketch) &&
|
||||||
|
!(await this.sketchService.isTemp(sketch))
|
||||||
|
) {
|
||||||
|
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
|
||||||
|
this.toDisposeOnStop.push(
|
||||||
|
this.fileService.onDidFilesChange(async (event) => {
|
||||||
|
for (const { type, resource } of event.changes) {
|
||||||
|
if (
|
||||||
|
type === FileChangeType.ADDED &&
|
||||||
|
resource.parent.toString() === sketch.uri
|
||||||
|
) {
|
||||||
|
const reloadedSketch = await this.sketchService.loadSketch(
|
||||||
|
sketch.uri
|
||||||
|
);
|
||||||
|
if (Sketch.isInSketch(resource, reloadedSketch)) {
|
||||||
|
this.commandService.executeCommand(
|
||||||
|
OpenSketchFiles.Commands.ENSURE_OPENED.id,
|
||||||
|
resource.toString(),
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
mode: 'open',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onStop(): void {
|
||||||
|
this.toDisposeOnStop.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,11 @@ import { ArduinoMenus } from '../menu/arduino-menus';
|
|||||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
import { NotificationCenter } from '../notification-center';
|
import { NotificationCenter } from '../notification-center';
|
||||||
import { Examples } from './examples';
|
import { Examples } from './examples';
|
||||||
import { SketchContainer } from '../../common/protocol';
|
import {
|
||||||
|
SketchContainer,
|
||||||
|
SketchesError,
|
||||||
|
SketchRef,
|
||||||
|
} from '../../common/protocol';
|
||||||
import { OpenSketch } from './open-sketch';
|
import { OpenSketch } from './open-sketch';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
|
||||||
@ -24,15 +28,14 @@ export class Sketchbook extends Examples {
|
|||||||
protected readonly notificationCenter: NotificationCenter;
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
override onStart(): void {
|
override onStart(): void {
|
||||||
this.sketchServiceClient.onSketchbookDidChange(() => {
|
this.sketchServiceClient.onSketchbookDidChange(() => this.update());
|
||||||
this.sketchService.getSketches({}).then((container) => {
|
|
||||||
this.register(container);
|
|
||||||
this.mainMenuManager.update();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async onReady(): Promise<void> {
|
override async onReady(): Promise<void> {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
this.sketchService.getSketches({}).then((container) => {
|
this.sketchService.getSketches({}).then((container) => {
|
||||||
this.register(container);
|
this.register(container);
|
||||||
this.mainMenuManager.update();
|
this.mainMenuManager.update();
|
||||||
@ -59,11 +62,24 @@ export class Sketchbook extends Examples {
|
|||||||
protected override createHandler(uri: string): CommandHandler {
|
protected override createHandler(uri: string): CommandHandler {
|
||||||
return {
|
return {
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
const sketch = await this.sketchService.loadSketch(uri);
|
let sketch: SketchRef | undefined = undefined;
|
||||||
return this.commandService.executeCommand(
|
try {
|
||||||
OpenSketch.Commands.OPEN_SKETCH.id,
|
sketch = await this.sketchService.loadSketch(uri);
|
||||||
sketch
|
} catch (err) {
|
||||||
);
|
if (SketchesError.NotFound.is(err)) {
|
||||||
|
// To handle the following:
|
||||||
|
// Open IDE2, delete a sketch from sketchbook, click on File > Sketchbook > the deleted sketch.
|
||||||
|
// Filesystem watcher misses out delete events on macOS; hence IDE2 has no chance to update the menu items.
|
||||||
|
this.messageService.error(err.message);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sketch) {
|
||||||
|
await this.commandService.executeCommand(
|
||||||
|
OpenSketch.Commands.OPEN_SKETCH.id,
|
||||||
|
sketch
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -227,7 +227,9 @@ export class UploadSketch extends CoreServiceContribution {
|
|||||||
fqbn,
|
fqbn,
|
||||||
};
|
};
|
||||||
let options: CoreService.Upload.Options | undefined = undefined;
|
let options: CoreService.Upload.Options | undefined = undefined;
|
||||||
const optimizeForDebug = this.editorMode.compileForDebug;
|
const optimizeForDebug = this.preferences.get(
|
||||||
|
'arduino.compile.optimizeForDebug'
|
||||||
|
);
|
||||||
const { selectedPort } = boardsConfig;
|
const { selectedPort } = boardsConfig;
|
||||||
const port = selectedPort;
|
const port = selectedPort;
|
||||||
const userFields =
|
const userFields =
|
||||||
|
@ -114,11 +114,14 @@ export class VerifySketch extends CoreServiceContribution {
|
|||||||
};
|
};
|
||||||
const verbose = this.preferences.get('arduino.compile.verbose');
|
const verbose = this.preferences.get('arduino.compile.verbose');
|
||||||
const compilerWarnings = this.preferences.get('arduino.compile.warnings');
|
const compilerWarnings = this.preferences.get('arduino.compile.warnings');
|
||||||
|
const optimizeForDebug = this.preferences.get(
|
||||||
|
'arduino.compile.optimizeForDebug'
|
||||||
|
);
|
||||||
this.outputChannelManager.getChannel('Arduino').clear();
|
this.outputChannelManager.getChannel('Arduino').clear();
|
||||||
await this.coreService.compile({
|
await this.coreService.compile({
|
||||||
sketch,
|
sketch,
|
||||||
board,
|
board,
|
||||||
optimizeForDebug: this.editorMode.compileForDebug,
|
optimizeForDebug,
|
||||||
verbose,
|
verbose,
|
||||||
exportBinaries,
|
exportBinaries,
|
||||||
sourceOverride,
|
sourceOverride,
|
||||||
|
@ -507,7 +507,8 @@ export class CreateApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private domain(apiVersion = 'v2'): string {
|
private domain(apiVersion = 'v2'): string {
|
||||||
const endpoint = this.arduinoPreferences['arduino.cloud.sketchSyncEnpoint'];
|
const endpoint =
|
||||||
|
this.arduinoPreferences['arduino.cloud.sketchSyncEndpoint'];
|
||||||
return `${endpoint}/${apiVersion}`;
|
return `${endpoint}/${apiVersion}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ import {
|
|||||||
IDEUpdater,
|
IDEUpdater,
|
||||||
IDEUpdaterClient,
|
IDEUpdaterClient,
|
||||||
ProgressInfo,
|
ProgressInfo,
|
||||||
|
SKIP_IDE_VERSION,
|
||||||
UpdateInfo,
|
UpdateInfo,
|
||||||
} from '../../../common/protocol/ide-updater';
|
} from '../../../common/protocol/ide-updater';
|
||||||
import { LocalStorageService } from '@theia/core/lib/browser';
|
import { LocalStorageService } from '@theia/core/lib/browser';
|
||||||
import { SKIP_IDE_VERSION } from '../../arduino-frontend-contribution';
|
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
|
||||||
import {
|
|
||||||
FrontendApplicationContribution,
|
|
||||||
FrontendApplication,
|
|
||||||
} from '@theia/core/lib/browser';
|
|
||||||
import { MainMenuManager } from '../common/main-menu-manager';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class EditorMode implements FrontendApplicationContribution {
|
|
||||||
@inject(MainMenuManager)
|
|
||||||
protected readonly mainMenuManager: MainMenuManager;
|
|
||||||
|
|
||||||
protected app: FrontendApplication;
|
|
||||||
|
|
||||||
onStart(app: FrontendApplication): void {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
get compileForDebug(): boolean {
|
|
||||||
const value = window.localStorage.getItem(EditorMode.COMPILE_FOR_DEBUG_KEY);
|
|
||||||
return value === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggleCompileForDebug(): Promise<void> {
|
|
||||||
const oldState = this.compileForDebug;
|
|
||||||
const newState = !oldState;
|
|
||||||
window.localStorage.setItem(
|
|
||||||
EditorMode.COMPILE_FOR_DEBUG_KEY,
|
|
||||||
String(newState)
|
|
||||||
);
|
|
||||||
this.mainMenuManager.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace EditorMode {
|
|
||||||
export const COMPILE_FOR_DEBUG_KEY = 'arduino-compile-for-debug';
|
|
||||||
}
|
|
@ -134,21 +134,3 @@ button.secondary[disabled], .theia-button.secondary[disabled] {
|
|||||||
.fa-reload {
|
.fa-reload {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* restore the old Theia spinner */
|
|
||||||
/* https://github.com/eclipse-theia/theia/pull/10761#issuecomment-1131476318 */
|
|
||||||
.old-theia-preload {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 50000;
|
|
||||||
background: var(--theia-editor-background);
|
|
||||||
background-image: var(--theia-preloader);
|
|
||||||
background-size: 60px 60px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-attachment: fixed;
|
|
||||||
background-position: center;
|
|
||||||
transition: opacity 0.8s;
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,7 @@ import { CommandService } from '@theia/core/lib/common/command';
|
|||||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
import { SketchesService } from '../../../common/protocol';
|
import { SketchesService } from '../../../common/protocol';
|
||||||
import { ArduinoCommands } from '../../arduino-commands';
|
import { OpenSketchFiles } from '../../contributions/open-sketch-files';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class FrontendApplication extends TheiaFrontendApplication {
|
export class FrontendApplication extends TheiaFrontendApplication {
|
||||||
@ -25,33 +25,11 @@ export class FrontendApplication extends TheiaFrontendApplication {
|
|||||||
this.workspaceService.roots.then(async (roots) => {
|
this.workspaceService.roots.then(async (roots) => {
|
||||||
for (const root of roots) {
|
for (const root of roots) {
|
||||||
await this.commandService.executeCommand(
|
await this.commandService.executeCommand(
|
||||||
ArduinoCommands.OPEN_SKETCH_FILES.id,
|
OpenSketchFiles.Commands.OPEN_SKETCH_FILES.id,
|
||||||
root.resource
|
root.resource
|
||||||
);
|
);
|
||||||
this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
|
this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override getStartupIndicator(
|
|
||||||
host: HTMLElement
|
|
||||||
): HTMLElement | undefined {
|
|
||||||
let startupElement = this.doGetStartupIndicator(host, 'old-theia-preload'); // https://github.com/eclipse-theia/theia/pull/10761#issuecomment-1131476318
|
|
||||||
if (!startupElement) {
|
|
||||||
startupElement = this.doGetStartupIndicator(host, 'theia-preload'); // We show the new Theia spinner in dev mode.
|
|
||||||
}
|
|
||||||
return startupElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
private doGetStartupIndicator(
|
|
||||||
host: HTMLElement,
|
|
||||||
classNames: string
|
|
||||||
): HTMLElement | undefined {
|
|
||||||
const elements = host.getElementsByClassName(classNames);
|
|
||||||
const first = elements[0];
|
|
||||||
if (first instanceof HTMLElement) {
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,11 @@ import {
|
|||||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||||
import { BoardsConfig } from '../../boards/boards-config';
|
import { BoardsConfig } from '../../boards/boards-config';
|
||||||
import { FileStat } from '@theia/filesystem/lib/common/files';
|
import { FileStat } from '@theia/filesystem/lib/common/files';
|
||||||
import { StartupTask } from '../../widgets/sketchbook/startup-task';
|
import {
|
||||||
|
StartupTask,
|
||||||
|
StartupTasks,
|
||||||
|
} from '../../widgets/sketchbook/startup-task';
|
||||||
|
import { setURL } from '../../utils/window';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class WorkspaceService extends TheiaWorkspaceService {
|
export class WorkspaceService extends TheiaWorkspaceService {
|
||||||
@ -60,6 +64,17 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
|||||||
this.onCurrentWidgetChange({ newValue, oldValue: null });
|
this.onCurrentWidgetChange({ newValue, oldValue: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async toFileStat(
|
||||||
|
uri: string | URI | undefined
|
||||||
|
): Promise<FileStat | undefined> {
|
||||||
|
const stat = await super.toFileStat(uri);
|
||||||
|
if (!stat) {
|
||||||
|
const newSketchUri = await this.sketchService.createNewSketch();
|
||||||
|
return this.toFileStat(newSketchUri.uri);
|
||||||
|
}
|
||||||
|
return stat;
|
||||||
|
}
|
||||||
|
|
||||||
// Was copied from the Theia implementation.
|
// Was copied from the Theia implementation.
|
||||||
// Unlike the default behavior, IDE2 does not check the existence of the workspace before open.
|
// Unlike the default behavior, IDE2 does not check the existence of the workspace before open.
|
||||||
protected override async doGetDefaultWorkspaceUri(): Promise<
|
protected override async doGetDefaultWorkspaceUri(): Promise<
|
||||||
@ -78,6 +93,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
|||||||
const wpPath = decodeURI(window.location.hash.substring(1));
|
const wpPath = decodeURI(window.location.hash.substring(1));
|
||||||
const workspaceUri = new URI().withPath(wpPath).withScheme('file');
|
const workspaceUri = new URI().withPath(wpPath).withScheme('file');
|
||||||
// ### Customization! Here, we do no check if the workspace exists.
|
// ### Customization! Here, we do no check if the workspace exists.
|
||||||
|
// ### The error or missing sketch handling is done in the customized `toFileStat`.
|
||||||
return workspaceUri.toString();
|
return workspaceUri.toString();
|
||||||
} else {
|
} else {
|
||||||
// Else, ask the server for its suggested workspace (usually the one
|
// Else, ask the server for its suggested workspace (usually the one
|
||||||
@ -127,7 +143,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
|||||||
protected override openWindow(uri: FileStat, options?: WorkspaceInput): void {
|
protected override openWindow(uri: FileStat, options?: WorkspaceInput): void {
|
||||||
const workspacePath = uri.resource.path.toString();
|
const workspacePath = uri.resource.path.toString();
|
||||||
if (this.shouldPreserveWindow(options)) {
|
if (this.shouldPreserveWindow(options)) {
|
||||||
this.reloadWindow();
|
this.reloadWindow(options); // Unlike Theia, IDE2 passes the `input` downstream.
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
this.openNewWindow(workspacePath, options); // Unlike Theia, IDE2 passes the `input` downstream.
|
this.openNewWindow(workspacePath, options); // Unlike Theia, IDE2 passes the `input` downstream.
|
||||||
@ -139,21 +155,25 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override reloadWindow(options?: WorkspaceInput): void {
|
||||||
|
if (StartupTasks.WorkspaceInput.is(options)) {
|
||||||
|
setURL(StartupTask.append(options.tasks, new URL(window.location.href)));
|
||||||
|
}
|
||||||
|
super.reloadWindow();
|
||||||
|
}
|
||||||
|
|
||||||
protected override openNewWindow(
|
protected override openNewWindow(
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
options?: WorkspaceInput
|
options?: WorkspaceInput
|
||||||
): void {
|
): void {
|
||||||
const { boardsConfig } = this.boardsServiceProvider;
|
const { boardsConfig } = this.boardsServiceProvider;
|
||||||
const url = BoardsConfig.Config.setConfig(
|
let url = BoardsConfig.Config.setConfig(
|
||||||
boardsConfig,
|
boardsConfig,
|
||||||
new URL(window.location.href)
|
new URL(window.location.href)
|
||||||
); // Set the current boards config for the new browser window.
|
); // Set the current boards config for the new browser window.
|
||||||
url.hash = workspacePath;
|
url.hash = workspacePath;
|
||||||
if (StartupTask.WorkspaceInput.is(options)) {
|
if (StartupTasks.WorkspaceInput.is(options)) {
|
||||||
url.searchParams.set(
|
url = StartupTask.append(options.tasks, url);
|
||||||
StartupTask.QUERY_STRING,
|
|
||||||
encodeURIComponent(JSON.stringify(options.tasks))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.windowService.openNewWindow(url.toString());
|
this.windowService.openNewWindow(url.toString());
|
||||||
|
@ -10,21 +10,31 @@ import {
|
|||||||
CurrentSketch,
|
CurrentSketch,
|
||||||
SketchesServiceClientImpl,
|
SketchesServiceClientImpl,
|
||||||
} from '../../../common/protocol/sketches-service-client-impl';
|
} from '../../../common/protocol/sketches-service-client-impl';
|
||||||
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContribution {
|
export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContribution {
|
||||||
@inject(SketchesServiceClientImpl)
|
@inject(SketchesServiceClientImpl)
|
||||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
private readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||||
|
|
||||||
protected currentSketch?: Sketch;
|
private currentSketch?: Sketch;
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected override init(): void {
|
protected override init(): void {
|
||||||
this.sketchesServiceClient.currentSketch().then((sketch) => {
|
const sketch = this.sketchesServiceClient.tryGetCurrentSketch();
|
||||||
if (CurrentSketch.isValid(sketch)) {
|
if (CurrentSketch.isValid(sketch)) {
|
||||||
this.currentSketch = sketch;
|
this.currentSketch = sketch;
|
||||||
}
|
} else {
|
||||||
});
|
const toDispose = new DisposableCollection();
|
||||||
|
toDispose.push(
|
||||||
|
this.sketchesServiceClient.onCurrentSketchDidChange((sketch) => {
|
||||||
|
if (CurrentSketch.isValid(sketch)) {
|
||||||
|
this.currentSketch = sketch;
|
||||||
|
}
|
||||||
|
toDispose.dispose();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override getResourceUri(): URI | undefined {
|
override getResourceUri(): URI | undefined {
|
||||||
|
7
arduino-ide-extension/src/browser/utils/window.ts
Normal file
7
arduino-ide-extension/src/browser/utils/window.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Changes the `window.location` without navigating away.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function setURL(url: URL, data: any = {}): void {
|
||||||
|
history.pushState(data, '', url);
|
||||||
|
}
|
@ -1,36 +1,86 @@
|
|||||||
import { injectable } from '@theia/core/shared/inversify';
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
import { WorkspaceInput as TheiaWorkspaceInput } from '@theia/workspace/lib/browser';
|
import { WorkspaceInput as TheiaWorkspaceInput } from '@theia/workspace/lib/browser';
|
||||||
import { Contribution } from '../../contributions/contribution';
|
import { Contribution } from '../../contributions/contribution';
|
||||||
|
import { setURL } from '../../utils/window';
|
||||||
|
|
||||||
export interface Task {
|
@injectable()
|
||||||
|
export class StartupTasks extends Contribution {
|
||||||
|
override onReady(): void {
|
||||||
|
const tasks = StartupTask.get(new URL(window.location.href));
|
||||||
|
console.log(`Executing startup tasks: ${JSON.stringify(tasks)}`);
|
||||||
|
tasks.forEach(({ command, args = [] }) =>
|
||||||
|
this.commandService
|
||||||
|
.executeCommand(command, ...args)
|
||||||
|
.catch((err) =>
|
||||||
|
console.error(
|
||||||
|
`Error occurred when executing the startup task '${command}'${
|
||||||
|
args?.length ? ` with args: '${JSON.stringify(args)}` : ''
|
||||||
|
}.`,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (tasks.length) {
|
||||||
|
// Remove the startup tasks after the execution.
|
||||||
|
// Otherwise, IDE2 executes them again on a window reload event.
|
||||||
|
setURL(StartupTask.set([], new URL(window.location.href)));
|
||||||
|
console.info(`Removed startup tasks from URL.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StartupTask {
|
||||||
command: string;
|
command: string;
|
||||||
/**
|
/**
|
||||||
* This must be JSON serializable.
|
* Must be JSON serializable.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
args?: any[];
|
args?: any[];
|
||||||
}
|
}
|
||||||
|
export namespace StartupTask {
|
||||||
@injectable()
|
const QUERY = 'startupTasks';
|
||||||
export class StartupTask extends Contribution {
|
export function is(arg: unknown): arg is StartupTasks {
|
||||||
override onReady(): void {
|
if (typeof arg === 'object') {
|
||||||
const params = new URLSearchParams(window.location.search);
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const encoded = params.get(StartupTask.QUERY_STRING);
|
const object = arg as any;
|
||||||
if (!encoded) return;
|
return 'command' in object && typeof object['command'] === 'string';
|
||||||
|
|
||||||
const commands = JSON.parse(decodeURIComponent(encoded));
|
|
||||||
|
|
||||||
if (Array.isArray(commands)) {
|
|
||||||
commands.forEach(({ command, args }) => {
|
|
||||||
this.commandService.executeCommand(command, ...args);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
export function get(url: URL): StartupTask[] {
|
||||||
|
const { searchParams } = url;
|
||||||
|
const encodedTasks = searchParams.get(QUERY);
|
||||||
|
if (encodedTasks) {
|
||||||
|
const rawTasks = decodeURIComponent(encodedTasks);
|
||||||
|
const tasks = JSON.parse(rawTasks);
|
||||||
|
if (Array.isArray(tasks)) {
|
||||||
|
return tasks.filter((task) => {
|
||||||
|
if (StartupTask.is(task)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
console.warn(`Was not a task: ${JSON.stringify(task)}. Ignoring.`);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
debugger;
|
||||||
|
console.warn(`Startup tasks was not an array: ${rawTasks}. Ignoring.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
export function set(tasks: StartupTask[], url: URL): URL {
|
||||||
|
const copy = new URL(url);
|
||||||
|
copy.searchParams.set(QUERY, encodeURIComponent(JSON.stringify(tasks)));
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
export function append(tasks: StartupTask[], url: URL): URL {
|
||||||
|
return set([...get(url), ...tasks], url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export namespace StartupTask {
|
|
||||||
export const QUERY_STRING = 'startupTasks';
|
export namespace StartupTasks {
|
||||||
export interface WorkspaceInput extends TheiaWorkspaceInput {
|
export interface WorkspaceInput extends TheiaWorkspaceInput {
|
||||||
tasks: Task[];
|
tasks: StartupTask[];
|
||||||
}
|
}
|
||||||
export namespace WorkspaceInput {
|
export namespace WorkspaceInput {
|
||||||
export function is(
|
export function is(
|
||||||
|
@ -69,3 +69,5 @@ export interface IDEUpdaterClient {
|
|||||||
notifyDownloadProgressChanged(message: ProgressInfo): void;
|
notifyDownloadProgressChanged(message: ProgressInfo): void;
|
||||||
notifyDownloadFinished(message: UpdateInfo): void;
|
notifyDownloadFinished(message: UpdateInfo): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const SKIP_IDE_VERSION = 'skipIDEVersion';
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
} from '../../browser/utils/constants';
|
} from '../../browser/utils/constants';
|
||||||
import * as monaco from '@theia/monaco-editor-core';
|
import * as monaco from '@theia/monaco-editor-core';
|
||||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
|
||||||
const READ_ONLY_FILES = ['sketch.json'];
|
const READ_ONLY_FILES = ['sketch.json'];
|
||||||
const READ_ONLY_FILES_REMOTE = ['thingProperties.h', 'thingsProperties.h'];
|
const READ_ONLY_FILES_REMOTE = ['thingProperties.h', 'thingsProperties.h'];
|
||||||
@ -47,7 +48,9 @@ export class SketchesServiceClientImpl
|
|||||||
@inject(ConfigService)
|
@inject(ConfigService)
|
||||||
protected readonly configService: ConfigService;
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
protected toDispose = new DisposableCollection();
|
@inject(FrontendApplicationStateService)
|
||||||
|
private readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
protected sketches = new Map<string, SketchRef>();
|
protected sketches = new Map<string, SketchRef>();
|
||||||
// TODO: rename this + event to the `onBlabla` pattern
|
// TODO: rename this + event to the `onBlabla` pattern
|
||||||
protected sketchbookDidChangeEmitter = new Emitter<{
|
protected sketchbookDidChangeEmitter = new Emitter<{
|
||||||
@ -55,8 +58,16 @@ export class SketchesServiceClientImpl
|
|||||||
removed: SketchRef[];
|
removed: SketchRef[];
|
||||||
}>();
|
}>();
|
||||||
readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event;
|
readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event;
|
||||||
|
protected currentSketchDidChangeEmitter = new Emitter<CurrentSketch>();
|
||||||
|
readonly onCurrentSketchDidChange = this.currentSketchDidChangeEmitter.event;
|
||||||
|
|
||||||
private _currentSketch = new Deferred<CurrentSketch>();
|
protected toDispose = new DisposableCollection(
|
||||||
|
this.sketchbookDidChangeEmitter,
|
||||||
|
this.currentSketchDidChangeEmitter
|
||||||
|
);
|
||||||
|
|
||||||
|
private _currentSketch: CurrentSketch | undefined;
|
||||||
|
private currentSketchLoaded = new Deferred<CurrentSketch>();
|
||||||
|
|
||||||
onStart(): void {
|
onStart(): void {
|
||||||
this.configService.getConfiguration().then(({ sketchDirUri }) => {
|
this.configService.getConfiguration().then(({ sketchDirUri }) => {
|
||||||
@ -110,9 +121,14 @@ export class SketchesServiceClientImpl
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.loadCurrentSketch().then((currentSketch) =>
|
this.appStateService
|
||||||
this._currentSketch.resolve(currentSketch)
|
.reachedState('started_contributions')
|
||||||
);
|
.then(async () => {
|
||||||
|
const currentSketch = await this.loadCurrentSketch();
|
||||||
|
this._currentSketch = currentSketch;
|
||||||
|
this.currentSketchDidChangeEmitter.fire(this._currentSketch);
|
||||||
|
this.currentSketchLoaded.resolve(this._currentSketch);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onStop(): void {
|
onStop(): void {
|
||||||
@ -143,7 +159,11 @@ export class SketchesServiceClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
async currentSketch(): Promise<CurrentSketch> {
|
async currentSketch(): Promise<CurrentSketch> {
|
||||||
return this._currentSketch.promise;
|
return this.currentSketchLoaded.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryGetCurrentSketch(): CurrentSketch | undefined {
|
||||||
|
return this._currentSketch;
|
||||||
}
|
}
|
||||||
|
|
||||||
async currentSketchFile(): Promise<string | undefined> {
|
async currentSketchFile(): Promise<string | undefined> {
|
||||||
|
@ -1,5 +1,21 @@
|
|||||||
|
import { ApplicationError } from '@theia/core/lib/common/application-error';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
|
||||||
|
export namespace SketchesError {
|
||||||
|
export const Codes = {
|
||||||
|
NotFound: 5001,
|
||||||
|
};
|
||||||
|
export const NotFound = ApplicationError.declare(
|
||||||
|
Codes.NotFound,
|
||||||
|
(message: string, uri: string) => {
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
data: { uri },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const SketchesServicePath = '/services/sketches-service';
|
export const SketchesServicePath = '/services/sketches-service';
|
||||||
export const SketchesService = Symbol('SketchesService');
|
export const SketchesService = Symbol('SketchesService');
|
||||||
export interface SketchesService {
|
export interface SketchesService {
|
||||||
|
@ -10,12 +10,13 @@ import { promisify } from 'util';
|
|||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { FileUri } from '@theia/core/lib/node';
|
import { FileUri } from '@theia/core/lib/node';
|
||||||
import { isWindows, isOSX } from '@theia/core/lib/common/os';
|
import { isWindows, isOSX } from '@theia/core/lib/common/os';
|
||||||
import { ConfigService } from '../common/protocol/config-service';
|
import { ConfigServiceImpl } from './config-service-impl';
|
||||||
import {
|
import {
|
||||||
SketchesService,
|
SketchesService,
|
||||||
Sketch,
|
Sketch,
|
||||||
SketchRef,
|
SketchRef,
|
||||||
SketchContainer,
|
SketchContainer,
|
||||||
|
SketchesError,
|
||||||
} from '../common/protocol/sketches-service';
|
} from '../common/protocol/sketches-service';
|
||||||
import { firstToLowerCase } from '../common/utils';
|
import { firstToLowerCase } from '../common/utils';
|
||||||
import { NotificationServiceServerImpl } from './notification-service-server';
|
import { NotificationServiceServerImpl } from './notification-service-server';
|
||||||
@ -28,6 +29,7 @@ import {
|
|||||||
import { duration } from '../common/decorators';
|
import { duration } from '../common/decorators';
|
||||||
import * as glob from 'glob';
|
import * as glob from 'glob';
|
||||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { ServiceError } from './service-error';
|
||||||
|
|
||||||
const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/;
|
const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/;
|
||||||
|
|
||||||
@ -48,8 +50,8 @@ export class SketchesServiceImpl
|
|||||||
? tempDir
|
? tempDir
|
||||||
: maybeNormalizeDrive(fs.realpathSync.native(tempDir));
|
: maybeNormalizeDrive(fs.realpathSync.native(tempDir));
|
||||||
|
|
||||||
@inject(ConfigService)
|
@inject(ConfigServiceImpl)
|
||||||
protected readonly configService: ConfigService;
|
protected readonly configService: ConfigServiceImpl;
|
||||||
|
|
||||||
@inject(NotificationServiceServerImpl)
|
@inject(NotificationServiceServerImpl)
|
||||||
protected readonly notificationService: NotificationServiceServerImpl;
|
protected readonly notificationService: NotificationServiceServerImpl;
|
||||||
@ -201,7 +203,18 @@ export class SketchesServiceImpl
|
|||||||
const sketch = await new Promise<SketchWithDetails>((resolve, reject) => {
|
const sketch = await new Promise<SketchWithDetails>((resolve, reject) => {
|
||||||
client.loadSketch(req, async (err, resp) => {
|
client.loadSketch(req, async (err, resp) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(
|
||||||
|
isNotFoundError(err)
|
||||||
|
? SketchesError.NotFound(
|
||||||
|
fixErrorMessage(
|
||||||
|
err,
|
||||||
|
requestSketchPath,
|
||||||
|
this.configService.cliConfiguration?.directories.user
|
||||||
|
),
|
||||||
|
uri
|
||||||
|
)
|
||||||
|
: err
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const responseSketchPath = maybeNormalizeDrive(resp.getLocationPath());
|
const responseSketchPath = maybeNormalizeDrive(resp.getLocationPath());
|
||||||
@ -448,26 +461,15 @@ void loop() {
|
|||||||
private async _isSketchFolder(
|
private async _isSketchFolder(
|
||||||
uri: string
|
uri: string
|
||||||
): Promise<SketchWithDetails | undefined> {
|
): Promise<SketchWithDetails | undefined> {
|
||||||
const fsPath = FileUri.fsPath(uri);
|
|
||||||
let stat: fs.Stats | undefined;
|
|
||||||
try {
|
try {
|
||||||
stat = await promisify(fs.lstat)(fsPath);
|
const sketch = await this.loadSketch(uri);
|
||||||
} catch {}
|
return sketch;
|
||||||
if (stat && stat.isDirectory()) {
|
} catch (err) {
|
||||||
const basename = path.basename(fsPath);
|
if (SketchesError.NotFound.is(err)) {
|
||||||
const files = await promisify(fs.readdir)(fsPath);
|
return undefined;
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
if (files[i] === basename + '.ino' || files[i] === basename + '.pde') {
|
|
||||||
try {
|
|
||||||
const sketch = await this.loadSketch(
|
|
||||||
FileUri.create(fsPath).toString()
|
|
||||||
);
|
|
||||||
return sketch;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async isTemp(sketch: SketchRef): Promise<boolean> {
|
async isTemp(sketch: SketchRef): Promise<boolean> {
|
||||||
@ -588,6 +590,40 @@ interface SketchWithDetails extends Sketch {
|
|||||||
readonly mtimeMs: number;
|
readonly mtimeMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/arduino/arduino-cli/issues/1797
|
||||||
|
function fixErrorMessage(
|
||||||
|
err: ServiceError,
|
||||||
|
sketchPath: string,
|
||||||
|
sketchbookPath: string | undefined
|
||||||
|
): string {
|
||||||
|
if (!sketchbookPath) {
|
||||||
|
return err.details; // No way to repair the error message. The current sketchbook path is not available.
|
||||||
|
}
|
||||||
|
// Original: `Can't open sketch: no valid sketch found in /Users/a.kitta/Documents/Arduino: missing /Users/a.kitta/Documents/Arduino/Arduino.ino`
|
||||||
|
// Fixed: `Can't open sketch: no valid sketch found in /Users/a.kitta/Documents/Arduino: missing $sketchPath`
|
||||||
|
const message = err.details;
|
||||||
|
const incorrectMessageSuffix = path.join(sketchbookPath, 'Arduino.ino');
|
||||||
|
if (
|
||||||
|
message.startsWith("Can't open sketch: no valid sketch found in") &&
|
||||||
|
message.endsWith(`${incorrectMessageSuffix}`)
|
||||||
|
) {
|
||||||
|
const sketchName = path.basename(sketchPath);
|
||||||
|
const correctMessagePrefix = message.substring(
|
||||||
|
0,
|
||||||
|
message.length - incorrectMessageSuffix.length
|
||||||
|
);
|
||||||
|
return `${correctMessagePrefix}${path.join(
|
||||||
|
sketchPath,
|
||||||
|
`${sketchName}.ino`
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
return err.details;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotFoundError(err: unknown): err is ServiceError {
|
||||||
|
return ServiceError.is(err) && err.code === 5; // `NOT_FOUND` https://grpc.github.io/grpc/core/md_doc_statuscodes.html
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If on Windows, will change the input `C:\\path\\to\\somewhere` to `c:\\path\\to\\somewhere`.
|
* If on Windows, will change the input `C:\\path\\to\\somewhere` to `c:\\path\\to\\somewhere`.
|
||||||
*/
|
*/
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
// Patch the Theia spinner: https://github.com/eclipse-theia/theia/pull/10761#issuecomment-1131476318
|
|
||||||
// Replaces the `theia-preload` selector with `old-theia-preload` in the generated `index.html`.
|
|
||||||
let arg = process.argv.splice(2)[0]
|
|
||||||
if (!arg) {
|
|
||||||
console.error("The path to the index.html to patch is missing. Use 'node patch-theia-preload.js ./path/to/index.html'")
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
(async () => {
|
|
||||||
const { promises: fs } = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const index = path.isAbsolute(arg) ? arg : path.join(process.cwd(), arg)
|
|
||||||
console.log(`>>> Patching 'theia-preload' with 'old-theia-preload' in ${index}.`)
|
|
||||||
const content = await fs.readFile(index, { encoding: 'utf-8' })
|
|
||||||
await fs.writeFile(index, content.replace(/theia-preload/g, 'old-theia-preload'), { encoding: 'utf-8' })
|
|
||||||
console.log(`<<< Successfully patched index.html.`)
|
|
||||||
})()
|
|
@ -23,7 +23,7 @@
|
|||||||
"package": "cross-env DEBUG=* && electron-builder --publish=never",
|
"package": "cross-env DEBUG=* && electron-builder --publish=never",
|
||||||
"package:publish": "cross-env DEBUG=* && electron-builder --publish=always",
|
"package:publish": "cross-env DEBUG=* && electron-builder --publish=always",
|
||||||
"download:plugins": "theia download:plugins",
|
"download:plugins": "theia download:plugins",
|
||||||
"patch": "ncp ./patch/backend/main.js ./src-gen/backend/main.js && node ./scripts/patch-theia-preload.js ./lib/index.html"
|
"patch": "ncp ./patch/backend/main.js ./src-gen/backend/main.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0 <15"
|
"node": ">=14.0.0 <15"
|
||||||
|
@ -254,9 +254,10 @@
|
|||||||
"cloud.pull.warn": "True if users should be warned before pulling a cloud sketch. Defaults to true.",
|
"cloud.pull.warn": "True if users should be warned before pulling a cloud sketch. Defaults to true.",
|
||||||
"cloud.push.warn": "True if users should be warned before pushing a cloud sketch. Defaults to true.",
|
"cloud.push.warn": "True if users should be warned before pushing a cloud sketch. Defaults to true.",
|
||||||
"cloud.pushpublic.warn": "True if users should be warned before pushing a public sketch to the cloud. Defaults to true.",
|
"cloud.pushpublic.warn": "True if users should be warned before pushing a public sketch to the cloud. Defaults to true.",
|
||||||
"cloud.sketchSyncEnpoint": "The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.",
|
"cloud.sketchSyncEndpoint": "The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.",
|
||||||
"compile": "compile",
|
"compile": "compile",
|
||||||
"compile.experimental": "True if the IDE should handle multiple compiler errors. False by default",
|
"compile.experimental": "True if the IDE should handle multiple compiler errors. False by default",
|
||||||
|
"compile.optimizeForDebug": "Optimize compile output for debug, not for release. It's 'false' by default.",
|
||||||
"compile.revealRange": "Adjusts how compiler errors are revealed in the editor after a failed verify/upload. Possible values: 'auto': Scroll vertically as necessary and reveal a line. 'center': Scroll vertically as necessary and reveal a line centered vertically. 'top': Scroll vertically as necessary and reveal a line close to the top of the viewport, optimized for viewing a code definition. 'centerIfOutsideViewport': Scroll vertically as necessary and reveal a line centered vertically only if it lies outside the viewport. The default value is '{0}'.",
|
"compile.revealRange": "Adjusts how compiler errors are revealed in the editor after a failed verify/upload. Possible values: 'auto': Scroll vertically as necessary and reveal a line. 'center': Scroll vertically as necessary and reveal a line centered vertically. 'top': Scroll vertically as necessary and reveal a line close to the top of the viewport, optimized for viewing a code definition. 'centerIfOutsideViewport': Scroll vertically as necessary and reveal a line centered vertically only if it lies outside the viewport. The default value is '{0}'.",
|
||||||
"compile.verbose": "True for verbose compile output. False by default",
|
"compile.verbose": "True for verbose compile output. False by default",
|
||||||
"compile.warnings": "Tells gcc which warning level to use. It's 'None' by default",
|
"compile.warnings": "Tells gcc which warning level to use. It's 'None' by default",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user