mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-16 07:46:32 +00:00
ATL-546: Added UI for settings.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
1742c53015
commit
1f544b2656
@ -1,3 +1,4 @@
|
||||
const debounce = require('lodash.debounce');
|
||||
import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, SelectionService, ILogger } from '@theia/core';
|
||||
import {
|
||||
ContextMenuRenderer,
|
||||
@ -23,6 +24,7 @@ import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspac
|
||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import * as React from 'react';
|
||||
import { remote } from 'electron';
|
||||
import { MainMenuManager } from '../common/main-menu-manager';
|
||||
import { BoardsService, CoreService, Port, SketchesService, ExecutableService } from '../common/protocol';
|
||||
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||
@ -42,11 +44,9 @@ import { WorkspaceService } from './theia/workspace/workspace-service';
|
||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
|
||||
const debounce = require('lodash.debounce');
|
||||
import { OutputService } from '../common/protocol/output-service';
|
||||
import { NotificationCenter } from './notification-center';
|
||||
import { Settings } from './contributions/settings';
|
||||
import { ArduinoPreferences } from './arduino-preferences';
|
||||
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
|
||||
@ -147,11 +147,15 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
|
||||
@inject(ExecutableService)
|
||||
protected executableService: ExecutableService;
|
||||
|
||||
@inject(OutputService)
|
||||
protected readonly outputService: OutputService;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
@inject(ArduinoPreferences)
|
||||
protected readonly arduinoPreferences: ArduinoPreferences;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
protected invalidConfigPopup: Promise<void | 'No' | 'Yes' | undefined> | undefined;
|
||||
|
||||
@ -192,7 +196,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
viewContribution.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(async ({ selectedBoard }) => {
|
||||
const start = async ({ selectedBoard }: BoardsConfig.Config) => {
|
||||
if (selectedBoard) {
|
||||
const { name, fqbn } = selectedBoard;
|
||||
if (fqbn) {
|
||||
@ -200,20 +204,22 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
this.startLanguageServer(fqbn, name);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.notificationCenter.onConfigChanged(({ config }) => {
|
||||
if (config) {
|
||||
this.invalidConfigPopup = undefined;
|
||||
} else {
|
||||
if (!this.invalidConfigPopup) {
|
||||
this.invalidConfigPopup = this.messageService.error(`Your CLI configuration is invalid. Do you want to correct it now?`, 'No', 'Yes')
|
||||
.then(answer => {
|
||||
if (answer === 'Yes') {
|
||||
this.commandRegistry.executeCommand(Settings.Commands.OPEN_CLI_CONFIG.id)
|
||||
};
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(start);
|
||||
this.arduinoPreferences.onPreferenceChanged(event => {
|
||||
if (event.preferenceName === 'arduino.language.log' && event.newValue !== event.oldValue) {
|
||||
start(this.boardsServiceClientImpl.boardsConfig);
|
||||
}
|
||||
this.invalidConfigPopup = undefined;
|
||||
});
|
||||
}
|
||||
this.arduinoPreferences.ready.then(() => {
|
||||
const webContents = remote.getCurrentWebContents();
|
||||
const zoomLevel = this.arduinoPreferences.get('arduino.window.zoomLevel');
|
||||
webContents.setZoomLevel(zoomLevel);
|
||||
});
|
||||
this.arduinoPreferences.onPreferenceChanged(event => {
|
||||
if (event.preferenceName === 'arduino.window.zoomLevel' && typeof event.newValue === 'number' && event.newValue !== event.oldValue) {
|
||||
const webContents = remote.getCurrentWebContents();
|
||||
webContents.setZoomLevel(event.newValue || 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -221,6 +227,14 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
protected startLanguageServer = debounce((fqbn: string, name: string | undefined) => this.doStartLanguageServer(fqbn, name));
|
||||
protected async doStartLanguageServer(fqbn: string, name: string | undefined): Promise<void> {
|
||||
this.logger.info(`Starting language server: ${fqbn}`);
|
||||
const log = this.arduinoPreferences.get('arduino.language.log');
|
||||
let currentSketchPath: string | undefined = undefined;
|
||||
if (log) {
|
||||
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||
if (currentSketch) {
|
||||
currentSketchPath = await this.fileSystem.fsPath(new URI(currentSketch.uri));
|
||||
}
|
||||
}
|
||||
const { clangdUri, cliUri, lsUri } = await this.executableService.list();
|
||||
const [clangdPath, cliPath, lsPath] = await Promise.all([
|
||||
this.fileSystem.fsPath(new URI(clangdUri)),
|
||||
@ -231,6 +245,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
lsPath,
|
||||
cliPath,
|
||||
clangdPath,
|
||||
log: currentSketchPath ? currentSketchPath : log,
|
||||
board: {
|
||||
fqbn,
|
||||
name: name ? `"${name}"` : undefined
|
||||
|
@ -136,6 +136,8 @@ import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationCo
|
||||
import { BoardSelection } from './contributions/board-selection';
|
||||
import { OpenRecentSketch } from './contributions/open-recent-sketch';
|
||||
import { Help } from './contributions/help';
|
||||
import { bindArduinoPreferences } from './arduino-preferences'
|
||||
import { SettingsService, SettingsDialog, SettingsWidget, SettingsDialogProps } from './settings';
|
||||
|
||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||
|
||||
@ -375,4 +377,16 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// To remove the `Run` menu item from the application menu.
|
||||
bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaDebugFrontendApplicationContribution).toService(DebugFrontendApplicationContribution);
|
||||
|
||||
// Preferences
|
||||
bindArduinoPreferences(bind);
|
||||
|
||||
// Settings wrapper for the preferences and the CLI config.
|
||||
bind(SettingsService).toSelf().inSingletonScope();
|
||||
// Settings dialog and widget
|
||||
bind(SettingsWidget).toSelf().inSingletonScope();
|
||||
bind(SettingsDialog).toSelf().inSingletonScope();
|
||||
bind(SettingsDialogProps).toConstantValue({
|
||||
title: 'Preferences'
|
||||
});
|
||||
});
|
||||
|
73
arduino-ide-extension/src/browser/arduino-preferences.ts
Normal file
73
arduino-ide-extension/src/browser/arduino-preferences.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { interfaces } from 'inversify';
|
||||
import {
|
||||
createPreferenceProxy,
|
||||
PreferenceProxy,
|
||||
PreferenceService,
|
||||
PreferenceContribution,
|
||||
PreferenceSchema
|
||||
} from '@theia/core/lib/browser/preferences';
|
||||
|
||||
export const ArduinoConfigSchema: PreferenceSchema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'arduino.language.log': {
|
||||
'type': 'boolean',
|
||||
'description': "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.",
|
||||
'default': false
|
||||
},
|
||||
'arduino.compile.verbose': {
|
||||
'type': 'boolean',
|
||||
'description': 'True for verbose compile output.',
|
||||
'default': true
|
||||
},
|
||||
'arduino.upload.verbose': {
|
||||
'type': 'boolean',
|
||||
'description': 'True for verbose upload output.',
|
||||
'default': true
|
||||
},
|
||||
'arduino.upload.verify': {
|
||||
'type': 'boolean',
|
||||
'default': false
|
||||
},
|
||||
'arduino.window.autoScale': {
|
||||
'type': 'boolean',
|
||||
'description': 'True if the user interface automatically scales with the font size.',
|
||||
'default': true
|
||||
},
|
||||
'arduino.window.zoomLevel': {
|
||||
'type': 'number',
|
||||
'description': 'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.',
|
||||
'default': 0
|
||||
},
|
||||
'arduino.ide.autoUpdate': {
|
||||
'type': 'boolean',
|
||||
'description': 'True to enable automatic update checks. The IDE will check for updates automatically and periodically.',
|
||||
'default': true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export interface ArduinoConfiguration {
|
||||
'arduino.language.log': boolean;
|
||||
'arduino.compile.verbose': boolean;
|
||||
'arduino.upload.verbose': boolean;
|
||||
'arduino.upload.verify': boolean;
|
||||
'arduino.window.autoScale': boolean;
|
||||
'arduino.window.zoomLevel': number;
|
||||
'arduino.ide.autoUpdate': boolean;
|
||||
}
|
||||
|
||||
export const ArduinoPreferences = Symbol('ArduinoPreferences');
|
||||
export type ArduinoPreferences = PreferenceProxy<ArduinoConfiguration>;
|
||||
|
||||
export function createArduinoPreferences(preferences: PreferenceService): ArduinoPreferences {
|
||||
return createPreferenceProxy(preferences, ArduinoConfigSchema);
|
||||
}
|
||||
|
||||
export function bindArduinoPreferences(bind: interfaces.Bind): void {
|
||||
bind(ArduinoPreferences).toDynamicValue(ctx => {
|
||||
const preferences = ctx.container.get<PreferenceService>(PreferenceService);
|
||||
return createArduinoPreferences(preferences);
|
||||
});
|
||||
bind(PreferenceContribution).toConstantValue({ schema: ArduinoConfigSchema });
|
||||
}
|
@ -57,6 +57,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
* See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ
|
||||
*/
|
||||
protected latestValidBoardsConfig: RecursiveRequired<BoardsConfig.Config> | undefined = undefined;
|
||||
protected latestBoardsConfig: BoardsConfig.Config | undefined = undefined;
|
||||
protected _boardsConfig: BoardsConfig.Config = {};
|
||||
protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only.
|
||||
protected _availablePorts: Port[] = [];
|
||||
@ -187,6 +188,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
|
||||
this.logger.info('Board config changed: ', JSON.stringify(config));
|
||||
this._boardsConfig = config;
|
||||
this.latestBoardsConfig = this._boardsConfig;
|
||||
if (this.canUploadTo(this._boardsConfig)) {
|
||||
this.latestValidBoardsConfig = this._boardsConfig;
|
||||
}
|
||||
@ -384,7 +386,10 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const key = this.getLastSelectedBoardOnPortKey(selectedPort);
|
||||
await this.storageService.setData(key, selectedBoard);
|
||||
}
|
||||
await this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig);
|
||||
await Promise.all([
|
||||
this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig),
|
||||
this.storageService.setData('latest-boards-config', this.latestBoardsConfig)
|
||||
]);
|
||||
}
|
||||
|
||||
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
|
||||
@ -393,15 +398,21 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
protected async loadState(): Promise<void> {
|
||||
const storedValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
||||
if (storedValidBoardsConfig) {
|
||||
this.latestValidBoardsConfig = storedValidBoardsConfig;
|
||||
const storedLatestValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
||||
if (storedLatestValidBoardsConfig) {
|
||||
this.latestValidBoardsConfig = storedLatestValidBoardsConfig;
|
||||
if (this.canUploadTo(this.latestValidBoardsConfig)) {
|
||||
this.boardsConfig = this.latestValidBoardsConfig;
|
||||
}
|
||||
} else {
|
||||
// If we could not restore the latest valid config, try to restore something, the board at least.
|
||||
const storedLatestBoardsConfig = await this.storageService.getData<BoardsConfig.Config | undefined>('latest-boards-config');
|
||||
if (storedLatestBoardsConfig) {
|
||||
this.latestBoardsConfig = storedLatestBoardsConfig;
|
||||
this.boardsConfig = this.latestBoardsConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,15 +47,19 @@ export class BurnBootloader extends SketchContribution {
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const port = boardsConfig.selectedPort?.address;
|
||||
const [fqbn, { selectedProgrammer: programmer }] = await Promise.all([
|
||||
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn)
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose')
|
||||
]);
|
||||
this.outputChannelManager.getChannel('Arduino: bootloader').clear();
|
||||
await this.coreService.burnBootloader({
|
||||
fqbn,
|
||||
programmer,
|
||||
port
|
||||
port,
|
||||
verify,
|
||||
verbose
|
||||
});
|
||||
this.messageService.info('Done burning bootloader.', { timeout: 1000 });
|
||||
} catch (e) {
|
||||
|
@ -10,11 +10,13 @@ import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
|
||||
import { MenuModelRegistry, MenuContribution } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry, KeybindingContribution } from '@theia/core/lib/browser/keybinding';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { Command, CommandRegistry, CommandContribution, CommandService } from '@theia/core/lib/common/command';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
import { SettingsService } from '../settings';
|
||||
import { SketchesServiceClientImpl } from '../../common/protocol/sketches-service-client-impl';
|
||||
import { SketchesService, ConfigService, FileSystemExt, Sketch } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
|
||||
import { ArduinoPreferences } from '../arduino-preferences';
|
||||
|
||||
export { Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, URI, Sketch, open };
|
||||
|
||||
@ -39,6 +41,9 @@ export abstract class Contribution implements CommandContribution, MenuContribut
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
|
||||
@inject(SettingsService)
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
||||
onStart(app: FrontendApplication): MaybePromise<void> {
|
||||
}
|
||||
|
||||
@ -77,6 +82,9 @@ export abstract class SketchContribution extends Contribution {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(ArduinoPreferences)
|
||||
protected readonly preferences: ArduinoPreferences;
|
||||
|
||||
}
|
||||
|
||||
export namespace Contribution {
|
||||
|
@ -3,7 +3,6 @@ import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribu
|
||||
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 { EDITOR_FONT_DEFAULTS } from '@theia/editor/lib/browser/editor-preferences';
|
||||
import { Contribution, Command, MenuModelRegistry, KeybindingRegistry, CommandRegistry } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@ -31,10 +30,28 @@ export class EditContributions extends Contribution {
|
||||
registry.registerCommand(EditContributions.Commands.FIND_PREVIOUS, { execute: () => this.run('editor.action.nextMatchFindAction') });
|
||||
registry.registerCommand(EditContributions.Commands.USE_FOR_FIND, { execute: () => this.run('editor.action.previousSelectionMatchFindAction') });
|
||||
registry.registerCommand(EditContributions.Commands.INCREASE_FONT_SIZE, {
|
||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) + 1)
|
||||
execute: async () => {
|
||||
const settings = await this.settingsService.settings();
|
||||
if (settings.autoScaleInterface) {
|
||||
settings.interfaceScale = settings.interfaceScale + 1;
|
||||
} else {
|
||||
settings.editorFontSize = settings.editorFontSize + 1;
|
||||
}
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
}
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.DECREASE_FONT_SIZE, {
|
||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) - 1)
|
||||
execute: async () => {
|
||||
const settings = await this.settingsService.settings();
|
||||
if (settings.autoScaleInterface) {
|
||||
settings.interfaceScale = settings.interfaceScale - 1;
|
||||
} else {
|
||||
settings.editorFontSize = settings.editorFontSize - 1;
|
||||
}
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
}
|
||||
});
|
||||
/* Tools */registry.registerCommand(EditContributions.Commands.AUTO_FORMAT, { execute: () => this.run('editor.action.formatDocument') });
|
||||
registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, {
|
||||
|
@ -1,27 +1,49 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { URI, Command, MenuModelRegistry, CommandRegistry, SketchContribution, open } from './contribution';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Command, MenuModelRegistry, CommandRegistry, SketchContribution, KeybindingRegistry } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { Settings as Preferences, SettingsDialog } from '../settings';
|
||||
|
||||
@injectable()
|
||||
export class Settings extends SketchContribution {
|
||||
|
||||
@inject(SettingsDialog)
|
||||
protected readonly settingsDialog: SettingsDialog;
|
||||
|
||||
protected settingsOpened = false;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Settings.Commands.OPEN_CLI_CONFIG, {
|
||||
execute: () => this.configService.getCliConfigFileUri().then(uri => open(this.openerService, new URI(uri)))
|
||||
registry.registerCommand(Settings.Commands.OPEN, {
|
||||
execute: async () => {
|
||||
let settings: Preferences | undefined = undefined;
|
||||
try {
|
||||
this.settingsOpened = true;
|
||||
settings = await this.settingsDialog.open();
|
||||
} finally {
|
||||
this.settingsOpened = false;
|
||||
}
|
||||
if (settings) {
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
} else {
|
||||
await this.settingsService.reset();
|
||||
}
|
||||
},
|
||||
isEnabled: () => !this.settingsOpened
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
||||
commandId: CommonCommands.OPEN_PREFERENCES.id,
|
||||
commandId: Settings.Commands.OPEN.id,
|
||||
label: 'Preferences...',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
||||
commandId: Settings.Commands.OPEN_CLI_CONFIG.id,
|
||||
label: 'Open CLI Configuration',
|
||||
order: '1',
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: Settings.Commands.OPEN.id,
|
||||
keybinding: 'CtrlCmd+,',
|
||||
});
|
||||
}
|
||||
|
||||
@ -29,9 +51,9 @@ export class Settings extends SketchContribution {
|
||||
|
||||
export namespace Settings {
|
||||
export namespace Commands {
|
||||
export const OPEN_CLI_CONFIG: Command = {
|
||||
id: 'arduino-open-cli-config',
|
||||
label: 'Open CLI Configuration',
|
||||
export const OPEN: Command = {
|
||||
id: 'arduino-settings-open',
|
||||
label: 'Open Preferences...',
|
||||
category: 'Arduino'
|
||||
}
|
||||
}
|
||||
|
@ -88,9 +88,11 @@ export class UploadSketch extends SketchContribution {
|
||||
}
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const [fqbn, { selectedProgrammer }] = await Promise.all([
|
||||
const [fqbn, { selectedProgrammer }, verify, verbose] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn)
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose')
|
||||
]);
|
||||
|
||||
let options: CoreService.Upload.Options | undefined = undefined;
|
||||
@ -106,14 +108,18 @@ export class UploadSketch extends SketchContribution {
|
||||
fqbn,
|
||||
optimizeForDebug,
|
||||
programmer,
|
||||
port
|
||||
port,
|
||||
verbose,
|
||||
verify
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
sketchUri,
|
||||
fqbn,
|
||||
optimizeForDebug,
|
||||
port
|
||||
port,
|
||||
verbose,
|
||||
verify
|
||||
};
|
||||
}
|
||||
this.outputChannelManager.getChannel('Arduino: upload').clear();
|
||||
|
@ -64,11 +64,13 @@ export class VerifySketch extends SketchContribution {
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const fqbn = await this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn);
|
||||
const verbose = this.preferences.get('arduino.compile.verbose');
|
||||
this.outputChannelManager.getChannel('Arduino: compile').clear();
|
||||
await this.coreService.compile({
|
||||
sketchUri: uri,
|
||||
fqbn,
|
||||
optimizeForDebug: this.editorMode.compileForDebug
|
||||
optimizeForDebug: this.editorMode.compileForDebug,
|
||||
verbose
|
||||
});
|
||||
this.messageService.info('Done compiling.', { timeout: 1000 });
|
||||
} catch (e) {
|
||||
|
603
arduino-ide-extension/src/browser/settings.tsx
Normal file
603
arduino-ide-extension/src/browser/settings.tsx
Normal file
@ -0,0 +1,603 @@
|
||||
import * as React from 'react';
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { Widget } from '@phosphor/widgets';
|
||||
import { Message } from '@phosphor/messaging';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { deepClone } from '@theia/core/lib/common/objects';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog/file-dialog-service';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { AbstractDialog, DialogProps, PreferenceService, PreferenceScope, DialogError, ReactWidget } from '@theia/core/lib/browser';
|
||||
import { Index } from '../common/types';
|
||||
import { ConfigService, FileSystemExt } from '../common/protocol';
|
||||
|
||||
export interface Settings extends Index {
|
||||
editorFontSize: number; // `editor.fontSize`
|
||||
themeId: string; // `workbench.colorTheme`
|
||||
autoSave: 'on' | 'off'; // `editor.autoSave`
|
||||
|
||||
autoScaleInterface: boolean; // `arduino.window.autoScale`
|
||||
interfaceScale: number; // `arduino.window.zoomLevel` https://github.com/eclipse-theia/theia/issues/8751
|
||||
checkForUpdates?: boolean; // `arduino.ide.autoUpdate`
|
||||
verboseOnCompile: boolean; // `arduino.compile.verbose`
|
||||
verboseOnUpload: boolean; // `arduino.upload.verbose`
|
||||
verifyAfterUpload: boolean; // `arduino.upload.verify`
|
||||
enableLsLogs: boolean; // `arduino.language.log`
|
||||
|
||||
sketchbookPath: string; // CLI
|
||||
additionalUrls: string[]; // CLI
|
||||
}
|
||||
export namespace Settings {
|
||||
|
||||
export function belongsToCli<K extends keyof Settings>(key: K): boolean {
|
||||
return key === 'sketchbookPath' || key === 'additionalUrls';
|
||||
}
|
||||
|
||||
}
|
||||
export type SettingsKey = keyof Settings;
|
||||
|
||||
@injectable()
|
||||
export class SettingsService {
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@inject(FileSystemExt)
|
||||
protected readonly fileSystemExt: FileSystemExt;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(PreferenceService)
|
||||
protected readonly preferenceService: PreferenceService;
|
||||
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
protected readonly onDidChangeEmitter = new Emitter<Readonly<Settings>>();
|
||||
readonly onDidChange = this.onDidChangeEmitter.event;
|
||||
|
||||
protected ready = new Deferred<void>();
|
||||
protected _settings: Settings;
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
await this.appStateService.reachedState('ready'); // Hack for https://github.com/eclipse-theia/theia/issues/8993
|
||||
const settings = await this.loadSettings();
|
||||
this._settings = deepClone(settings);
|
||||
this.ready.resolve();
|
||||
}
|
||||
|
||||
protected async loadSettings(): Promise<Settings> {
|
||||
await this.preferenceService.ready;
|
||||
const [
|
||||
editorFontSize,
|
||||
themeId,
|
||||
autoSave,
|
||||
autoScaleInterface,
|
||||
interfaceScale,
|
||||
// checkForUpdates,
|
||||
verboseOnCompile,
|
||||
verboseOnUpload,
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
cliConfig
|
||||
] = await Promise.all([
|
||||
this.preferenceService.get<number>('editor.fontSize', 12),
|
||||
this.preferenceService.get<string>('workbench.colorTheme', 'arduino-theme'),
|
||||
this.preferenceService.get<'on' | 'off'>('editor.autoSave', 'on'),
|
||||
this.preferenceService.get<boolean>('arduino.window.autoScale', true),
|
||||
this.preferenceService.get<number>('arduino.window.zoomLevel', 0),
|
||||
// this.preferenceService.get<string>('arduino.ide.autoUpdate', true),
|
||||
this.preferenceService.get<boolean>('arduino.compile.verbose', true),
|
||||
this.preferenceService.get<boolean>('arduino.upload.verbose', true),
|
||||
this.preferenceService.get<boolean>('arduino.upload.verify', true),
|
||||
this.preferenceService.get<boolean>('arduino.language.log', true),
|
||||
this.configService.getConfiguration()
|
||||
]);
|
||||
const { additionalUrls, sketchDirUri } = cliConfig;
|
||||
const sketchbookPath = await this.fileService.fsPath(new URI(sketchDirUri));
|
||||
return {
|
||||
editorFontSize,
|
||||
themeId,
|
||||
autoSave,
|
||||
autoScaleInterface,
|
||||
interfaceScale,
|
||||
// checkForUpdates,
|
||||
verboseOnCompile,
|
||||
verboseOnUpload,
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
additionalUrls,
|
||||
sketchbookPath
|
||||
};
|
||||
}
|
||||
|
||||
async settings(): Promise<Settings> {
|
||||
await this.ready.promise;
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
async update(settings: Settings, fireDidChange: boolean = false): Promise<void> {
|
||||
await this.ready.promise;
|
||||
for (const key of Object.keys(settings)) {
|
||||
this._settings[key] = settings[key];
|
||||
}
|
||||
if (fireDidChange) {
|
||||
this.onDidChangeEmitter.fire(this._settings);
|
||||
}
|
||||
}
|
||||
|
||||
async reset(): Promise<void> {
|
||||
const settings = await this.loadSettings();
|
||||
return this.update(settings, true);
|
||||
}
|
||||
|
||||
async validate(settings: MaybePromise<Settings> = this.settings()): Promise<string | true> {
|
||||
try {
|
||||
const { sketchbookPath, editorFontSize, themeId } = await settings;
|
||||
const sketchbookDir = await this.fileSystemExt.getUri(sketchbookPath);
|
||||
if (!await this.fileService.exists(new URI(sketchbookDir))) {
|
||||
return `Invalid sketchbook location: ${sketchbookPath}`;
|
||||
}
|
||||
if (editorFontSize <= 0) {
|
||||
return `Invalid editor font size. It must be a positive integer.`;
|
||||
}
|
||||
if (!ThemeService.get().getThemes().find(({ id }) => id === themeId)) {
|
||||
return `Invalid theme.`;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return err.message;
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
}
|
||||
|
||||
async save(): Promise<string | true> {
|
||||
await this.ready.promise;
|
||||
const {
|
||||
editorFontSize,
|
||||
themeId,
|
||||
autoSave,
|
||||
autoScaleInterface,
|
||||
interfaceScale,
|
||||
// checkForUpdates,
|
||||
verboseOnCompile,
|
||||
verboseOnUpload,
|
||||
verifyAfterUpload,
|
||||
enableLsLogs,
|
||||
sketchbookPath,
|
||||
additionalUrls
|
||||
} = this._settings;
|
||||
const [config, sketchDirUri] = await Promise.all([
|
||||
this.configService.getConfiguration(),
|
||||
this.fileSystemExt.getUri(sketchbookPath)
|
||||
]);
|
||||
(config as any).additionalUrls = additionalUrls;
|
||||
(config as any).sketchDirUri = sketchDirUri;
|
||||
|
||||
await Promise.all([
|
||||
this.preferenceService.set('editor.fontSize', editorFontSize, PreferenceScope.User),
|
||||
this.preferenceService.set('workbench.colorTheme', themeId, PreferenceScope.User),
|
||||
this.preferenceService.set('editor.autoSave', autoSave, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.window.autoScale', autoScaleInterface, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.window.zoomLevel', interfaceScale, PreferenceScope.User),
|
||||
// this.preferenceService.set('arduino.ide.autoUpdate', checkForUpdates, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.compile.verbose', verboseOnCompile, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.upload.verbose', verboseOnUpload, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.upload.verify', verifyAfterUpload, PreferenceScope.User),
|
||||
this.preferenceService.set('arduino.language.log', enableLsLogs, PreferenceScope.User),
|
||||
this.configService.setConfiguration(config)
|
||||
]);
|
||||
this.onDidChangeEmitter.fire(this._settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class SettingsComponent extends React.Component<SettingsComponent.Props, SettingsComponent.State> {
|
||||
|
||||
readonly toDispose = new DisposableCollection();
|
||||
|
||||
constructor(props: SettingsComponent.Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidUpdate(_: SettingsComponent.Props, prevState: SettingsComponent.State): void {
|
||||
if (this.state && prevState && JSON.stringify(this.state) !== JSON.stringify(prevState)) {
|
||||
this.props.settingsService.update(this.state, true);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.props.settingsService.settings().then(settings => this.setState(settings));
|
||||
this.toDispose.push(this.props.settingsService.onDidChange(settings => this.setState(settings)));
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
if (!this.state) {
|
||||
return <div />;
|
||||
}
|
||||
return <div className='content noselect'>
|
||||
Sketchbook location:
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input stretch'
|
||||
type='text'
|
||||
value={this.state.sketchbookPath}
|
||||
onChange={this.sketchpathDidChange} />
|
||||
<button className='theia-button shrink' onClick={this.browseSketchbookDidClick}>Browse</button>
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<div className='column'>
|
||||
<div className='flex-line'>Editor font size:</div>
|
||||
<div className='flex-line'>Interface scale:</div>
|
||||
<div className='flex-line'>Theme:</div>
|
||||
<div className='flex-line'>Show verbose output during:</div>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<div className='flex-line'>
|
||||
<input
|
||||
className='theia-input small'
|
||||
type='number'
|
||||
step={1}
|
||||
pattern='[0-9]+'
|
||||
onKeyDown={this.numbersOnlyKeyDown}
|
||||
value={this.state.editorFontSize}
|
||||
onChange={this.editorFontSizeDidChange} />
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.autoScaleInterface}
|
||||
onChange={this.autoScaleInterfaceDidChange} />
|
||||
Automatic
|
||||
</label>
|
||||
<input
|
||||
className='theia-input small with-margin'
|
||||
type='number'
|
||||
step={20}
|
||||
pattern='[0-9]+'
|
||||
onKeyDown={this.noopKeyDown}
|
||||
value={100 + this.state.interfaceScale * 20}
|
||||
onChange={this.interfaceScaleDidChange} />
|
||||
%
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<select
|
||||
className='theia-select'
|
||||
value={ThemeService.get().getCurrentTheme().label}
|
||||
onChange={this.themeDidChange}>
|
||||
{ThemeService.get().getThemes().map(({ id, label }) => <option key={id} value={label}>{label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className='flex-line'>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.verboseOnCompile}
|
||||
onChange={this.verboseOnCompileDidChange} />
|
||||
compile
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.verboseOnUpload}
|
||||
onChange={this.verboseOnUploadDidChange} />
|
||||
upload
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.verifyAfterUpload}
|
||||
onChange={this.verifyAfterUploadDidChange} />
|
||||
Verify code after upload
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.checkForUpdates}
|
||||
onChange={this.checkForUpdatesDidChange}
|
||||
disabled={true} />
|
||||
Check for updates on startup
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.autoSave === 'on'}
|
||||
onChange={this.autoSaveDidChange} />
|
||||
Auto save
|
||||
</label>
|
||||
<label className='flex-line'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={this.state.enableLsLogs}
|
||||
onChange={this.enableLsLogsDidChange} />
|
||||
Enable language server logging
|
||||
</label>
|
||||
<div className='flex-line'>
|
||||
Additional boards manager URLs:
|
||||
<input
|
||||
className='theia-input stretch with-margin'
|
||||
type='text'
|
||||
value={this.state.additionalUrls.join(',')}
|
||||
onChange={this.additionalUrlsDidChange} />
|
||||
<i className='fa fa-window-restore theia-button shrink' onClick={this.editAdditionalUrlDidClick} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected noopKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
event.nativeEvent.preventDefault();
|
||||
event.nativeEvent.returnValue = false;
|
||||
}
|
||||
|
||||
protected numbersOnlyKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const key = Number(event.key)
|
||||
if (isNaN(key) || event.key === null || event.key === ' ') {
|
||||
event.nativeEvent.preventDefault();
|
||||
event.nativeEvent.returnValue = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected browseSketchbookDidClick = async () => {
|
||||
const uri = await this.props.fileDialogService.showOpenDialog({
|
||||
title: 'Select new sketchbook location',
|
||||
openLabel: 'Chose',
|
||||
canSelectFiles: false,
|
||||
canSelectMany: false,
|
||||
canSelectFolders: true
|
||||
});
|
||||
if (uri) {
|
||||
const sketchbookPath = await this.props.fileService.fsPath(uri);
|
||||
this.setState({ sketchbookPath });
|
||||
}
|
||||
};
|
||||
|
||||
protected editAdditionalUrlDidClick = async () => {
|
||||
const additionalUrls = await new AdditionalUrlsDialog(this.state.additionalUrls, this.props.windowService).open();
|
||||
if (additionalUrls) {
|
||||
this.setState({ additionalUrls });
|
||||
}
|
||||
};
|
||||
|
||||
protected editorFontSizeDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
if (value) {
|
||||
this.setState({ editorFontSize: parseInt(value, 10) });
|
||||
}
|
||||
};
|
||||
|
||||
protected additionalUrlsDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ additionalUrls: event.target.value.split(',').map(url => url.trim()) });
|
||||
};
|
||||
|
||||
protected autoScaleInterfaceDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ autoScaleInterface: event.target.checked });
|
||||
};
|
||||
|
||||
protected enableLsLogsDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ enableLsLogs: event.target.checked });
|
||||
};
|
||||
|
||||
protected interfaceScaleDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
const percentage = parseInt(value, 10);
|
||||
if (isNaN(percentage)) {
|
||||
return;
|
||||
}
|
||||
let interfaceScale = (percentage - 100) / 20;
|
||||
if (!isNaN(interfaceScale)) {
|
||||
this.setState({ interfaceScale });
|
||||
}
|
||||
};
|
||||
|
||||
protected verifyAfterUploadDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ verifyAfterUpload: event.target.checked });
|
||||
};
|
||||
|
||||
protected checkForUpdatesDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ checkForUpdates: event.target.checked });
|
||||
};
|
||||
|
||||
protected autoSaveDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ autoSave: event.target.checked ? 'on' : 'off' });
|
||||
};
|
||||
|
||||
protected themeDidChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const { selectedIndex } = event.target.options;
|
||||
const theme = ThemeService.get().getThemes()[selectedIndex];
|
||||
if (theme) {
|
||||
this.setState({ themeId: theme.id });
|
||||
}
|
||||
};
|
||||
|
||||
protected verboseOnCompileDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ verboseOnCompile: event.target.checked });
|
||||
};
|
||||
|
||||
protected verboseOnUploadDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ verboseOnUpload: event.target.checked });
|
||||
};
|
||||
|
||||
protected sketchpathDidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const sketchbookPath = event.target.value;
|
||||
if (sketchbookPath) {
|
||||
this.setState({ sketchbookPath });
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
export namespace SettingsComponent {
|
||||
export interface Props {
|
||||
readonly settingsService: SettingsService;
|
||||
readonly fileService: FileService;
|
||||
readonly fileDialogService: FileDialogService;
|
||||
readonly windowService: WindowService;
|
||||
}
|
||||
export interface State extends Settings { }
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsWidget extends ReactWidget {
|
||||
|
||||
@inject(SettingsService)
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@inject(FileDialogService)
|
||||
protected readonly fileDialogService: FileDialogService;
|
||||
|
||||
@inject(WindowService)
|
||||
protected readonly windowService: WindowService;
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return <SettingsComponent
|
||||
settingsService={this.settingsService}
|
||||
fileService={this.fileService}
|
||||
fileDialogService={this.fileDialogService}
|
||||
windowService={this.windowService} />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsDialogProps extends DialogProps {
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SettingsDialog extends AbstractDialog<Promise<Settings>> {
|
||||
|
||||
@inject(SettingsService)
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
||||
@inject(SettingsWidget)
|
||||
protected readonly widget: SettingsWidget;
|
||||
|
||||
constructor(@inject(SettingsDialogProps) protected readonly props: SettingsDialogProps) {
|
||||
super(props);
|
||||
this.contentNode.classList.add('arduino-settings-dialog');
|
||||
this.appendCloseButton('CANCEL');
|
||||
this.appendAcceptButton('OK');
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDispose.push(this.settingsService.onDidChange(this.validate.bind(this)));
|
||||
}
|
||||
|
||||
protected async isValid(settings: Promise<Settings>): Promise<DialogError> {
|
||||
const result = await this.settingsService.validate(settings);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
get value(): Promise<Settings> {
|
||||
return this.settingsService.settings();
|
||||
}
|
||||
|
||||
protected onAfterAttach(msg: Message): void {
|
||||
if (this.widget.isAttached) {
|
||||
Widget.detach(this.widget);
|
||||
}
|
||||
Widget.attach(this.widget, this.contentNode);
|
||||
this.toDisposeOnDetach.push(this.settingsService.onDidChange(() => this.update()));
|
||||
super.onAfterAttach(msg);
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected onUpdateRequest(msg: Message) {
|
||||
super.onUpdateRequest(msg);
|
||||
this.widget.update();
|
||||
}
|
||||
|
||||
protected onActivateRequest(msg: Message): void {
|
||||
super.onActivateRequest(msg);
|
||||
this.widget.activate();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class AdditionalUrlsDialog extends AbstractDialog<string[]> {
|
||||
|
||||
protected readonly textArea: HTMLTextAreaElement;
|
||||
|
||||
constructor(urls: string[], windowService: WindowService) {
|
||||
super({ title: 'Additional Boards Manager URLs' });
|
||||
|
||||
this.contentNode.classList.add('additional-urls-dialog');
|
||||
|
||||
const description = document.createElement('div');
|
||||
description.textContent = 'Enter additional URLs, one for each row';
|
||||
description.style.marginBottom = '5px';
|
||||
this.contentNode.appendChild(description);
|
||||
|
||||
this.textArea = document.createElement('textarea');
|
||||
this.textArea.className = 'theia-input';
|
||||
this.textArea.setAttribute('style', 'flex: 0;');
|
||||
this.textArea.value = urls.filter(url => url.trim()).filter(url => !!url).join('\n');
|
||||
this.textArea.wrap = 'soft';
|
||||
this.textArea.cols = 90;
|
||||
this.textArea.rows = 5;
|
||||
this.contentNode.appendChild(this.textArea);
|
||||
|
||||
const anchor = document.createElement('div');
|
||||
anchor.classList.add('link');
|
||||
anchor.textContent = 'Click for a list of unofficial board support URLs';
|
||||
anchor.style.marginTop = '5px';
|
||||
anchor.style.cursor = 'pointer';
|
||||
this.addEventListener(
|
||||
anchor,
|
||||
'click',
|
||||
() => windowService.openNewWindow('https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls', { external: true })
|
||||
);
|
||||
this.contentNode.appendChild(anchor);
|
||||
|
||||
this.appendAcceptButton('OK');
|
||||
this.appendCloseButton('Cancel');
|
||||
}
|
||||
|
||||
get value(): string[] {
|
||||
return this.textArea.value.split('\n').map(url => url.trim());
|
||||
}
|
||||
|
||||
protected onAfterAttach(message: Message): void {
|
||||
super.onAfterAttach(message);
|
||||
this.addUpdateListener(this.textArea, 'input');
|
||||
}
|
||||
|
||||
protected onActivateRequest(message: Message): void {
|
||||
super.onActivateRequest(message);
|
||||
this.textArea.focus();
|
||||
}
|
||||
|
||||
protected handleEnter(event: KeyboardEvent): boolean | void {
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
return super.handleEnter(event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
@import './status-bar.css';
|
||||
@import './terminal.css';
|
||||
@import './editor.css';
|
||||
@import './settings-dialog.css';
|
||||
|
||||
.theia-input.warning:focus {
|
||||
outline-width: 1px;
|
||||
|
43
arduino-ide-extension/src/browser/style/settings-dialog.css
Normal file
43
arduino-ide-extension/src/browser/style/settings-dialog.css
Normal file
@ -0,0 +1,43 @@
|
||||
.arduino-settings-dialog {
|
||||
width: 740px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .content {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .flex-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .with-margin {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .theia-select {
|
||||
background: var(--theia-input-background) !important;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .column > div {
|
||||
height: 26px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .flex-line .theia-button.shrink {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .theia-input.stretch {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.arduino-settings-dialog .theia-input.small {
|
||||
max-width: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.additional-urls-dialog .link:hover {
|
||||
color: var(--theia-textLink-activeForeground);
|
||||
}
|
@ -24,13 +24,7 @@ export class EditorManager extends TheiaEditorManager {
|
||||
}
|
||||
|
||||
protected async isReadOnly(uri: URI): Promise<boolean> {
|
||||
const [config, configFileUri] = await Promise.all([
|
||||
this.configService.getConfiguration(),
|
||||
this.configService.getCliConfigFileUri()
|
||||
]);
|
||||
if (new URI(configFileUri).toString(true) === uri.toString(true)) {
|
||||
return false;
|
||||
}
|
||||
const config = await this.configService.getConfiguration();
|
||||
return new URI(config.dataDirUri).isEqualOrParent(uri)
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,7 @@ export class PreferencesContribution extends TheiaPreferencesContribution {
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
// https://github.com/eclipse-theia/theia/issues/8202
|
||||
registry.registerKeybinding({
|
||||
command: CommonCommands.OPEN_PREFERENCES.id,
|
||||
keybinding: 'CtrlCmd+,',
|
||||
});
|
||||
registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ export const ConfigService = Symbol('ConfigService');
|
||||
export interface ConfigService {
|
||||
getVersion(): Promise<Readonly<{ version: string, commit: string, status?: string }>>;
|
||||
getConfiguration(): Promise<Config>;
|
||||
getCliConfigFileUri(): Promise<string>;
|
||||
setConfiguration(config: Config): Promise<void>;
|
||||
getConfigurationFileSchemaUri(): Promise<string>;
|
||||
isInDataDir(uri: string): Promise<boolean>;
|
||||
isInSketchDir(uri: string): Promise<boolean>;
|
||||
@ -15,3 +15,20 @@ export interface Config {
|
||||
readonly downloadsDirUri: string;
|
||||
readonly additionalUrls: string[];
|
||||
}
|
||||
export namespace Config {
|
||||
export function sameAs(left: Config, right: Config): boolean {
|
||||
const leftUrls = left.additionalUrls.sort();
|
||||
const rightUrls = right.additionalUrls.sort();
|
||||
if (leftUrls.length !== rightUrls.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < leftUrls.length; i++) {
|
||||
if (leftUrls[i] !== rightUrls[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return left.dataDirUri === right.dataDirUri
|
||||
&& left.downloadsDirUri === right.downloadsDirUri
|
||||
&& left.sketchDirUri === right.sketchDirUri;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export namespace CoreService {
|
||||
readonly sketchUri: string;
|
||||
readonly fqbn?: string | undefined;
|
||||
readonly optimizeForDebug: boolean;
|
||||
readonly verbose: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +24,7 @@ export namespace CoreService {
|
||||
export interface Options extends Compile.Options {
|
||||
readonly port?: string | undefined;
|
||||
readonly programmer?: Programmer | undefined;
|
||||
readonly verify: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +33,8 @@ export namespace CoreService {
|
||||
readonly fqbn?: string | undefined;
|
||||
readonly port?: string | undefined;
|
||||
readonly programmer?: Programmer | undefined;
|
||||
readonly verbose: boolean;
|
||||
readonly verify: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
export type RecursiveRequired<T> = {
|
||||
[P in keyof T]-?: RecursiveRequired<T[P]>;
|
||||
};
|
||||
|
||||
export interface Index {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ interface ISettingsService extends grpc.ServiceDefinition<grpc.UntypedServiceImp
|
||||
merge: ISettingsService_IMerge;
|
||||
getValue: ISettingsService_IGetValue;
|
||||
setValue: ISettingsService_ISetValue;
|
||||
write: ISettingsService_IWrite;
|
||||
}
|
||||
|
||||
interface ISettingsService_IGetAll extends grpc.MethodDefinition<settings_settings_pb.GetAllRequest, settings_settings_pb.RawData> {
|
||||
@ -51,6 +52,15 @@ interface ISettingsService_ISetValue extends grpc.MethodDefinition<settings_sett
|
||||
responseSerialize: grpc.serialize<settings_settings_pb.SetValueResponse>;
|
||||
responseDeserialize: grpc.deserialize<settings_settings_pb.SetValueResponse>;
|
||||
}
|
||||
interface ISettingsService_IWrite extends grpc.MethodDefinition<settings_settings_pb.WriteRequest, settings_settings_pb.WriteResponse> {
|
||||
path: "/cc.arduino.cli.settings.Settings/Write";
|
||||
requestStream: false;
|
||||
responseStream: false;
|
||||
requestSerialize: grpc.serialize<settings_settings_pb.WriteRequest>;
|
||||
requestDeserialize: grpc.deserialize<settings_settings_pb.WriteRequest>;
|
||||
responseSerialize: grpc.serialize<settings_settings_pb.WriteResponse>;
|
||||
responseDeserialize: grpc.deserialize<settings_settings_pb.WriteResponse>;
|
||||
}
|
||||
|
||||
export const SettingsService: ISettingsService;
|
||||
|
||||
@ -59,6 +69,7 @@ export interface ISettingsServer {
|
||||
merge: grpc.handleUnaryCall<settings_settings_pb.RawData, settings_settings_pb.MergeResponse>;
|
||||
getValue: grpc.handleUnaryCall<settings_settings_pb.GetValueRequest, settings_settings_pb.Value>;
|
||||
setValue: grpc.handleUnaryCall<settings_settings_pb.Value, settings_settings_pb.SetValueResponse>;
|
||||
write: grpc.handleUnaryCall<settings_settings_pb.WriteRequest, settings_settings_pb.WriteResponse>;
|
||||
}
|
||||
|
||||
export interface ISettingsClient {
|
||||
@ -74,6 +85,9 @@ export interface ISettingsClient {
|
||||
setValue(request: settings_settings_pb.Value, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall;
|
||||
setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall;
|
||||
setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall;
|
||||
write(request: settings_settings_pb.WriteRequest, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall;
|
||||
write(request: settings_settings_pb.WriteRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall;
|
||||
write(request: settings_settings_pb.WriteRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall;
|
||||
}
|
||||
|
||||
export class SettingsClient extends grpc.Client implements ISettingsClient {
|
||||
@ -90,4 +104,7 @@ export class SettingsClient extends grpc.Client implements ISettingsClient {
|
||||
public setValue(request: settings_settings_pb.Value, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall;
|
||||
public setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall;
|
||||
public setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall;
|
||||
public write(request: settings_settings_pb.WriteRequest, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall;
|
||||
public write(request: settings_settings_pb.WriteRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall;
|
||||
public write(request: settings_settings_pb.WriteRequest, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.WriteResponse) => void): grpc.ClientUnaryCall;
|
||||
}
|
||||
|
@ -85,6 +85,28 @@ function deserialize_cc_arduino_cli_settings_Value(buffer_arg) {
|
||||
return settings_settings_pb.Value.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_cc_arduino_cli_settings_WriteRequest(arg) {
|
||||
if (!(arg instanceof settings_settings_pb.WriteRequest)) {
|
||||
throw new Error('Expected argument of type cc.arduino.cli.settings.WriteRequest');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_cc_arduino_cli_settings_WriteRequest(buffer_arg) {
|
||||
return settings_settings_pb.WriteRequest.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
function serialize_cc_arduino_cli_settings_WriteResponse(arg) {
|
||||
if (!(arg instanceof settings_settings_pb.WriteResponse)) {
|
||||
throw new Error('Expected argument of type cc.arduino.cli.settings.WriteResponse');
|
||||
}
|
||||
return Buffer.from(arg.serializeBinary());
|
||||
}
|
||||
|
||||
function deserialize_cc_arduino_cli_settings_WriteResponse(buffer_arg) {
|
||||
return settings_settings_pb.WriteResponse.deserializeBinary(new Uint8Array(buffer_arg));
|
||||
}
|
||||
|
||||
|
||||
// The Settings service provides an interface to Arduino CLI's configuration
|
||||
// options
|
||||
@ -137,5 +159,17 @@ setValue: {
|
||||
responseSerialize: serialize_cc_arduino_cli_settings_SetValueResponse,
|
||||
responseDeserialize: deserialize_cc_arduino_cli_settings_SetValueResponse,
|
||||
},
|
||||
// Writes to file settings currently stored in memory
|
||||
write: {
|
||||
path: '/cc.arduino.cli.settings.Settings/Write',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestType: settings_settings_pb.WriteRequest,
|
||||
responseType: settings_settings_pb.WriteResponse,
|
||||
requestSerialize: serialize_cc_arduino_cli_settings_WriteRequest,
|
||||
requestDeserialize: deserialize_cc_arduino_cli_settings_WriteRequest,
|
||||
responseSerialize: serialize_cc_arduino_cli_settings_WriteResponse,
|
||||
responseDeserialize: deserialize_cc_arduino_cli_settings_WriteResponse,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -123,3 +123,41 @@ export namespace SetValueResponse {
|
||||
export type AsObject = {
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteRequest extends jspb.Message {
|
||||
getFilepath(): string;
|
||||
setFilepath(value: string): WriteRequest;
|
||||
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): WriteRequest.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: WriteRequest): WriteRequest.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: WriteRequest, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): WriteRequest;
|
||||
static deserializeBinaryFromReader(message: WriteRequest, reader: jspb.BinaryReader): WriteRequest;
|
||||
}
|
||||
|
||||
export namespace WriteRequest {
|
||||
export type AsObject = {
|
||||
filepath: string,
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteResponse extends jspb.Message {
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): WriteResponse.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: WriteResponse): WriteResponse.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: WriteResponse, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): WriteResponse;
|
||||
static deserializeBinaryFromReader(message: WriteResponse, reader: jspb.BinaryReader): WriteResponse;
|
||||
}
|
||||
|
||||
export namespace WriteResponse {
|
||||
export type AsObject = {
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ goog.exportSymbol('proto.cc.arduino.cli.settings.MergeResponse', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.settings.RawData', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.settings.SetValueResponse', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.settings.Value', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.settings.WriteRequest', null, global);
|
||||
goog.exportSymbol('proto.cc.arduino.cli.settings.WriteResponse', null, global);
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
@ -146,6 +148,48 @@ if (goog.DEBUG && !COMPILED) {
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.SetValueResponse.displayName = 'proto.cc.arduino.cli.settings.SetValueResponse';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.cc.arduino.cli.settings.WriteRequest, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.displayName = 'proto.cc.arduino.cli.settings.WriteRequest';
|
||||
}
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
* @param {Array=} opt_data Optional initial data array, typically from a
|
||||
* server response, or constructed directly in Javascript. The array is used
|
||||
* in place and becomes part of the constructed object. It is not cloned.
|
||||
* If no data is provided, the constructed object will be empty, but still
|
||||
* valid.
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.cc.arduino.cli.settings.WriteResponse, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
/**
|
||||
* @public
|
||||
* @override
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse.displayName = 'proto.cc.arduino.cli.settings.WriteResponse';
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -869,4 +913,235 @@ proto.cc.arduino.cli.settings.SetValueResponse.serializeBinaryToWriter = functio
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.cc.arduino.cli.settings.WriteRequest.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.cc.arduino.cli.settings.WriteRequest} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
filepath: jspb.Message.getFieldWithDefault(msg, 1, "")
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.cc.arduino.cli.settings.WriteRequest}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.cc.arduino.cli.settings.WriteRequest;
|
||||
return proto.cc.arduino.cli.settings.WriteRequest.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.cc.arduino.cli.settings.WriteRequest} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.cc.arduino.cli.settings.WriteRequest}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setFilepath(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.cc.arduino.cli.settings.WriteRequest.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.cc.arduino.cli.settings.WriteRequest} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
f = message.getFilepath();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string filePath = 1;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.prototype.getFilepath = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @return {!proto.cc.arduino.cli.settings.WriteRequest} returns this
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteRequest.prototype.setFilepath = function(value) {
|
||||
return jspb.Message.setProto3StringField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
/**
|
||||
* Creates an object representation of this proto.
|
||||
* Field names that are reserved in JavaScript and will be renamed to pb_name.
|
||||
* Optional fields that are not set will be set to undefined.
|
||||
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
|
||||
* For the list of reserved names please see:
|
||||
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
|
||||
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
|
||||
* JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.cc.arduino.cli.settings.WriteResponse.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static version of the {@see toObject} method.
|
||||
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
|
||||
* the JSPB instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.cc.arduino.cli.settings.WriteResponse} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
obj.$jspbMessageInstance = msg;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.cc.arduino.cli.settings.WriteResponse}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.cc.arduino.cli.settings.WriteResponse;
|
||||
return proto.cc.arduino.cli.settings.WriteResponse.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.cc.arduino.cli.settings.WriteResponse} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.cc.arduino.cli.settings.WriteResponse}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
}
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
proto.cc.arduino.cli.settings.WriteResponse.serializeBinaryToWriter(this, writer);
|
||||
return writer.getResultBuffer();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes the given message to binary data (in protobuf wire
|
||||
* format), writing to the given BinaryWriter.
|
||||
* @param {!proto.cc.arduino.cli.settings.WriteResponse} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
* @suppress {unusedLocalVariables} f is only used for nested messages
|
||||
*/
|
||||
proto.cc.arduino.cli.settings.WriteResponse.serializeBinaryToWriter = function(message, writer) {
|
||||
var f = undefined;
|
||||
};
|
||||
|
||||
|
||||
goog.object.extend(exports, proto.cc.arduino.cli.settings);
|
||||
|
@ -12,7 +12,7 @@ import { BackendApplicationContribution } from '@theia/core/lib/node/backend-app
|
||||
import { ConfigService, Config, NotificationServiceServer } from '../common/protocol';
|
||||
import * as fs from './fs-extra';
|
||||
import { spawnCommand } from './exec-util';
|
||||
import { RawData } from './cli-protocol/settings/settings_pb';
|
||||
import { RawData, WriteRequest } from './cli-protocol/settings/settings_pb';
|
||||
import { SettingsClient } from './cli-protocol/settings/settings_grpc_pb';
|
||||
import * as serviceGrpcPb from './cli-protocol/settings/settings_grpc_pb';
|
||||
import { ConfigFileValidator } from './config-file-validator';
|
||||
@ -20,8 +20,8 @@ import { ArduinoDaemonImpl } from './arduino-daemon-impl';
|
||||
import { DefaultCliConfig, CLI_CONFIG_SCHEMA_PATH, CLI_CONFIG } from './cli-config';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
import { deepClone } from '@theia/core';
|
||||
|
||||
const debounce = require('lodash.debounce');
|
||||
const track = temp.track();
|
||||
|
||||
@injectable()
|
||||
@ -43,7 +43,6 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
|
||||
protected updating = false;
|
||||
protected config: Config;
|
||||
protected cliConfig: DefaultCliConfig | undefined;
|
||||
protected ready = new Deferred<void>();
|
||||
@ -51,18 +50,17 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
|
||||
async onStart(): Promise<void> {
|
||||
await this.ensureCliConfigExists();
|
||||
await this.watchCliConfig();
|
||||
this.cliConfig = await this.loadCliConfig();
|
||||
if (this.cliConfig) {
|
||||
const config = await this.mapCliConfigToAppConfig(this.cliConfig);
|
||||
if (config) {
|
||||
this.config = config;
|
||||
this.ready.resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.fireInvalidConfig();
|
||||
}
|
||||
}
|
||||
|
||||
async getCliConfigFileUri(): Promise<string> {
|
||||
const configDirUri = await this.envVariablesServer.getConfigDirUri();
|
||||
@ -78,6 +76,35 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
return this.config;
|
||||
}
|
||||
|
||||
async setConfiguration(config: Config): Promise<void> {
|
||||
await this.ready.promise;
|
||||
if (Config.sameAs(this.config, config)) {
|
||||
return;
|
||||
}
|
||||
let copyDefaultCliConfig: DefaultCliConfig | undefined = deepClone(this.cliConfig);
|
||||
if (!copyDefaultCliConfig) {
|
||||
copyDefaultCliConfig = await this.getFallbackCliConfig();
|
||||
}
|
||||
const { additionalUrls, dataDirUri, downloadsDirUri, sketchDirUri } = config;
|
||||
copyDefaultCliConfig.directories = {
|
||||
data: FileUri.fsPath(dataDirUri),
|
||||
downloads: FileUri.fsPath(downloadsDirUri),
|
||||
user: FileUri.fsPath(sketchDirUri)
|
||||
};
|
||||
copyDefaultCliConfig.board_manager = {
|
||||
additional_urls: [
|
||||
...additionalUrls
|
||||
]
|
||||
};
|
||||
const { port } = copyDefaultCliConfig.daemon;
|
||||
await this.updateDaemon(port, copyDefaultCliConfig);
|
||||
await this.writeDaemonState(port);
|
||||
|
||||
this.config = deepClone(config);
|
||||
this.cliConfig = copyDefaultCliConfig;
|
||||
this.fireConfigChanged(this.config);
|
||||
}
|
||||
|
||||
get cliConfiguration(): DefaultCliConfig | undefined {
|
||||
return this.cliConfig;
|
||||
}
|
||||
@ -124,7 +151,7 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
resolve(dirPath);
|
||||
});
|
||||
});
|
||||
await spawnCommand(`"${cliPath}"`, ['config', 'init', '--dest-dir', throwawayDirPath]);
|
||||
await spawnCommand(`"${cliPath}"`, ['config', 'init', '--dest-dir', `"${throwawayDirPath}"`]);
|
||||
const rawYaml = await fs.readFile(path.join(throwawayDirPath, CLI_CONFIG), { encoding: 'utf-8' });
|
||||
const model = yaml.safeLoad(rawYaml.trim());
|
||||
return model as DefaultCliConfig;
|
||||
@ -163,63 +190,8 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
};
|
||||
}
|
||||
|
||||
protected async watchCliConfig(): Promise<void> {
|
||||
const configDirUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(configDirUri);
|
||||
const listener = debounce(async () => {
|
||||
if (this.updating) {
|
||||
return;
|
||||
} else {
|
||||
this.updating = true;
|
||||
}
|
||||
|
||||
const cliConfig = await this.loadCliConfig();
|
||||
// Could not parse the YAML content.
|
||||
if (!cliConfig) {
|
||||
this.updating = false;
|
||||
this.fireInvalidConfig();
|
||||
return;
|
||||
}
|
||||
const valid = await this.validator.validate(cliConfig);
|
||||
if (!valid) {
|
||||
this.updating = false;
|
||||
this.fireInvalidConfig();
|
||||
return;
|
||||
}
|
||||
const shouldUpdate = !this.cliConfig || !DefaultCliConfig.sameAs(this.cliConfig, cliConfig);
|
||||
if (!shouldUpdate) {
|
||||
this.fireConfigChanged(this.config);
|
||||
this.updating = false;
|
||||
return;
|
||||
}
|
||||
// We use the gRPC `Settings` API iff the `daemon.port` has not changed.
|
||||
// Otherwise, we restart the daemon.
|
||||
const canUpdateSettings = this.cliConfig && this.cliConfig.daemon.port === cliConfig.daemon.port;
|
||||
try {
|
||||
const config = await this.mapCliConfigToAppConfig(cliConfig);
|
||||
const update = new Promise<void>(resolve => {
|
||||
if (canUpdateSettings) {
|
||||
return this.updateDaemon(cliConfig.daemon.port, cliConfig).then(resolve);
|
||||
}
|
||||
return this.daemon.stopDaemon()
|
||||
.then(() => this.daemon.startDaemon())
|
||||
.then(resolve);
|
||||
})
|
||||
update.then(() => {
|
||||
this.cliConfig = cliConfig;
|
||||
this.config = config;
|
||||
this.configChangeEmitter.fire(this.config);
|
||||
this.notificationService.notifyConfigChanged({ config: this.config });
|
||||
}).finally(() => this.updating = false);
|
||||
} catch (err) {
|
||||
this.logger.error('Failed to update the daemon with the current CLI configuration.', err);
|
||||
}
|
||||
}, 200);
|
||||
fs.watchFile(cliConfigPath, listener);
|
||||
this.logger.info(`Started watching the Arduino CLI configuration: '${cliConfigPath}'.`);
|
||||
}
|
||||
|
||||
protected fireConfigChanged(config: Config): void {
|
||||
this.configChangeEmitter.fire(config);
|
||||
this.notificationService.notifyConfigChanged({ config });
|
||||
}
|
||||
|
||||
@ -227,30 +199,51 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
this.notificationService.notifyConfigChanged({ config: undefined });
|
||||
}
|
||||
|
||||
protected async unwatchCliConfig(): Promise<void> {
|
||||
const cliConfigFileUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigFileUri);
|
||||
fs.unwatchFile(cliConfigPath);
|
||||
this.logger.info(`Stopped watching the Arduino CLI configuration: '${cliConfigPath}'.`);
|
||||
}
|
||||
|
||||
protected async updateDaemon(port: string | number, config: DefaultCliConfig): Promise<void> {
|
||||
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
|
||||
// @ts-ignore
|
||||
const SettingsClient = grpc.makeClientConstructor(serviceGrpcPb['cc.arduino.cli.settings.Settings'], 'SettingsService') as any;
|
||||
const client = new SettingsClient(`localhost:${port}`, grpc.credentials.createInsecure()) as SettingsClient;
|
||||
const client = this.createClient(port);
|
||||
const data = new RawData();
|
||||
data.setJsondata(JSON.stringify(config, null, 2));
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client.merge(data, error => {
|
||||
try {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
client.close();
|
||||
resolve();
|
||||
})
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected async writeDaemonState(port: string | number): Promise<void> {
|
||||
const client = this.createClient(port);
|
||||
const req = new WriteRequest();
|
||||
const cliConfigUri = await this.getCliConfigFileUri();
|
||||
const cliConfigPath = FileUri.fsPath(cliConfigUri);
|
||||
req.setFilepath(cliConfigPath);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client.write(req, error => {
|
||||
try {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createClient(port: string | number): SettingsClient {
|
||||
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
|
||||
// @ts-ignore
|
||||
const SettingsClient = grpc.makeClientConstructor(serviceGrpcPb['cc.arduino.cli.settings.Settings'], 'SettingsService') as any;
|
||||
return new SettingsClient(`localhost:${port}`, grpc.credentials.createInsecure()) as SettingsClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { dirname } from 'path';
|
||||
import { CoreService } from '../common/protocol/core-service';
|
||||
import { CompileReq, CompileResp } from './cli-protocol/commands/compile_pb';
|
||||
import { CoreClientProvider } from './core-client-provider';
|
||||
import { UploadReq, UploadResp, BurnBootloaderReq, BurnBootloaderResp, UploadUsingProgrammerReq, UploadUsingProgrammerResp } from './cli-protocol/commands/upload_pb';
|
||||
import { OutputService } from '../common/protocol/output-service';
|
||||
import { NotificationServiceServer } from '../common/protocol';
|
||||
import { NotificationServiceServer, ConfigService } from '../common/protocol';
|
||||
import { ClientReadableStream } from '@grpc/grpc-js';
|
||||
import { ArduinoCoreClient } from './cli-protocol/commands/commands_grpc_pb';
|
||||
import { firstToUpperCase, firstToLowerCase } from '../common/utils';
|
||||
@ -23,16 +23,23 @@ export class CoreServiceImpl implements CoreService {
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly notificationService: NotificationServiceServer;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.coreClient().then(({ client, instance }) => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
async compile(options: CoreService.Compile.Options): Promise<void> {
|
||||
this.outputService.append({ name: 'compile', chunk: 'Compile...\n' + JSON.stringify(options, null, 2) + '\n--------------------------\n' });
|
||||
const { sketchUri, fqbn } = options;
|
||||
const sketchFilePath = FileUri.fsPath(sketchUri);
|
||||
const sketchpath = dirname(sketchFilePath);
|
||||
|
||||
const coreClient = await this.coreClientProvider.client();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
}
|
||||
const coreClient = await this.coreClient();
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const compilerReq = new CompileReq();
|
||||
@ -43,7 +50,7 @@ export class CoreServiceImpl implements CoreService {
|
||||
}
|
||||
compilerReq.setOptimizefordebug(options.optimizeForDebug);
|
||||
compilerReq.setPreprocess(false);
|
||||
compilerReq.setVerbose(true);
|
||||
compilerReq.setVerbose(options.verbose);
|
||||
compilerReq.setQuiet(false);
|
||||
|
||||
const result = client.compile(compilerReq);
|
||||
@ -84,10 +91,7 @@ export class CoreServiceImpl implements CoreService {
|
||||
const sketchFilePath = FileUri.fsPath(sketchUri);
|
||||
const sketchpath = dirname(sketchFilePath);
|
||||
|
||||
const coreClient = await this.coreClientProvider.client();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
}
|
||||
const coreClient = await this.coreClient();
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const req = requestProvider();
|
||||
@ -102,6 +106,8 @@ export class CoreServiceImpl implements CoreService {
|
||||
if (programmer) {
|
||||
req.setProgrammer(programmer.id);
|
||||
}
|
||||
req.setVerbose(options.verbose);
|
||||
req.setVerify(options.verify);
|
||||
const result = responseHandler(client, req);
|
||||
|
||||
try {
|
||||
@ -121,12 +127,9 @@ export class CoreServiceImpl implements CoreService {
|
||||
}
|
||||
|
||||
async burnBootloader(options: CoreService.Bootloader.Options): Promise<void> {
|
||||
const coreClient = await this.coreClientProvider.client();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
}
|
||||
const { fqbn, port, programmer } = options;
|
||||
const coreClient = await this.coreClient();
|
||||
const { client, instance } = coreClient;
|
||||
const { fqbn, port, programmer } = options;
|
||||
const burnReq = new BurnBootloaderReq();
|
||||
burnReq.setInstance(instance);
|
||||
if (fqbn) {
|
||||
@ -138,6 +141,8 @@ export class CoreServiceImpl implements CoreService {
|
||||
if (programmer) {
|
||||
burnReq.setProgrammer(programmer.id);
|
||||
}
|
||||
burnReq.setVerify(options.verify);
|
||||
burnReq.setVerbose(options.verbose);
|
||||
const result = client.burnBootloader(burnReq);
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
@ -154,4 +159,23 @@ export class CoreServiceImpl implements CoreService {
|
||||
}
|
||||
}
|
||||
|
||||
private async coreClient(): Promise<CoreClientProvider.Client> {
|
||||
const coreClient = await new Promise<CoreClientProvider.Client>(async resolve => {
|
||||
const client = await this.coreClientProvider.client();
|
||||
if (client) {
|
||||
resolve(client);
|
||||
return;
|
||||
}
|
||||
const toDispose = this.coreClientProvider.onClientReady(async () => {
|
||||
const client = await this.coreClientProvider.client();
|
||||
if (client) {
|
||||
toDispose.dispose();
|
||||
resolve(client);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
return coreClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user