mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-12 05:46:32 +00:00
Use eslint&prettier for code linting&formatting
This commit is contained in:
parent
2a3873a923
commit
0592199858
49
.eslintrc.js
Normal file
49
.eslintrc.js
Normal file
@ -0,0 +1,49 @@
|
||||
module.exports = {
|
||||
ignorePatterns: [
|
||||
'node_modules/*',
|
||||
'/*',
|
||||
'!arduino-ide-extension',
|
||||
'arduino-ide-extension/*',
|
||||
'!arduino-ide-extension/src',
|
||||
'arduino-ide-extension/src/node/cli-protocol',
|
||||
],
|
||||
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
ecmaFeatures: {
|
||||
jsx: true, // Allows for the parsing of JSX
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||
'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
|
||||
'plugin:react-hooks/recommended', // Uses recommended rules from react hooks
|
||||
'plugin:prettier/recommended',
|
||||
'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
||||
],
|
||||
plugins: ['prettier'],
|
||||
root: true,
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-namespace': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-empty-interface': 'warn',
|
||||
'react/display-name': 'warn',
|
||||
eqeqeq: ['error', 'smart'],
|
||||
'guard-for-in': 'off',
|
||||
'id-blacklist': 'off',
|
||||
'id-match': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'no-var': 'error',
|
||||
radix: 'error',
|
||||
'prettier/prettier': 'warn',
|
||||
},
|
||||
};
|
13
.prettierrc
Normal file
13
.prettierrc
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.{json,yml}",
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
@ -1,21 +1,9 @@
|
||||
{
|
||||
"tslint.enable": true,
|
||||
"tslint.configFile": "./tslint.json",
|
||||
"editor.formatOnSave": true,
|
||||
"files.exclude": {
|
||||
"**/lib": false
|
||||
},
|
||||
"editor.insertSpaces": true,
|
||||
"editor.detectIndentation": false,
|
||||
"[typescript]": {
|
||||
"editor.tabSize": 4
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"[json]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"download-ls": "node ./scripts/download-ls.js",
|
||||
"download-examples": "node ./scripts/download-examples.js",
|
||||
"generate-protocol": "node ./scripts/generate-protocol.js",
|
||||
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
||||
"lint": "eslint",
|
||||
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
|
||||
"watch": "tsc -w",
|
||||
"test": "mocha \"./lib/test/**/*.test.js\"",
|
||||
|
@ -4,20 +4,18 @@ 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'
|
||||
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'
|
||||
id: 'arduino-open-sketch-files',
|
||||
};
|
||||
|
||||
export const OPEN_BOARDS_DIALOG: Command = {
|
||||
id: 'arduino-open-boards-dialog'
|
||||
id: 'arduino-open-boards-dialog',
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,38 @@
|
||||
import { Mutex } from 'async-mutex';
|
||||
import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, SelectionService, ILogger, DisposableCollection } from '@theia/core';
|
||||
import {
|
||||
MAIN_MENU_BAR,
|
||||
MenuContribution,
|
||||
MenuModelRegistry,
|
||||
SelectionService,
|
||||
ILogger,
|
||||
DisposableCollection,
|
||||
} from '@theia/core';
|
||||
import {
|
||||
ContextMenuRenderer,
|
||||
FrontendApplication, FrontendApplicationContribution,
|
||||
OpenerService, StatusBar, StatusBarAlignment
|
||||
FrontendApplication,
|
||||
FrontendApplicationContribution,
|
||||
OpenerService,
|
||||
StatusBar,
|
||||
StatusBarAlignment,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import {
|
||||
TabBarToolbarContribution,
|
||||
TabBarToolbarRegistry,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import {
|
||||
CommandContribution,
|
||||
CommandRegistry,
|
||||
} from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorMainMenu, EditorManager, EditorOpenerOptions } from '@theia/editor/lib/browser';
|
||||
import {
|
||||
EditorMainMenu,
|
||||
EditorManager,
|
||||
EditorOpenerOptions,
|
||||
} from '@theia/editor/lib/browser';
|
||||
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
||||
@ -26,7 +46,14 @@ 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, Sketch } from '../common/protocol';
|
||||
import {
|
||||
BoardsService,
|
||||
CoreService,
|
||||
Port,
|
||||
SketchesService,
|
||||
ExecutableService,
|
||||
Sketch,
|
||||
} from '../common/protocol';
|
||||
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
import { FileSystemExt } from '../common/protocol/filesystem-ext';
|
||||
@ -52,9 +79,14 @@ import { FileChangeType } from '@theia/filesystem/lib/browser';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
|
||||
TabBarToolbarContribution, CommandContribution, MenuContribution, ColorContribution {
|
||||
|
||||
export class ArduinoFrontendContribution
|
||||
implements
|
||||
FrontendApplicationContribution,
|
||||
TabBarToolbarContribution,
|
||||
CommandContribution,
|
||||
MenuContribution,
|
||||
ColorContribution
|
||||
{
|
||||
@inject(ILogger)
|
||||
protected logger: ILogger;
|
||||
|
||||
@ -163,48 +195,74 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
protected invalidConfigPopup: Promise<void | 'No' | 'Yes' | undefined> | undefined;
|
||||
protected invalidConfigPopup:
|
||||
| Promise<void | 'No' | 'Yes' | undefined>
|
||||
| undefined;
|
||||
protected toDisposeOnStop = new DisposableCollection();
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
if (!window.navigator.onLine) {
|
||||
// tslint:disable-next-line:max-line-length
|
||||
this.messageService.warn('You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.');
|
||||
this.messageService.warn(
|
||||
'You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.'
|
||||
);
|
||||
}
|
||||
const updateStatusBar = ({ selectedBoard, selectedPort }: BoardsConfig.Config) => {
|
||||
const updateStatusBar = ({
|
||||
selectedBoard,
|
||||
selectedPort,
|
||||
}: BoardsConfig.Config) => {
|
||||
this.statusBar.setElement('arduino-selected-board', {
|
||||
alignment: StatusBarAlignment.RIGHT,
|
||||
text: selectedBoard ? `$(microchip) ${selectedBoard.name}` : '$(close) no board selected',
|
||||
className: 'arduino-selected-board'
|
||||
text: selectedBoard
|
||||
? `$(microchip) ${selectedBoard.name}`
|
||||
: '$(close) no board selected',
|
||||
className: 'arduino-selected-board',
|
||||
});
|
||||
if (selectedBoard) {
|
||||
this.statusBar.setElement('arduino-selected-port', {
|
||||
alignment: StatusBarAlignment.RIGHT,
|
||||
text: selectedPort ? `on ${Port.toString(selectedPort)}` : '[not connected]',
|
||||
className: 'arduino-selected-port'
|
||||
text: selectedPort
|
||||
? `on ${Port.toString(selectedPort)}`
|
||||
: '[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 (sketch && (!await this.sketchService.isTemp(sketch))) {
|
||||
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
|
||||
this.toDisposeOnStop.push(this.fileService.onDidFilesChange(async event => {
|
||||
if (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' });
|
||||
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' }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
@ -215,7 +273,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
this.outlineContribution,
|
||||
this.problemContribution,
|
||||
this.scmContribution,
|
||||
this.siwContribution] as Array<FrontendApplicationContribution>) {
|
||||
this.siwContribution,
|
||||
] as Array<FrontendApplicationContribution>) {
|
||||
if (viewContribution.initializeLayout) {
|
||||
viewContribution.initializeLayout(app);
|
||||
}
|
||||
@ -229,18 +288,27 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
}
|
||||
};
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(start);
|
||||
this.arduinoPreferences.onPreferenceChanged(event => {
|
||||
if (event.preferenceName === 'arduino.language.log' && event.newValue !== event.oldValue) {
|
||||
this.arduinoPreferences.onPreferenceChanged((event) => {
|
||||
if (
|
||||
event.preferenceName === 'arduino.language.log' &&
|
||||
event.newValue !== event.oldValue
|
||||
) {
|
||||
start(this.boardsServiceClientImpl.boardsConfig);
|
||||
}
|
||||
});
|
||||
this.arduinoPreferences.ready.then(() => {
|
||||
const webContents = remote.getCurrentWebContents();
|
||||
const zoomLevel = this.arduinoPreferences.get('arduino.window.zoomLevel');
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
@ -254,21 +322,33 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
|
||||
protected languageServerFqbn?: string;
|
||||
protected languageServerStartMutex = new Mutex();
|
||||
protected async startLanguageServer(fqbn: string, name: string | undefined): Promise<void> {
|
||||
protected async startLanguageServer(
|
||||
fqbn: string,
|
||||
name: string | undefined
|
||||
): Promise<void> {
|
||||
const release = await this.languageServerStartMutex.acquire();
|
||||
try {
|
||||
await this.hostedPluginSupport.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.`);
|
||||
console.info(
|
||||
`Could not start language server for ${fqbn}. The core is not installed for the board.`
|
||||
);
|
||||
if (this.languageServerFqbn) {
|
||||
try {
|
||||
await this.commandRegistry.executeCommand('arduino.languageserver.stop');
|
||||
console.info(`Stopped language server process for ${this.languageServerFqbn}.`);
|
||||
await this.commandRegistry.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);
|
||||
console.error(
|
||||
`Failed to start language server process for ${this.languageServerFqbn}`,
|
||||
e
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@ -282,21 +362,35 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
const log = this.arduinoPreferences.get('arduino.language.log');
|
||||
let currentSketchPath: string | undefined = undefined;
|
||||
if (log) {
|
||||
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||
const currentSketch =
|
||||
await this.sketchServiceClient.currentSketch();
|
||||
if (currentSketch) {
|
||||
currentSketchPath = await this.fileService.fsPath(new URI(currentSketch.uri));
|
||||
currentSketchPath = await this.fileService.fsPath(
|
||||
new URI(currentSketch.uri)
|
||||
);
|
||||
}
|
||||
}
|
||||
const { clangdUri, cliUri, lsUri } = await this.executableService.list();
|
||||
const [clangdPath, cliPath, lsPath, cliConfigPath] = await Promise.all([
|
||||
const { clangdUri, cliUri, lsUri } =
|
||||
await this.executableService.list();
|
||||
const [clangdPath, cliPath, lsPath, cliConfigPath] =
|
||||
await Promise.all([
|
||||
this.fileService.fsPath(new URI(clangdUri)),
|
||||
this.fileService.fsPath(new URI(cliUri)),
|
||||
this.fileService.fsPath(new URI(lsUri)),
|
||||
this.fileService.fsPath(new URI(await this.configService.getCliConfigFileUri()))
|
||||
this.fileService.fsPath(
|
||||
new URI(await this.configService.getCliConfigFileUri())
|
||||
),
|
||||
]);
|
||||
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', {
|
||||
new Promise<undefined>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error(`Timeout after ${20_000} ms.`)),
|
||||
20_000
|
||||
)
|
||||
),
|
||||
this.commandRegistry.executeCommand<string>(
|
||||
'arduino.languageserver.start',
|
||||
{
|
||||
lsPath,
|
||||
cliPath,
|
||||
clangdPath,
|
||||
@ -304,9 +398,10 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
cliConfigPath,
|
||||
board: {
|
||||
fqbn,
|
||||
name: name ? `"${name}"` : undefined
|
||||
name: name ? `"${name}"` : undefined,
|
||||
},
|
||||
}
|
||||
})
|
||||
),
|
||||
]);
|
||||
} catch (e) {
|
||||
console.log(`Failed to start language server for ${fqbn}`, e);
|
||||
@ -319,29 +414,33 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: BoardsToolBarItem.TOOLBAR_ID,
|
||||
render: () => <BoardsToolBarItem
|
||||
key='boardsToolbarItem'
|
||||
render: () => (
|
||||
<BoardsToolBarItem
|
||||
key="boardsToolbarItem"
|
||||
commands={this.commandRegistry}
|
||||
boardsServiceClient={this.boardsServiceClientImpl} />,
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
priority: 7
|
||||
boardsServiceClient={this.boardsServiceClientImpl}
|
||||
/>
|
||||
),
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
priority: 7,
|
||||
});
|
||||
registry.registerItem({
|
||||
id: 'toggle-serial-monitor',
|
||||
command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR,
|
||||
tooltip: 'Serial Monitor'
|
||||
tooltip: 'Serial Monitor',
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, {
|
||||
execute: () => this.editorMode.toggleCompileForDebug(),
|
||||
isToggled: () => this.editorMode.compileForDebug
|
||||
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) => {
|
||||
@ -349,7 +448,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
if (boardsConfig) {
|
||||
this.boardsServiceClientImpl.boardsConfig = boardsConfig;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -358,10 +457,14 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
const index = menuPath.length - 1;
|
||||
const menuId = menuPath[index];
|
||||
return menuId;
|
||||
}
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(MonacoMenus.SELECTION));
|
||||
};
|
||||
registry
|
||||
.getMenu(MAIN_MENU_BAR)
|
||||
.removeNode(menuId(MonacoMenus.SELECTION));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(EditorMainMenu.GO));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL));
|
||||
registry
|
||||
.getMenu(MAIN_MENU_BAR)
|
||||
.removeNode(menuId(TerminalMenus.TERMINAL));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW));
|
||||
|
||||
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
|
||||
@ -369,7 +472,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
|
||||
label: 'Optimize for Debugging',
|
||||
order: '4'
|
||||
order: '4',
|
||||
});
|
||||
}
|
||||
|
||||
@ -383,9 +486,18 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
await this.ensureOpened(mainFileUri, true);
|
||||
if (mainFileUri.endsWith('.pde')) {
|
||||
const message = `The '${sketch.name}' still uses the old \`.pde\` format. Do you want to switch to the new \`.ino\` extension?`;
|
||||
this.messageService.info(message, 'Later', 'Yes').then(async answer => {
|
||||
this.messageService
|
||||
.info(message, 'Later', 'Yes')
|
||||
.then(async (answer) => {
|
||||
if (answer === 'Yes') {
|
||||
this.commandRegistry.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { execOnlyIfTemp: false, openAfterMove: true, wipeOriginal: false });
|
||||
this.commandRegistry.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true,
|
||||
wipeOriginal: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -396,8 +508,14 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
}
|
||||
}
|
||||
|
||||
protected async ensureOpened(uri: string, forceOpen: boolean = false, options?: EditorOpenerOptions | undefined): Promise<any> {
|
||||
const widget = this.editorManager.all.find(widget => widget.editor.uri.toString() === uri);
|
||||
protected async ensureOpened(
|
||||
uri: string,
|
||||
forceOpen = false,
|
||||
options?: EditorOpenerOptions | undefined
|
||||
): Promise<any> {
|
||||
const widget = this.editorManager.all.find(
|
||||
(widget) => widget.editor.uri.toString() === uri
|
||||
);
|
||||
if (!widget || forceOpen) {
|
||||
return this.editorManager.open(new URI(uri), options);
|
||||
}
|
||||
@ -409,73 +527,78 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
id: 'arduino.branding.primary',
|
||||
defaults: {
|
||||
dark: 'statusBar.background',
|
||||
light: 'statusBar.background'
|
||||
light: 'statusBar.background',
|
||||
},
|
||||
description: 'The primary branding color, such as dialog titles, library, and board manager list labels.'
|
||||
description:
|
||||
'The primary branding color, such as dialog titles, library, and board manager list labels.',
|
||||
},
|
||||
{
|
||||
id: 'arduino.branding.secondary',
|
||||
defaults: {
|
||||
dark: 'statusBar.background',
|
||||
light: 'statusBar.background'
|
||||
light: 'statusBar.background',
|
||||
},
|
||||
description: 'Secondary branding color for list selections, dropdowns, and widget borders.'
|
||||
description:
|
||||
'Secondary branding color for list selections, dropdowns, and widget borders.',
|
||||
},
|
||||
{
|
||||
id: 'arduino.foreground',
|
||||
defaults: {
|
||||
dark: 'editorWidget.background',
|
||||
light: 'editorWidget.background',
|
||||
hc: 'editorWidget.background'
|
||||
hc: 'editorWidget.background',
|
||||
},
|
||||
description: 'Color of the Arduino IDE foreground which is used for dialogs, such as the Select Board dialog.'
|
||||
description:
|
||||
'Color of the Arduino IDE foreground which is used for dialogs, such as the Select Board dialog.',
|
||||
},
|
||||
{
|
||||
id: 'arduino.toolbar.background',
|
||||
defaults: {
|
||||
dark: 'button.background',
|
||||
light: 'button.background',
|
||||
hc: 'activityBar.inactiveForeground'
|
||||
hc: 'activityBar.inactiveForeground',
|
||||
},
|
||||
description: 'Background color of the toolbar items. Such as Upload, Verify, etc.'
|
||||
description:
|
||||
'Background color of the toolbar items. Such as Upload, Verify, etc.',
|
||||
},
|
||||
{
|
||||
id: 'arduino.toolbar.hoverBackground',
|
||||
defaults: {
|
||||
dark: 'button.hoverBackground',
|
||||
light: 'button.foreground',
|
||||
hc: 'textLink.foreground'
|
||||
hc: 'textLink.foreground',
|
||||
},
|
||||
description: 'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.'
|
||||
description:
|
||||
'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.',
|
||||
},
|
||||
{
|
||||
id: 'arduino.toolbar.toggleBackground',
|
||||
defaults: {
|
||||
dark: 'editor.selectionBackground',
|
||||
light: 'editor.selectionBackground',
|
||||
hc: 'textPreformat.foreground'
|
||||
hc: 'textPreformat.foreground',
|
||||
},
|
||||
description: 'Toggle color of the toolbar items when they are currently toggled (the command is in progress)'
|
||||
description:
|
||||
'Toggle color of the toolbar items when they are currently toggled (the command is in progress)',
|
||||
},
|
||||
{
|
||||
id: 'arduino.output.foreground',
|
||||
defaults: {
|
||||
dark: 'editor.foreground',
|
||||
light: 'editor.foreground',
|
||||
hc: 'editor.foreground'
|
||||
hc: 'editor.foreground',
|
||||
},
|
||||
description: 'Color of the text in the Output view.'
|
||||
description: 'Color of the text in the Output view.',
|
||||
},
|
||||
{
|
||||
id: 'arduino.output.background',
|
||||
defaults: {
|
||||
dark: 'editor.background',
|
||||
light: 'editor.background',
|
||||
hc: 'editor.background'
|
||||
hc: 'editor.background',
|
||||
},
|
||||
description: 'Background color of the Output view.'
|
||||
description: 'Background color of the Output view.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,14 +3,29 @@ import { ContainerModule } from 'inversify';
|
||||
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
|
||||
import { CommandContribution } from '@theia/core/lib/common/command';
|
||||
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
import { TabBarToolbarContribution, TabBarToolbarFactory } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import {
|
||||
TabBarToolbarContribution,
|
||||
TabBarToolbarFactory,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
|
||||
import { FrontendApplicationContribution, FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application'
|
||||
import {
|
||||
FrontendApplicationContribution,
|
||||
FrontendApplication as TheiaFrontendApplication,
|
||||
} from '@theia/core/lib/browser/frontend-application';
|
||||
import { LibraryListWidget } from './library/library-list-widget';
|
||||
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
|
||||
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
|
||||
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
|
||||
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
|
||||
import {
|
||||
LibraryService,
|
||||
LibraryServicePath,
|
||||
} from '../common/protocol/library-service';
|
||||
import {
|
||||
BoardsService,
|
||||
BoardsServicePath,
|
||||
} from '../common/protocol/boards-service';
|
||||
import {
|
||||
SketchesService,
|
||||
SketchesServicePath,
|
||||
} from '../common/protocol/sketches-service';
|
||||
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
|
||||
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
|
||||
import { BoardsListWidget } from './boards/boards-list-widget';
|
||||
@ -39,12 +54,15 @@ import {
|
||||
TabBarRendererFactory,
|
||||
ContextMenuRenderer,
|
||||
createTreeContainer,
|
||||
TreeWidget
|
||||
TreeWidget,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { MenuContribution } from '@theia/core/lib/common/menu';
|
||||
import { ApplicationShell } from './theia/core/application-shell';
|
||||
import { FrontendApplication } from './theia/core/frontend-application';
|
||||
import { BoardsConfigDialog, BoardsConfigDialogProps } from './boards/boards-config-dialog';
|
||||
import {
|
||||
BoardsConfigDialog,
|
||||
BoardsConfigDialogProps,
|
||||
} from './boards/boards-config-dialog';
|
||||
import { BoardsConfigDialogWidget } from './boards/boards-config-dialog-widget';
|
||||
import { ScmContribution as TheiaScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { ScmContribution } from './theia/scm/scm-contribution';
|
||||
@ -52,8 +70,15 @@ import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontend
|
||||
import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution';
|
||||
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
|
||||
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
|
||||
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
|
||||
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
|
||||
import {
|
||||
MonitorServicePath,
|
||||
MonitorService,
|
||||
MonitorServiceClient,
|
||||
} from '../common/protocol/monitor-service';
|
||||
import {
|
||||
ConfigService,
|
||||
ConfigServicePath,
|
||||
} from '../common/protocol/config-service';
|
||||
import { MonitorWidget } from './monitor/monitor-widget';
|
||||
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||
import { MonitorConnection } from './monitor/monitor-connection';
|
||||
@ -68,23 +93,35 @@ import { EditorMode } from './editor-mode';
|
||||
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||
import { ArduinoDaemonPath, ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||
import {
|
||||
ArduinoDaemonPath,
|
||||
ArduinoDaemon,
|
||||
} from '../common/protocol/arduino-daemon';
|
||||
import { EditorCommandContribution as TheiaEditorCommandContribution } from '@theia/editor/lib/browser';
|
||||
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from './theia/core/connection-status-service';
|
||||
import {
|
||||
FrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution,
|
||||
} from './theia/core/connection-status-service';
|
||||
import {
|
||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater';
|
||||
import { BoardsDataStore } from './boards/boards-data-store';
|
||||
import { ILogger } from '@theia/core';
|
||||
import { FileSystemExt, FileSystemExtPath } from '../common/protocol/filesystem-ext';
|
||||
import {
|
||||
FileSystemExt,
|
||||
FileSystemExtPath,
|
||||
} from '../common/protocol/filesystem-ext';
|
||||
import {
|
||||
WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution,
|
||||
FileMenuContribution as TheiaFileMenuContribution,
|
||||
WorkspaceCommandContribution as TheiaWorkspaceCommandContribution
|
||||
WorkspaceCommandContribution as TheiaWorkspaceCommandContribution,
|
||||
} from '@theia/workspace/lib/browser';
|
||||
import { WorkspaceFrontendContribution, ArduinoFileMenuContribution } from './theia/workspace/workspace-frontend-contribution';
|
||||
import {
|
||||
WorkspaceFrontendContribution,
|
||||
ArduinoFileMenuContribution,
|
||||
} from './theia/workspace/workspace-frontend-contribution';
|
||||
import { Contribution } from './contributions/contribution';
|
||||
import { NewSketch } from './contributions/new-sketch';
|
||||
import { OpenSketch } from './contributions/open-sketch';
|
||||
@ -111,19 +148,31 @@ import { EditorWidgetFactory } from './theia/editor/editor-widget-factory';
|
||||
import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { OutputWidget } from './theia/output/output-widget';
|
||||
import { BurnBootloader } from './contributions/burn-bootloader';
|
||||
import { ExamplesServicePath, ExamplesService } from '../common/protocol/examples-service';
|
||||
import {
|
||||
ExamplesServicePath,
|
||||
ExamplesService,
|
||||
} from '../common/protocol/examples-service';
|
||||
import { BuiltInExamples, LibraryExamples } from './contributions/examples';
|
||||
import { IncludeLibrary } from './contributions/include-library';
|
||||
import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||
import { OutputChannelManager } from './theia/output/output-channel';
|
||||
import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, OutputChannelRegistryMainImpl } from './theia/plugin-ext/output-channel-registry-main';
|
||||
import {
|
||||
OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl,
|
||||
OutputChannelRegistryMainImpl,
|
||||
} from './theia/plugin-ext/output-channel-registry-main';
|
||||
import { ExecutableService, ExecutableServicePath } from '../common/protocol';
|
||||
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
|
||||
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
|
||||
import { ResponseServiceImpl } from './response-service-impl';
|
||||
import { ResponseServicePath, ResponseService } from '../common/protocol/response-service';
|
||||
import {
|
||||
ResponseServicePath,
|
||||
ResponseService,
|
||||
} from '../common/protocol/response-service';
|
||||
import { NotificationCenter } from './notification-center';
|
||||
import { NotificationServicePath, NotificationServiceServer } from '../common/protocol';
|
||||
import {
|
||||
NotificationServicePath,
|
||||
NotificationServiceServer,
|
||||
} from '../common/protocol';
|
||||
import { About } from './contributions/about';
|
||||
import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service';
|
||||
import { TabBarRenderer } from './theia/core/tab-bars';
|
||||
@ -139,8 +188,13 @@ 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';
|
||||
import { bindArduinoPreferences } from './arduino-preferences';
|
||||
import {
|
||||
SettingsService,
|
||||
SettingsDialog,
|
||||
SettingsWidget,
|
||||
SettingsDialogProps,
|
||||
} from './settings';
|
||||
import { AddFile } from './contributions/add-file';
|
||||
import { ArchiveSketch } from './contributions/archive-sketch';
|
||||
import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
@ -170,7 +224,7 @@ MonacoThemingService.register({
|
||||
id: 'arduino-theme',
|
||||
label: 'Light (Arduino)',
|
||||
uiTheme: 'vs',
|
||||
json: require('../../src/browser/data/arduino.color-theme.json')
|
||||
json: require('../../src/browser/data/arduino.color-theme.json'),
|
||||
});
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
@ -182,7 +236,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(CommandContribution).toService(ArduinoFrontendContribution);
|
||||
bind(MenuContribution).toService(ArduinoFrontendContribution);
|
||||
bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution);
|
||||
bind(FrontendApplicationContribution).toService(ArduinoFrontendContribution);
|
||||
bind(FrontendApplicationContribution).toService(
|
||||
ArduinoFrontendContribution
|
||||
);
|
||||
bind(ColorContribution).toService(ArduinoFrontendContribution);
|
||||
|
||||
bind(ArduinoToolbarContribution).toSelf().inSingletonScope();
|
||||
@ -192,40 +248,75 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ListItemRenderer).toSelf().inSingletonScope();
|
||||
|
||||
// Library service
|
||||
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
|
||||
bind(LibraryService)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
LibraryServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// Library list widget
|
||||
bind(LibraryListWidget).toSelf();
|
||||
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
|
||||
bind(WidgetFactory).toDynamicValue(context => ({
|
||||
bind(WidgetFactory).toDynamicValue((context) => ({
|
||||
id: LibraryListWidget.WIDGET_ID,
|
||||
createWidget: () => context.container.get(LibraryListWidget)
|
||||
createWidget: () => context.container.get(LibraryListWidget),
|
||||
}));
|
||||
bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution);
|
||||
bind(FrontendApplicationContribution).toService(
|
||||
LibraryListWidgetFrontendContribution
|
||||
);
|
||||
|
||||
// Sketch list service
|
||||
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
|
||||
bind(SketchesService)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
SketchesServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
bind(SketchesServiceClientImpl).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(SketchesServiceClientImpl);
|
||||
|
||||
// Config service
|
||||
bind(ConfigService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ConfigServicePath)).inSingletonScope();
|
||||
bind(ConfigService)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
ConfigServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// Boards service
|
||||
bind(BoardsService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath)).inSingletonScope();
|
||||
bind(BoardsService)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
BoardsServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
// Boards service client to receive and delegate notifications from the backend.
|
||||
bind(BoardsServiceProvider).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsServiceProvider);
|
||||
|
||||
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
|
||||
bind(FrontendApplicationContribution).to(BoardsDataMenuUpdater).inSingletonScope();
|
||||
bind(FrontendApplicationContribution)
|
||||
.to(BoardsDataMenuUpdater)
|
||||
.inSingletonScope();
|
||||
bind(BoardsDataStore).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsDataStore);
|
||||
// Logger for the Arduino daemon
|
||||
bind(ILogger).toDynamicValue(ctx => {
|
||||
bind(ILogger)
|
||||
.toDynamicValue((ctx) => {
|
||||
const parentLogger = ctx.container.get<ILogger>(ILogger);
|
||||
return parentLogger.child('store');
|
||||
}).inSingletonScope().whenTargetNamed('store');
|
||||
})
|
||||
.inSingletonScope()
|
||||
.whenTargetNamed('store');
|
||||
|
||||
// Boards auto-installer
|
||||
bind(BoardsAutoInstaller).toSelf().inSingletonScope();
|
||||
@ -234,21 +325,30 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// Boards list widget
|
||||
bind(BoardsListWidget).toSelf();
|
||||
bindViewContribution(bind, BoardsListWidgetFrontendContribution);
|
||||
bind(WidgetFactory).toDynamicValue(context => ({
|
||||
bind(WidgetFactory).toDynamicValue((context) => ({
|
||||
id: BoardsListWidget.WIDGET_ID,
|
||||
createWidget: () => context.container.get(BoardsListWidget)
|
||||
createWidget: () => context.container.get(BoardsListWidget),
|
||||
}));
|
||||
bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution);
|
||||
bind(FrontendApplicationContribution).toService(
|
||||
BoardsListWidgetFrontendContribution
|
||||
);
|
||||
|
||||
// Board select dialog
|
||||
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
|
||||
bind(BoardsConfigDialog).toSelf().inSingletonScope();
|
||||
bind(BoardsConfigDialogProps).toConstantValue({
|
||||
title: 'Select Board'
|
||||
})
|
||||
title: 'Select Board',
|
||||
});
|
||||
|
||||
// Core service
|
||||
bind(CoreService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath)).inSingletonScope();
|
||||
bind(CoreService)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
CoreServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// Serial monitor
|
||||
bind(MonitorModel).toSelf().inSingletonScope();
|
||||
@ -256,64 +356,103 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(MonitorWidget).toSelf();
|
||||
bindViewContribution(bind, MonitorViewContribution);
|
||||
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
|
||||
bind(WidgetFactory).toDynamicValue(context => ({
|
||||
bind(WidgetFactory).toDynamicValue((context) => ({
|
||||
id: MonitorWidget.ID,
|
||||
createWidget: () => context.container.get(MonitorWidget)
|
||||
createWidget: () => context.container.get(MonitorWidget),
|
||||
}));
|
||||
// Frontend binding for the serial monitor service
|
||||
bind(MonitorService).toDynamicValue(context => {
|
||||
const connection = context.container.get(WebSocketConnectionProvider);
|
||||
bind(MonitorService)
|
||||
.toDynamicValue((context) => {
|
||||
const connection = context.container.get(
|
||||
WebSocketConnectionProvider
|
||||
);
|
||||
const client = context.container.get(MonitorServiceClientImpl);
|
||||
return connection.createProxy(MonitorServicePath, client);
|
||||
}).inSingletonScope();
|
||||
})
|
||||
.inSingletonScope();
|
||||
bind(MonitorConnection).toSelf().inSingletonScope();
|
||||
// Serial monitor service client to receive and delegate notifications from the backend.
|
||||
bind(MonitorServiceClientImpl).toSelf().inSingletonScope();
|
||||
bind(MonitorServiceClient).toDynamicValue(context => {
|
||||
bind(MonitorServiceClient)
|
||||
.toDynamicValue((context) => {
|
||||
const client = context.container.get(MonitorServiceClientImpl);
|
||||
WebSocketConnectionProvider.createProxy(context.container, MonitorServicePath, client);
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
MonitorServicePath,
|
||||
client
|
||||
);
|
||||
return client;
|
||||
}).inSingletonScope();
|
||||
})
|
||||
.inSingletonScope();
|
||||
|
||||
bind(WorkspaceService).toSelf().inSingletonScope();
|
||||
rebind(TheiaWorkspaceService).toService(WorkspaceService);
|
||||
bind(WorkspaceVariableContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaWorkspaceVariableContribution).toService(WorkspaceVariableContribution);
|
||||
rebind(TheiaWorkspaceVariableContribution).toService(
|
||||
WorkspaceVariableContribution
|
||||
);
|
||||
|
||||
// Customizing default Theia layout based on the editor mode: `pro-mode` or `classic`.
|
||||
bind(EditorMode).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(EditorMode);
|
||||
|
||||
// Layout and shell customizations.
|
||||
rebind(TheiaOutlineViewContribution).to(OutlineViewContribution).inSingletonScope();
|
||||
rebind(TheiaOutlineViewContribution)
|
||||
.to(OutlineViewContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaProblemContribution).to(ProblemContribution).inSingletonScope();
|
||||
rebind(TheiaFileNavigatorContribution).to(FileNavigatorContribution).inSingletonScope();
|
||||
rebind(TheiaKeymapsFrontendContribution).to(KeymapsFrontendContribution).inSingletonScope();
|
||||
rebind(TheiaFileNavigatorContribution)
|
||||
.to(FileNavigatorContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaKeymapsFrontendContribution)
|
||||
.to(KeymapsFrontendContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaEditorContribution).to(EditorContribution).inSingletonScope();
|
||||
rebind(TheiaMonacoStatusBarContribution).to(MonacoStatusBarContribution).inSingletonScope();
|
||||
rebind(TheiaMonacoStatusBarContribution)
|
||||
.to(MonacoStatusBarContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaApplicationShell).to(ApplicationShell).inSingletonScope();
|
||||
rebind(TheiaScmContribution).to(ScmContribution).inSingletonScope();
|
||||
rebind(TheiaSearchInWorkspaceFrontendContribution).to(SearchInWorkspaceFrontendContribution).inSingletonScope();
|
||||
rebind(TheiaSearchInWorkspaceFrontendContribution)
|
||||
.to(SearchInWorkspaceFrontendContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaFrontendApplication).to(FrontendApplication).inSingletonScope();
|
||||
rebind(TheiaWorkspaceFrontendContribution).to(WorkspaceFrontendContribution).inSingletonScope();
|
||||
rebind(TheiaFileMenuContribution).to(ArduinoFileMenuContribution).inSingletonScope();
|
||||
rebind(TheiaCommonFrontendContribution).to(CommonFrontendContribution).inSingletonScope();
|
||||
rebind(TheiaPreferencesContribution).to(PreferencesContribution).inSingletonScope();
|
||||
rebind(TheiaWorkspaceFrontendContribution)
|
||||
.to(WorkspaceFrontendContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaFileMenuContribution)
|
||||
.to(ArduinoFileMenuContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaCommonFrontendContribution)
|
||||
.to(CommonFrontendContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaPreferencesContribution)
|
||||
.to(PreferencesContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope();
|
||||
rebind(TheiaWorkspaceCommandContribution).to(WorkspaceCommandContribution).inSingletonScope();
|
||||
rebind(TheiaWorkspaceDeleteHandler).to(WorkspaceDeleteHandler).inSingletonScope();
|
||||
rebind(TheiaWorkspaceCommandContribution)
|
||||
.to(WorkspaceCommandContribution)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaWorkspaceDeleteHandler)
|
||||
.to(WorkspaceDeleteHandler)
|
||||
.inSingletonScope();
|
||||
rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope();
|
||||
rebind(TabBarToolbarFactory).toFactory(({ container: parentContainer }) => () => {
|
||||
rebind(TabBarToolbarFactory).toFactory(
|
||||
({ container: parentContainer }) =>
|
||||
() => {
|
||||
const container = parentContainer.createChild();
|
||||
container.bind(TabBarToolbar).toSelf().inSingletonScope();
|
||||
return container.get(TabBarToolbar);
|
||||
});
|
||||
}
|
||||
);
|
||||
bind(OutputWidget).toSelf().inSingletonScope();
|
||||
rebind(TheiaOutputWidget).toService(OutputWidget);
|
||||
bind(OutputChannelManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaOutputChannelManager).toService(OutputChannelManager);
|
||||
bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope();
|
||||
rebind(TheiaOutputChannelRegistryMainImpl).toService(OutputChannelRegistryMainImpl);
|
||||
rebind(TheiaOutputChannelRegistryMainImpl).toService(
|
||||
OutputChannelRegistryMainImpl
|
||||
);
|
||||
bind(MonacoTextModelService).toSelf().inSingletonScope();
|
||||
rebind(TheiaMonacoTextModelService).toService(MonacoTextModelService);
|
||||
bind(MonacoEditorProvider).toSelf().inSingletonScope();
|
||||
@ -321,18 +460,26 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
|
||||
bind(SearchInWorkspaceWidget).toSelf();
|
||||
rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget);
|
||||
rebind(TheiaSearchInWorkspaceResultTreeWidget).toDynamicValue(({ container }) => {
|
||||
rebind(TheiaSearchInWorkspaceResultTreeWidget).toDynamicValue(
|
||||
({ container }) => {
|
||||
const childContainer = createTreeContainer(container);
|
||||
childContainer.bind(SearchInWorkspaceResultTreeWidget).toSelf()
|
||||
childContainer.rebind(TreeWidget).toService(SearchInWorkspaceResultTreeWidget);
|
||||
childContainer.bind(SearchInWorkspaceResultTreeWidget).toSelf();
|
||||
childContainer
|
||||
.rebind(TreeWidget)
|
||||
.toService(SearchInWorkspaceResultTreeWidget);
|
||||
return childContainer.get(SearchInWorkspaceResultTreeWidget);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Show a disconnected status bar, when the daemon is not available
|
||||
bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaApplicationConnectionStatusContribution).toService(ApplicationConnectionStatusContribution);
|
||||
rebind(TheiaApplicationConnectionStatusContribution).toService(
|
||||
ApplicationConnectionStatusContribution
|
||||
);
|
||||
bind(FrontendConnectionStatusService).toSelf().inSingletonScope();
|
||||
rebind(TheiaFrontendConnectionStatusService).toService(FrontendConnectionStatusService);
|
||||
rebind(TheiaFrontendConnectionStatusService).toService(
|
||||
FrontendConnectionStatusService
|
||||
);
|
||||
|
||||
// Decorator customizations
|
||||
bind(TabBarDecoratorService).toSelf().inSingletonScope();
|
||||
@ -350,16 +497,44 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(OutputToolbarContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaOutputToolbarContribution).toService(OutputToolbarContribution);
|
||||
|
||||
bind(ArduinoDaemon).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath)).inSingletonScope();
|
||||
bind(ArduinoDaemon)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
ArduinoDaemonPath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// File-system extension
|
||||
bind(FileSystemExt).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, FileSystemExtPath)).inSingletonScope();
|
||||
bind(FileSystemExt)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
FileSystemExtPath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// Examples service@
|
||||
bind(ExamplesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ExamplesServicePath)).inSingletonScope();
|
||||
bind(ExamplesService)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
ExamplesServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// Executable URIs known by the backend
|
||||
bind(ExecutableService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ExecutableServicePath)).inSingletonScope();
|
||||
bind(ExecutableService)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
ExecutableServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
Contribution.configure(bind, NewSketch);
|
||||
Contribution.configure(bind, OpenSketch);
|
||||
@ -387,22 +562,44 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, ArchiveSketch);
|
||||
Contribution.configure(bind, AddZipLibrary);
|
||||
|
||||
bind(ResponseServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, responseService) => {
|
||||
WebSocketConnectionProvider.createProxy(container, ResponseServicePath, responseService);
|
||||
bind(ResponseServiceImpl)
|
||||
.toSelf()
|
||||
.inSingletonScope()
|
||||
.onActivation(({ container }, responseService) => {
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
container,
|
||||
ResponseServicePath,
|
||||
responseService
|
||||
);
|
||||
return responseService;
|
||||
});
|
||||
bind(ResponseService).toService(ResponseServiceImpl);
|
||||
|
||||
bind(NotificationCenter).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(NotificationCenter);
|
||||
bind(NotificationServiceServer).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, NotificationServicePath)).inSingletonScope();
|
||||
bind(NotificationServiceServer)
|
||||
.toDynamicValue((context) =>
|
||||
WebSocketConnectionProvider.createProxy(
|
||||
context.container,
|
||||
NotificationServicePath
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// Enable the dirty indicator on uncloseable widgets.
|
||||
rebind(TabBarRendererFactory).toFactory(context => () => {
|
||||
const contextMenuRenderer = context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
|
||||
const decoratorService = context.container.get<TabBarDecoratorService>(TabBarDecoratorService);
|
||||
const iconThemeService = context.container.get<IconThemeService>(IconThemeService);
|
||||
return new TabBarRenderer(contextMenuRenderer, decoratorService, iconThemeService);
|
||||
rebind(TabBarRendererFactory).toFactory((context) => () => {
|
||||
const contextMenuRenderer =
|
||||
context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
|
||||
const decoratorService = context.container.get<TabBarDecoratorService>(
|
||||
TabBarDecoratorService
|
||||
);
|
||||
const iconThemeService =
|
||||
context.container.get<IconThemeService>(IconThemeService);
|
||||
return new TabBarRenderer(
|
||||
contextMenuRenderer,
|
||||
decoratorService,
|
||||
iconThemeService
|
||||
);
|
||||
});
|
||||
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/8722
|
||||
@ -419,15 +616,23 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
|
||||
// To remove the `Run` menu item from the application menu.
|
||||
bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaDebugFrontendApplicationContribution).toService(DebugFrontendApplicationContribution);
|
||||
rebind(TheiaDebugFrontendApplicationContribution).toService(
|
||||
DebugFrontendApplicationContribution
|
||||
);
|
||||
// To be able to use a `launch.json` from outside of the workspace.
|
||||
bind(DebugConfigurationManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaDebugConfigurationManager).toService(DebugConfigurationManager);
|
||||
|
||||
// Patch for the debug hover: https://github.com/eclipse-theia/theia/pull/9256/
|
||||
rebind(DebugEditorModelFactory).toDynamicValue(({ container }) => <DebugEditorModelFactory>(editor =>
|
||||
DebugEditorModel.createModel(container, editor)
|
||||
)).inSingletonScope();
|
||||
rebind(DebugEditorModelFactory)
|
||||
.toDynamicValue(
|
||||
({ container }) =>
|
||||
<DebugEditorModelFactory>(
|
||||
((editor) =>
|
||||
DebugEditorModel.createModel(container, editor))
|
||||
)
|
||||
)
|
||||
.inSingletonScope();
|
||||
|
||||
// Preferences
|
||||
bindArduinoPreferences(bind);
|
||||
@ -438,7 +643,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(SettingsWidget).toSelf().inSingletonScope();
|
||||
bind(SettingsDialog).toSelf().inSingletonScope();
|
||||
bind(SettingsDialogProps).toConstantValue({
|
||||
title: 'Preferences'
|
||||
title: 'Preferences',
|
||||
});
|
||||
|
||||
bind(StorageWrapper).toSelf().inSingletonScope();
|
||||
|
@ -1,50 +1,61 @@
|
||||
import { interfaces } from 'inversify';
|
||||
import { createPreferenceProxy, PreferenceProxy, PreferenceService, PreferenceContribution, PreferenceSchema } from '@theia/core/lib/browser/preferences';
|
||||
import {
|
||||
createPreferenceProxy,
|
||||
PreferenceProxy,
|
||||
PreferenceService,
|
||||
PreferenceContribution,
|
||||
PreferenceSchema,
|
||||
} from '@theia/core/lib/browser/preferences';
|
||||
import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol';
|
||||
|
||||
export const ArduinoConfigSchema: PreferenceSchema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
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
|
||||
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. False by default',
|
||||
'default': false
|
||||
type: 'boolean',
|
||||
description: 'True for verbose compile output. False by default',
|
||||
default: false,
|
||||
},
|
||||
'arduino.compile.warnings': {
|
||||
'enum': [...CompilerWarningLiterals],
|
||||
'description': "Tells gcc which warning level to use. It's 'None' by default",
|
||||
'default': 'None'
|
||||
enum: [...CompilerWarningLiterals],
|
||||
description:
|
||||
"Tells gcc which warning level to use. It's 'None' by default",
|
||||
default: 'None',
|
||||
},
|
||||
'arduino.upload.verbose': {
|
||||
'type': 'boolean',
|
||||
'description': 'True for verbose upload output. False by default.',
|
||||
'default': false
|
||||
type: 'boolean',
|
||||
description: 'True for verbose upload output. False by default.',
|
||||
default: false,
|
||||
},
|
||||
'arduino.upload.verify': {
|
||||
'type': 'boolean',
|
||||
'default': false
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
'arduino.window.autoScale': {
|
||||
'type': 'boolean',
|
||||
'description': 'True if the user interface automatically scales with the font size.',
|
||||
'default': true
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
type: 'boolean',
|
||||
description:
|
||||
'True to enable automatic update checks. The IDE will check for updates automatically and periodically.',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export interface ArduinoConfiguration {
|
||||
@ -61,14 +72,19 @@ export interface ArduinoConfiguration {
|
||||
export const ArduinoPreferences = Symbol('ArduinoPreferences');
|
||||
export type ArduinoPreferences = PreferenceProxy<ArduinoConfiguration>;
|
||||
|
||||
export function createArduinoPreferences(preferences: PreferenceService): ArduinoPreferences {
|
||||
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);
|
||||
bind(ArduinoPreferences).toDynamicValue((ctx) => {
|
||||
const preferences =
|
||||
ctx.container.get<PreferenceService>(PreferenceService);
|
||||
return createArduinoPreferences(preferences);
|
||||
});
|
||||
bind(PreferenceContribution).toConstantValue({ schema: ArduinoConfigSchema });
|
||||
bind(PreferenceContribution).toConstantValue({
|
||||
schema: ArduinoConfigSchema,
|
||||
});
|
||||
}
|
||||
|
@ -24,20 +24,24 @@ namespace ArduinoWorkspaceRootResolver {
|
||||
readonly isValid: (uri: string) => MaybePromise<boolean>;
|
||||
}
|
||||
export interface ResolveOptions {
|
||||
readonly hash?: string
|
||||
readonly hash?: string;
|
||||
readonly recentWorkspaces: string[];
|
||||
// Gathered from the default sketch folder. The default sketch folder is defined by the CLI.
|
||||
readonly recentSketches: string[];
|
||||
}
|
||||
}
|
||||
export class ArduinoWorkspaceRootResolver {
|
||||
constructor(protected options: ArduinoWorkspaceRootResolver.InitOptions) {}
|
||||
|
||||
constructor(protected options: ArduinoWorkspaceRootResolver.InitOptions) {
|
||||
}
|
||||
|
||||
async resolve(options: ArduinoWorkspaceRootResolver.ResolveOptions): Promise<{ uri: string } | undefined> {
|
||||
async resolve(
|
||||
options: ArduinoWorkspaceRootResolver.ResolveOptions
|
||||
): Promise<{ uri: string } | undefined> {
|
||||
const { hash, recentWorkspaces, recentSketches } = options;
|
||||
for (const uri of [this.hashToUri(hash), ...recentWorkspaces, ...recentSketches].filter(notEmpty)) {
|
||||
for (const uri of [
|
||||
this.hashToUri(hash),
|
||||
...recentWorkspaces,
|
||||
...recentSketches,
|
||||
].filter(notEmpty)) {
|
||||
const valid = await this.isValid(uri);
|
||||
if (valid) {
|
||||
return { uri };
|
||||
@ -56,13 +60,14 @@ export class ArduinoWorkspaceRootResolver {
|
||||
// - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L143 and
|
||||
// - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L423
|
||||
protected hashToUri(hash: string | undefined): string | undefined {
|
||||
if (hash
|
||||
&& hash.length > 1
|
||||
&& hash.startsWith('#')) {
|
||||
if (hash && hash.length > 1 && hash.startsWith('#')) {
|
||||
const path = hash.slice(1); // Trim the leading `#`.
|
||||
return new URI(toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0))).withScheme('file').toString();
|
||||
return new URI(
|
||||
toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0))
|
||||
)
|
||||
.withScheme('file')
|
||||
.toString();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { BoardsService, BoardsPackage, Board } from '../../common/protocol/boards-service';
|
||||
import {
|
||||
BoardsService,
|
||||
BoardsPackage,
|
||||
Board,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
@ -14,7 +18,6 @@ import { ResponseServiceImpl } from '../response-service-impl';
|
||||
*/
|
||||
@injectable()
|
||||
export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
@ -34,37 +37,67 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
protected notifications: Board[] = [];
|
||||
|
||||
onStart(): void {
|
||||
this.boardsServiceClient.onBoardsConfigChanged(this.ensureCoreExists.bind(this));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(
|
||||
this.ensureCoreExists.bind(this)
|
||||
);
|
||||
this.ensureCoreExists(this.boardsServiceClient.boardsConfig);
|
||||
}
|
||||
|
||||
protected ensureCoreExists(config: BoardsConfig.Config): void {
|
||||
const { selectedBoard } = config;
|
||||
if (selectedBoard && !this.notifications.find(board => Board.sameAs(board, selectedBoard))) {
|
||||
if (
|
||||
selectedBoard &&
|
||||
!this.notifications.find((board) =>
|
||||
Board.sameAs(board, selectedBoard)
|
||||
)
|
||||
) {
|
||||
this.notifications.push(selectedBoard);
|
||||
this.boardsService.search({}).then(packages => {
|
||||
|
||||
this.boardsService.search({}).then((packages) => {
|
||||
// filter packagesForBoard selecting matches from the cli (installed packages)
|
||||
// and matches based on the board name
|
||||
// NOTE: this ensures the Deprecated & new packages are all in the array
|
||||
// so that we can check if any of the valid packages is already installed
|
||||
const packagesForBoard = packages.filter(pkg => BoardsPackage.contains(selectedBoard, pkg) || pkg.boards.some(board => board.name === selectedBoard.name));
|
||||
const packagesForBoard = packages.filter(
|
||||
(pkg) =>
|
||||
BoardsPackage.contains(selectedBoard, pkg) ||
|
||||
pkg.boards.some(
|
||||
(board) => board.name === selectedBoard.name
|
||||
)
|
||||
);
|
||||
|
||||
// check if one of the packages for the board is already installed. if so, no hint
|
||||
if (packagesForBoard.some(({ installedVersion }) => !!installedVersion)) { return; }
|
||||
if (
|
||||
packagesForBoard.some(
|
||||
({ installedVersion }) => !!installedVersion
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// filter the installable (not installed) packages,
|
||||
// CLI returns the packages already sorted with the deprecated ones at the end of the list
|
||||
// in order to ensure the new ones are preferred
|
||||
const candidates = packagesForBoard
|
||||
.filter(({ installable, installedVersion }) => installable && !installedVersion);
|
||||
const candidates = packagesForBoard.filter(
|
||||
({ installable, installedVersion }) =>
|
||||
installable && !installedVersion
|
||||
);
|
||||
|
||||
const candidate = candidates[0];
|
||||
if (candidate) {
|
||||
const version = candidate.availableVersions[0] ? `[v ${candidate.availableVersions[0]}]` : '';
|
||||
const version = candidate.availableVersions[0]
|
||||
? `[v ${candidate.availableVersions[0]}]`
|
||||
: '';
|
||||
// tslint:disable-next-line:max-line-length
|
||||
this.messageService.info(`The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Install Manually', 'Yes').then(async answer => {
|
||||
const index = this.notifications.findIndex(board => Board.sameAs(board, selectedBoard));
|
||||
this.messageService
|
||||
.info(
|
||||
`The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`,
|
||||
'Install Manually',
|
||||
'Yes'
|
||||
)
|
||||
.then(async (answer) => {
|
||||
const index = this.notifications.findIndex(
|
||||
(board) => Board.sameAs(board, selectedBoard)
|
||||
);
|
||||
if (index !== -1) {
|
||||
this.notifications.splice(index, 1);
|
||||
}
|
||||
@ -74,17 +107,22 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||
item: candidate,
|
||||
messageService: this.messageService,
|
||||
responseService: this.responseService,
|
||||
version: candidate.availableVersions[0]
|
||||
version: candidate.availableVersions[0],
|
||||
});
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (answer) {
|
||||
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));
|
||||
this.boardsManagerFrontendContribution
|
||||
.openView({ reveal: true })
|
||||
.then((widget) =>
|
||||
widget.refresh(
|
||||
candidate.name.toLocaleLowerCase()
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import { NotificationCenter } from '../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class BoardsConfigDialogWidget extends ReactWidget {
|
||||
|
||||
@inject(BoardsService)
|
||||
protected readonly boardsService: BoardsService;
|
||||
|
||||
@ -20,7 +19,8 @@ export class BoardsConfigDialogWidget extends ReactWidget {
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
protected readonly onFilterTextDidChangeEmitter = new Emitter<string>();
|
||||
protected readonly onBoardConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||
protected readonly onBoardConfigChangedEmitter =
|
||||
new Emitter<BoardsConfig.Config>();
|
||||
readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event;
|
||||
|
||||
protected focusNode: HTMLElement | undefined;
|
||||
@ -30,7 +30,7 @@ export class BoardsConfigDialogWidget extends ReactWidget {
|
||||
this.id = 'select-board-dialog';
|
||||
this.toDispose.pushAll([
|
||||
this.onBoardConfigChangedEmitter,
|
||||
this.onFilterTextDidChangeEmitter
|
||||
this.onFilterTextDidChangeEmitter,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -40,21 +40,26 @@ export class BoardsConfigDialogWidget extends ReactWidget {
|
||||
|
||||
protected fireConfigChanged = (config: BoardsConfig.Config) => {
|
||||
this.onBoardConfigChangedEmitter.fire(config);
|
||||
}
|
||||
};
|
||||
|
||||
protected setFocusNode = (element: HTMLElement | undefined) => {
|
||||
this.focusNode = element;
|
||||
}
|
||||
};
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return <div className='selectBoardContainer'>
|
||||
return (
|
||||
<div className="selectBoardContainer">
|
||||
<BoardsConfig
|
||||
boardsServiceProvider={this.boardsServiceClient}
|
||||
notificationCenter={this.notificationCenter}
|
||||
onConfigChange={this.fireConfigChanged}
|
||||
onFocusNodeSet={this.setFocusNode}
|
||||
onFilteredTextDidChangeEvent={this.onFilterTextDidChangeEmitter.event} />
|
||||
</div>;
|
||||
onFilteredTextDidChangeEvent={
|
||||
this.onFilterTextDidChangeEmitter.event
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected onActivateRequest(msg: Message): void {
|
||||
@ -64,5 +69,4 @@ export class BoardsConfigDialogWidget extends ReactWidget {
|
||||
}
|
||||
(this.focusNode || this.node).focus();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { Message } from '@phosphor/messaging';
|
||||
import { AbstractDialog, DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
|
||||
import {
|
||||
AbstractDialog,
|
||||
DialogProps,
|
||||
Widget,
|
||||
DialogError,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { BoardsService } from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
|
||||
|
||||
@injectable()
|
||||
export class BoardsConfigDialogProps extends DialogProps {
|
||||
}
|
||||
export class BoardsConfigDialogProps extends DialogProps {}
|
||||
|
||||
@injectable()
|
||||
export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
|
||||
@inject(BoardsConfigDialogWidget)
|
||||
protected readonly widget: BoardsConfigDialogWidget;
|
||||
|
||||
@ -24,7 +27,10 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
|
||||
protected config: BoardsConfig.Config = {};
|
||||
|
||||
constructor(@inject(BoardsConfigDialogProps) protected readonly props: BoardsConfigDialogProps) {
|
||||
constructor(
|
||||
@inject(BoardsConfigDialogProps)
|
||||
protected readonly props: BoardsConfigDialogProps
|
||||
) {
|
||||
super(props);
|
||||
|
||||
this.contentNode.classList.add('select-board-dialog');
|
||||
@ -36,16 +42,20 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDispose.push(this.boardsServiceClient.onBoardsConfigChanged(config => {
|
||||
this.toDispose.push(
|
||||
this.boardsServiceClient.onBoardsConfigChanged((config) => {
|
||||
this.config = config;
|
||||
this.update();
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in an empty string if you want to reset the search term. Using `undefined` has no effect.
|
||||
*/
|
||||
async open(query: string | undefined = undefined): Promise<BoardsConfig.Config | undefined> {
|
||||
async open(
|
||||
query: string | undefined = undefined
|
||||
): Promise<BoardsConfig.Config | undefined> {
|
||||
if (typeof query === 'string') {
|
||||
this.widget.search(query);
|
||||
}
|
||||
@ -67,7 +77,7 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
|
||||
for (const paragraph of [
|
||||
'Select both a Board and a Port if you want to upload a sketch.',
|
||||
'If you only select a Board you will be able just to compile, but not to upload your sketch.'
|
||||
'If you only select a Board you will be able just to compile, but not to upload your sketch.',
|
||||
]) {
|
||||
const p = document.createElement('div');
|
||||
p.textContent = paragraph;
|
||||
@ -82,10 +92,12 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
Widget.detach(this.widget);
|
||||
}
|
||||
Widget.attach(this.widget, this.contentNode);
|
||||
this.toDisposeOnDetach.push(this.widget.onBoardConfigChanged(config => {
|
||||
this.toDisposeOnDetach.push(
|
||||
this.widget.onBoardConfigChanged((config) => {
|
||||
this.config = config;
|
||||
this.update();
|
||||
}));
|
||||
})
|
||||
);
|
||||
super.onAfterAttach(msg);
|
||||
this.update();
|
||||
}
|
||||
@ -119,5 +131,4 @@ export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||
get value(): BoardsConfig.Config {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,12 +3,16 @@ import { Event } from '@theia/core/lib/common/event';
|
||||
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Board, Port, AttachedBoardsChangeEvent, BoardWithPackage } from '../../common/protocol/boards-service';
|
||||
import {
|
||||
Board,
|
||||
Port,
|
||||
AttachedBoardsChangeEvent,
|
||||
BoardWithPackage,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
|
||||
export namespace BoardsConfig {
|
||||
|
||||
export interface Config {
|
||||
selectedBoard?: Board;
|
||||
selectedPort?: Port;
|
||||
@ -28,18 +32,16 @@ export namespace BoardsConfig {
|
||||
showAllPorts: boolean;
|
||||
query: string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class Item<T> extends React.Component<{
|
||||
item: T,
|
||||
label: string,
|
||||
selected: boolean,
|
||||
onClick: (item: T) => void,
|
||||
missing?: boolean,
|
||||
details?: string
|
||||
item: T;
|
||||
label: string;
|
||||
selected: boolean;
|
||||
onClick: (item: T) => void;
|
||||
missing?: boolean;
|
||||
details?: string;
|
||||
}> {
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { selected, label, missing, details } = this.props;
|
||||
const classNames = ['item'];
|
||||
@ -47,25 +49,36 @@ export abstract class Item<T> extends React.Component<{
|
||||
classNames.push('selected');
|
||||
}
|
||||
if (missing === true) {
|
||||
classNames.push('missing')
|
||||
classNames.push('missing');
|
||||
}
|
||||
return <div onClick={this.onClick} className={classNames.join(' ')} title={`${label}${!details ? '' : details}`}>
|
||||
<div className='label'>
|
||||
{label}
|
||||
return (
|
||||
<div
|
||||
onClick={this.onClick}
|
||||
className={classNames.join(' ')}
|
||||
title={`${label}${!details ? '' : details}`}
|
||||
>
|
||||
<div className="label">{label}</div>
|
||||
{!details ? '' : <div className="details">{details}</div>}
|
||||
{!selected ? (
|
||||
''
|
||||
) : (
|
||||
<div className="selected-icon">
|
||||
<i className="fa fa-check" />
|
||||
</div>
|
||||
{!details ? '' : <div className='details'>{details}</div>}
|
||||
{!selected ? '' : <div className='selected-icon'><i className='fa fa-check' /></div>}
|
||||
</div>;
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected onClick = () => {
|
||||
this.props.onClick(this.props.item);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConfig.State> {
|
||||
|
||||
export class BoardsConfig extends React.Component<
|
||||
BoardsConfig.Props,
|
||||
BoardsConfig.State
|
||||
> {
|
||||
protected toDispose = new DisposableCollection();
|
||||
|
||||
constructor(props: BoardsConfig.Props) {
|
||||
@ -77,24 +90,51 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
knownPorts: [],
|
||||
showAllPorts: false,
|
||||
query: '',
|
||||
...boardsConfig
|
||||
}
|
||||
...boardsConfig,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateBoards();
|
||||
this.updatePorts(this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty));
|
||||
this.updatePorts(
|
||||
this.props.boardsServiceProvider.availableBoards
|
||||
.map(({ port }) => port)
|
||||
.filter(notEmpty)
|
||||
);
|
||||
this.toDispose.pushAll([
|
||||
this.props.notificationCenter.onAttachedBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
|
||||
this.props.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
|
||||
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
|
||||
}),
|
||||
this.props.notificationCenter.onPlatformInstalled(() => this.updateBoards(this.state.query)),
|
||||
this.props.notificationCenter.onPlatformUninstalled(() => this.updateBoards(this.state.query)),
|
||||
this.props.notificationCenter.onIndexUpdated(() => this.updateBoards(this.state.query)),
|
||||
this.props.notificationCenter.onDaemonStarted(() => this.updateBoards(this.state.query)),
|
||||
this.props.notificationCenter.onDaemonStopped(() => this.setState({ searchResults: [] })),
|
||||
this.props.onFilteredTextDidChangeEvent(query => this.setState({ query }, () => this.updateBoards(this.state.query)))
|
||||
this.props.notificationCenter.onAttachedBoardsChanged((event) =>
|
||||
this.updatePorts(
|
||||
event.newState.ports,
|
||||
AttachedBoardsChangeEvent.diff(event).detached.ports
|
||||
)
|
||||
),
|
||||
this.props.boardsServiceProvider.onBoardsConfigChanged(
|
||||
({ selectedBoard, selectedPort }) => {
|
||||
this.setState({ selectedBoard, selectedPort }, () =>
|
||||
this.fireConfigChanged()
|
||||
);
|
||||
}
|
||||
),
|
||||
this.props.notificationCenter.onPlatformInstalled(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onPlatformUninstalled(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onIndexUpdated(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onDaemonStarted(() =>
|
||||
this.updateBoards(this.state.query)
|
||||
),
|
||||
this.props.notificationCenter.onDaemonStopped(() =>
|
||||
this.setState({ searchResults: [] })
|
||||
),
|
||||
this.props.onFilteredTextDidChangeEvent((query) =>
|
||||
this.setState({ query }, () =>
|
||||
this.updateBoards(this.state.query)
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -107,73 +147,96 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
this.props.onConfigChange({ selectedBoard, selectedPort });
|
||||
}
|
||||
|
||||
protected updateBoards = (eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = '') => {
|
||||
const query = typeof eventOrQuery === 'string'
|
||||
protected updateBoards = (
|
||||
eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = ''
|
||||
) => {
|
||||
const query =
|
||||
typeof eventOrQuery === 'string'
|
||||
? eventOrQuery
|
||||
: eventOrQuery.target.value.toLowerCase();
|
||||
this.setState({ query });
|
||||
this.queryBoards({ query }).then(searchResults => this.setState({ searchResults }));
|
||||
}
|
||||
this.queryBoards({ query }).then((searchResults) =>
|
||||
this.setState({ searchResults })
|
||||
);
|
||||
};
|
||||
|
||||
protected updatePorts = (ports: Port[] = [], removedPorts: Port[] = []) => {
|
||||
this.queryPorts(Promise.resolve(ports)).then(({ knownPorts }) => {
|
||||
let { selectedPort } = this.state;
|
||||
// If the currently selected port is not available anymore, unset the selected port.
|
||||
if (removedPorts.some(port => Port.equals(port, selectedPort))) {
|
||||
if (removedPorts.some((port) => Port.equals(port, selectedPort))) {
|
||||
selectedPort = undefined;
|
||||
}
|
||||
this.setState({ knownPorts, selectedPort }, () => this.fireConfigChanged());
|
||||
this.setState({ knownPorts, selectedPort }, () =>
|
||||
this.fireConfigChanged()
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
protected queryBoards = (options: { query?: string } = {}): Promise<Array<BoardWithPackage>> => {
|
||||
protected queryBoards = (
|
||||
options: { query?: string } = {}
|
||||
): Promise<Array<BoardWithPackage>> => {
|
||||
return this.props.boardsServiceProvider.searchBoards(options);
|
||||
}
|
||||
};
|
||||
|
||||
protected get availablePorts(): MaybePromise<Port[]> {
|
||||
return this.props.boardsServiceProvider.availableBoards.map(({ port }) => port).filter(notEmpty);
|
||||
return this.props.boardsServiceProvider.availableBoards
|
||||
.map(({ port }) => port)
|
||||
.filter(notEmpty);
|
||||
}
|
||||
|
||||
protected queryPorts = async (availablePorts: MaybePromise<Port[]> = this.availablePorts) => {
|
||||
protected queryPorts = async (
|
||||
availablePorts: MaybePromise<Port[]> = this.availablePorts
|
||||
) => {
|
||||
const ports = await availablePorts;
|
||||
return { knownPorts: ports.sort(Port.compare) };
|
||||
}
|
||||
};
|
||||
|
||||
protected toggleFilterPorts = () => {
|
||||
this.setState({ showAllPorts: !this.state.showAllPorts });
|
||||
}
|
||||
};
|
||||
|
||||
protected selectPort = (selectedPort: Port | undefined) => {
|
||||
this.setState({ selectedPort }, () => this.fireConfigChanged());
|
||||
}
|
||||
};
|
||||
|
||||
protected selectBoard = (selectedBoard: BoardWithPackage | undefined) => {
|
||||
this.setState({ selectedBoard }, () => this.fireConfigChanged());
|
||||
}
|
||||
};
|
||||
|
||||
protected focusNodeSet = (element: HTMLElement | null) => {
|
||||
this.props.onFocusNodeSet(element || undefined);
|
||||
}
|
||||
};
|
||||
|
||||
render(): React.ReactNode {
|
||||
return <div className='body'>
|
||||
return (
|
||||
<div className="body">
|
||||
{this.renderContainer('boards', this.renderBoards.bind(this))}
|
||||
{this.renderContainer('ports', this.renderPorts.bind(this), this.renderPortsFooter.bind(this))}
|
||||
</div>;
|
||||
{this.renderContainer(
|
||||
'ports',
|
||||
this.renderPorts.bind(this),
|
||||
this.renderPortsFooter.bind(this)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderContainer(title: string, contentRenderer: () => React.ReactNode, footerRenderer?: () => React.ReactNode): React.ReactNode {
|
||||
return <div className='container'>
|
||||
<div className='content'>
|
||||
<div className='title'>
|
||||
{title}
|
||||
</div>
|
||||
protected renderContainer(
|
||||
title: string,
|
||||
contentRenderer: () => React.ReactNode,
|
||||
footerRenderer?: () => React.ReactNode
|
||||
): React.ReactNode {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="content">
|
||||
<div className="title">{title}</div>
|
||||
{contentRenderer()}
|
||||
<div className='footer'>
|
||||
{(footerRenderer ? footerRenderer() : '')}
|
||||
<div className="footer">
|
||||
{footerRenderer ? footerRenderer() : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderBoards(): React.ReactNode {
|
||||
@ -181,28 +244,36 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
// Board names are not unique per core https://github.com/arduino/arduino-pro-ide/issues/262#issuecomment-661019560
|
||||
// It is tricky when the core is not yet installed, no FQBNs are available.
|
||||
const distinctBoards = new Map<string, Board.Detailed>();
|
||||
const toKey = ({ name, packageName, fqbn }: Board.Detailed) => !!fqbn ? `${name}-${packageName}-${fqbn}` : `${name}-${packageName}`;
|
||||
for (const board of Board.decorateBoards(selectedBoard, searchResults)) {
|
||||
const toKey = ({ name, packageName, fqbn }: Board.Detailed) =>
|
||||
!!fqbn
|
||||
? `${name}-${packageName}-${fqbn}`
|
||||
: `${name}-${packageName}`;
|
||||
for (const board of Board.decorateBoards(
|
||||
selectedBoard,
|
||||
searchResults
|
||||
)) {
|
||||
const key = toKey(board);
|
||||
if (!distinctBoards.has(key)) {
|
||||
distinctBoards.set(key, board);
|
||||
}
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className='search'>
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="search">
|
||||
<input
|
||||
type='search'
|
||||
type="search"
|
||||
value={query}
|
||||
className='theia-input'
|
||||
placeholder='SEARCH BOARD'
|
||||
className="theia-input"
|
||||
placeholder="SEARCH BOARD"
|
||||
onChange={this.updateBoards}
|
||||
ref={this.focusNodeSet}
|
||||
/>
|
||||
<i className='fa fa-search'></i>
|
||||
<i className="fa fa-search"></i>
|
||||
</div>
|
||||
<div className='boards list'>
|
||||
{Array.from(distinctBoards.values()).map(board => <Item<BoardWithPackage>
|
||||
<div className="boards list">
|
||||
{Array.from(distinctBoards.values()).map((board) => (
|
||||
<Item<BoardWithPackage>
|
||||
key={`${board.name}-${board.packageName}`}
|
||||
item={board}
|
||||
label={board.name}
|
||||
@ -210,69 +281,74 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
selected={board.selected}
|
||||
onClick={this.selectBoard}
|
||||
missing={board.missing}
|
||||
/>)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderPorts(): React.ReactNode {
|
||||
const filter = this.state.showAllPorts ? () => true : Port.isBoardPort;
|
||||
const ports = this.state.knownPorts.filter(filter);
|
||||
return !ports.length ?
|
||||
(
|
||||
<div className='loading noselect'>
|
||||
No ports discovered
|
||||
</div>
|
||||
) :
|
||||
(
|
||||
<div className='ports list'>
|
||||
{ports.map(port => <Item<Port>
|
||||
return !ports.length ? (
|
||||
<div className="loading noselect">No ports discovered</div>
|
||||
) : (
|
||||
<div className="ports list">
|
||||
{ports.map((port) => (
|
||||
<Item<Port>
|
||||
key={Port.toString(port)}
|
||||
item={port}
|
||||
label={Port.toString(port)}
|
||||
selected={Port.equals(this.state.selectedPort, port)}
|
||||
onClick={this.selectPort}
|
||||
/>)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderPortsFooter(): React.ReactNode {
|
||||
return <div className='noselect'>
|
||||
<label
|
||||
title='Shows all available ports when enabled'>
|
||||
return (
|
||||
<div className="noselect">
|
||||
<label title="Shows all available ports when enabled">
|
||||
<input
|
||||
type='checkbox'
|
||||
type="checkbox"
|
||||
defaultChecked={this.state.showAllPorts}
|
||||
onChange={this.toggleFilterPorts}
|
||||
/>
|
||||
<span>Show all ports</span>
|
||||
</label>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace BoardsConfig {
|
||||
|
||||
export namespace Config {
|
||||
|
||||
export function sameAs(config: Config, other: Config | Board): boolean {
|
||||
const { selectedBoard, selectedPort } = config;
|
||||
if (Board.is(other)) {
|
||||
return !!selectedBoard
|
||||
&& Board.equals(other, selectedBoard)
|
||||
&& Port.sameAs(selectedPort, other.port);
|
||||
return (
|
||||
!!selectedBoard &&
|
||||
Board.equals(other, selectedBoard) &&
|
||||
Port.sameAs(selectedPort, other.port)
|
||||
);
|
||||
}
|
||||
return sameAs(config, other);
|
||||
}
|
||||
|
||||
export function equals(left: Config, right: Config): boolean {
|
||||
return left.selectedBoard === right.selectedBoard
|
||||
&& left.selectedPort === right.selectedPort;
|
||||
return (
|
||||
left.selectedBoard === right.selectedBoard &&
|
||||
left.selectedPort === right.selectedPort
|
||||
);
|
||||
}
|
||||
|
||||
export function toString(config: Config, options: { default: string } = { default: '' }): string {
|
||||
export function toString(
|
||||
config: Config,
|
||||
options: { default: string } = { default: '' }
|
||||
): string {
|
||||
const { selectedBoard, selectedPort: port } = config;
|
||||
if (!selectedBoard) {
|
||||
return options.default;
|
||||
@ -281,17 +357,33 @@ export namespace BoardsConfig {
|
||||
return `${name}${port ? ' at ' + Port.toString(port) : ''}`;
|
||||
}
|
||||
|
||||
export function setConfig(config: Config | undefined, urlToAttachTo: URL): URL {
|
||||
export function setConfig(
|
||||
config: Config | undefined,
|
||||
urlToAttachTo: URL
|
||||
): URL {
|
||||
const copy = new URL(urlToAttachTo.toString());
|
||||
if (!config) {
|
||||
copy.searchParams.delete('boards-config');
|
||||
return copy;
|
||||
}
|
||||
|
||||
const selectedBoard = config.selectedBoard ? { name: config.selectedBoard.name, fqbn: config.selectedBoard.fqbn } : undefined;
|
||||
const selectedPort = config.selectedPort ? { protocol: config.selectedPort.protocol, address: config.selectedPort.address } : undefined;
|
||||
const selectedBoard = config.selectedBoard
|
||||
? {
|
||||
name: config.selectedBoard.name,
|
||||
fqbn: config.selectedBoard.fqbn,
|
||||
}
|
||||
: undefined;
|
||||
const selectedPort = config.selectedPort
|
||||
? {
|
||||
protocol: config.selectedPort.protocol,
|
||||
address: config.selectedPort.address,
|
||||
}
|
||||
: undefined;
|
||||
const jsonConfig = JSON.stringify({ selectedBoard, selectedPort });
|
||||
copy.searchParams.set('boards-config', encodeURIComponent(jsonConfig));
|
||||
copy.searchParams.set(
|
||||
'boards-config',
|
||||
encodeURIComponent(jsonConfig)
|
||||
);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@ -306,14 +398,14 @@ export namespace BoardsConfig {
|
||||
if (typeof candidate === 'object') {
|
||||
return candidate;
|
||||
}
|
||||
console.warn(`Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}`);
|
||||
console.warn(
|
||||
`Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}`
|
||||
);
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
console.log(`Could not get board config from URL: ${url}.`, e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,10 @@ import * as PQueue from 'p-queue';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { BoardsServiceProvider } from './boards-service-provider';
|
||||
import { Board, ConfigOption, Programmer } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
@ -12,7 +15,6 @@ import { ArduinoMenus, unregisterSubmenu } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@ -32,63 +34,153 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
protected readonly toDisposeOnBoardChange = new DisposableCollection();
|
||||
|
||||
async onStart(): Promise<void> {
|
||||
this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard);
|
||||
this.boardsDataStore.onChanged(() => this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.updateMenuActions(selectedBoard));
|
||||
this.updateMenuActions(
|
||||
this.boardsServiceClient.boardsConfig.selectedBoard
|
||||
);
|
||||
this.boardsDataStore.onChanged(() =>
|
||||
this.updateMenuActions(
|
||||
this.boardsServiceClient.boardsConfig.selectedBoard
|
||||
)
|
||||
);
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) =>
|
||||
this.updateMenuActions(selectedBoard)
|
||||
);
|
||||
}
|
||||
|
||||
protected async updateMenuActions(selectedBoard: Board | undefined): Promise<void> {
|
||||
protected async updateMenuActions(
|
||||
selectedBoard: Board | undefined
|
||||
): Promise<void> {
|
||||
return this.queue.add(async () => {
|
||||
this.toDisposeOnBoardChange.dispose();
|
||||
this.mainMenuManager.update();
|
||||
if (selectedBoard) {
|
||||
const { fqbn } = selectedBoard;
|
||||
if (fqbn) {
|
||||
const { configOptions, programmers, selectedProgrammer } = await this.boardsDataStore.getData(fqbn);
|
||||
const { configOptions, programmers, selectedProgrammer } =
|
||||
await this.boardsDataStore.getData(fqbn);
|
||||
if (configOptions.length) {
|
||||
const boardsConfigMenuPath = [...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, 'z01_boardsConfig']; // `z_` is for ordering.
|
||||
for (const { label, option, values } of configOptions.sort(ConfigOption.LABEL_COMPARATOR)) {
|
||||
const menuPath = [...boardsConfigMenuPath, `${option}`];
|
||||
const commands = new Map<string, Disposable & { label: string }>()
|
||||
const boardsConfigMenuPath = [
|
||||
...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP,
|
||||
'z01_boardsConfig',
|
||||
]; // `z_` is for ordering.
|
||||
for (const {
|
||||
label,
|
||||
option,
|
||||
values,
|
||||
} of configOptions.sort(
|
||||
ConfigOption.LABEL_COMPARATOR
|
||||
)) {
|
||||
const menuPath = [
|
||||
...boardsConfigMenuPath,
|
||||
`${option}`,
|
||||
];
|
||||
const commands = new Map<
|
||||
string,
|
||||
Disposable & { label: string }
|
||||
>();
|
||||
for (const value of values) {
|
||||
const id = `${fqbn}-${option}--${value.value}`;
|
||||
const command = { id };
|
||||
const selectedValue = value.value;
|
||||
const handler = {
|
||||
execute: () => this.boardsDataStore.selectConfigOption({ fqbn, option, selectedValue }),
|
||||
isToggled: () => value.selected
|
||||
execute: () =>
|
||||
this.boardsDataStore.selectConfigOption(
|
||||
{ fqbn, option, selectedValue }
|
||||
),
|
||||
isToggled: () => value.selected,
|
||||
};
|
||||
commands.set(id, Object.assign(this.commandRegistry.registerCommand(command, handler), { label: value.label }));
|
||||
commands.set(
|
||||
id,
|
||||
Object.assign(
|
||||
this.commandRegistry.registerCommand(
|
||||
command,
|
||||
handler
|
||||
),
|
||||
{ label: value.label }
|
||||
)
|
||||
);
|
||||
}
|
||||
this.menuRegistry.registerSubmenu(menuPath, label);
|
||||
this.toDisposeOnBoardChange.pushAll([
|
||||
...commands.values(),
|
||||
Disposable.create(() => unregisterSubmenu(menuPath, this.menuRegistry)),
|
||||
...Array.from(commands.keys()).map((commandId, i) => {
|
||||
const { label } = commands.get(commandId)!;
|
||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: `${i}`, label });
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId));
|
||||
})
|
||||
Disposable.create(() =>
|
||||
unregisterSubmenu(
|
||||
menuPath,
|
||||
this.menuRegistry
|
||||
)
|
||||
),
|
||||
...Array.from(commands.keys()).map(
|
||||
(commandId, i) => {
|
||||
const { label } =
|
||||
commands.get(commandId)!;
|
||||
this.menuRegistry.registerMenuAction(
|
||||
menuPath,
|
||||
{ commandId, order: `${i}`, label }
|
||||
);
|
||||
return Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuAction(
|
||||
commandId
|
||||
)
|
||||
);
|
||||
}
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (programmers.length) {
|
||||
const programmersMenuPath = [...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, 'z02_programmers'];
|
||||
const label = selectedProgrammer ? `Programmer: "${selectedProgrammer.name}"` : 'Programmer'
|
||||
this.menuRegistry.registerSubmenu(programmersMenuPath, label);
|
||||
this.toDisposeOnBoardChange.push(Disposable.create(() => unregisterSubmenu(programmersMenuPath, this.menuRegistry)));
|
||||
const programmersMenuPath = [
|
||||
...ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP,
|
||||
'z02_programmers',
|
||||
];
|
||||
const label = selectedProgrammer
|
||||
? `Programmer: "${selectedProgrammer.name}"`
|
||||
: 'Programmer';
|
||||
this.menuRegistry.registerSubmenu(
|
||||
programmersMenuPath,
|
||||
label
|
||||
);
|
||||
this.toDisposeOnBoardChange.push(
|
||||
Disposable.create(() =>
|
||||
unregisterSubmenu(
|
||||
programmersMenuPath,
|
||||
this.menuRegistry
|
||||
)
|
||||
)
|
||||
);
|
||||
for (const programmer of programmers) {
|
||||
const { id, name } = programmer;
|
||||
const command = { id: `${fqbn}-programmer--${id}` };
|
||||
const handler = {
|
||||
execute: () => this.boardsDataStore.selectProgrammer({ fqbn, selectedProgrammer: programmer }),
|
||||
isToggled: () => Programmer.equals(programmer, selectedProgrammer)
|
||||
execute: () =>
|
||||
this.boardsDataStore.selectProgrammer({
|
||||
fqbn,
|
||||
selectedProgrammer: programmer,
|
||||
}),
|
||||
isToggled: () =>
|
||||
Programmer.equals(
|
||||
programmer,
|
||||
selectedProgrammer
|
||||
),
|
||||
};
|
||||
this.menuRegistry.registerMenuAction(programmersMenuPath, { commandId: command.id, label: name });
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.menuRegistry.registerMenuAction(
|
||||
programmersMenuPath,
|
||||
{ commandId: command.id, label: name }
|
||||
);
|
||||
this.commandRegistry.registerCommand(
|
||||
command,
|
||||
handler
|
||||
);
|
||||
this.toDisposeOnBoardChange.pushAll([
|
||||
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command.id))
|
||||
Disposable.create(() =>
|
||||
this.commandRegistry.unregisterCommand(
|
||||
command
|
||||
)
|
||||
),
|
||||
Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuAction(
|
||||
command.id
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -97,5 +189,4 @@ export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,14 +3,22 @@ import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { deepClone } from '@theia/core/lib/common/objects';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||
import {
|
||||
FrontendApplicationContribution,
|
||||
LocalStorageService,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { notEmpty } from '../../common/utils';
|
||||
import { BoardsService, ConfigOption, Installable, BoardDetails, Programmer } from '../../common/protocol';
|
||||
import {
|
||||
BoardsService,
|
||||
ConfigOption,
|
||||
Installable,
|
||||
BoardDetails,
|
||||
Programmer,
|
||||
} from '../../common/protocol';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
|
||||
@inject(ILogger)
|
||||
@named('store')
|
||||
protected readonly logger: ILogger;
|
||||
@ -33,9 +41,14 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
return;
|
||||
}
|
||||
let shouldFireChanged = false;
|
||||
for (const fqbn of item.boards.map(({ fqbn }) => fqbn).filter(notEmpty).filter(fqbn => !!fqbn)) {
|
||||
for (const fqbn of item.boards
|
||||
.map(({ fqbn }) => fqbn)
|
||||
.filter(notEmpty)
|
||||
.filter((fqbn) => !!fqbn)) {
|
||||
const key = this.getStorageKey(fqbn, version);
|
||||
let data = await this.storageService.getData<ConfigOption[] | undefined>(key);
|
||||
let data = await this.storageService.getData<
|
||||
ConfigOption[] | undefined
|
||||
>(key);
|
||||
if (!data || !data.length) {
|
||||
const details = await this.getBoardDetailsSafe(fqbn);
|
||||
if (details) {
|
||||
@ -59,20 +72,27 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
|
||||
async appendConfigToFqbn(
|
||||
fqbn: string | undefined,
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<string | undefined> {
|
||||
|
||||
boardsPackageVersion: MaybePromise<
|
||||
Installable.Version | undefined
|
||||
> = this.getBoardsPackageVersion(fqbn)
|
||||
): Promise<string | undefined> {
|
||||
if (!fqbn) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { configOptions } = await this.getData(fqbn, boardsPackageVersion);
|
||||
const { configOptions } = await this.getData(
|
||||
fqbn,
|
||||
boardsPackageVersion
|
||||
);
|
||||
return ConfigOption.decorate(fqbn, configOptions);
|
||||
}
|
||||
|
||||
async getData(
|
||||
fqbn: string | undefined,
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<BoardsDataStore.Data> {
|
||||
|
||||
boardsPackageVersion: MaybePromise<
|
||||
Installable.Version | undefined
|
||||
> = this.getBoardsPackageVersion(fqbn)
|
||||
): Promise<BoardsDataStore.Data> {
|
||||
if (!fqbn) {
|
||||
return BoardsDataStore.Data.EMPTY;
|
||||
}
|
||||
@ -82,7 +102,9 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
return BoardsDataStore.Data.EMPTY;
|
||||
}
|
||||
const key = this.getStorageKey(fqbn, version);
|
||||
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
|
||||
let data = await this.storageService.getData<
|
||||
BoardsDataStore.Data | undefined
|
||||
>(key, undefined);
|
||||
if (BoardsDataStore.Data.is(data)) {
|
||||
return data;
|
||||
}
|
||||
@ -92,18 +114,28 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
return BoardsDataStore.Data.EMPTY;
|
||||
}
|
||||
|
||||
data = { configOptions: boardDetails.configOptions, programmers: boardDetails.programmers };
|
||||
data = {
|
||||
configOptions: boardDetails.configOptions,
|
||||
programmers: boardDetails.programmers,
|
||||
};
|
||||
await this.storageService.setData(key, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async selectProgrammer(
|
||||
{ fqbn, selectedProgrammer }: { fqbn: string, selectedProgrammer: Programmer },
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<boolean> {
|
||||
|
||||
{
|
||||
fqbn,
|
||||
selectedProgrammer,
|
||||
}: { fqbn: string; selectedProgrammer: Programmer },
|
||||
boardsPackageVersion: MaybePromise<
|
||||
Installable.Version | undefined
|
||||
> = this.getBoardsPackageVersion(fqbn)
|
||||
): Promise<boolean> {
|
||||
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
|
||||
const { programmers } = data;
|
||||
if (!programmers.find(p => Programmer.equals(selectedProgrammer, p))) {
|
||||
if (
|
||||
!programmers.find((p) => Programmer.equals(selectedProgrammer, p))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -112,18 +144,28 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.setData({ fqbn, data: { ...data, selectedProgrammer }, version });
|
||||
await this.setData({
|
||||
fqbn,
|
||||
data: { ...data, selectedProgrammer },
|
||||
version,
|
||||
});
|
||||
this.fireChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
async selectConfigOption(
|
||||
{ fqbn, option, selectedValue }: { fqbn: string, option: string, selectedValue: string },
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<boolean> {
|
||||
|
||||
{
|
||||
fqbn,
|
||||
option,
|
||||
selectedValue,
|
||||
}: { fqbn: string; option: string; selectedValue: string },
|
||||
boardsPackageVersion: MaybePromise<
|
||||
Installable.Version | undefined
|
||||
> = this.getBoardsPackageVersion(fqbn)
|
||||
): Promise<boolean> {
|
||||
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
|
||||
const { configOptions } = data;
|
||||
const configOption = configOptions.find(c => c.option === option);
|
||||
const configOption = configOptions.find((c) => c.option === option);
|
||||
if (!configOption) {
|
||||
return false;
|
||||
}
|
||||
@ -149,26 +191,46 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async setData(
|
||||
{ fqbn, data, version }: { fqbn: string, data: BoardsDataStore.Data, version: Installable.Version }): Promise<void> {
|
||||
|
||||
protected async setData({
|
||||
fqbn,
|
||||
data,
|
||||
version,
|
||||
}: {
|
||||
fqbn: string;
|
||||
data: BoardsDataStore.Data;
|
||||
version: Installable.Version;
|
||||
}): Promise<void> {
|
||||
const key = this.getStorageKey(fqbn, version);
|
||||
return this.storageService.setData(key, data);
|
||||
}
|
||||
|
||||
protected getStorageKey(fqbn: string, version: Installable.Version): string {
|
||||
protected getStorageKey(
|
||||
fqbn: string,
|
||||
version: Installable.Version
|
||||
): string {
|
||||
return `.arduinoIDE-configOptions-${version}-${fqbn}`;
|
||||
}
|
||||
|
||||
protected async getBoardDetailsSafe(fqbn: string): Promise<BoardDetails | undefined> {
|
||||
protected async getBoardDetailsSafe(
|
||||
fqbn: string
|
||||
): Promise<BoardDetails | undefined> {
|
||||
try {
|
||||
const details = this.boardsService.getBoardDetails({ fqbn });
|
||||
return details;
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('loading board data') && err.message.includes('is not installed')) {
|
||||
this.logger.warn(`The boards package is not installed for board with FQBN: ${fqbn}`);
|
||||
if (
|
||||
err instanceof Error &&
|
||||
err.message.includes('loading board data') &&
|
||||
err.message.includes('is not installed')
|
||||
) {
|
||||
this.logger.warn(
|
||||
`The boards package is not installed for board with FQBN: ${fqbn}`
|
||||
);
|
||||
} else {
|
||||
this.logger.error(`An unexpected error occurred while retrieving the board details for ${fqbn}.`, err);
|
||||
this.logger.error(
|
||||
`An unexpected error occurred while retrieving the board details for ${fqbn}.`,
|
||||
err
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -178,17 +240,20 @@ export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
this.onChangedEmitter.fire();
|
||||
}
|
||||
|
||||
protected async getBoardsPackageVersion(fqbn: string | undefined): Promise<Installable.Version | undefined> {
|
||||
protected async getBoardsPackageVersion(
|
||||
fqbn: string | undefined
|
||||
): Promise<Installable.Version | undefined> {
|
||||
if (!fqbn) {
|
||||
return undefined;
|
||||
}
|
||||
const boardsPackage = await this.boardsService.getContainerBoardPackage({ fqbn });
|
||||
const boardsPackage = await this.boardsService.getContainerBoardPackage(
|
||||
{ fqbn }
|
||||
);
|
||||
if (!boardsPackage) {
|
||||
return undefined;
|
||||
}
|
||||
return boardsPackage.installedVersion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace BoardsDataStore {
|
||||
@ -200,12 +265,16 @@ export namespace BoardsDataStore {
|
||||
export namespace Data {
|
||||
export const EMPTY: Data = {
|
||||
configOptions: [],
|
||||
programmers: []
|
||||
programmers: [],
|
||||
};
|
||||
export function is(arg: any): arg is Data {
|
||||
return !!arg
|
||||
&& 'configOptions' in arg && Array.isArray(arg['configOptions'])
|
||||
&& 'programmers' in arg && Array.isArray(arg['programmers'])
|
||||
return (
|
||||
!!arg &&
|
||||
'configOptions' in arg &&
|
||||
Array.isArray(arg['configOptions']) &&
|
||||
'programmers' in arg &&
|
||||
Array.isArray(arg['programmers'])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { BoardsPackage, BoardsService } from '../../common/protocol/boards-service';
|
||||
import {
|
||||
BoardsPackage,
|
||||
BoardsService,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
||||
|
||||
static WIDGET_ID = 'boards-list-widget';
|
||||
static WIDGET_LABEL = 'Boards Manager';
|
||||
|
||||
constructor(
|
||||
@inject(BoardsService) protected service: BoardsService,
|
||||
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<BoardsPackage>) {
|
||||
|
||||
@inject(ListItemRenderer)
|
||||
protected itemRenderer: ListItemRenderer<BoardsPackage>
|
||||
) {
|
||||
super({
|
||||
id: BoardsListWidget.WIDGET_ID,
|
||||
label: BoardsListWidget.WIDGET_LABEL,
|
||||
@ -21,7 +24,7 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
||||
installable: service,
|
||||
itemLabel: (item: BoardsPackage) => item.name,
|
||||
itemDeprecated: (item: BoardsPackage) => item.deprecated,
|
||||
itemRenderer
|
||||
itemRenderer,
|
||||
});
|
||||
}
|
||||
|
||||
@ -29,19 +32,42 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.toDispose.pushAll([
|
||||
this.notificationCenter.onPlatformInstalled(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onPlatformUninstalled(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onPlatformInstalled(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
this.notificationCenter.onPlatformUninstalled(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
protected async install({ item, progressId, version }: { item: BoardsPackage, progressId: string, version: string; }): Promise<void> {
|
||||
protected async install({
|
||||
item,
|
||||
progressId,
|
||||
version,
|
||||
}: {
|
||||
item: BoardsPackage;
|
||||
progressId: string;
|
||||
version: string;
|
||||
}): Promise<void> {
|
||||
await super.install({ item, progressId, version });
|
||||
this.messageService.info(`Successfully installed platform ${item.name}:${version}`, { timeout: 3000 });
|
||||
this.messageService.info(
|
||||
`Successfully installed platform ${item.name}:${version}`,
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
}
|
||||
|
||||
protected async uninstall({ item, progressId }: { item: BoardsPackage, progressId: string }): Promise<void> {
|
||||
protected async uninstall({
|
||||
item,
|
||||
progressId,
|
||||
}: {
|
||||
item: BoardsPackage;
|
||||
progressId: string;
|
||||
}): Promise<void> {
|
||||
await super.uninstall({ item, progressId });
|
||||
this.messageService.info(`Successfully uninstalled platform ${item.name}:${item.installedVersion}`, { timeout: 3000 });
|
||||
this.messageService.info(
|
||||
`Successfully uninstalled platform ${item.name}:${item.installedVersion}`,
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
BoardsService,
|
||||
BoardsPackage,
|
||||
AttachedBoardsChangeEvent,
|
||||
BoardWithPackage
|
||||
BoardWithPackage,
|
||||
} from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { naturalCompare } from '../../common/utils';
|
||||
@ -21,14 +21,12 @@ import { StorageWrapper } from '../storage-wrapper';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
|
||||
@inject(ILogger)
|
||||
protected logger: ILogger;
|
||||
|
||||
@inject(MessageService)
|
||||
protected messageService: MessageService;
|
||||
|
||||
|
||||
@inject(BoardsService)
|
||||
protected boardsService: BoardsService;
|
||||
|
||||
@ -38,8 +36,11 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
@inject(NotificationCenter)
|
||||
protected notificationCenter: NotificationCenter;
|
||||
|
||||
protected readonly onBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||
protected readonly onAvailableBoardsChangedEmitter = new Emitter<AvailableBoard[]>();
|
||||
protected readonly onBoardsConfigChangedEmitter =
|
||||
new Emitter<BoardsConfig.Config>();
|
||||
protected readonly onAvailableBoardsChangedEmitter = new Emitter<
|
||||
AvailableBoard[]
|
||||
>();
|
||||
|
||||
/**
|
||||
* Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it.
|
||||
@ -48,7 +49,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
* We have to listen on such changes and auto-reconnect the same board on another port.
|
||||
* See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ
|
||||
*/
|
||||
protected latestValidBoardsConfig: RecursiveRequired<BoardsConfig.Config> | undefined = undefined;
|
||||
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.
|
||||
@ -63,17 +66,24 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
* This even also emitted when the board package for the currently selected board was uninstalled.
|
||||
*/
|
||||
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
|
||||
readonly onAvailableBoardsChanged = this.onAvailableBoardsChangedEmitter.event;
|
||||
readonly onAvailableBoardsChanged =
|
||||
this.onAvailableBoardsChangedEmitter.event;
|
||||
|
||||
onStart(): void {
|
||||
this.notificationCenter.onAttachedBoardsChanged(this.notifyAttachedBoardsChanged.bind(this));
|
||||
this.notificationCenter.onPlatformInstalled(this.notifyPlatformInstalled.bind(this));
|
||||
this.notificationCenter.onPlatformUninstalled(this.notifyPlatformUninstalled.bind(this));
|
||||
this.notificationCenter.onAttachedBoardsChanged(
|
||||
this.notifyAttachedBoardsChanged.bind(this)
|
||||
);
|
||||
this.notificationCenter.onPlatformInstalled(
|
||||
this.notifyPlatformInstalled.bind(this)
|
||||
);
|
||||
this.notificationCenter.onPlatformUninstalled(
|
||||
this.notifyPlatformUninstalled.bind(this)
|
||||
);
|
||||
|
||||
Promise.all([
|
||||
this.boardsService.getAttachedBoards(),
|
||||
this.boardsService.getAvailablePorts(),
|
||||
this.loadState()
|
||||
this.loadState(),
|
||||
]).then(([attachedBoards, availablePorts]) => {
|
||||
this._attachedBoards = attachedBoards;
|
||||
this._availablePorts = availablePorts;
|
||||
@ -81,7 +91,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
});
|
||||
}
|
||||
|
||||
protected notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||
protected notifyAttachedBoardsChanged(
|
||||
event: AttachedBoardsChangeEvent
|
||||
): void {
|
||||
if (!AttachedBoardsChangeEvent.isEmpty(event)) {
|
||||
this.logger.info('Attached boards and available ports changed:');
|
||||
this.logger.info(AttachedBoardsChangeEvent.toString(event));
|
||||
@ -97,12 +109,20 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const { selectedBoard } = this.boardsConfig;
|
||||
const { installedVersion, id } = event.item;
|
||||
if (selectedBoard) {
|
||||
const installedBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
|
||||
if (installedBoard && (!selectedBoard.fqbn || selectedBoard.fqbn === installedBoard.fqbn)) {
|
||||
this.logger.info(`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`);
|
||||
const installedBoard = event.item.boards.find(
|
||||
({ name }) => name === selectedBoard.name
|
||||
);
|
||||
if (
|
||||
installedBoard &&
|
||||
(!selectedBoard.fqbn ||
|
||||
selectedBoard.fqbn === installedBoard.fqbn)
|
||||
) {
|
||||
this.logger.info(
|
||||
`Board package ${id}[${installedVersion}] was installed. Updating the FQBN of the currently selected ${selectedBoard.name} board. [FQBN: ${installedBoard.fqbn}]`
|
||||
);
|
||||
this.boardsConfig = {
|
||||
...this.boardsConfig,
|
||||
selectedBoard: installedBoard
|
||||
selectedBoard: installedBoard,
|
||||
};
|
||||
return;
|
||||
}
|
||||
@ -110,13 +130,26 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
// This logic handles it "gracefully" by unselecting the board, so that we can avoid no FQBN is set error.
|
||||
// https://github.com/arduino/arduino-cli/issues/620
|
||||
// https://github.com/arduino/arduino-pro-ide/issues/374
|
||||
if (BoardWithPackage.is(selectedBoard) && selectedBoard.packageId === event.item.id && !installedBoard) {
|
||||
this.messageService.warn(`Could not find previously selected board '${selectedBoard.name}' in installed platform '${event.item.name}'. Please manually reselect the board you want to use. Do you want to reselect it now?`, 'Reselect later', 'Yes').then(async answer => {
|
||||
if (
|
||||
BoardWithPackage.is(selectedBoard) &&
|
||||
selectedBoard.packageId === event.item.id &&
|
||||
!installedBoard
|
||||
) {
|
||||
this.messageService
|
||||
.warn(
|
||||
`Could not find previously selected board '${selectedBoard.name}' in installed platform '${event.item.name}'. Please manually reselect the board you want to use. Do you want to reselect it now?`,
|
||||
'Reselect later',
|
||||
'Yes'
|
||||
)
|
||||
.then(async (answer) => {
|
||||
if (answer === 'Yes') {
|
||||
this.commandService.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id, selectedBoard.name);
|
||||
this.commandService.executeCommand(
|
||||
ArduinoCommands.OPEN_BOARDS_DIALOG.id,
|
||||
selectedBoard.name
|
||||
);
|
||||
}
|
||||
});
|
||||
this.boardsConfig = {}
|
||||
this.boardsConfig = {};
|
||||
return;
|
||||
}
|
||||
// Trigger a board re-set. See: https://github.com/arduino/arduino-cli/issues/954
|
||||
@ -129,8 +162,13 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
this.logger.info('Boards package uninstalled: ', JSON.stringify(event));
|
||||
const { selectedBoard } = this.boardsConfig;
|
||||
if (selectedBoard && selectedBoard.fqbn) {
|
||||
const uninstalledBoard = event.item.boards.find(({ name }) => name === selectedBoard.name);
|
||||
if (uninstalledBoard && uninstalledBoard.fqbn === selectedBoard.fqbn) {
|
||||
const uninstalledBoard = event.item.boards.find(
|
||||
({ name }) => name === selectedBoard.name
|
||||
);
|
||||
if (
|
||||
uninstalledBoard &&
|
||||
uninstalledBoard.fqbn === selectedBoard.fqbn
|
||||
) {
|
||||
// We should not unset the FQBN, if the selected board is an attached, recognized board.
|
||||
// Attach Uno and install AVR, select Uno. Uninstall the AVR core while Uno is selected. We do not want to discard the FQBN of the Uno board.
|
||||
// Dev note: We cannot assume the `selectedBoard` is a type of `AvailableBoard`.
|
||||
@ -138,43 +176,68 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
// it is just a FQBN, so we need to find the `selected` board among the `AvailableBoards`
|
||||
const selectedAvailableBoard = AvailableBoard.is(selectedBoard)
|
||||
? selectedBoard
|
||||
: this._availableBoards.find(availableBoard => Board.sameAs(availableBoard, selectedBoard));
|
||||
if (selectedAvailableBoard && selectedAvailableBoard.selected && selectedAvailableBoard.state === AvailableBoard.State.recognized) {
|
||||
: this._availableBoards.find((availableBoard) =>
|
||||
Board.sameAs(availableBoard, selectedBoard)
|
||||
);
|
||||
if (
|
||||
selectedAvailableBoard &&
|
||||
selectedAvailableBoard.selected &&
|
||||
selectedAvailableBoard.state ===
|
||||
AvailableBoard.State.recognized
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.logger.info(`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`);
|
||||
this.logger.info(
|
||||
`Board package ${event.item.id} was uninstalled. Discarding the FQBN of the currently selected ${selectedBoard.name} board.`
|
||||
);
|
||||
const selectedBoardWithoutFqbn = {
|
||||
name: selectedBoard.name
|
||||
name: selectedBoard.name,
|
||||
// No FQBN
|
||||
};
|
||||
this.boardsConfig = {
|
||||
...this.boardsConfig,
|
||||
selectedBoard: selectedBoardWithoutFqbn
|
||||
selectedBoard: selectedBoardWithoutFqbn,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async tryReconnect(): Promise<boolean> {
|
||||
if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) {
|
||||
for (const board of this.availableBoards.filter(({ state }) => state !== AvailableBoard.State.incomplete)) {
|
||||
if (this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn
|
||||
&& this.latestValidBoardsConfig.selectedBoard.name === board.name
|
||||
&& Port.sameAs(this.latestValidBoardsConfig.selectedPort, board.port)) {
|
||||
|
||||
if (
|
||||
this.latestValidBoardsConfig &&
|
||||
!this.canUploadTo(this.boardsConfig)
|
||||
) {
|
||||
for (const board of this.availableBoards.filter(
|
||||
({ state }) => state !== AvailableBoard.State.incomplete
|
||||
)) {
|
||||
if (
|
||||
this.latestValidBoardsConfig.selectedBoard.fqbn ===
|
||||
board.fqbn &&
|
||||
this.latestValidBoardsConfig.selectedBoard.name ===
|
||||
board.name &&
|
||||
Port.sameAs(
|
||||
this.latestValidBoardsConfig.selectedPort,
|
||||
board.port
|
||||
)
|
||||
) {
|
||||
this.boardsConfig = this.latestValidBoardsConfig;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed.
|
||||
// See documentation on `latestValidBoardsConfig`.
|
||||
for (const board of this.availableBoards.filter(({ state }) => state !== AvailableBoard.State.incomplete)) {
|
||||
if (this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn
|
||||
&& this.latestValidBoardsConfig.selectedBoard.name === board.name) {
|
||||
|
||||
for (const board of this.availableBoards.filter(
|
||||
({ state }) => state !== AvailableBoard.State.incomplete
|
||||
)) {
|
||||
if (
|
||||
this.latestValidBoardsConfig.selectedBoard.fqbn ===
|
||||
board.fqbn &&
|
||||
this.latestValidBoardsConfig.selectedBoard.name ===
|
||||
board.name
|
||||
) {
|
||||
this.boardsConfig = {
|
||||
...this.latestValidBoardsConfig,
|
||||
selectedPort: board.port
|
||||
selectedPort: board.port,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
@ -185,7 +248,15 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
|
||||
set boardsConfig(config: BoardsConfig.Config) {
|
||||
this.doSetBoardsConfig(config);
|
||||
this.saveState().finally(() => this.reconcileAvailableBoards().finally(() => this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)));
|
||||
this.saveState().finally(() =>
|
||||
this.reconcileAvailableBoards().finally(() =>
|
||||
this.onBoardsConfigChangedEmitter.fire(this._boardsConfig)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get boardsConfig(): BoardsConfig.Config {
|
||||
return this._boardsConfig;
|
||||
}
|
||||
|
||||
protected doSetBoardsConfig(config: BoardsConfig.Config): void {
|
||||
@ -197,29 +268,33 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
}
|
||||
}
|
||||
|
||||
async searchBoards({ query, cores }: { query?: string, cores?: string[] }): Promise<BoardWithPackage[]> {
|
||||
async searchBoards({
|
||||
query,
|
||||
cores,
|
||||
}: {
|
||||
query?: string;
|
||||
cores?: string[];
|
||||
}): Promise<BoardWithPackage[]> {
|
||||
const boards = await this.boardsService.searchBoards({ query });
|
||||
return boards;
|
||||
}
|
||||
|
||||
get boardsConfig(): BoardsConfig.Config {
|
||||
return this._boardsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
|
||||
*/
|
||||
canVerify(
|
||||
config: BoardsConfig.Config | undefined = this.boardsConfig,
|
||||
options: { silent: boolean } = { silent: true }): config is BoardsConfig.Config & { selectedBoard: Board } {
|
||||
|
||||
options: { silent: boolean } = { silent: true }
|
||||
): config is BoardsConfig.Config & { selectedBoard: Board } {
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.selectedBoard) {
|
||||
if (!options.silent) {
|
||||
this.messageService.warn('No boards selected.', { timeout: 3000 });
|
||||
this.messageService.warn('No boards selected.', {
|
||||
timeout: 3000,
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -232,8 +307,8 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
*/
|
||||
canUploadTo(
|
||||
config: BoardsConfig.Config | undefined = this.boardsConfig,
|
||||
options: { silent: boolean } = { silent: true }): config is RecursiveRequired<BoardsConfig.Config> {
|
||||
|
||||
options: { silent: boolean } = { silent: true }
|
||||
): config is RecursiveRequired<BoardsConfig.Config> {
|
||||
if (!this.canVerify(config, options)) {
|
||||
return false;
|
||||
}
|
||||
@ -241,14 +316,20 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const { name } = config.selectedBoard;
|
||||
if (!config.selectedPort) {
|
||||
if (!options.silent) {
|
||||
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
|
||||
this.messageService.warn(
|
||||
`No ports selected for board: '${name}'.`,
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.selectedBoard.fqbn) {
|
||||
if (!options.silent) {
|
||||
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
|
||||
this.messageService.warn(
|
||||
`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`,
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -260,25 +341,46 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
return this._availableBoards;
|
||||
}
|
||||
|
||||
async waitUntilAvailable(what: Board & { port: Port }, timeout?: number): Promise<void> {
|
||||
const find = (needle: Board & { port: Port }, haystack: AvailableBoard[]) =>
|
||||
haystack.find(board => Board.equals(needle, board) && Port.equals(needle.port, board.port));
|
||||
const timeoutTask = !!timeout && timeout > 0
|
||||
? new Promise<void>((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${timeout} ms.`)), timeout))
|
||||
: new Promise<void>(() => { /* never */ });
|
||||
const waitUntilTask = new Promise<void>(resolve => {
|
||||
async waitUntilAvailable(
|
||||
what: Board & { port: Port },
|
||||
timeout?: number
|
||||
): Promise<void> {
|
||||
const find = (
|
||||
needle: Board & { port: Port },
|
||||
haystack: AvailableBoard[]
|
||||
) =>
|
||||
haystack.find(
|
||||
(board) =>
|
||||
Board.equals(needle, board) &&
|
||||
Port.equals(needle.port, board.port)
|
||||
);
|
||||
const timeoutTask =
|
||||
!!timeout && timeout > 0
|
||||
? new Promise<void>((_, reject) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(new Error(`Timeout after ${timeout} ms.`)),
|
||||
timeout
|
||||
)
|
||||
)
|
||||
: new Promise<void>(() => {
|
||||
/* never */
|
||||
});
|
||||
const waitUntilTask = new Promise<void>((resolve) => {
|
||||
let candidate = find(what, this.availableBoards);
|
||||
if (candidate) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const disposable = this.onAvailableBoardsChanged(availableBoards => {
|
||||
const disposable = this.onAvailableBoardsChanged(
|
||||
(availableBoards) => {
|
||||
candidate = find(what, availableBoards);
|
||||
if (candidate) {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
return await Promise.race([waitUntilTask, timeoutTask]);
|
||||
}
|
||||
@ -287,54 +389,90 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
const attachedBoards = this._attachedBoards;
|
||||
const availablePorts = this._availablePorts;
|
||||
// Unset the port on the user's config, if it is not available anymore.
|
||||
if (this.boardsConfig.selectedPort && !availablePorts.some(port => Port.sameAs(port, this.boardsConfig.selectedPort))) {
|
||||
this.doSetBoardsConfig({ selectedBoard: this.boardsConfig.selectedBoard, selectedPort: undefined });
|
||||
if (
|
||||
this.boardsConfig.selectedPort &&
|
||||
!availablePorts.some((port) =>
|
||||
Port.sameAs(port, this.boardsConfig.selectedPort)
|
||||
)
|
||||
) {
|
||||
this.doSetBoardsConfig({
|
||||
selectedBoard: this.boardsConfig.selectedBoard,
|
||||
selectedPort: undefined,
|
||||
});
|
||||
this.onBoardsConfigChangedEmitter.fire(this._boardsConfig);
|
||||
}
|
||||
const boardsConfig = this.boardsConfig;
|
||||
const currentAvailableBoards = this._availableBoards;
|
||||
const availableBoards: AvailableBoard[] = [];
|
||||
const availableBoardPorts = availablePorts.filter(Port.isBoardPort);
|
||||
const attachedSerialBoards = attachedBoards.filter(({ port }) => !!port);
|
||||
const attachedSerialBoards = attachedBoards.filter(
|
||||
({ port }) => !!port
|
||||
);
|
||||
|
||||
for (const boardPort of availableBoardPorts) {
|
||||
let state = AvailableBoard.State.incomplete; // Initial pessimism.
|
||||
let board = attachedSerialBoards.find(({ port }) => Port.sameAs(boardPort, port));
|
||||
let board = attachedSerialBoards.find(({ port }) =>
|
||||
Port.sameAs(boardPort, port)
|
||||
);
|
||||
if (board) {
|
||||
state = AvailableBoard.State.recognized;
|
||||
} else {
|
||||
// If the selected board is not recognized because it is a 3rd party board: https://github.com/arduino/arduino-cli/issues/623
|
||||
// We still want to show it without the red X in the boards toolbar: https://github.com/arduino/arduino-pro-ide/issues/198#issuecomment-599355836
|
||||
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(boardPort);
|
||||
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
|
||||
boardPort
|
||||
);
|
||||
if (lastSelectedBoard) {
|
||||
board = {
|
||||
...lastSelectedBoard,
|
||||
port: boardPort
|
||||
port: boardPort,
|
||||
};
|
||||
state = AvailableBoard.State.guessed;
|
||||
}
|
||||
}
|
||||
if (!board) {
|
||||
availableBoards.push({ name: 'Unknown', port: boardPort, state });
|
||||
availableBoards.push({
|
||||
name: 'Unknown',
|
||||
port: boardPort,
|
||||
state,
|
||||
});
|
||||
} else {
|
||||
const selected = BoardsConfig.Config.sameAs(boardsConfig, board);
|
||||
availableBoards.push({ ...board, state, selected, port: boardPort });
|
||||
const selected = BoardsConfig.Config.sameAs(
|
||||
boardsConfig,
|
||||
board
|
||||
);
|
||||
availableBoards.push({
|
||||
...board,
|
||||
state,
|
||||
selected,
|
||||
port: boardPort,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (boardsConfig.selectedBoard && !availableBoards.some(({ selected }) => selected)) {
|
||||
if (
|
||||
boardsConfig.selectedBoard &&
|
||||
!availableBoards.some(({ selected }) => selected)
|
||||
) {
|
||||
availableBoards.push({
|
||||
...boardsConfig.selectedBoard,
|
||||
port: boardsConfig.selectedPort,
|
||||
selected: true,
|
||||
state: AvailableBoard.State.incomplete
|
||||
state: AvailableBoard.State.incomplete,
|
||||
});
|
||||
}
|
||||
|
||||
const sortedAvailableBoards = availableBoards.sort(AvailableBoard.compare);
|
||||
let hasChanged = sortedAvailableBoards.length !== currentAvailableBoards.length;
|
||||
const sortedAvailableBoards = availableBoards.sort(
|
||||
AvailableBoard.compare
|
||||
);
|
||||
let hasChanged =
|
||||
sortedAvailableBoards.length !== currentAvailableBoards.length;
|
||||
for (let i = 0; !hasChanged && i < sortedAvailableBoards.length; i++) {
|
||||
hasChanged = AvailableBoard.compare(sortedAvailableBoards[i], currentAvailableBoards[i]) !== 0;
|
||||
hasChanged =
|
||||
AvailableBoard.compare(
|
||||
sortedAvailableBoards[i],
|
||||
currentAvailableBoards[i]
|
||||
) !== 0;
|
||||
}
|
||||
if (hasChanged) {
|
||||
this._availableBoards = sortedAvailableBoards;
|
||||
@ -342,7 +480,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
}
|
||||
}
|
||||
|
||||
protected async getLastSelectedBoardOnPort(port: Port | string | undefined): Promise<Board | undefined> {
|
||||
protected async getLastSelectedBoardOnPort(
|
||||
port: Port | string | undefined
|
||||
): Promise<Board | undefined> {
|
||||
if (!port) {
|
||||
return undefined;
|
||||
}
|
||||
@ -361,18 +501,25 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
await this.setData(key, selectedBoard);
|
||||
}
|
||||
await Promise.all([
|
||||
this.setData('latest-valid-boards-config', this.latestValidBoardsConfig),
|
||||
this.setData('latest-boards-config', this.latestBoardsConfig)
|
||||
this.setData(
|
||||
'latest-valid-boards-config',
|
||||
this.latestValidBoardsConfig
|
||||
),
|
||||
this.setData('latest-boards-config', this.latestBoardsConfig),
|
||||
]);
|
||||
}
|
||||
|
||||
protected getLastSelectedBoardOnPortKey(port: Port | string): string {
|
||||
// TODO: we lose the port's `protocol` info (`serial`, `network`, etc.) here if the `port` is a `string`.
|
||||
return `last-selected-board-on-port:${typeof port === 'string' ? port : Port.toString(port)}`;
|
||||
return `last-selected-board-on-port:${
|
||||
typeof port === 'string' ? port : Port.toString(port)
|
||||
}`;
|
||||
}
|
||||
|
||||
protected async loadState(): Promise<void> {
|
||||
const storedLatestValidBoardsConfig = await this.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
||||
const storedLatestValidBoardsConfig = await this.getData<
|
||||
RecursiveRequired<BoardsConfig.Config>
|
||||
>('latest-valid-boards-config');
|
||||
if (storedLatestValidBoardsConfig) {
|
||||
this.latestValidBoardsConfig = storedLatestValidBoardsConfig;
|
||||
if (this.canUploadTo(this.latestValidBoardsConfig)) {
|
||||
@ -380,10 +527,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
}
|
||||
} else {
|
||||
// If we could not restore the latest valid config, try to restore something, the board at least.
|
||||
let storedLatestBoardsConfig = await this.getData<BoardsConfig.Config | undefined>('latest-boards-config');
|
||||
let storedLatestBoardsConfig = await this.getData<
|
||||
BoardsConfig.Config | undefined
|
||||
>('latest-boards-config');
|
||||
// Try to get from the URL if it was not persisted.
|
||||
if (!storedLatestBoardsConfig) {
|
||||
storedLatestBoardsConfig = BoardsConfig.Config.getConfig(new URL(window.location.href));
|
||||
storedLatestBoardsConfig = BoardsConfig.Config.getConfig(
|
||||
new URL(window.location.href)
|
||||
);
|
||||
}
|
||||
if (storedLatestBoardsConfig) {
|
||||
this.latestBoardsConfig = storedLatestBoardsConfig;
|
||||
@ -393,11 +544,18 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
private setData<T>(key: string, value: T): Promise<void> {
|
||||
return this.commandService.executeCommand(StorageWrapper.Commands.SET_DATA.id, key, value);
|
||||
return this.commandService.executeCommand(
|
||||
StorageWrapper.Commands.SET_DATA.id,
|
||||
key,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
private getData<T>(key: string): Promise<T | undefined> {
|
||||
return this.commandService.executeCommand<T>(StorageWrapper.Commands.GET_DATA.id, key);
|
||||
return this.commandService.executeCommand<T>(
|
||||
StorageWrapper.Commands.GET_DATA.id,
|
||||
key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,7 +572,6 @@ export interface AvailableBoard extends Board {
|
||||
}
|
||||
|
||||
export namespace AvailableBoard {
|
||||
|
||||
export enum State {
|
||||
/**
|
||||
* Retrieved from the CLI via the `board list` command.
|
||||
@ -427,14 +584,16 @@ export namespace AvailableBoard {
|
||||
/**
|
||||
* We do not know anything about this board, probably a 3rd party. The user has not selected a board for this port yet.
|
||||
*/
|
||||
'incomplete'
|
||||
'incomplete',
|
||||
}
|
||||
|
||||
export function is(board: any): board is AvailableBoard {
|
||||
return Board.is(board) && 'state' in board;
|
||||
}
|
||||
|
||||
export function hasPort(board: AvailableBoard): board is AvailableBoard & { port: Port } {
|
||||
export function hasPort(
|
||||
board: AvailableBoard
|
||||
): board is AvailableBoard & { port: Port } {
|
||||
return !!board.port;
|
||||
}
|
||||
|
||||
@ -468,6 +627,5 @@ export namespace AvailableBoard {
|
||||
return 1;
|
||||
}
|
||||
return left.state - right.state;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { Port } from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { ArduinoCommands } from '../arduino-commands';
|
||||
import { BoardsServiceProvider, AvailableBoard } from './boards-service-provider';
|
||||
import {
|
||||
BoardsServiceProvider,
|
||||
AvailableBoard,
|
||||
} from './boards-service-provider';
|
||||
|
||||
export interface BoardsDropDownListCoords {
|
||||
readonly top: number;
|
||||
@ -17,13 +20,14 @@ export interface BoardsDropDownListCoords {
|
||||
export namespace BoardsDropDown {
|
||||
export interface Props {
|
||||
readonly coords: BoardsDropDownListCoords | 'hidden';
|
||||
readonly items: Array<AvailableBoard & { onClick: () => void, port: Port }>;
|
||||
readonly items: Array<
|
||||
AvailableBoard & { onClick: () => void; port: Port }
|
||||
>;
|
||||
readonly openBoardsConfig: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
||||
|
||||
protected dropdownElement: HTMLElement;
|
||||
|
||||
constructor(props: BoardsDropDown.Props) {
|
||||
@ -47,35 +51,61 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
||||
if (coords === 'hidden') {
|
||||
return '';
|
||||
}
|
||||
return <div className='arduino-boards-dropdown-list'
|
||||
return (
|
||||
<div
|
||||
className="arduino-boards-dropdown-list"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
...coords
|
||||
}}>
|
||||
...coords,
|
||||
}}
|
||||
>
|
||||
{this.renderItem({
|
||||
label: 'Select Other Board & Port',
|
||||
onClick: () => this.props.openBoardsConfig()
|
||||
onClick: () => this.props.openBoardsConfig(),
|
||||
})}
|
||||
{items.map(({ name, port, selected, onClick }) => ({ label: `${name} at ${Port.toString(port)}`, selected, onClick })).map(this.renderItem)}
|
||||
{items
|
||||
.map(({ name, port, selected, onClick }) => ({
|
||||
label: `${name} at ${Port.toString(port)}`,
|
||||
selected,
|
||||
onClick,
|
||||
}))
|
||||
.map(this.renderItem)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderItem({ label, selected, onClick }: { label: string, selected?: boolean, onClick: () => void }): React.ReactNode {
|
||||
return <div key={label} className={`arduino-boards-dropdown-item ${selected ? 'selected' : ''}`} onClick={onClick}>
|
||||
<div>
|
||||
{label}
|
||||
</div>
|
||||
{selected ? <span className='fa fa-check' /> : ''}
|
||||
protected renderItem({
|
||||
label,
|
||||
selected,
|
||||
onClick,
|
||||
}: {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
onClick: () => void;
|
||||
}): React.ReactNode {
|
||||
return (
|
||||
<div
|
||||
key={label}
|
||||
className={`arduino-boards-dropdown-item ${
|
||||
selected ? 'selected' : ''
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div>{label}</div>
|
||||
{selected ? <span className="fa fa-check" /> : ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props, BoardsToolBarItem.State> {
|
||||
|
||||
export class BoardsToolBarItem extends React.Component<
|
||||
BoardsToolBarItem.Props,
|
||||
BoardsToolBarItem.State
|
||||
> {
|
||||
static TOOLBAR_ID: 'boards-toolbar';
|
||||
|
||||
protected readonly toDispose: DisposableCollection = new DisposableCollection();
|
||||
protected readonly toDispose: DisposableCollection =
|
||||
new DisposableCollection();
|
||||
|
||||
constructor(props: BoardsToolBarItem.Props) {
|
||||
super(props);
|
||||
@ -83,7 +113,7 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
const { availableBoards } = props.boardsServiceClient;
|
||||
this.state = {
|
||||
availableBoards,
|
||||
coords: 'hidden'
|
||||
coords: 'hidden',
|
||||
};
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
@ -92,7 +122,9 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.boardsServiceClient.onAvailableBoardsChanged(availableBoards => this.setState({ availableBoards }));
|
||||
this.props.boardsServiceClient.onAvailableBoardsChanged(
|
||||
(availableBoards) => this.setState({ availableBoards })
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
@ -109,8 +141,8 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
paddingTop: rect.height
|
||||
}
|
||||
paddingTop: rect.height,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.setState({ coords: 'hidden' });
|
||||
@ -123,63 +155,76 @@ export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props,
|
||||
render(): React.ReactNode {
|
||||
const { coords, availableBoards } = this.state;
|
||||
const boardsConfig = this.props.boardsServiceClient.boardsConfig;
|
||||
const title = BoardsConfig.Config.toString(boardsConfig, { default: 'no board selected' });
|
||||
const title = BoardsConfig.Config.toString(boardsConfig, {
|
||||
default: 'no board selected',
|
||||
});
|
||||
const decorator = (() => {
|
||||
const selectedBoard = availableBoards.find(({ selected }) => selected);
|
||||
const selectedBoard = availableBoards.find(
|
||||
({ selected }) => selected
|
||||
);
|
||||
if (!selectedBoard || !selectedBoard.port) {
|
||||
return 'fa fa-times notAttached'
|
||||
return 'fa fa-times notAttached';
|
||||
}
|
||||
if (selectedBoard.state === AvailableBoard.State.guessed) {
|
||||
return 'fa fa-exclamation-triangle guessed'
|
||||
return 'fa fa-exclamation-triangle guessed';
|
||||
}
|
||||
return ''
|
||||
return '';
|
||||
})();
|
||||
|
||||
return <React.Fragment>
|
||||
<div className='arduino-boards-toolbar-item-container'>
|
||||
<div className='arduino-boards-toolbar-item' title={title}>
|
||||
<div className='inner-container' onClick={this.show}>
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="arduino-boards-toolbar-item-container">
|
||||
<div className="arduino-boards-toolbar-item" title={title}>
|
||||
<div className="inner-container" onClick={this.show}>
|
||||
<span className={decorator} />
|
||||
<div className='label noWrapInfo'>
|
||||
<div className='noWrapInfo noselect'>
|
||||
<div className="label noWrapInfo">
|
||||
<div className="noWrapInfo noselect">
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<span className='fa fa-caret-down caret' />
|
||||
<span className="fa fa-caret-down caret" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BoardsDropDown
|
||||
coords={coords}
|
||||
items={availableBoards.filter(AvailableBoard.hasPort).map(board => ({
|
||||
items={availableBoards
|
||||
.filter(AvailableBoard.hasPort)
|
||||
.map((board) => ({
|
||||
...board,
|
||||
onClick: () => {
|
||||
if (board.state === AvailableBoard.State.incomplete) {
|
||||
this.props.boardsServiceClient.boardsConfig = {
|
||||
selectedPort: board.port
|
||||
if (
|
||||
board.state ===
|
||||
AvailableBoard.State.incomplete
|
||||
) {
|
||||
this.props.boardsServiceClient.boardsConfig =
|
||||
{
|
||||
selectedPort: board.port,
|
||||
};
|
||||
this.openDialog();
|
||||
} else {
|
||||
this.props.boardsServiceClient.boardsConfig = {
|
||||
this.props.boardsServiceClient.boardsConfig =
|
||||
{
|
||||
selectedBoard: board,
|
||||
selectedPort: board.port
|
||||
}
|
||||
}
|
||||
selectedPort: board.port,
|
||||
};
|
||||
}
|
||||
},
|
||||
}))}
|
||||
openBoardsConfig={this.openDialog}>
|
||||
</BoardsDropDown>
|
||||
</React.Fragment>;
|
||||
openBoardsConfig={this.openDialog}
|
||||
></BoardsDropDown>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
protected openDialog = () => {
|
||||
this.props.commands.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id);
|
||||
this.props.commands.executeCommand(
|
||||
ArduinoCommands.OPEN_BOARDS_DIALOG.id
|
||||
);
|
||||
this.setState({ coords: 'hidden' });
|
||||
};
|
||||
|
||||
}
|
||||
export namespace BoardsToolBarItem {
|
||||
|
||||
export interface Props {
|
||||
readonly boardsServiceClient: BoardsServiceProvider;
|
||||
readonly commands: CommandRegistry;
|
||||
@ -189,5 +234,4 @@ export namespace BoardsToolBarItem {
|
||||
availableBoards: AvailableBoard[];
|
||||
coords: BoardsDropDownListCoords | 'hidden';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,22 +5,20 @@ import { ListWidgetFrontendContribution } from '../widgets/component-list/list-w
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
widgetId: BoardsListWidget.WIDGET_ID,
|
||||
widgetName: BoardsListWidget.WIDGET_LABEL,
|
||||
defaultWidgetOptions: {
|
||||
area: 'left',
|
||||
rank: 2
|
||||
rank: 2,
|
||||
},
|
||||
toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
|
||||
toggleKeybinding: 'CtrlCmd+Shift+B'
|
||||
toggleKeybinding: 'CtrlCmd+Shift+B',
|
||||
});
|
||||
}
|
||||
|
||||
async initializeLayout(): Promise<void> {
|
||||
this.openView();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,13 +4,17 @@ import { remote } from 'electron';
|
||||
import { isOSX, isWindows } from '@theia/core/lib/common/os';
|
||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
||||
import { Contribution, Command, MenuModelRegistry, CommandRegistry } from './contribution';
|
||||
import {
|
||||
Contribution,
|
||||
Command,
|
||||
MenuModelRegistry,
|
||||
CommandRegistry,
|
||||
} from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ConfigService } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class About extends Contribution {
|
||||
|
||||
@inject(ClipboardService)
|
||||
protected readonly clipboardService: ClipboardService;
|
||||
|
||||
@ -19,7 +23,7 @@ export class About extends Contribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(About.Commands.ABOUT_APP, {
|
||||
execute: () => this.showAbout()
|
||||
execute: () => this.showAbout(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -27,15 +31,23 @@ export class About extends Contribution {
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__ABOUT_GROUP, {
|
||||
commandId: About.Commands.ABOUT_APP.id,
|
||||
label: `About ${this.applicationName}`,
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
}
|
||||
|
||||
async showAbout(): Promise<void> {
|
||||
const { version, commit, status: cliStatus } = await this.configService.getVersion();
|
||||
const {
|
||||
version,
|
||||
commit,
|
||||
status: cliStatus,
|
||||
} = await this.configService.getVersion();
|
||||
const buildDate = this.buildDate;
|
||||
const detail = (showAll: boolean) => `Version: ${remote.app.getVersion()}
|
||||
Date: ${buildDate ? buildDate : 'dev build'}${buildDate && showAll ? ` (${this.ago(buildDate)})` : ''}
|
||||
const detail = (
|
||||
showAll: boolean
|
||||
) => `Version: ${remote.app.getVersion()}
|
||||
Date: ${buildDate ? buildDate : 'dev build'}${
|
||||
buildDate && showAll ? ` (${this.ago(buildDate)})` : ''
|
||||
}
|
||||
CLI Version: ${version}${cliStatus ? ` ${cliStatus}` : ''} [${commit}]
|
||||
|
||||
${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
|
||||
@ -43,7 +55,9 @@ ${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
|
||||
const ok = 'OK';
|
||||
const copy = 'Copy';
|
||||
const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy];
|
||||
const { response } = await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const { response } = await remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
message: `${this.applicationName}`,
|
||||
title: `${this.applicationName}`,
|
||||
type: 'info',
|
||||
@ -51,8 +65,9 @@ ${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
|
||||
buttons,
|
||||
noLink: true,
|
||||
defaultId: buttons.indexOf(ok),
|
||||
cancelId: buttons.indexOf(ok)
|
||||
});
|
||||
cancelId: buttons.indexOf(ok),
|
||||
}
|
||||
);
|
||||
|
||||
if (buttons[response] === copy) {
|
||||
await this.clipboardService.writeText(detail(false).trim());
|
||||
@ -72,7 +87,9 @@ ${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
|
||||
const other = moment(isoTime);
|
||||
let result = now.diff(other, 'minute');
|
||||
if (result < 60) {
|
||||
return result === 1 ? `${result} minute ago` : `${result} minute ago`;
|
||||
return result === 1
|
||||
? `${result} minute ago`
|
||||
: `${result} minute ago`;
|
||||
}
|
||||
result = now.diff(other, 'hour');
|
||||
if (result < 25) {
|
||||
@ -88,18 +105,19 @@ ${showAll ? `Copyright © ${new Date().getFullYear()} Arduino SA` : ''}
|
||||
}
|
||||
result = now.diff(other, 'month');
|
||||
if (result < 13) {
|
||||
return result === 1 ? `${result} month ago` : `${result} months ago`;
|
||||
return result === 1
|
||||
? `${result} month ago`
|
||||
: `${result} months ago`;
|
||||
}
|
||||
result = now.diff(other, 'year');
|
||||
return result === 1 ? `${result} year ago` : `${result} years ago`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace About {
|
||||
export namespace Commands {
|
||||
export const ABOUT_APP: Command = {
|
||||
id: 'arduino-about'
|
||||
id: 'arduino-about',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,23 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, URI } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
URI,
|
||||
} from './contribution';
|
||||
import { FileDialogService } from '@theia/filesystem/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class AddFile extends SketchContribution {
|
||||
|
||||
@inject(FileDialogService)
|
||||
protected readonly fileDialogService: FileDialogService;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(AddFile.Commands.ADD_FILE, {
|
||||
execute: () => this.addFile()
|
||||
execute: () => this.addFile(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -20,7 +25,7 @@ export class AddFile extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
|
||||
commandId: AddFile.Commands.ADD_FILE.id,
|
||||
label: 'Add File...',
|
||||
order: '2'
|
||||
order: '2',
|
||||
});
|
||||
}
|
||||
|
||||
@ -33,7 +38,7 @@ export class AddFile extends SketchContribution {
|
||||
title: 'Add File',
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false
|
||||
canSelectMany: false,
|
||||
});
|
||||
if (!toAddUri) {
|
||||
return;
|
||||
@ -47,22 +52,24 @@ export class AddFile extends SketchContribution {
|
||||
type: 'question',
|
||||
title: 'Replace',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
message: `Replace the existing version of ${filename}?`
|
||||
message: `Replace the existing version of ${filename}?`,
|
||||
});
|
||||
if (response === 0) { // Cancel
|
||||
if (response === 0) {
|
||||
// Cancel
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.fileService.copy(toAddUri, targetUri, { overwrite: true });
|
||||
this.messageService.info('One file added to the sketch.', { timeout: 2000 });
|
||||
this.messageService.info('One file added to the sketch.', {
|
||||
timeout: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace AddFile {
|
||||
export namespace Commands {
|
||||
export const ADD_FILE: Command = {
|
||||
id: 'arduino-add-file'
|
||||
id: 'arduino-add-file',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,15 @@ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ResponseServiceImpl } from '../response-service-impl';
|
||||
import { Installable, LibraryService } from '../../common/protocol';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class AddZipLibrary extends SketchContribution {
|
||||
|
||||
@inject(EnvVariablesServer)
|
||||
protected readonly envVariableServer: EnvVariablesServer;
|
||||
|
||||
@ -22,18 +26,23 @@ export class AddZipLibrary extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(AddZipLibrary.Commands.ADD_ZIP_LIBRARY, {
|
||||
execute: () => this.addZipLibrary()
|
||||
execute: () => this.addZipLibrary(),
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
const includeLibMenuPath = [...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include'];
|
||||
const includeLibMenuPath = [
|
||||
...ArduinoMenus.SKETCH__UTILS_GROUP,
|
||||
'0_include',
|
||||
];
|
||||
// TODO: do we need it? calling `registerSubmenu` multiple times is noop, so it does not hurt.
|
||||
registry.registerSubmenu(includeLibMenuPath, 'Include Library', { order: '1' });
|
||||
registry.registerSubmenu(includeLibMenuPath, 'Include Library', {
|
||||
order: '1',
|
||||
});
|
||||
registry.registerMenuAction([...includeLibMenuPath, '1_install'], {
|
||||
commandId: AddZipLibrary.Commands.ADD_ZIP_LIBRARY.id,
|
||||
label: 'Add .ZIP Library...',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
}
|
||||
|
||||
@ -47,9 +56,9 @@ export class AddZipLibrary extends SketchContribution {
|
||||
filters: [
|
||||
{
|
||||
name: 'Library',
|
||||
extensions: ['zip']
|
||||
}
|
||||
]
|
||||
extensions: ['zip'],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!canceled && filePaths.length) {
|
||||
const zipUri = await this.fileSystemExt.getUri(filePaths[0]);
|
||||
@ -61,7 +70,7 @@ export class AddZipLibrary extends SketchContribution {
|
||||
msg: error.message,
|
||||
title: 'Do you want to overwrite the existing library?',
|
||||
ok: 'Yes',
|
||||
cancel: 'No'
|
||||
cancel: 'No',
|
||||
}).open();
|
||||
if (result) {
|
||||
await this.doInstall(zipUri, true);
|
||||
@ -71,24 +80,40 @@ export class AddZipLibrary extends SketchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
private async doInstall(zipUri: string, overwrite?: boolean): Promise<void> {
|
||||
private async doInstall(
|
||||
zipUri: string,
|
||||
overwrite?: boolean
|
||||
): Promise<void> {
|
||||
try {
|
||||
await Installable.doWithProgress({
|
||||
messageService: this.messageService,
|
||||
progressText: `Processing ${new URI(zipUri).path.base}`,
|
||||
responseService: this.responseService,
|
||||
run: () => this.libraryService.installZip({ zipUri, overwrite })
|
||||
run: () =>
|
||||
this.libraryService.installZip({ zipUri, overwrite }),
|
||||
});
|
||||
this.messageService.info(`Successfully installed library from ${new URI(zipUri).path.base} archive`, { timeout: 3000 });
|
||||
this.messageService.info(
|
||||
`Successfully installed library from ${
|
||||
new URI(zipUri).path.base
|
||||
} archive`,
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
const match = error.message.match(/library (.*?) already installed/);
|
||||
const match = error.message.match(
|
||||
/library (.*?) already installed/
|
||||
);
|
||||
if (match && match.length >= 2) {
|
||||
const name = match[1].trim();
|
||||
if (name) {
|
||||
throw new AlreadyInstalledError(`A library folder named ${name} already exists. Do you want to overwrite it?`, name);
|
||||
throw new AlreadyInstalledError(
|
||||
`A library folder named ${name} already exists. Do you want to overwrite it?`,
|
||||
name
|
||||
);
|
||||
} else {
|
||||
throw new AlreadyInstalledError('A library already exists. Do you want to overwrite it?');
|
||||
throw new AlreadyInstalledError(
|
||||
'A library already exists. Do you want to overwrite it?'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,22 +121,19 @@ export class AddZipLibrary extends SketchContribution {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AlreadyInstalledError extends Error {
|
||||
|
||||
constructor(message: string, readonly libraryName?: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, AlreadyInstalledError.prototype);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace AddZipLibrary {
|
||||
export namespace Commands {
|
||||
export const ADD_ZIP_LIBRARY: Command = {
|
||||
id: 'arduino-add-zip-library'
|
||||
id: 'arduino-add-zip-library',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,18 @@ import { remote } from 'electron';
|
||||
import * as dateFormat from 'dateformat';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class ArchiveSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, {
|
||||
execute: () => this.archiveSketch()
|
||||
execute: () => this.archiveSketch(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -18,21 +22,29 @@ export class ArchiveSketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id,
|
||||
label: 'Archive Sketch',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
}
|
||||
|
||||
protected async archiveSketch(): Promise<void> {
|
||||
const [sketch, config] = await Promise.all([
|
||||
this.sketchServiceClient.currentSketch(),
|
||||
this.configService.getConfiguration()
|
||||
this.configService.getConfiguration(),
|
||||
]);
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
const archiveBasename = `${sketch.name}-${dateFormat(new Date(), 'yymmdd')}a.zip`;
|
||||
const defaultPath = await this.fileService.fsPath(new URI(config.sketchDirUri).resolve(archiveBasename));
|
||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
||||
const archiveBasename = `${sketch.name}-${dateFormat(
|
||||
new Date(),
|
||||
'yymmdd'
|
||||
)}a.zip`;
|
||||
const defaultPath = await this.fileService.fsPath(
|
||||
new URI(config.sketchDirUri).resolve(archiveBasename)
|
||||
);
|
||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({
|
||||
title: 'Save sketch folder as...',
|
||||
defaultPath,
|
||||
});
|
||||
if (!filePath || canceled) {
|
||||
return;
|
||||
}
|
||||
@ -41,15 +53,16 @@ export class ArchiveSketch extends SketchContribution {
|
||||
return;
|
||||
}
|
||||
await this.sketchService.archive(sketch, destinationUri.toString());
|
||||
this.messageService.info(`Created archive '${archiveBasename}'.`, { timeout: 2000 });
|
||||
this.messageService.info(`Created archive '${archiveBasename}'.`, {
|
||||
timeout: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace ArchiveSketch {
|
||||
export namespace Commands {
|
||||
export const ARCHIVE_SKETCH: Command = {
|
||||
id: 'arduino-archive-sketch'
|
||||
id: 'arduino-archive-sketch',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,31 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
DisposableCollection,
|
||||
Disposable,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { firstToUpperCase } from '../../common/utils';
|
||||
import { BoardsConfig } from '../boards/boards-config';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { BoardsListWidget } from '../boards/boards-list-widget';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ArduinoMenus, PlaceholderMenuNode, unregisterSubmenu } from '../menu/arduino-menus';
|
||||
import { BoardsService, InstalledBoardWithPackage, AvailablePorts, Port } from '../../common/protocol';
|
||||
import {
|
||||
ArduinoMenus,
|
||||
PlaceholderMenuNode,
|
||||
unregisterSubmenu,
|
||||
} from '../menu/arduino-menus';
|
||||
import {
|
||||
BoardsService,
|
||||
InstalledBoardWithPackage,
|
||||
AvailablePorts,
|
||||
Port,
|
||||
} from '../../common/protocol';
|
||||
import { SketchContribution, Command, CommandRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class BoardSelection extends SketchContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@ -38,81 +49,138 @@ export class BoardSelection extends SketchContribution {
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, {
|
||||
execute: async () => {
|
||||
const { selectedBoard, selectedPort } = this.boardsServiceProvider.boardsConfig;
|
||||
const { selectedBoard, selectedPort } =
|
||||
this.boardsServiceProvider.boardsConfig;
|
||||
if (!selectedBoard) {
|
||||
this.messageService.info('Please select a board to obtain board info.');
|
||||
this.messageService.info(
|
||||
'Please select a board to obtain board info.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!selectedBoard.fqbn) {
|
||||
this.messageService.info(`The platform for the selected '${selectedBoard.name}' board is not installed.`);
|
||||
this.messageService.info(
|
||||
`The platform for the selected '${selectedBoard.name}' board is not installed.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!selectedPort) {
|
||||
this.messageService.info('Please select a port to obtain board info.');
|
||||
this.messageService.info(
|
||||
'Please select a port to obtain board info.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const boardDetails = await this.boardsService.getBoardDetails({ fqbn: selectedBoard.fqbn });
|
||||
const boardDetails = await this.boardsService.getBoardDetails({
|
||||
fqbn: selectedBoard.fqbn,
|
||||
});
|
||||
if (boardDetails) {
|
||||
const { VID, PID } = boardDetails;
|
||||
const detail = `BN: ${selectedBoard.name}
|
||||
VID: ${VID}
|
||||
PID: ${PID}`;
|
||||
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
await remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
message: 'Board Info',
|
||||
title: 'Board Info',
|
||||
type: 'info',
|
||||
detail,
|
||||
buttons: ['OK']
|
||||
});
|
||||
buttons: ['OK'],
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onStart(): void {
|
||||
this.updateMenus();
|
||||
this.notificationCenter.onPlatformInstalled(this.updateMenus.bind(this));
|
||||
this.notificationCenter.onPlatformUninstalled(this.updateMenus.bind(this));
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(this.updateMenus.bind(this));
|
||||
this.boardsServiceProvider.onAvailableBoardsChanged(this.updateMenus.bind(this));
|
||||
this.notificationCenter.onPlatformInstalled(
|
||||
this.updateMenus.bind(this)
|
||||
);
|
||||
this.notificationCenter.onPlatformUninstalled(
|
||||
this.updateMenus.bind(this)
|
||||
);
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(
|
||||
this.updateMenus.bind(this)
|
||||
);
|
||||
this.boardsServiceProvider.onAvailableBoardsChanged(
|
||||
this.updateMenus.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
protected async updateMenus(): Promise<void> {
|
||||
const [installedBoards, availablePorts, config] = await Promise.all([
|
||||
this.installedBoards(),
|
||||
this.boardsService.getState(),
|
||||
this.boardsServiceProvider.boardsConfig
|
||||
this.boardsServiceProvider.boardsConfig,
|
||||
]);
|
||||
this.rebuildMenus(installedBoards, availablePorts, config);
|
||||
}
|
||||
|
||||
protected rebuildMenus(installedBoards: InstalledBoardWithPackage[], availablePorts: AvailablePorts, config: BoardsConfig.Config): void {
|
||||
protected rebuildMenus(
|
||||
installedBoards: InstalledBoardWithPackage[],
|
||||
availablePorts: AvailablePorts,
|
||||
config: BoardsConfig.Config
|
||||
): void {
|
||||
this.toDisposeBeforeMenuRebuild.dispose();
|
||||
|
||||
// Boards submenu
|
||||
const boardsSubmenuPath = [...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, '1_boards'];
|
||||
const boardsSubmenuPath = [
|
||||
...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
|
||||
'1_boards',
|
||||
];
|
||||
const boardsSubmenuLabel = config.selectedBoard?.name;
|
||||
// Note: The submenu order starts from `100` because `Auto Format`, `Serial Monitor`, etc starts from `0` index.
|
||||
// The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order.
|
||||
this.menuModelRegistry.registerSubmenu(boardsSubmenuPath, `Board${!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''}`, { order: '100' });
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => unregisterSubmenu(boardsSubmenuPath, this.menuModelRegistry)));
|
||||
this.menuModelRegistry.registerSubmenu(
|
||||
boardsSubmenuPath,
|
||||
`Board${!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''}`,
|
||||
{ order: '100' }
|
||||
);
|
||||
this.toDisposeBeforeMenuRebuild.push(
|
||||
Disposable.create(() =>
|
||||
unregisterSubmenu(boardsSubmenuPath, this.menuModelRegistry)
|
||||
)
|
||||
);
|
||||
|
||||
// Ports submenu
|
||||
const portsSubmenuPath = [...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, '2_ports'];
|
||||
const portsSubmenuPath = [
|
||||
...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
|
||||
'2_ports',
|
||||
];
|
||||
const portsSubmenuLabel = config.selectedPort?.address;
|
||||
this.menuModelRegistry.registerSubmenu(portsSubmenuPath, `Port${!!portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''}`, { order: '101' });
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => unregisterSubmenu(portsSubmenuPath, this.menuModelRegistry)));
|
||||
this.menuModelRegistry.registerSubmenu(
|
||||
portsSubmenuPath,
|
||||
`Port${!!portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''}`,
|
||||
{ order: '101' }
|
||||
);
|
||||
this.toDisposeBeforeMenuRebuild.push(
|
||||
Disposable.create(() =>
|
||||
unregisterSubmenu(portsSubmenuPath, this.menuModelRegistry)
|
||||
)
|
||||
);
|
||||
|
||||
const getBoardInfo = { commandId: BoardSelection.Commands.GET_BOARD_INFO.id, label: 'Get Board Info', order: '103' };
|
||||
this.menuModelRegistry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP, getBoardInfo);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.menuModelRegistry.unregisterMenuAction(getBoardInfo)));
|
||||
const getBoardInfo = {
|
||||
commandId: BoardSelection.Commands.GET_BOARD_INFO.id,
|
||||
label: 'Get Board Info',
|
||||
order: '103',
|
||||
};
|
||||
this.menuModelRegistry.registerMenuAction(
|
||||
ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
|
||||
getBoardInfo
|
||||
);
|
||||
this.toDisposeBeforeMenuRebuild.push(
|
||||
Disposable.create(() =>
|
||||
this.menuModelRegistry.unregisterMenuAction(getBoardInfo)
|
||||
)
|
||||
);
|
||||
|
||||
const boardsManagerGroup = [...boardsSubmenuPath, '0_manager'];
|
||||
const boardsPackagesGroup = [...boardsSubmenuPath, '1_packages'];
|
||||
|
||||
this.menuModelRegistry.registerMenuAction(boardsManagerGroup, {
|
||||
commandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
|
||||
label: 'Boards Manager...'
|
||||
label: 'Boards Manager...',
|
||||
});
|
||||
|
||||
// Installed boards
|
||||
@ -122,31 +190,50 @@ PID: ${PID}`;
|
||||
// Platform submenu
|
||||
const platformMenuPath = [...boardsPackagesGroup, packageId];
|
||||
// Note: Registering the same submenu twice is a noop. No need to group the boards per platform.
|
||||
this.menuModelRegistry.registerSubmenu(platformMenuPath, packageName);
|
||||
this.menuModelRegistry.registerSubmenu(
|
||||
platformMenuPath,
|
||||
packageName
|
||||
);
|
||||
|
||||
const id = `arduino-select-board--${fqbn}`;
|
||||
const command = { id };
|
||||
const handler = {
|
||||
execute: () => {
|
||||
if (fqbn !== this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn) {
|
||||
if (
|
||||
fqbn !==
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard
|
||||
?.fqbn
|
||||
) {
|
||||
this.boardsServiceProvider.boardsConfig = {
|
||||
selectedBoard: {
|
||||
name,
|
||||
fqbn,
|
||||
port: this.boardsServiceProvider.boardsConfig.selectedBoard?.port // TODO: verify!
|
||||
port: this.boardsServiceProvider.boardsConfig
|
||||
.selectedBoard?.port, // TODO: verify!
|
||||
},
|
||||
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort
|
||||
}
|
||||
selectedPort:
|
||||
this.boardsServiceProvider.boardsConfig
|
||||
.selectedPort,
|
||||
};
|
||||
}
|
||||
},
|
||||
isToggled: () => fqbn === this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn
|
||||
isToggled: () =>
|
||||
fqbn ===
|
||||
this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn,
|
||||
};
|
||||
|
||||
// Board menu
|
||||
const menuAction = { commandId: id, label: name };
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||
this.menuModelRegistry.registerMenuAction(platformMenuPath, menuAction);
|
||||
this.toDisposeBeforeMenuRebuild.push(
|
||||
Disposable.create(() =>
|
||||
this.commandRegistry.unregisterCommand(command)
|
||||
)
|
||||
);
|
||||
this.menuModelRegistry.registerMenuAction(
|
||||
platformMenuPath,
|
||||
menuAction
|
||||
);
|
||||
// Note: we do not dispose the menu actions individually. Calling `unregisterSubmenu` on the parent will wipe the children menu nodes recursively.
|
||||
}
|
||||
|
||||
@ -161,47 +248,77 @@ PID: ${PID}`;
|
||||
const [port] = ports[addresses[0]];
|
||||
const protocol = port.protocol;
|
||||
const menuPath = [...portsSubmenuPath, protocol];
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, `${firstToUpperCase(port.protocol)} ports`);
|
||||
const placeholder = new PlaceholderMenuNode(
|
||||
menuPath,
|
||||
`${firstToUpperCase(port.protocol)} ports`
|
||||
);
|
||||
this.menuModelRegistry.registerMenuNode(menuPath, placeholder);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.menuModelRegistry.unregisterMenuNode(placeholder.id)));
|
||||
this.toDisposeBeforeMenuRebuild.push(
|
||||
Disposable.create(() =>
|
||||
this.menuModelRegistry.unregisterMenuNode(placeholder.id)
|
||||
)
|
||||
);
|
||||
|
||||
for (const address of addresses) {
|
||||
if (!!ports[address]) {
|
||||
const [port, boards] = ports[address];
|
||||
if (!boards.length) {
|
||||
boards.push({
|
||||
name: ''
|
||||
name: '',
|
||||
});
|
||||
}
|
||||
for (const { name, fqbn } of boards) {
|
||||
const id = `arduino-select-port--${address}${fqbn ? `--${fqbn}` : ''}`;
|
||||
const id = `arduino-select-port--${address}${
|
||||
fqbn ? `--${fqbn}` : ''
|
||||
}`;
|
||||
const command = { id };
|
||||
const handler = {
|
||||
execute: () => {
|
||||
if (!Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)) {
|
||||
if (
|
||||
!Port.equals(
|
||||
port,
|
||||
this.boardsServiceProvider.boardsConfig
|
||||
.selectedPort
|
||||
)
|
||||
) {
|
||||
this.boardsServiceProvider.boardsConfig = {
|
||||
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
|
||||
selectedPort: port
|
||||
}
|
||||
selectedBoard:
|
||||
this.boardsServiceProvider
|
||||
.boardsConfig.selectedBoard,
|
||||
selectedPort: port,
|
||||
};
|
||||
}
|
||||
},
|
||||
isToggled: () => Port.equals(port, this.boardsServiceProvider.boardsConfig.selectedPort)
|
||||
isToggled: () =>
|
||||
Port.equals(
|
||||
port,
|
||||
this.boardsServiceProvider.boardsConfig
|
||||
.selectedPort
|
||||
),
|
||||
};
|
||||
const label = `${address}${name ? ` (${name})` : ''}`;
|
||||
const menuAction = {
|
||||
commandId: id,
|
||||
label,
|
||||
order: `1${label}` // `1` comes after the placeholder which has order `0`
|
||||
order: `1${label}`, // `1` comes after the placeholder which has order `0`
|
||||
};
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.toDisposeBeforeMenuRebuild.push(Disposable.create(() => this.commandRegistry.unregisterCommand(command)));
|
||||
this.menuModelRegistry.registerMenuAction(menuPath, menuAction);
|
||||
}
|
||||
this.toDisposeBeforeMenuRebuild.push(
|
||||
Disposable.create(() =>
|
||||
this.commandRegistry.unregisterCommand(command)
|
||||
)
|
||||
);
|
||||
this.menuModelRegistry.registerMenuAction(
|
||||
menuPath,
|
||||
menuAction
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { serial, network, unknown } = AvailablePorts.groupByProtocol(availablePorts);
|
||||
const { serial, network, unknown } =
|
||||
AvailablePorts.groupByProtocol(availablePorts);
|
||||
registerPorts(serial);
|
||||
registerPorts(network);
|
||||
registerPorts(unknown);
|
||||
@ -213,7 +330,6 @@ PID: ${PID}`;
|
||||
const allBoards = await this.boardsService.searchBoards({});
|
||||
return allBoards.filter(InstalledBoardWithPackage.is);
|
||||
}
|
||||
|
||||
}
|
||||
export namespace BoardSelection {
|
||||
export namespace Commands {
|
||||
|
@ -5,11 +5,15 @@ import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { MonitorConnection } from '../monitor/monitor-connection';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class BurnBootloader extends SketchContribution {
|
||||
|
||||
@inject(CoreService)
|
||||
protected readonly coreService: CoreService;
|
||||
|
||||
@ -27,7 +31,7 @@ export class BurnBootloader extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, {
|
||||
execute: () => this.burnBootloader()
|
||||
execute: () => this.burnBootloader(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -35,7 +39,7 @@ export class BurnBootloader extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, {
|
||||
commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id,
|
||||
label: 'Burn Bootloader',
|
||||
order: 'z99'
|
||||
order: 'z99',
|
||||
});
|
||||
}
|
||||
|
||||
@ -47,11 +51,16 @@ export class BurnBootloader extends SketchContribution {
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const port = boardsConfig.selectedPort?.address;
|
||||
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
|
||||
await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(
|
||||
boardsConfig.selectedBoard?.fqbn
|
||||
),
|
||||
this.boardsDataStore.getData(
|
||||
boardsConfig.selectedBoard?.fqbn
|
||||
),
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose')
|
||||
this.preferences.get('arduino.upload.verbose'),
|
||||
]);
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
await this.coreService.burnBootloader({
|
||||
@ -59,9 +68,11 @@ export class BurnBootloader extends SketchContribution {
|
||||
programmer,
|
||||
port,
|
||||
verify,
|
||||
verbose
|
||||
verbose,
|
||||
});
|
||||
this.messageService.info('Done burning bootloader.', {
|
||||
timeout: 3000,
|
||||
});
|
||||
this.messageService.info('Done burning bootloader.', { timeout: 3000 });
|
||||
} catch (e) {
|
||||
this.messageService.error(e.toString());
|
||||
} finally {
|
||||
@ -70,13 +81,12 @@ export class BurnBootloader extends SketchContribution {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace BurnBootloader {
|
||||
export namespace Commands {
|
||||
export const BURN_BOOTLOADER: Command = {
|
||||
id: 'arduino-burn-bootloader'
|
||||
id: 'arduino-burn-bootloader',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,20 @@ import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shel
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SaveAsSketch } from './save-as-sketch';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, URI } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
URI,
|
||||
} from './contribution';
|
||||
|
||||
/**
|
||||
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
|
||||
*/
|
||||
@injectable()
|
||||
export class Close extends SketchContribution {
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
@ -27,7 +33,6 @@ export class Close extends SketchContribution {
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Close.Commands.CLOSE, {
|
||||
execute: async () => {
|
||||
|
||||
// Close current editor if closeable.
|
||||
const { currentEditor } = this.editorManager;
|
||||
if (currentEditor && currentEditor.title.closable) {
|
||||
@ -38,8 +43,13 @@ export class Close extends SketchContribution {
|
||||
// Close current widget from the main area if possible.
|
||||
const { currentWidget } = this.shell;
|
||||
if (currentWidget) {
|
||||
const currentWidgetInMain = toArray(this.shell.mainPanel.widgets()).find(widget => widget === currentWidget);
|
||||
if (currentWidgetInMain && currentWidgetInMain.title.closable) {
|
||||
const currentWidgetInMain = toArray(
|
||||
this.shell.mainPanel.widgets()
|
||||
).find((widget) => widget === currentWidget);
|
||||
if (
|
||||
currentWidgetInMain &&
|
||||
currentWidgetInMain.title.closable
|
||||
) {
|
||||
return currentWidgetInMain.close();
|
||||
}
|
||||
}
|
||||
@ -54,25 +64,32 @@ export class Close extends SketchContribution {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
if (isTemp && await this.wasTouched(uri)) {
|
||||
if (isTemp && (await this.wasTouched(uri))) {
|
||||
const { response } = await remote.dialog.showMessageBox({
|
||||
type: 'question',
|
||||
buttons: ["Don't Save", 'Cancel', 'Save'],
|
||||
message: 'Do you want to save changes to this sketch before closing?',
|
||||
detail: "If you don't save, your changes will be lost."
|
||||
message:
|
||||
'Do you want to save changes to this sketch before closing?',
|
||||
detail: "If you don't save, your changes will be lost.",
|
||||
});
|
||||
if (response === 1) { // Cancel
|
||||
if (response === 1) {
|
||||
// Cancel
|
||||
return;
|
||||
}
|
||||
if (response === 2) { // Save
|
||||
const saved = await this.commandService.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { openAfterMove: false, execOnlyIfTemp: true });
|
||||
if (!saved) { // If it was not saved, do bail the close.
|
||||
if (response === 2) {
|
||||
// Save
|
||||
const saved = await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
{ openAfterMove: false, execOnlyIfTemp: true }
|
||||
);
|
||||
if (!saved) {
|
||||
// If it was not saved, do bail the close.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,14 +97,14 @@ export class Close extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: Close.Commands.CLOSE.id,
|
||||
label: 'Close',
|
||||
order: '5'
|
||||
order: '5',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: Close.Commands.CLOSE.id,
|
||||
keybinding: 'CtrlCmd+W'
|
||||
keybinding: 'CtrlCmd+W',
|
||||
});
|
||||
}
|
||||
|
||||
@ -99,7 +116,10 @@ export class Close extends SketchContribution {
|
||||
if (editorWidget) {
|
||||
const { editor } = editorWidget;
|
||||
if (editor instanceof MonacoEditor) {
|
||||
const versionId = editor.getControl().getModel()?.getVersionId();
|
||||
const versionId = editor
|
||||
.getControl()
|
||||
.getModel()
|
||||
?.getVersionId();
|
||||
if (Number.isInteger(versionId) && versionId! > 1) {
|
||||
return true;
|
||||
}
|
||||
@ -107,13 +127,12 @@ export class Close extends SketchContribution {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Close {
|
||||
export namespace Commands {
|
||||
export const CLOSE: Command = {
|
||||
id: 'arduino-close'
|
||||
id: 'arduino-close',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -10,22 +10,59 @@ import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
|
||||
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||
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 {
|
||||
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 {
|
||||
SketchesService,
|
||||
ConfigService,
|
||||
FileSystemExt,
|
||||
Sketch,
|
||||
} from '../../common/protocol';
|
||||
import { ArduinoPreferences } from '../arduino-preferences';
|
||||
|
||||
export { Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, URI, Sketch, open };
|
||||
export {
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
URI,
|
||||
Sketch,
|
||||
open,
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export abstract class Contribution implements CommandContribution, MenuContribution, KeybindingContribution, TabBarToolbarContribution, FrontendApplicationContribution {
|
||||
|
||||
export abstract class Contribution
|
||||
implements
|
||||
CommandContribution,
|
||||
MenuContribution,
|
||||
KeybindingContribution,
|
||||
TabBarToolbarContribution,
|
||||
FrontendApplicationContribution
|
||||
{
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@ -47,26 +84,19 @@ export abstract class Contribution implements CommandContribution, MenuContribut
|
||||
@inject(SettingsService)
|
||||
protected readonly settingsService: SettingsService;
|
||||
|
||||
onStart(app: FrontendApplication): MaybePromise<void> {
|
||||
}
|
||||
onStart(app: FrontendApplication): MaybePromise<void> {}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
}
|
||||
registerCommands(registry: CommandRegistry): void {}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
}
|
||||
registerMenus(registry: MenuModelRegistry): void {}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
}
|
||||
registerKeybindings(registry: KeybindingRegistry): void {}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class SketchContribution extends Contribution {
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@ -100,18 +130,23 @@ export abstract class SketchContribution extends Contribution {
|
||||
if (sketch) {
|
||||
for (const editor of this.editorManager.all) {
|
||||
const uri = editor.editor.uri;
|
||||
if (Saveable.isDirty(editor) && Sketch.isInSketch(uri, sketch)) {
|
||||
if (
|
||||
Saveable.isDirty(editor) &&
|
||||
Sketch.isInSketch(uri, sketch)
|
||||
) {
|
||||
override[uri.toString()] = editor.editor.document.getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
return override;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Contribution {
|
||||
export function configure<T>(bind: interfaces.Bind, serviceIdentifier: typeof Contribution): void {
|
||||
export function configure<T>(
|
||||
bind: interfaces.Bind,
|
||||
serviceIdentifier: typeof Contribution
|
||||
): void {
|
||||
bind(serviceIdentifier).toSelf().inSingletonScope();
|
||||
bind(CommandContribution).toService(serviceIdentifier);
|
||||
bind(MenuContribution).toService(serviceIdentifier);
|
||||
|
@ -5,11 +5,16 @@ import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { Board, BoardsService, ExecutableService } from '../../common/protocol';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { URI, Command, CommandRegistry, SketchContribution, TabBarToolbarRegistry } from './contribution';
|
||||
import {
|
||||
URI,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
SketchContribution,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class Debug extends SketchContribution {
|
||||
|
||||
@inject(HostedPluginSupport)
|
||||
protected hostedPluginSupport: HostedPluginSupport;
|
||||
|
||||
@ -29,8 +34,11 @@ export class Debug extends SketchContribution {
|
||||
* If `undefined`, debugging is enabled. Otherwise, the reason why it's disabled.
|
||||
*/
|
||||
protected _disabledMessages?: string = 'No board selected'; // Initial pessimism.
|
||||
protected disabledMessageDidChangeEmitter = new Emitter<string | undefined>();
|
||||
protected onDisabledMessageDidChange = this.disabledMessageDidChangeEmitter.event;
|
||||
protected disabledMessageDidChangeEmitter = new Emitter<
|
||||
string | undefined
|
||||
>();
|
||||
protected onDisabledMessageDidChange =
|
||||
this.disabledMessageDidChangeEmitter.event;
|
||||
|
||||
protected get disabledMessage(): string | undefined {
|
||||
return this._disabledMessages;
|
||||
@ -43,14 +51,28 @@ export class Debug extends SketchContribution {
|
||||
protected readonly debugToolbarItem = {
|
||||
id: Debug.Commands.START_DEBUGGING.id,
|
||||
command: Debug.Commands.START_DEBUGGING.id,
|
||||
tooltip: `${this.disabledMessage ? `Debug - ${this.disabledMessage}` : 'Start Debugging'}`,
|
||||
tooltip: `${
|
||||
this.disabledMessage
|
||||
? `Debug - ${this.disabledMessage}`
|
||||
: 'Start Debugging'
|
||||
}`,
|
||||
priority: 3,
|
||||
onDidChange: this.onDisabledMessageDidChange as Event<void>
|
||||
onDidChange: this.onDisabledMessageDidChange as Event<void>,
|
||||
};
|
||||
|
||||
onStart(): void {
|
||||
this.onDisabledMessageDidChange(() => this.debugToolbarItem.tooltip = `${this.disabledMessage ? `Debug - ${this.disabledMessage}` : 'Start Debugging'}`);
|
||||
const refreshState = async (board: Board | undefined = this.boardsServiceProvider.boardsConfig.selectedBoard) => {
|
||||
this.onDisabledMessageDidChange(
|
||||
() =>
|
||||
(this.debugToolbarItem.tooltip = `${
|
||||
this.disabledMessage
|
||||
? `Debug - ${this.disabledMessage}`
|
||||
: 'Start Debugging'
|
||||
}`)
|
||||
);
|
||||
const refreshState = async (
|
||||
board: Board | undefined = this.boardsServiceProvider.boardsConfig
|
||||
.selectedBoard
|
||||
) => {
|
||||
if (!board) {
|
||||
this.disabledMessage = 'No board selected';
|
||||
return;
|
||||
@ -71,8 +93,10 @@ export class Debug extends SketchContribution {
|
||||
} else {
|
||||
this.disabledMessage = undefined;
|
||||
}
|
||||
}
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) => refreshState(selectedBoard));
|
||||
};
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(({ selectedBoard }) =>
|
||||
refreshState(selectedBoard)
|
||||
);
|
||||
this.notificationCenter.onPlatformInstalled(() => refreshState());
|
||||
this.notificationCenter.onPlatformUninstalled(() => refreshState());
|
||||
refreshState();
|
||||
@ -81,8 +105,9 @@ export class Debug extends SketchContribution {
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Debug.Commands.START_DEBUGGING, {
|
||||
execute: () => this.startDebug(),
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: () => !this.disabledMessage
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: () => !this.disabledMessage,
|
||||
});
|
||||
}
|
||||
|
||||
@ -90,7 +115,10 @@ export class Debug extends SketchContribution {
|
||||
registry.registerItem(this.debugToolbarItem);
|
||||
}
|
||||
|
||||
protected async startDebug(board: Board | undefined = this.boardsServiceProvider.boardsConfig.selectedBoard): Promise<void> {
|
||||
protected async startDebug(
|
||||
board: Board | undefined = this.boardsServiceProvider.boardsConfig
|
||||
.selectedBoard
|
||||
): Promise<void> {
|
||||
if (!board) {
|
||||
return;
|
||||
}
|
||||
@ -101,29 +129,33 @@ export class Debug extends SketchContribution {
|
||||
await this.hostedPluginSupport.didStart;
|
||||
const [sketch, executables] = await Promise.all([
|
||||
this.sketchServiceClient.currentSketch(),
|
||||
this.executableService.list()
|
||||
this.executableService.list(),
|
||||
]);
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri(sketch);
|
||||
const ideTempFolderUri = await this.sketchService.getIdeTempFolderUri(
|
||||
sketch
|
||||
);
|
||||
const [cliPath, sketchPath, configPath] = await Promise.all([
|
||||
this.fileService.fsPath(new URI(executables.cliUri)),
|
||||
this.fileService.fsPath(new URI(sketch.uri)),
|
||||
this.fileService.fsPath(new URI(ideTempFolderUri)),
|
||||
])
|
||||
]);
|
||||
const config = {
|
||||
cliPath,
|
||||
board: {
|
||||
fqbn,
|
||||
name
|
||||
name,
|
||||
},
|
||||
sketchPath,
|
||||
configPath
|
||||
configPath,
|
||||
};
|
||||
return this.commandService.executeCommand('arduino.debug.start', config);
|
||||
return this.commandService.executeCommand(
|
||||
'arduino.debug.start',
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Debug {
|
||||
@ -131,7 +163,7 @@ export namespace Debug {
|
||||
export const START_DEBUGGING: Command = {
|
||||
id: 'arduino-start-debug',
|
||||
label: 'Start Debugging',
|
||||
category: 'Arduino'
|
||||
}
|
||||
category: 'Arduino',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,19 @@ 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 { Contribution, Command, MenuModelRegistry, KeybindingRegistry, CommandRegistry } from './contribution';
|
||||
import {
|
||||
Contribution,
|
||||
Command,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
CommandRegistry,
|
||||
} from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
// TODO: [macOS]: to remove `Start Dictation...` and `Emoji & Symbol` see this thread: https://github.com/electron/electron/issues/8283#issuecomment-269522072
|
||||
// Depends on https://github.com/eclipse-theia/theia/pull/7964
|
||||
@injectable()
|
||||
export class EditContributions extends Contribution {
|
||||
|
||||
@inject(MonacoEditorService)
|
||||
protected readonly codeEditorService: MonacoEditorService;
|
||||
|
||||
@ -21,15 +26,34 @@ export class EditContributions extends Contribution {
|
||||
protected readonly preferences: PreferenceService;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(EditContributions.Commands.GO_TO_LINE, { execute: () => this.run('editor.action.gotoLine') });
|
||||
registry.registerCommand(EditContributions.Commands.TOGGLE_COMMENT, { execute: () => this.run('editor.action.commentLine') });
|
||||
registry.registerCommand(EditContributions.Commands.INDENT_LINES, { execute: () => this.run('editor.action.indentLines') });
|
||||
registry.registerCommand(EditContributions.Commands.OUTDENT_LINES, { execute: () => this.run('editor.action.outdentLines') });
|
||||
registry.registerCommand(EditContributions.Commands.FIND, { execute: () => this.run('actions.find') });
|
||||
registry.registerCommand(EditContributions.Commands.FIND_NEXT, { execute: () => this.run('actions.findWithSelection') });
|
||||
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, {
|
||||
registry.registerCommand(EditContributions.Commands.GO_TO_LINE, {
|
||||
execute: () => this.run('editor.action.gotoLine'),
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.TOGGLE_COMMENT, {
|
||||
execute: () => this.run('editor.action.commentLine'),
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.INDENT_LINES, {
|
||||
execute: () => this.run('editor.action.indentLines'),
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.OUTDENT_LINES, {
|
||||
execute: () => this.run('editor.action.outdentLines'),
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.FIND, {
|
||||
execute: () => this.run('actions.find'),
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.FIND_NEXT, {
|
||||
execute: () => this.run('actions.findWithSelection'),
|
||||
});
|
||||
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: async () => {
|
||||
const settings = await this.settingsService.settings();
|
||||
if (settings.autoScaleInterface) {
|
||||
@ -39,9 +63,12 @@ export class EditContributions extends Contribution {
|
||||
}
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
},
|
||||
}
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.DECREASE_FONT_SIZE, {
|
||||
);
|
||||
registry.registerCommand(
|
||||
EditContributions.Commands.DECREASE_FONT_SIZE,
|
||||
{
|
||||
execute: async () => {
|
||||
const settings = await this.settingsService.settings();
|
||||
if (settings.autoScaleInterface) {
|
||||
@ -51,18 +78,22 @@ export class EditContributions extends Contribution {
|
||||
}
|
||||
await this.settingsService.update(settings);
|
||||
await this.settingsService.save();
|
||||
},
|
||||
}
|
||||
});
|
||||
/* Tools */registry.registerCommand(EditContributions.Commands.AUTO_FORMAT, { execute: () => this.run('editor.action.formatDocument') });
|
||||
);
|
||||
/* Tools */ registry.registerCommand(
|
||||
EditContributions.Commands.AUTO_FORMAT,
|
||||
{ execute: () => this.run('editor.action.formatDocument') }
|
||||
);
|
||||
registry.registerCommand(EditContributions.Commands.COPY_FOR_FORUM, {
|
||||
execute: async () => {
|
||||
const value = await this.currentValue();
|
||||
if (value !== undefined) {
|
||||
this.clipboardService.writeText(`[code]
|
||||
${value}
|
||||
[/code]`)
|
||||
}
|
||||
[/code]`);
|
||||
}
|
||||
},
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.COPY_FOR_GITHUB, {
|
||||
execute: async () => {
|
||||
@ -70,98 +101,98 @@ ${value}
|
||||
if (value !== undefined) {
|
||||
this.clipboardService.writeText(`\`\`\`cpp
|
||||
${value}
|
||||
\`\`\``)
|
||||
}
|
||||
\`\`\``);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: CommonCommands.CUT.id,
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: CommonCommands.COPY.id,
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.COPY_FOR_FORUM.id,
|
||||
label: 'Copy for Forum',
|
||||
order: '2'
|
||||
order: '2',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.COPY_FOR_GITHUB.id,
|
||||
label: 'Copy for GitHub',
|
||||
order: '3'
|
||||
order: '3',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: CommonCommands.PASTE.id,
|
||||
order: '4'
|
||||
order: '4',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: CommonCommands.SELECT_ALL.id,
|
||||
order: '5'
|
||||
order: '5',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.GO_TO_LINE.id,
|
||||
label: 'Go to Line...',
|
||||
order: '6'
|
||||
order: '6',
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.TOGGLE_COMMENT.id,
|
||||
label: 'Comment/Uncomment',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.INDENT_LINES.id,
|
||||
label: 'Increase Indent',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.OUTDENT_LINES.id,
|
||||
label: 'Decrease Indent',
|
||||
order: '2'
|
||||
order: '2',
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.INCREASE_FONT_SIZE.id,
|
||||
label: 'Increase Font Size',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.DECREASE_FONT_SIZE.id,
|
||||
label: 'Decrease Font Size',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
|
||||
commandId: EditContributions.Commands.FIND.id,
|
||||
label: 'Find',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
|
||||
commandId: EditContributions.Commands.FIND_NEXT.id,
|
||||
label: 'Find Next',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
|
||||
commandId: EditContributions.Commands.FIND_PREVIOUS.id,
|
||||
label: 'Find Previous',
|
||||
order: '2'
|
||||
order: '2',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
|
||||
commandId: EditContributions.Commands.USE_FOR_FIND.id,
|
||||
label: 'Use Selection for Find', // XXX: The Java IDE uses `Use Selection For Find`.
|
||||
order: '3'
|
||||
order: '3',
|
||||
});
|
||||
|
||||
// `Tools`
|
||||
registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: EditContributions.Commands.AUTO_FORMAT.id,
|
||||
label: 'Auto Format', // XXX: The Java IDE uses `Use Selection For Find`.
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
}
|
||||
|
||||
@ -169,60 +200,63 @@ ${value}
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.COPY_FOR_FORUM.id,
|
||||
keybinding: 'CtrlCmd+Shift+C',
|
||||
when: 'editorFocus'
|
||||
when: 'editorFocus',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.COPY_FOR_GITHUB.id,
|
||||
keybinding: 'CtrlCmd+Alt+C',
|
||||
when: 'editorFocus'
|
||||
when: 'editorFocus',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.GO_TO_LINE.id,
|
||||
keybinding: 'CtrlCmd+L',
|
||||
when: 'editorFocus'
|
||||
when: 'editorFocus',
|
||||
});
|
||||
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.TOGGLE_COMMENT.id,
|
||||
keybinding: 'CtrlCmd+/',
|
||||
when: 'editorFocus'
|
||||
when: 'editorFocus',
|
||||
});
|
||||
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.INCREASE_FONT_SIZE.id,
|
||||
keybinding: 'CtrlCmd+='
|
||||
keybinding: 'CtrlCmd+=',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.DECREASE_FONT_SIZE.id,
|
||||
keybinding: 'CtrlCmd+-'
|
||||
keybinding: 'CtrlCmd+-',
|
||||
});
|
||||
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.FIND.id,
|
||||
keybinding: 'CtrlCmd+F'
|
||||
keybinding: 'CtrlCmd+F',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.FIND_NEXT.id,
|
||||
keybinding: 'CtrlCmd+G'
|
||||
keybinding: 'CtrlCmd+G',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.FIND_PREVIOUS.id,
|
||||
keybinding: 'CtrlCmd+Shift+G'
|
||||
keybinding: 'CtrlCmd+Shift+G',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.USE_FOR_FIND.id,
|
||||
keybinding: 'CtrlCmd+E'
|
||||
keybinding: 'CtrlCmd+E',
|
||||
});
|
||||
|
||||
// `Tools`
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.AUTO_FORMAT.id,
|
||||
keybinding: 'CtrlCmd+T'
|
||||
keybinding: 'CtrlCmd+T',
|
||||
});
|
||||
}
|
||||
|
||||
protected async current(): Promise<monaco.editor.ICodeEditor | undefined> {
|
||||
return this.codeEditorService.getFocusedCodeEditor() || this.codeEditorService.getActiveCodeEditor();
|
||||
return (
|
||||
this.codeEditorService.getFocusedCodeEditor() ||
|
||||
this.codeEditorService.getActiveCodeEditor()
|
||||
);
|
||||
}
|
||||
|
||||
protected async currentValue(): Promise<string | undefined> {
|
||||
@ -246,49 +280,48 @@ ${value}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace EditContributions {
|
||||
export namespace Commands {
|
||||
export const COPY_FOR_FORUM: Command = {
|
||||
id: 'arduino-copy-for-forum'
|
||||
id: 'arduino-copy-for-forum',
|
||||
};
|
||||
export const COPY_FOR_GITHUB: Command = {
|
||||
id: 'arduino-copy-for-github'
|
||||
id: 'arduino-copy-for-github',
|
||||
};
|
||||
export const GO_TO_LINE: Command = {
|
||||
id: 'arduino-go-to-line'
|
||||
id: 'arduino-go-to-line',
|
||||
};
|
||||
export const TOGGLE_COMMENT: Command = {
|
||||
id: 'arduino-toggle-comment'
|
||||
id: 'arduino-toggle-comment',
|
||||
};
|
||||
export const INDENT_LINES: Command = {
|
||||
id: 'arduino-indent-lines'
|
||||
id: 'arduino-indent-lines',
|
||||
};
|
||||
export const OUTDENT_LINES: Command = {
|
||||
id: 'arduino-outdent-lines'
|
||||
id: 'arduino-outdent-lines',
|
||||
};
|
||||
export const FIND: Command = {
|
||||
id: 'arduino-find'
|
||||
id: 'arduino-find',
|
||||
};
|
||||
export const FIND_NEXT: Command = {
|
||||
id: 'arduino-find-next'
|
||||
id: 'arduino-find-next',
|
||||
};
|
||||
export const FIND_PREVIOUS: Command = {
|
||||
id: 'arduino-find-previous'
|
||||
id: 'arduino-find-previous',
|
||||
};
|
||||
export const USE_FOR_FIND: Command = {
|
||||
id: 'arduino-for-find'
|
||||
id: 'arduino-for-find',
|
||||
};
|
||||
export const INCREASE_FONT_SIZE: Command = {
|
||||
id: 'arduino-increase-font-size'
|
||||
id: 'arduino-increase-font-size',
|
||||
};
|
||||
export const DECREASE_FONT_SIZE: Command = {
|
||||
id: 'arduino-decrease-font-size'
|
||||
id: 'arduino-decrease-font-size',
|
||||
};
|
||||
export const AUTO_FORMAT: Command = {
|
||||
id: 'arduino-auto-format' // `Auto Format` should belong to `Tool`.
|
||||
id: 'arduino-auto-format', // `Auto Format` should belong to `Tool`.
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,30 @@
|
||||
import * as PQueue from 'p-queue';
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { CommandHandler } from '@theia/core/lib/common/command';
|
||||
import { MenuPath, CompositeMenuNode, SubMenuOptions } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
MenuPath,
|
||||
CompositeMenuNode,
|
||||
SubMenuOptions,
|
||||
} from '@theia/core/lib/common/menu';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { ExamplesService } from '../../common/protocol/examples-service';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
} from './contribution';
|
||||
import { NotificationCenter } from '../notification-center';
|
||||
import { Board, Sketch, SketchContainer } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export abstract class Examples extends SketchContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@ -34,7 +44,9 @@ export abstract class Examples extends SketchContribution {
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.handleBoardChanged(selectedBoard));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) =>
|
||||
this.handleBoardChanged(selectedBoard)
|
||||
);
|
||||
}
|
||||
|
||||
protected handleBoardChanged(board: Board | undefined): void {
|
||||
@ -46,8 +58,13 @@ export abstract class Examples extends SketchContribution {
|
||||
// This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222.
|
||||
const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1;
|
||||
const menuId = ArduinoMenus.FILE__EXAMPLES_SUBMENU[index];
|
||||
const groupPath = index === 0 ? [] : ArduinoMenus.FILE__EXAMPLES_SUBMENU.slice(0, index);
|
||||
const parent: CompositeMenuNode = (registry as any).findGroup(groupPath);
|
||||
const groupPath =
|
||||
index === 0
|
||||
? []
|
||||
: ArduinoMenus.FILE__EXAMPLES_SUBMENU.slice(0, index);
|
||||
const parent: CompositeMenuNode = (registry as any).findGroup(
|
||||
groupPath
|
||||
);
|
||||
const examples = new CompositeMenuNode(menuId, '', { order: '4' });
|
||||
parent.addNode(examples);
|
||||
} catch (e) {
|
||||
@ -56,19 +73,33 @@ export abstract class Examples extends SketchContribution {
|
||||
}
|
||||
// Registering the same submenu multiple times has no side-effect.
|
||||
// TODO: unregister submenu? https://github.com/eclipse-theia/theia/issues/7300
|
||||
registry.registerSubmenu(ArduinoMenus.FILE__EXAMPLES_SUBMENU, 'Examples', { order: '4' });
|
||||
registry.registerSubmenu(
|
||||
ArduinoMenus.FILE__EXAMPLES_SUBMENU,
|
||||
'Examples',
|
||||
{ order: '4' }
|
||||
);
|
||||
}
|
||||
|
||||
registerRecursively(
|
||||
sketchContainerOrPlaceholder: SketchContainer | (Sketch | SketchContainer)[] | string,
|
||||
sketchContainerOrPlaceholder:
|
||||
| SketchContainer
|
||||
| (Sketch | SketchContainer)[]
|
||||
| string,
|
||||
menuPath: MenuPath,
|
||||
pushToDispose: DisposableCollection = new DisposableCollection(),
|
||||
subMenuOptions?: SubMenuOptions | undefined): void {
|
||||
|
||||
subMenuOptions?: SubMenuOptions | undefined
|
||||
): void {
|
||||
if (typeof sketchContainerOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, sketchContainerOrPlaceholder);
|
||||
const placeholder = new PlaceholderMenuNode(
|
||||
menuPath,
|
||||
sketchContainerOrPlaceholder
|
||||
);
|
||||
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id)));
|
||||
pushToDispose.push(
|
||||
Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuNode(placeholder.id)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const sketches: Sketch[] = [];
|
||||
const children: SketchContainer[] = [];
|
||||
@ -77,7 +108,11 @@ export abstract class Examples extends SketchContribution {
|
||||
if (SketchContainer.is(sketchContainerOrPlaceholder)) {
|
||||
const { label } = sketchContainerOrPlaceholder;
|
||||
submenuPath = [...menuPath, label];
|
||||
this.menuRegistry.registerSubmenu(submenuPath, label, subMenuOptions);
|
||||
this.menuRegistry.registerSubmenu(
|
||||
submenuPath,
|
||||
label,
|
||||
subMenuOptions
|
||||
);
|
||||
sketches.push(...sketchContainerOrPlaceholder.sketches);
|
||||
children.push(...sketchContainerOrPlaceholder.children);
|
||||
} else {
|
||||
@ -89,15 +124,29 @@ export abstract class Examples extends SketchContribution {
|
||||
}
|
||||
}
|
||||
}
|
||||
children.forEach(child => this.registerRecursively(child, submenuPath, pushToDispose));
|
||||
children.forEach((child) =>
|
||||
this.registerRecursively(child, submenuPath, pushToDispose)
|
||||
);
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const commandId = `arduino-open-example-${submenuPath.join(':')}--${uri}`;
|
||||
const commandId = `arduino-open-example-${submenuPath.join(
|
||||
':'
|
||||
)}--${uri}`;
|
||||
const command = { id: commandId };
|
||||
const handler = this.createHandler(uri);
|
||||
pushToDispose.push(this.commandRegistry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(submenuPath, { commandId, label: sketch.name, order: sketch.name.toLocaleLowerCase() });
|
||||
pushToDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
pushToDispose.push(
|
||||
this.commandRegistry.registerCommand(command, handler)
|
||||
);
|
||||
this.menuRegistry.registerMenuAction(submenuPath, {
|
||||
commandId,
|
||||
label: sketch.name,
|
||||
order: sketch.name.toLocaleLowerCase(),
|
||||
});
|
||||
pushToDispose.push(
|
||||
Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuAction(command)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,16 +155,17 @@ export abstract class Examples extends SketchContribution {
|
||||
return {
|
||||
execute: async () => {
|
||||
const sketch = await this.sketchService.cloneExample(uri);
|
||||
return this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
|
||||
return this.commandService.executeCommand(
|
||||
OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
sketch
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BuiltInExamples extends Examples {
|
||||
|
||||
onStart(): void {
|
||||
this.register(); // no `await`
|
||||
}
|
||||
@ -126,21 +176,25 @@ export class BuiltInExamples extends Examples {
|
||||
sketchContainers = await this.examplesService.builtIns();
|
||||
} catch (e) {
|
||||
console.error('Could not initialize built-in examples.', e);
|
||||
this.messageService.error('Could not initialize built-in examples.');
|
||||
this.messageService.error(
|
||||
'Could not initialize built-in examples.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.toDispose.dispose();
|
||||
for (const container of ['Built-in examples', ...sketchContainers]) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__BUILT_IN_GROUP, this.toDispose);
|
||||
this.registerRecursively(
|
||||
container,
|
||||
ArduinoMenus.EXAMPLES__BUILT_IN_GROUP,
|
||||
this.toDispose
|
||||
);
|
||||
}
|
||||
this.menuManager.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class LibraryExamples extends Examples {
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
@ -156,13 +210,18 @@ export class LibraryExamples extends Examples {
|
||||
this.register(board);
|
||||
}
|
||||
|
||||
protected async register(board: Board | undefined = this.boardsServiceClient.boardsConfig.selectedBoard): Promise<void> {
|
||||
protected async register(
|
||||
board: Board | undefined = this.boardsServiceClient.boardsConfig
|
||||
.selectedBoard
|
||||
): Promise<void> {
|
||||
return this.queue.add(async () => {
|
||||
this.toDispose.dispose();
|
||||
const fqbn = board?.fqbn;
|
||||
const name = board?.name;
|
||||
// Shows all examples when no board is selected, or the platform of the currently selected board is not installed.
|
||||
const { user, current, any } = await this.examplesService.installed({ fqbn });
|
||||
const { user, current, any } = await this.examplesService.installed(
|
||||
{ fqbn }
|
||||
);
|
||||
if (user.length) {
|
||||
(user as any).unshift('Examples from Custom Libraries');
|
||||
}
|
||||
@ -173,16 +232,27 @@ export class LibraryExamples extends Examples {
|
||||
(any as any).unshift('Examples for any board');
|
||||
}
|
||||
for (const container of user) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__USER_LIBS_GROUP, this.toDispose);
|
||||
this.registerRecursively(
|
||||
container,
|
||||
ArduinoMenus.EXAMPLES__USER_LIBS_GROUP,
|
||||
this.toDispose
|
||||
);
|
||||
}
|
||||
for (const container of current) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__CURRENT_BOARD_GROUP, this.toDispose);
|
||||
this.registerRecursively(
|
||||
container,
|
||||
ArduinoMenus.EXAMPLES__CURRENT_BOARD_GROUP,
|
||||
this.toDispose
|
||||
);
|
||||
}
|
||||
for (const container of any) {
|
||||
this.registerRecursively(container, ArduinoMenus.EXAMPLES__ANY_BOARD_GROUP, this.toDispose);
|
||||
this.registerRecursively(
|
||||
container,
|
||||
ArduinoMenus.EXAMPLES__ANY_BOARD_GROUP,
|
||||
this.toDispose
|
||||
);
|
||||
}
|
||||
this.menuManager.update();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,11 +5,16 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { CommandHandler } from '@theia/core/lib/common/command';
|
||||
import { QuickInputService } from '@theia/core/lib/browser/quick-open/quick-input-service';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { Contribution, Command, MenuModelRegistry, CommandRegistry, KeybindingRegistry } from './contribution';
|
||||
import {
|
||||
Contribution,
|
||||
Command,
|
||||
MenuModelRegistry,
|
||||
CommandRegistry,
|
||||
KeybindingRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class Help extends Contribution {
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
@ -20,80 +25,110 @@ export class Help extends Contribution {
|
||||
protected readonly quickInputService: QuickInputService;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
const open = (url: string) => this.windowService.openNewWindow(url, { external: true });
|
||||
const createOpenHandler = (url: string) => <CommandHandler>{
|
||||
execute: () => open(url)
|
||||
const open = (url: string) =>
|
||||
this.windowService.openNewWindow(url, { external: true });
|
||||
const createOpenHandler = (url: string) =>
|
||||
<CommandHandler>{
|
||||
execute: () => open(url),
|
||||
};
|
||||
registry.registerCommand(Help.Commands.GETTING_STARTED, createOpenHandler('https://www.arduino.cc/en/Guide'));
|
||||
registry.registerCommand(Help.Commands.ENVIRONMENT, createOpenHandler('https://www.arduino.cc/en/Guide/Environment'));
|
||||
registry.registerCommand(Help.Commands.TROUBLESHOOTING, createOpenHandler('https://support.arduino.cc/hc/en-us'));
|
||||
registry.registerCommand(Help.Commands.REFERENCE, createOpenHandler('https://www.arduino.cc/reference/en/'));
|
||||
registry.registerCommand(
|
||||
Help.Commands.GETTING_STARTED,
|
||||
createOpenHandler('https://www.arduino.cc/en/Guide')
|
||||
);
|
||||
registry.registerCommand(
|
||||
Help.Commands.ENVIRONMENT,
|
||||
createOpenHandler('https://www.arduino.cc/en/Guide/Environment')
|
||||
);
|
||||
registry.registerCommand(
|
||||
Help.Commands.TROUBLESHOOTING,
|
||||
createOpenHandler('https://support.arduino.cc/hc/en-us')
|
||||
);
|
||||
registry.registerCommand(
|
||||
Help.Commands.REFERENCE,
|
||||
createOpenHandler('https://www.arduino.cc/reference/en/')
|
||||
);
|
||||
registry.registerCommand(Help.Commands.FIND_IN_REFERENCE, {
|
||||
execute: async () => {
|
||||
let searchFor: string | undefined = undefined;
|
||||
const { currentEditor } = this.editorManager;
|
||||
if (currentEditor && currentEditor.editor instanceof MonacoEditor) {
|
||||
if (
|
||||
currentEditor &&
|
||||
currentEditor.editor instanceof MonacoEditor
|
||||
) {
|
||||
const codeEditor = currentEditor.editor.getControl();
|
||||
const selection = codeEditor.getSelection();
|
||||
const model = codeEditor.getModel();
|
||||
if (model && selection && !monaco.Range.isEmpty(selection)) {
|
||||
if (
|
||||
model &&
|
||||
selection &&
|
||||
!monaco.Range.isEmpty(selection)
|
||||
) {
|
||||
searchFor = model.getValueInRange(selection);
|
||||
}
|
||||
}
|
||||
if (!searchFor) {
|
||||
searchFor = await this.quickInputService.open({
|
||||
prompt: 'Search on Arduino.cc',
|
||||
placeHolder: 'Type a keyword'
|
||||
placeHolder: 'Type a keyword',
|
||||
});
|
||||
}
|
||||
if (searchFor) {
|
||||
return open(`https://www.arduino.cc/search?q=${encodeURIComponent(searchFor)}&tab=reference`);
|
||||
}
|
||||
return open(
|
||||
`https://www.arduino.cc/search?q=${encodeURIComponent(
|
||||
searchFor
|
||||
)}&tab=reference`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
registry.registerCommand(Help.Commands.FAQ, createOpenHandler('https://support.arduino.cc/hc/en-us'));
|
||||
registry.registerCommand(Help.Commands.VISIT_ARDUINO, createOpenHandler('https://www.arduino.cc/'));
|
||||
registry.registerCommand(
|
||||
Help.Commands.FAQ,
|
||||
createOpenHandler('https://support.arduino.cc/hc/en-us')
|
||||
);
|
||||
registry.registerCommand(
|
||||
Help.Commands.VISIT_ARDUINO,
|
||||
createOpenHandler('https://www.arduino.cc/')
|
||||
);
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.GETTING_STARTED.id,
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.ENVIRONMENT.id,
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.TROUBLESHOOTING.id,
|
||||
order: '2'
|
||||
order: '2',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__MAIN_GROUP, {
|
||||
commandId: Help.Commands.REFERENCE.id,
|
||||
order: '3'
|
||||
order: '3',
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||
commandId: Help.Commands.FIND_IN_REFERENCE.id,
|
||||
order: '4'
|
||||
order: '4',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||
commandId: Help.Commands.FAQ.id,
|
||||
order: '5'
|
||||
order: '5',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.HELP__FIND_GROUP, {
|
||||
commandId: Help.Commands.VISIT_ARDUINO.id,
|
||||
order: '6'
|
||||
order: '6',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: Help.Commands.FIND_IN_REFERENCE.id,
|
||||
keybinding: 'CtrlCmd+Shift+F'
|
||||
keybinding: 'CtrlCmd+Shift+F',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Help {
|
||||
@ -101,37 +136,37 @@ export namespace Help {
|
||||
export const GETTING_STARTED: Command = {
|
||||
id: 'arduino-getting-started',
|
||||
label: 'Getting Started',
|
||||
category: 'Arduino'
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const ENVIRONMENT: Command = {
|
||||
id: 'arduino-environment',
|
||||
label: 'Environment',
|
||||
category: 'Arduino'
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const TROUBLESHOOTING: Command = {
|
||||
id: 'arduino-troubleshooting',
|
||||
label: 'Troubleshooting',
|
||||
category: 'Arduino'
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const REFERENCE: Command = {
|
||||
id: 'arduino-reference',
|
||||
label: 'Reference',
|
||||
category: 'Arduino'
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const FIND_IN_REFERENCE: Command = {
|
||||
id: 'arduino-find-in-reference',
|
||||
label: 'Find in Reference',
|
||||
category: 'Arduino'
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const FAQ: Command = {
|
||||
id: 'arduino-faq',
|
||||
label: 'Frequently Asked Questions',
|
||||
category: 'Arduino'
|
||||
category: 'Arduino',
|
||||
};
|
||||
export const VISIT_ARDUINO: Command = {
|
||||
id: 'arduino-visit-arduino',
|
||||
label: 'Visit Arduino.cc',
|
||||
category: 'Arduino'
|
||||
category: 'Arduino',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,10 @@ import URI from '@theia/core/lib/common/uri';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MenuModelRegistry, MenuPath } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
|
||||
import { LibraryPackage, LibraryService } from '../../common/protocol';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
@ -15,7 +18,6 @@ import { NotificationCenter } from '../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class IncludeLibrary extends SketchContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@ -42,29 +44,40 @@ export class IncludeLibrary extends SketchContribution {
|
||||
|
||||
onStart(): void {
|
||||
this.updateMenuActions();
|
||||
this.boardsServiceClient.onBoardsConfigChanged(() => this.updateMenuActions())
|
||||
this.notificationCenter.onLibraryInstalled(() => this.updateMenuActions());
|
||||
this.notificationCenter.onLibraryUninstalled(() => this.updateMenuActions());
|
||||
this.boardsServiceClient.onBoardsConfigChanged(() =>
|
||||
this.updateMenuActions()
|
||||
);
|
||||
this.notificationCenter.onLibraryInstalled(() =>
|
||||
this.updateMenuActions()
|
||||
);
|
||||
this.notificationCenter.onLibraryUninstalled(() =>
|
||||
this.updateMenuActions()
|
||||
);
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
// `Include Library` submenu
|
||||
const includeLibMenuPath = [...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include'];
|
||||
registry.registerSubmenu(includeLibMenuPath, 'Include Library', { order: '1' });
|
||||
const includeLibMenuPath = [
|
||||
...ArduinoMenus.SKETCH__UTILS_GROUP,
|
||||
'0_include',
|
||||
];
|
||||
registry.registerSubmenu(includeLibMenuPath, 'Include Library', {
|
||||
order: '1',
|
||||
});
|
||||
// `Manage Libraries...` group.
|
||||
registry.registerMenuAction([...includeLibMenuPath, '0_manage'], {
|
||||
commandId: `${LibraryListWidget.WIDGET_ID}:toggle`,
|
||||
label: 'Manage Libraries...'
|
||||
label: 'Manage Libraries...',
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY, {
|
||||
execute: async arg => {
|
||||
execute: async (arg) => {
|
||||
if (LibraryPackage.is(arg)) {
|
||||
this.includeLibrary(arg);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -72,13 +85,17 @@ export class IncludeLibrary extends SketchContribution {
|
||||
return this.queue.add(async () => {
|
||||
this.toDispose.dispose();
|
||||
this.mainMenuManager.update();
|
||||
const libraries: LibraryPackage[] = []
|
||||
const fqbn = this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn;
|
||||
const libraries: LibraryPackage[] = [];
|
||||
const fqbn =
|
||||
this.boardsServiceClient.boardsConfig.selectedBoard?.fqbn;
|
||||
// Show all libraries, when no board is selected.
|
||||
// Otherwise, show libraries only for the selected board.
|
||||
libraries.push(...await this.libraryService.list({ fqbn }));
|
||||
libraries.push(...(await this.libraryService.list({ fqbn })));
|
||||
|
||||
const includeLibMenuPath = [...ArduinoMenus.SKETCH__UTILS_GROUP, '0_include'];
|
||||
const includeLibMenuPath = [
|
||||
...ArduinoMenus.SKETCH__UTILS_GROUP,
|
||||
'0_include',
|
||||
];
|
||||
// `Add .ZIP Library...`
|
||||
// TODO: implement it
|
||||
|
||||
@ -94,30 +111,50 @@ export class IncludeLibrary extends SketchContribution {
|
||||
}
|
||||
|
||||
for (const library of user) {
|
||||
this.toDispose.push(this.registerLibrary(library, userMenuPath));
|
||||
this.toDispose.push(
|
||||
this.registerLibrary(library, userMenuPath)
|
||||
);
|
||||
}
|
||||
for (const library of rest) {
|
||||
this.toDispose.push(this.registerLibrary(library, packageMenuPath));
|
||||
this.toDispose.push(
|
||||
this.registerLibrary(library, packageMenuPath)
|
||||
);
|
||||
}
|
||||
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
}
|
||||
|
||||
protected registerLibrary(libraryOrPlaceholder: LibraryPackage | string, menuPath: MenuPath): Disposable {
|
||||
protected registerLibrary(
|
||||
libraryOrPlaceholder: LibraryPackage | string,
|
||||
menuPath: MenuPath
|
||||
): Disposable {
|
||||
if (typeof libraryOrPlaceholder === 'string') {
|
||||
const placeholder = new PlaceholderMenuNode(menuPath, libraryOrPlaceholder);
|
||||
const placeholder = new PlaceholderMenuNode(
|
||||
menuPath,
|
||||
libraryOrPlaceholder
|
||||
);
|
||||
this.menuRegistry.registerMenuNode(menuPath, placeholder);
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuNode(placeholder.id));
|
||||
return Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuNode(placeholder.id)
|
||||
);
|
||||
}
|
||||
const commandId = `arduino-include-library--${libraryOrPlaceholder.name}:${libraryOrPlaceholder.author}`;
|
||||
const command = { id: commandId };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(IncludeLibrary.Commands.INCLUDE_LIBRARY.id, libraryOrPlaceholder) };
|
||||
const handler = {
|
||||
execute: () =>
|
||||
this.commandRegistry.executeCommand(
|
||||
IncludeLibrary.Commands.INCLUDE_LIBRARY.id,
|
||||
libraryOrPlaceholder
|
||||
),
|
||||
};
|
||||
const menuAction = { commandId, label: libraryOrPlaceholder.name };
|
||||
this.menuRegistry.registerMenuAction(menuPath, menuAction);
|
||||
return new DisposableCollection(
|
||||
this.commandRegistry.registerCommand(command, handler),
|
||||
Disposable.create(() => this.menuRegistry.unregisterMenuAction(menuAction)),
|
||||
Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuAction(menuAction)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -131,13 +168,19 @@ export class IncludeLibrary extends SketchContribution {
|
||||
let codeEditor: monaco.editor.IStandaloneCodeEditor | undefined;
|
||||
const editor = this.editorManager.currentEditor?.editor;
|
||||
if (editor instanceof MonacoEditor) {
|
||||
if (sketch.additionalFileUris.some(uri => uri === editor.uri.toString())) {
|
||||
if (
|
||||
sketch.additionalFileUris.some(
|
||||
(uri) => uri === editor.uri.toString()
|
||||
)
|
||||
) {
|
||||
codeEditor = editor.getControl();
|
||||
}
|
||||
}
|
||||
|
||||
if (!codeEditor) {
|
||||
const widget = await this.editorManager.open(new URI(sketch.mainFileUri));
|
||||
const widget = await this.editorManager.open(
|
||||
new URI(sketch.mainFileUri)
|
||||
);
|
||||
if (widget.editor instanceof MonacoEditor) {
|
||||
codeEditor = widget.editor.getControl();
|
||||
}
|
||||
@ -155,22 +198,29 @@ export class IncludeLibrary extends SketchContribution {
|
||||
const eol = textModel.getEOL();
|
||||
const includes = library.includes.slice();
|
||||
includes.push(''); // For the trailing new line.
|
||||
const text = includes.map(include => include ? `#include <${include}>` : eol).join(eol);
|
||||
const text = includes
|
||||
.map((include) => (include ? `#include <${include}>` : eol))
|
||||
.join(eol);
|
||||
textModel.pushStackElement(); // Start a fresh operation.
|
||||
textModel.pushEditOperations(cursorState, [{
|
||||
textModel.pushEditOperations(
|
||||
cursorState,
|
||||
[
|
||||
{
|
||||
range: new monaco.Range(1, 1, 1, 1),
|
||||
text,
|
||||
forceMoveMarkers: true
|
||||
}], () => cursorState);
|
||||
forceMoveMarkers: true,
|
||||
},
|
||||
],
|
||||
() => cursorState
|
||||
);
|
||||
textModel.pushStackElement(); // Make it undoable.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace IncludeLibrary {
|
||||
export namespace Commands {
|
||||
export const INCLUDE_LIBRARY: Command = {
|
||||
id: 'arduino-include-library'
|
||||
id: 'arduino-include-library',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,27 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { SketchContribution, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
URI,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class NewSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(NewSketch.Commands.NEW_SKETCH, {
|
||||
execute: () => this.newSketch()
|
||||
execute: () => this.newSketch(),
|
||||
});
|
||||
registry.registerCommand(NewSketch.Commands.NEW_SKETCH__TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () => registry.executeCommand(NewSketch.Commands.NEW_SKETCH.id)
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () =>
|
||||
registry.executeCommand(NewSketch.Commands.NEW_SKETCH.id),
|
||||
});
|
||||
}
|
||||
|
||||
@ -20,14 +29,14 @@ export class NewSketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: NewSketch.Commands.NEW_SKETCH.id,
|
||||
label: 'New',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: NewSketch.Commands.NEW_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+N'
|
||||
keybinding: 'CtrlCmd+N',
|
||||
});
|
||||
}
|
||||
|
||||
@ -36,7 +45,7 @@ export class NewSketch extends SketchContribution {
|
||||
id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
|
||||
command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
|
||||
tooltip: 'New',
|
||||
priority: 3
|
||||
priority: 3,
|
||||
});
|
||||
}
|
||||
|
||||
@ -48,16 +57,15 @@ export class NewSketch extends SketchContribution {
|
||||
await this.messageService.error(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace NewSketch {
|
||||
export namespace Commands {
|
||||
export const NEW_SKETCH: Command = {
|
||||
id: 'arduino-new-sketch'
|
||||
id: 'arduino-new-sketch',
|
||||
};
|
||||
export const NEW_SKETCH__TOOLBAR: Command = {
|
||||
id: 'arduino-new-sketch--toolbar'
|
||||
id: 'arduino-new-sketch--toolbar',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,15 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { WorkspaceServer } from '@theia/workspace/lib/common/workspace-protocol';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
SketchContribution,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
Sketch,
|
||||
} from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { OpenSketch } from './open-sketch';
|
||||
@ -9,7 +17,6 @@ import { NotificationCenter } from '../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class OpenRecentSketch extends SketchContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@ -32,16 +39,22 @@ export class OpenRecentSketch extends SketchContribution {
|
||||
this.register(sketches);
|
||||
this.mainMenuManager.update();
|
||||
};
|
||||
this.notificationCenter.onRecentSketchesChanged(({ sketches }) => refreshMenu(sketches));
|
||||
this.notificationCenter.onRecentSketchesChanged(({ sketches }) =>
|
||||
refreshMenu(sketches)
|
||||
);
|
||||
this.sketchService.recentlyOpenedSketches().then(refreshMenu);
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerSubmenu(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, 'Open Recent', { order: '2' });
|
||||
registry.registerSubmenu(
|
||||
ArduinoMenus.FILE__OPEN_RECENT_SUBMENU,
|
||||
'Open Recent',
|
||||
{ order: '2' }
|
||||
);
|
||||
}
|
||||
|
||||
protected register(sketches: Sketch[]): void {
|
||||
let order = 0;
|
||||
const order = 0;
|
||||
for (const sketch of sketches) {
|
||||
const { uri } = sketch;
|
||||
const toDispose = this.toDisposeBeforeRegister.get(uri);
|
||||
@ -49,14 +62,33 @@ export class OpenRecentSketch extends SketchContribution {
|
||||
toDispose.dispose();
|
||||
}
|
||||
const command = { id: `arduino-open-recent--${uri}` };
|
||||
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
|
||||
const handler = {
|
||||
execute: () =>
|
||||
this.commandRegistry.executeCommand(
|
||||
OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
sketch
|
||||
),
|
||||
};
|
||||
this.commandRegistry.registerCommand(command, handler);
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, { commandId: command.id, label: sketch.name, order: String(order) });
|
||||
this.toDisposeBeforeRegister.set(sketch.uri, new DisposableCollection(
|
||||
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
|
||||
));
|
||||
this.menuRegistry.registerMenuAction(
|
||||
ArduinoMenus.FILE__OPEN_RECENT_SUBMENU,
|
||||
{
|
||||
commandId: command.id,
|
||||
label: sketch.name,
|
||||
order: String(order),
|
||||
}
|
||||
);
|
||||
this.toDisposeBeforeRegister.set(
|
||||
sketch.uri,
|
||||
new DisposableCollection(
|
||||
Disposable.create(() =>
|
||||
this.commandRegistry.unregisterCommand(command)
|
||||
),
|
||||
Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuAction(command)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,14 +2,19 @@ import { injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class OpenSketchExternal extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, {
|
||||
execute: () => this.openExternal()
|
||||
execute: () => this.openExternal(),
|
||||
});
|
||||
}
|
||||
|
||||
@ -17,14 +22,14 @@ export class OpenSketchExternal extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
|
||||
commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
|
||||
label: 'Show Sketch Folder',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
|
||||
keybinding: 'CtrlCmd+Alt+K'
|
||||
keybinding: 'CtrlCmd+Alt+K',
|
||||
});
|
||||
}
|
||||
|
||||
@ -40,13 +45,12 @@ export class OpenSketchExternal extends SketchContribution {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace OpenSketchExternal {
|
||||
export namespace Commands {
|
||||
export const OPEN_EXTERNAL: Command = {
|
||||
id: 'arduino-open-sketch-external'
|
||||
id: 'arduino-open-sketch-external',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,22 @@ import { inject, injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { Widget, ContextMenuRenderer } from '@theia/core/lib/browser';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { SketchContribution, Sketch, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Sketch,
|
||||
URI,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
import { ExamplesService } from '../../common/protocol/examples-service';
|
||||
import { BuiltInExamples } from './examples';
|
||||
import { Sketchbook } from './sketchbook';
|
||||
@ -13,7 +25,6 @@ import { SketchContainer } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class OpenSketch extends SketchContribution {
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@ -33,12 +44,16 @@ export class OpenSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, {
|
||||
execute: arg => Sketch.is(arg) ? this.openSketch(arg) : this.openSketch()
|
||||
execute: (arg) =>
|
||||
Sketch.is(arg) ? this.openSketch(arg) : this.openSketch(),
|
||||
});
|
||||
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH__TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: async (_: Widget, target: EventTarget) => {
|
||||
const container = await this.sketchService.getSketches({ exclude: ['**/hardware/**'] });
|
||||
const container = await this.sketchService.getSketches({
|
||||
exclude: ['**/hardware/**'],
|
||||
});
|
||||
if (SketchContainer.isEmpty(container)) {
|
||||
this.openSketch();
|
||||
} else {
|
||||
@ -51,30 +66,53 @@ export class OpenSketch extends SketchContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.OPEN_SKETCH__CONTEXT__OPEN_GROUP, {
|
||||
this.menuRegistry.registerMenuAction(
|
||||
ArduinoMenus.OPEN_SKETCH__CONTEXT__OPEN_GROUP,
|
||||
{
|
||||
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
label: 'Open...'
|
||||
});
|
||||
this.toDispose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(OpenSketch.Commands.OPEN_SKETCH)));
|
||||
this.sketchbook.registerRecursively([...container.children, ...container.sketches], ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP, this.toDispose);
|
||||
label: 'Open...',
|
||||
}
|
||||
);
|
||||
this.toDispose.push(
|
||||
Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuAction(
|
||||
OpenSketch.Commands.OPEN_SKETCH
|
||||
)
|
||||
)
|
||||
);
|
||||
this.sketchbook.registerRecursively(
|
||||
[...container.children, ...container.sketches],
|
||||
ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP,
|
||||
this.toDispose
|
||||
);
|
||||
try {
|
||||
const containers = await this.examplesService.builtIns();
|
||||
const containers =
|
||||
await this.examplesService.builtIns();
|
||||
for (const container of containers) {
|
||||
this.builtInExamples.registerRecursively(container, ArduinoMenus.OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP, this.toDispose);
|
||||
this.builtInExamples.registerRecursively(
|
||||
container,
|
||||
ArduinoMenus.OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP,
|
||||
this.toDispose
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error when collecting built-in examples.', e);
|
||||
console.error(
|
||||
'Error when collecting built-in examples.',
|
||||
e
|
||||
);
|
||||
}
|
||||
const options = {
|
||||
menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT,
|
||||
anchor: {
|
||||
x: parentElement.getBoundingClientRect().left,
|
||||
y: parentElement.getBoundingClientRect().top + parentElement.offsetHeight
|
||||
}
|
||||
}
|
||||
y:
|
||||
parentElement.getBoundingClientRect().top +
|
||||
parentElement.offsetHeight,
|
||||
},
|
||||
};
|
||||
this.contextMenuRenderer.render(options);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -82,14 +120,14 @@ export class OpenSketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
label: 'Open...',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+O'
|
||||
keybinding: 'CtrlCmd+O',
|
||||
});
|
||||
}
|
||||
|
||||
@ -98,11 +136,13 @@ export class OpenSketch extends SketchContribution {
|
||||
id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
|
||||
command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
|
||||
tooltip: 'Open',
|
||||
priority: 4
|
||||
priority: 4,
|
||||
});
|
||||
}
|
||||
|
||||
async openSketch(toOpen: MaybePromise<Sketch | undefined> = this.selectSketch()): Promise<void> {
|
||||
async openSketch(
|
||||
toOpen: MaybePromise<Sketch | undefined> = this.selectSketch()
|
||||
): Promise<void> {
|
||||
const sketch = await toOpen;
|
||||
if (sketch) {
|
||||
this.workspaceService.open(new URI(sketch.uri));
|
||||
@ -111,22 +151,26 @@ export class OpenSketch extends SketchContribution {
|
||||
|
||||
protected async selectSketch(): Promise<Sketch | undefined> {
|
||||
const config = await this.configService.getConfiguration();
|
||||
const defaultPath = await this.fileService.fsPath(new URI(config.sketchDirUri));
|
||||
const defaultPath = await this.fileService.fsPath(
|
||||
new URI(config.sketchDirUri)
|
||||
);
|
||||
const { filePaths } = await remote.dialog.showOpenDialog({
|
||||
defaultPath,
|
||||
properties: ['createDirectory', 'openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: 'Sketch',
|
||||
extensions: ['ino', 'pde']
|
||||
}
|
||||
]
|
||||
extensions: ['ino', 'pde'],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!filePaths.length) {
|
||||
return undefined;
|
||||
}
|
||||
if (filePaths.length > 1) {
|
||||
this.logger.warn(`Multiple sketches were selected: ${filePaths}. Using the first one.`);
|
||||
this.logger.warn(
|
||||
`Multiple sketches were selected: ${filePaths}. Using the first one.`
|
||||
);
|
||||
}
|
||||
const sketchFilePath = filePaths[0];
|
||||
const sketchFileUri = await this.fileSystemExt.getUri(sketchFilePath);
|
||||
@ -136,40 +180,49 @@ export class OpenSketch extends SketchContribution {
|
||||
}
|
||||
if (Sketch.isSketchFile(sketchFileUri)) {
|
||||
const name = new URI(sketchFileUri).path.name;
|
||||
const nameWithExt = this.labelProvider.getName(new URI(sketchFileUri));
|
||||
const nameWithExt = this.labelProvider.getName(
|
||||
new URI(sketchFileUri)
|
||||
);
|
||||
const { response } = await remote.dialog.showMessageBox({
|
||||
title: 'Moving',
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
message: `The file "${nameWithExt}" needs to be inside a sketch folder named as "${name}".\nCreate this folder, move the file, and continue?`
|
||||
message: `The file "${nameWithExt}" needs to be inside a sketch folder named as "${name}".\nCreate this folder, move the file, and continue?`,
|
||||
});
|
||||
if (response === 1) { // OK
|
||||
const newSketchUri = new URI(sketchFileUri).parent.resolve(name);
|
||||
if (response === 1) {
|
||||
// OK
|
||||
const newSketchUri = new URI(sketchFileUri).parent.resolve(
|
||||
name
|
||||
);
|
||||
const exists = await this.fileService.exists(newSketchUri);
|
||||
if (exists) {
|
||||
await remote.dialog.showMessageBox({
|
||||
type: 'error',
|
||||
title: 'Error',
|
||||
message: `A folder named "${name}" already exists. Can't open sketch.`
|
||||
message: `A folder named "${name}" already exists. Can't open sketch.`,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
await this.fileService.createFolder(newSketchUri);
|
||||
await this.fileService.move(new URI(sketchFileUri), new URI(newSketchUri.resolve(nameWithExt).toString()));
|
||||
return this.sketchService.getSketchFolder(newSketchUri.toString());
|
||||
await this.fileService.move(
|
||||
new URI(sketchFileUri),
|
||||
new URI(newSketchUri.resolve(nameWithExt).toString())
|
||||
);
|
||||
return this.sketchService.getSketchFolder(
|
||||
newSketchUri.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace OpenSketch {
|
||||
export namespace Commands {
|
||||
export const OPEN_SKETCH: Command = {
|
||||
id: 'arduino-open-sketch'
|
||||
id: 'arduino-open-sketch',
|
||||
};
|
||||
export const OPEN_SKETCH__TOOLBAR: Command = {
|
||||
id: 'arduino-open-sketch--toolbar'
|
||||
id: 'arduino-open-sketch--toolbar',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,21 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { Contribution, Command, MenuModelRegistry, KeybindingRegistry, CommandRegistry } from './contribution';
|
||||
import {
|
||||
Contribution,
|
||||
Command,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
CommandRegistry,
|
||||
} from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class QuitApp extends Contribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
if (!isOSX) {
|
||||
registry.registerCommand(QuitApp.Commands.QUIT_APP, {
|
||||
execute: () => remote.app.quit()
|
||||
execute: () => remote.app.quit(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -21,7 +26,7 @@ export class QuitApp extends Contribution {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, {
|
||||
commandId: QuitApp.Commands.QUIT_APP.id,
|
||||
label: 'Quit',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -30,17 +35,16 @@ export class QuitApp extends Contribution {
|
||||
if (!isOSX) {
|
||||
registry.registerKeybinding({
|
||||
command: QuitApp.Commands.QUIT_APP.id,
|
||||
keybinding: 'CtrlCmd+Q'
|
||||
keybinding: 'CtrlCmd+Q',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace QuitApp {
|
||||
export namespace Commands {
|
||||
export const QUIT_APP: Command = {
|
||||
id: 'arduino-quit-app'
|
||||
id: 'arduino-quit-app',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,20 @@ import { injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import * as dateFormat from 'dateformat';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
URI,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class SaveAsSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, {
|
||||
execute: args => this.saveAs(args)
|
||||
execute: (args) => this.saveAs(args),
|
||||
});
|
||||
}
|
||||
|
||||
@ -17,21 +23,27 @@ export class SaveAsSketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
label: 'Save As...',
|
||||
order: '7'
|
||||
order: '7',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+Shift+S'
|
||||
keybinding: 'CtrlCmd+Shift+S',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves `true` if the sketch was successfully saved as something.
|
||||
*/
|
||||
async saveAs({ execOnlyIfTemp, openAfterMove, wipeOriginal }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT): Promise<boolean> {
|
||||
async saveAs(
|
||||
{
|
||||
execOnlyIfTemp,
|
||||
openAfterMove,
|
||||
wipeOriginal,
|
||||
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
|
||||
): Promise<boolean> {
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return false;
|
||||
@ -44,13 +56,29 @@ export class SaveAsSketch extends SketchContribution {
|
||||
|
||||
// If target does not exist, propose a `directories.user`/${sketch.name} path
|
||||
// If target exists, propose `directories.user`/${sketch.name}_copy_${yyyymmddHHMMss}
|
||||
const sketchDirUri = new URI((await this.configService.getConfiguration()).sketchDirUri);
|
||||
const exists = await this.fileService.exists(sketchDirUri.resolve(sketch.name));
|
||||
const sketchDirUri = new URI(
|
||||
(await this.configService.getConfiguration()).sketchDirUri
|
||||
);
|
||||
const exists = await this.fileService.exists(
|
||||
sketchDirUri.resolve(sketch.name)
|
||||
);
|
||||
const defaultUri = exists
|
||||
? sketchDirUri.resolve(sketchDirUri.resolve(`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`).toString())
|
||||
? sketchDirUri.resolve(
|
||||
sketchDirUri
|
||||
.resolve(
|
||||
`${sketch.name}_copy_${dateFormat(
|
||||
new Date(),
|
||||
'yyyymmddHHMMss'
|
||||
)}`
|
||||
)
|
||||
.toString()
|
||||
)
|
||||
: sketchDirUri.resolve(sketch.name);
|
||||
const defaultPath = await this.fileService.fsPath(defaultUri);
|
||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({
|
||||
title: 'Save sketch folder as...',
|
||||
defaultPath,
|
||||
});
|
||||
if (!filePath || canceled) {
|
||||
return false;
|
||||
}
|
||||
@ -58,24 +86,31 @@ export class SaveAsSketch extends SketchContribution {
|
||||
if (!destinationUri) {
|
||||
return false;
|
||||
}
|
||||
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
||||
const workspaceUri = await this.sketchService.copy(sketch, {
|
||||
destinationUri,
|
||||
});
|
||||
if (workspaceUri && openAfterMove) {
|
||||
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||
try {
|
||||
await this.fileService.delete(new URI(sketch.uri), { recursive: true });
|
||||
} catch { /* NOOP: from time to time, it's not possible to wipe the old resource from the temp dir on Windows */ }
|
||||
await this.fileService.delete(new URI(sketch.uri), {
|
||||
recursive: true,
|
||||
});
|
||||
} catch {
|
||||
/* NOOP: from time to time, it's not possible to wipe the old resource from the temp dir on Windows */
|
||||
}
|
||||
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
||||
}
|
||||
this.workspaceService.open(new URI(workspaceUri), {
|
||||
preserveWindow: true,
|
||||
});
|
||||
}
|
||||
return !!workspaceUri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace SaveAsSketch {
|
||||
export namespace Commands {
|
||||
export const SAVE_AS_SKETCH: Command = {
|
||||
id: 'arduino-save-as-sketch'
|
||||
id: 'arduino-save-as-sketch',
|
||||
};
|
||||
}
|
||||
export interface Options {
|
||||
@ -90,7 +125,7 @@ export namespace SaveAsSketch {
|
||||
export const DEFAULT: Options = {
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true,
|
||||
wipeOriginal: false
|
||||
wipeOriginal: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,26 @@ import { injectable } from 'inversify';
|
||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class SaveSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, {
|
||||
execute: () => this.saveSketch()
|
||||
execute: () => this.saveSketch(),
|
||||
});
|
||||
registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH__TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () => registry.executeCommand(SaveSketch.Commands.SAVE_SKETCH.id)
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () =>
|
||||
registry.executeCommand(SaveSketch.Commands.SAVE_SKETCH.id),
|
||||
});
|
||||
}
|
||||
|
||||
@ -21,14 +29,14 @@ export class SaveSketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: SaveSketch.Commands.SAVE_SKETCH.id,
|
||||
label: 'Save',
|
||||
order: '6'
|
||||
order: '6',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: SaveSketch.Commands.SAVE_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+S'
|
||||
keybinding: 'CtrlCmd+S',
|
||||
});
|
||||
}
|
||||
|
||||
@ -37,23 +45,22 @@ export class SaveSketch extends SketchContribution {
|
||||
id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
|
||||
command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
|
||||
tooltip: 'Save',
|
||||
priority: 5
|
||||
priority: 5,
|
||||
});
|
||||
}
|
||||
|
||||
async saveSketch(): Promise<void> {
|
||||
return this.commandService.executeCommand(CommonCommands.SAVE_ALL.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace SaveSketch {
|
||||
export namespace Commands {
|
||||
export const SAVE_SKETCH: Command = {
|
||||
id: 'arduino-save-sketch'
|
||||
id: 'arduino-save-sketch',
|
||||
};
|
||||
export const SAVE_SKETCH__TOOLBAR: Command = {
|
||||
id: 'arduino-save-sketch--toolbar'
|
||||
id: 'arduino-save-sketch--toolbar',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Command, MenuModelRegistry, CommandRegistry, SketchContribution, KeybindingRegistry } from './contribution';
|
||||
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;
|
||||
|
||||
@ -28,7 +33,7 @@ export class Settings extends SketchContribution {
|
||||
await this.settingsService.reset();
|
||||
}
|
||||
},
|
||||
isEnabled: () => !this.settingsOpened
|
||||
isEnabled: () => !this.settingsOpened,
|
||||
});
|
||||
}
|
||||
|
||||
@ -36,9 +41,12 @@ export class Settings extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__PREFERENCES_GROUP, {
|
||||
commandId: Settings.Commands.OPEN.id,
|
||||
label: 'Preferences...',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
registry.registerSubmenu(ArduinoMenus.FILE__ADVANCED_SUBMENU, 'Advanced');
|
||||
registry.registerSubmenu(
|
||||
ArduinoMenus.FILE__ADVANCED_SUBMENU,
|
||||
'Advanced'
|
||||
);
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
@ -47,7 +55,6 @@ export class Settings extends SketchContribution {
|
||||
keybinding: 'CtrlCmd+,',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Settings {
|
||||
@ -55,7 +62,7 @@ export namespace Settings {
|
||||
export const OPEN: Command = {
|
||||
id: 'arduino-settings-open',
|
||||
label: 'Open Preferences...',
|
||||
category: 'Arduino'
|
||||
}
|
||||
category: 'Arduino',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,24 @@ import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribu
|
||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||
import { WorkspaceCommands } from '@theia/workspace/lib/browser';
|
||||
import { ContextMenuRenderer } from '@theia/core/lib/browser/context-menu-renderer';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { URI, SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, open } from './contribution';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
URI,
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
open,
|
||||
} from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class SketchControl extends SketchContribution {
|
||||
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
@ -19,19 +30,26 @@ export class SketchControl extends SketchContribution {
|
||||
@inject(ContextMenuRenderer)
|
||||
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
||||
|
||||
protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection();
|
||||
protected readonly toDisposeBeforeCreateNewContextMenu =
|
||||
new DisposableCollection();
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR, {
|
||||
isVisible: widget => this.shell.getWidgets('main').indexOf(widget) !== -1,
|
||||
registry.registerCommand(
|
||||
SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR,
|
||||
{
|
||||
isVisible: (widget) =>
|
||||
this.shell.getWidgets('main').indexOf(widget) !== -1,
|
||||
execute: async () => {
|
||||
this.toDisposeBeforeCreateNewContextMenu.dispose();
|
||||
const sketch = await this.sketchServiceClient.currentSketch();
|
||||
const sketch =
|
||||
await this.sketchServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = document.getElementById(SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id);
|
||||
const target = document.getElementById(
|
||||
SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id
|
||||
);
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
@ -40,90 +58,121 @@ export class SketchControl extends SketchContribution {
|
||||
return;
|
||||
}
|
||||
|
||||
const { mainFileUri, rootFolderFileUris } = await this.sketchService.loadSketch(sketch.uri);
|
||||
const { mainFileUri, rootFolderFileUris } =
|
||||
await this.sketchService.loadSketch(sketch.uri);
|
||||
const uris = [mainFileUri, ...rootFolderFileUris];
|
||||
for (let i = 0; i < uris.length; i++) {
|
||||
const uri = new URI(uris[i]);
|
||||
const command = { id: `arduino-focus-file--${uri.toString()}` };
|
||||
const handler = { execute: () => open(this.openerService, uri) };
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(registry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP, {
|
||||
const command = {
|
||||
id: `arduino-focus-file--${uri.toString()}`,
|
||||
};
|
||||
const handler = {
|
||||
execute: () => open(this.openerService, uri),
|
||||
};
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(
|
||||
registry.registerCommand(command, handler)
|
||||
);
|
||||
this.menuRegistry.registerMenuAction(
|
||||
ArduinoMenus.SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP,
|
||||
{
|
||||
commandId: command.id,
|
||||
label: this.labelProvider.getName(uri),
|
||||
order: `${i}`
|
||||
});
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
order: `${i}`,
|
||||
}
|
||||
);
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(
|
||||
Disposable.create(() =>
|
||||
this.menuRegistry.unregisterMenuAction(command)
|
||||
)
|
||||
);
|
||||
}
|
||||
const options = {
|
||||
menuPath: ArduinoMenus.SKETCH_CONTROL__CONTEXT,
|
||||
anchor: {
|
||||
x: parentElement.getBoundingClientRect().left,
|
||||
y: parentElement.getBoundingClientRect().top + parentElement.offsetHeight
|
||||
}
|
||||
}
|
||||
y:
|
||||
parentElement.getBoundingClientRect().top +
|
||||
parentElement.offsetHeight,
|
||||
},
|
||||
};
|
||||
this.contextMenuRenderer.render(options);
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, {
|
||||
registry.registerMenuAction(
|
||||
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
|
||||
{
|
||||
commandId: WorkspaceCommands.NEW_FILE.id,
|
||||
label: 'New Tab',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, {
|
||||
order: '0',
|
||||
}
|
||||
);
|
||||
registry.registerMenuAction(
|
||||
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
|
||||
{
|
||||
commandId: WorkspaceCommands.FILE_RENAME.id,
|
||||
label: 'Rename',
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, {
|
||||
order: '1',
|
||||
}
|
||||
);
|
||||
registry.registerMenuAction(
|
||||
ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP,
|
||||
{
|
||||
commandId: WorkspaceCommands.FILE_DELETE.id, // TODO: customize delete. Wipe sketch if deleting main file. Close window.
|
||||
label: 'Delete',
|
||||
order: '2'
|
||||
});
|
||||
order: '2',
|
||||
}
|
||||
);
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP, {
|
||||
registry.registerMenuAction(
|
||||
ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP,
|
||||
{
|
||||
commandId: CommonCommands.PREVIOUS_TAB.id,
|
||||
label: 'Previous Tab',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP, {
|
||||
order: '0',
|
||||
}
|
||||
);
|
||||
registry.registerMenuAction(
|
||||
ArduinoMenus.SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP,
|
||||
{
|
||||
commandId: CommonCommands.NEXT_TAB.id,
|
||||
label: 'Next Tab',
|
||||
order: '0'
|
||||
});
|
||||
order: '0',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: WorkspaceCommands.NEW_FILE.id,
|
||||
keybinding: 'CtrlCmd+Shift+N'
|
||||
keybinding: 'CtrlCmd+Shift+N',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: CommonCommands.PREVIOUS_TAB.id,
|
||||
keybinding: 'CtrlCmd+Alt+Left' // TODO: check why electron does not show the keybindings in the UI.
|
||||
keybinding: 'CtrlCmd+Alt+Left', // TODO: check why electron does not show the keybindings in the UI.
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: CommonCommands.NEXT_TAB.id,
|
||||
keybinding: 'CtrlCmd+Alt+Right'
|
||||
keybinding: 'CtrlCmd+Alt+Right',
|
||||
});
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id,
|
||||
command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id
|
||||
command: SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace SketchControl {
|
||||
export namespace Commands {
|
||||
export const OPEN_SKETCH_CONTROL__TOOLBAR: Command = {
|
||||
id: 'arduino-open-sketch-control--toolbar',
|
||||
iconClass: 'fa fa-caret-down'
|
||||
iconClass: 'fa fa-caret-down',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import { OpenSketch } from './open-sketch';
|
||||
|
||||
@injectable()
|
||||
export class Sketchbook extends Examples {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@ -24,12 +23,12 @@ export class Sketchbook extends Examples {
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
onStart(): void {
|
||||
this.sketchService.getSketches({}).then(container => {
|
||||
this.sketchService.getSketches({}).then((container) => {
|
||||
this.register(container);
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
this.sketchServiceClient.onSketchbookDidChange(() => {
|
||||
this.sketchService.getSketches({}).then(container => {
|
||||
this.sketchService.getSketches({}).then((container) => {
|
||||
this.register(container);
|
||||
this.mainMenuManager.update();
|
||||
});
|
||||
@ -37,21 +36,31 @@ export class Sketchbook extends Examples {
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerSubmenu(ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, 'Sketchbook', { order: '3' });
|
||||
registry.registerSubmenu(
|
||||
ArduinoMenus.FILE__SKETCHBOOK_SUBMENU,
|
||||
'Sketchbook',
|
||||
{ order: '3' }
|
||||
);
|
||||
}
|
||||
|
||||
protected register(container: SketchContainer): void {
|
||||
this.toDispose.dispose();
|
||||
this.registerRecursively([...container.children, ...container.sketches], ArduinoMenus.FILE__SKETCHBOOK_SUBMENU, this.toDispose);
|
||||
this.registerRecursively(
|
||||
[...container.children, ...container.sketches],
|
||||
ArduinoMenus.FILE__SKETCHBOOK_SUBMENU,
|
||||
this.toDispose
|
||||
);
|
||||
}
|
||||
|
||||
protected createHandler(uri: string): CommandHandler {
|
||||
return {
|
||||
execute: async () => {
|
||||
const sketch = await this.sketchService.loadSketch(uri);
|
||||
return this.commandService.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch);
|
||||
return this.commandService.executeCommand(
|
||||
OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
sketch
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,11 +6,17 @@ import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { MonitorConnection } from '../monitor/monitor-connection';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class UploadSketch extends SketchContribution {
|
||||
|
||||
@inject(CoreService)
|
||||
protected readonly coreService: CoreService;
|
||||
|
||||
@ -33,15 +39,20 @@ export class UploadSketch extends SketchContribution {
|
||||
execute: () => this.uploadSketch(),
|
||||
isEnabled: () => !this.uploadInProgress,
|
||||
});
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER, {
|
||||
registry.registerCommand(
|
||||
UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER,
|
||||
{
|
||||
execute: () => this.uploadSketch(true),
|
||||
isEnabled: () => !this.uploadInProgress,
|
||||
});
|
||||
}
|
||||
);
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: () => !this.uploadInProgress,
|
||||
isToggled: () => this.uploadInProgress,
|
||||
execute: () => registry.executeCommand(UploadSketch.Commands.UPLOAD_SKETCH.id)
|
||||
execute: () =>
|
||||
registry.executeCommand(UploadSketch.Commands.UPLOAD_SKETCH.id),
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,23 +60,23 @@ export class UploadSketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||
label: 'Upload',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||
label: 'Upload Using Programmer',
|
||||
order: '2'
|
||||
order: '2',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+U'
|
||||
keybinding: 'CtrlCmd+U',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||
keybinding: 'CtrlCmd+Shift+U'
|
||||
keybinding: 'CtrlCmd+Shift+U',
|
||||
});
|
||||
}
|
||||
|
||||
@ -75,12 +86,11 @@ export class UploadSketch extends SketchContribution {
|
||||
command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
|
||||
tooltip: 'Upload',
|
||||
priority: 1,
|
||||
onDidChange: this.onDidChange
|
||||
onDidChange: this.onDidChange,
|
||||
});
|
||||
}
|
||||
|
||||
async uploadSketch(usingProgrammer: boolean = false): Promise<void> {
|
||||
|
||||
async uploadSketch(usingProgrammer = false): Promise<void> {
|
||||
// even with buttons disabled, better to double check if an upload is already in progress
|
||||
if (this.uploadInProgress) {
|
||||
return;
|
||||
@ -105,12 +115,20 @@ export class UploadSketch extends SketchContribution {
|
||||
}
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
const [
|
||||
fqbn,
|
||||
{ selectedProgrammer },
|
||||
verify,
|
||||
verbose,
|
||||
sourceOverride,
|
||||
] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(
|
||||
boardsConfig.selectedBoard?.fqbn
|
||||
),
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard?.fqbn),
|
||||
this.preferences.get('arduino.upload.verify'),
|
||||
this.preferences.get('arduino.upload.verbose'),
|
||||
this.sourceOverride()
|
||||
this.sourceOverride(),
|
||||
]);
|
||||
|
||||
let options: CoreService.Upload.Options | undefined = undefined;
|
||||
@ -129,7 +147,7 @@ export class UploadSketch extends SketchContribution {
|
||||
port,
|
||||
verbose,
|
||||
verify,
|
||||
sourceOverride
|
||||
sourceOverride,
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
@ -139,7 +157,7 @@ export class UploadSketch extends SketchContribution {
|
||||
port,
|
||||
verbose,
|
||||
verify,
|
||||
sourceOverride
|
||||
sourceOverride,
|
||||
};
|
||||
}
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
@ -158,7 +176,10 @@ export class UploadSketch extends SketchContribution {
|
||||
if (monitorConfig) {
|
||||
const { board, port } = monitorConfig;
|
||||
try {
|
||||
await this.boardsServiceClientImpl.waitUntilAvailable(Object.assign(board, { port }), 10_000);
|
||||
await this.boardsServiceClientImpl.waitUntilAvailable(
|
||||
Object.assign(board, { port }),
|
||||
10_000
|
||||
);
|
||||
if (shouldAutoConnect) {
|
||||
// Enabling auto-connect will trigger a connect.
|
||||
this.monitorConnection.autoConnect = true;
|
||||
@ -166,24 +187,25 @@ export class UploadSketch extends SketchContribution {
|
||||
await this.monitorConnection.connect(monitorConfig);
|
||||
}
|
||||
} catch (waitError) {
|
||||
this.messageService.error(`Could not reconnect to serial monitor. ${waitError.toString()}`);
|
||||
this.messageService.error(
|
||||
`Could not reconnect to serial monitor. ${waitError.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace UploadSketch {
|
||||
export namespace Commands {
|
||||
export const UPLOAD_SKETCH: Command = {
|
||||
id: 'arduino-upload-sketch'
|
||||
id: 'arduino-upload-sketch',
|
||||
};
|
||||
export const UPLOAD_SKETCH_USING_PROGRAMMER: Command = {
|
||||
id: 'arduino-upload-sketch-using-programmer'
|
||||
id: 'arduino-upload-sketch-using-programmer',
|
||||
};
|
||||
export const UPLOAD_SKETCH_TOOLBAR: Command = {
|
||||
id: 'arduino-upload-sketch--toolbar'
|
||||
id: 'arduino-upload-sketch--toolbar',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,17 @@ import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
import {
|
||||
SketchContribution,
|
||||
Command,
|
||||
CommandRegistry,
|
||||
MenuModelRegistry,
|
||||
KeybindingRegistry,
|
||||
TabBarToolbarRegistry,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class VerifySketch extends SketchContribution {
|
||||
|
||||
@inject(CoreService)
|
||||
protected readonly coreService: CoreService;
|
||||
|
||||
@ -34,10 +40,12 @@ export class VerifySketch extends SketchContribution {
|
||||
isEnabled: () => !this.verifyInProgress,
|
||||
});
|
||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
isEnabled: () => !this.verifyInProgress,
|
||||
isToggled: () => this.verifyInProgress,
|
||||
execute: () => registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id)
|
||||
execute: () =>
|
||||
registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id),
|
||||
});
|
||||
}
|
||||
|
||||
@ -45,23 +53,23 @@ export class VerifySketch extends SketchContribution {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||
label: 'Verify/Compile',
|
||||
order: '0'
|
||||
order: '0',
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: VerifySketch.Commands.EXPORT_BINARIES.id,
|
||||
label: 'Export compiled Binary',
|
||||
order: '3'
|
||||
order: '3',
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+R'
|
||||
keybinding: 'CtrlCmd+R',
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: VerifySketch.Commands.EXPORT_BINARIES.id,
|
||||
keybinding: 'CtrlCmd+Alt+S'
|
||||
keybinding: 'CtrlCmd+Alt+S',
|
||||
});
|
||||
}
|
||||
|
||||
@ -71,12 +79,11 @@ export class VerifySketch extends SketchContribution {
|
||||
command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
|
||||
tooltip: 'Verify',
|
||||
priority: 0,
|
||||
onDidChange: this.onDidChange
|
||||
onDidChange: this.onDidChange,
|
||||
});
|
||||
}
|
||||
|
||||
async verifySketch(exportBinaries?: boolean): Promise<void> {
|
||||
|
||||
// even with buttons disabled, better to double check if a verify is already in progress
|
||||
if (this.verifyInProgress) {
|
||||
return;
|
||||
@ -94,11 +101,15 @@ export class VerifySketch extends SketchContribution {
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
const [fqbn, sourceOverride] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard?.fqbn),
|
||||
this.sourceOverride()
|
||||
this.boardsDataStore.appendConfigToFqbn(
|
||||
boardsConfig.selectedBoard?.fqbn
|
||||
),
|
||||
this.sourceOverride(),
|
||||
]);
|
||||
const verbose = this.preferences.get('arduino.compile.verbose');
|
||||
const compilerWarnings = this.preferences.get('arduino.compile.warnings');
|
||||
const compilerWarnings = this.preferences.get(
|
||||
'arduino.compile.warnings'
|
||||
);
|
||||
this.outputChannelManager.getChannel('Arduino').clear();
|
||||
await this.coreService.compile({
|
||||
sketchUri: sketch.uri,
|
||||
@ -107,7 +118,7 @@ export class VerifySketch extends SketchContribution {
|
||||
verbose,
|
||||
exportBinaries,
|
||||
sourceOverride,
|
||||
compilerWarnings
|
||||
compilerWarnings,
|
||||
});
|
||||
this.messageService.info('Done compiling.', { timeout: 3000 });
|
||||
} catch (e) {
|
||||
@ -117,19 +128,18 @@ export class VerifySketch extends SketchContribution {
|
||||
this.onDidChangeEmitter.fire();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace VerifySketch {
|
||||
export namespace Commands {
|
||||
export const VERIFY_SKETCH: Command = {
|
||||
id: 'arduino-verify-sketch'
|
||||
id: 'arduino-verify-sketch',
|
||||
};
|
||||
export const EXPORT_BINARIES: Command = {
|
||||
id: 'arduino-export-binaries'
|
||||
id: 'arduino-export-binaries',
|
||||
};
|
||||
export const VERIFY_SKETCH_TOOLBAR: Command = {
|
||||
id: 'arduino-verify-sketch--toolbar'
|
||||
id: 'arduino-verify-sketch--toolbar',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
|
||||
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;
|
||||
|
||||
@ -15,17 +17,21 @@ export class EditorMode implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
get compileForDebug(): boolean {
|
||||
const value = window.localStorage.getItem(EditorMode.COMPILE_FOR_DEBUG_KEY);
|
||||
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));
|
||||
window.localStorage.setItem(
|
||||
EditorMode.COMPILE_FOR_DEBUG_KEY,
|
||||
String(newState)
|
||||
);
|
||||
this.mainMenuManager.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace EditorMode {
|
||||
|
@ -2,21 +2,24 @@ import { injectable, postConstruct, inject } from 'inversify';
|
||||
import { Message } from '@phosphor/messaging';
|
||||
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
|
||||
import { AbstractDialog, DialogProps } from '@theia/core/lib/browser/dialogs';
|
||||
import { LibraryPackage, LibraryService } from '../../common/protocol/library-service';
|
||||
import {
|
||||
LibraryPackage,
|
||||
LibraryService,
|
||||
} from '../../common/protocol/library-service';
|
||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { Installable } from '../../common/protocol';
|
||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
|
||||
static WIDGET_ID = 'library-list-widget';
|
||||
static WIDGET_LABEL = 'Library Manager';
|
||||
|
||||
constructor(
|
||||
@inject(LibraryService) protected service: LibraryService,
|
||||
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<LibraryPackage>) {
|
||||
|
||||
@inject(ListItemRenderer)
|
||||
protected itemRenderer: ListItemRenderer<LibraryPackage>
|
||||
) {
|
||||
super({
|
||||
id: LibraryListWidget.WIDGET_ID,
|
||||
label: LibraryListWidget.WIDGET_LABEL,
|
||||
@ -25,7 +28,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
installable: service,
|
||||
itemLabel: (item: LibraryPackage) => item.name,
|
||||
itemDeprecated: (item: LibraryPackage) => item.deprecated,
|
||||
itemRenderer
|
||||
itemRenderer,
|
||||
});
|
||||
}
|
||||
|
||||
@ -33,17 +36,39 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.toDispose.pushAll([
|
||||
this.notificationCenter.onLibraryInstalled(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onLibraryUninstalled(() => this.refresh(undefined)),
|
||||
this.notificationCenter.onLibraryInstalled(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
this.notificationCenter.onLibraryUninstalled(() =>
|
||||
this.refresh(undefined)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
protected async install({ item, progressId, version }: { item: LibraryPackage, progressId: string, version: Installable.Version }): Promise<void> {
|
||||
const dependencies = await this.service.listDependencies({ item, version, filterSelf: true });
|
||||
protected async install({
|
||||
item,
|
||||
progressId,
|
||||
version,
|
||||
}: {
|
||||
item: LibraryPackage;
|
||||
progressId: string;
|
||||
version: Installable.Version;
|
||||
}): Promise<void> {
|
||||
const dependencies = await this.service.listDependencies({
|
||||
item,
|
||||
version,
|
||||
filterSelf: true,
|
||||
});
|
||||
let installDependencies: boolean | undefined = undefined;
|
||||
if (dependencies.length) {
|
||||
const message = document.createElement('div');
|
||||
message.innerHTML = `The library <b>${item.name}:${version}</b> needs ${dependencies.length === 1 ? 'another dependency' : 'some other dependencies'} currently not installed:`;
|
||||
message.innerHTML = `The library <b>${
|
||||
item.name
|
||||
}:${version}</b> needs ${
|
||||
dependencies.length === 1
|
||||
? 'another dependency'
|
||||
: 'some other dependencies'
|
||||
} currently not installed:`;
|
||||
const listContainer = document.createElement('div');
|
||||
listContainer.style.maxHeight = '300px';
|
||||
listContainer.style.overflowY = 'auto';
|
||||
@ -58,24 +83,26 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
listContainer.appendChild(list);
|
||||
message.appendChild(listContainer);
|
||||
const question = document.createElement('div');
|
||||
question.textContent = `Would you like to install ${dependencies.length === 1 ? 'the missing dependency' : 'all the missing dependencies'}?`;
|
||||
question.textContent = `Would you like to install ${
|
||||
dependencies.length === 1
|
||||
? 'the missing dependency'
|
||||
: 'all the missing dependencies'
|
||||
}?`;
|
||||
message.appendChild(question);
|
||||
const result = await new MessageBoxDialog({
|
||||
title: `Dependencies for library ${item.name}:${version}`,
|
||||
message,
|
||||
buttons: [
|
||||
'Install all',
|
||||
`Install ${item.name} only`,
|
||||
'Cancel'
|
||||
],
|
||||
maxWidth: 740 // Aligned with `settings-dialog.css`.
|
||||
buttons: ['Install all', `Install ${item.name} only`, 'Cancel'],
|
||||
maxWidth: 740, // Aligned with `settings-dialog.css`.
|
||||
}).open();
|
||||
|
||||
if (result) {
|
||||
const { response } = result;
|
||||
if (response === 0) { // All
|
||||
if (response === 0) {
|
||||
// All
|
||||
installDependencies = true;
|
||||
} else if (response === 1) { // Current only
|
||||
} else if (response === 1) {
|
||||
// Current only
|
||||
installDependencies = false;
|
||||
}
|
||||
}
|
||||
@ -85,33 +112,52 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
|
||||
}
|
||||
|
||||
if (typeof installDependencies === 'boolean') {
|
||||
await this.service.install({ item, version, progressId, installDependencies });
|
||||
this.messageService.info(`Successfully installed library ${item.name}:${version}`, { timeout: 3000 });
|
||||
await this.service.install({
|
||||
item,
|
||||
version,
|
||||
progressId,
|
||||
installDependencies,
|
||||
});
|
||||
this.messageService.info(
|
||||
`Successfully installed library ${item.name}:${version}`,
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected async uninstall({ item, progressId }: { item: LibraryPackage, progressId: string }): Promise<void> {
|
||||
protected async uninstall({
|
||||
item,
|
||||
progressId,
|
||||
}: {
|
||||
item: LibraryPackage;
|
||||
progressId: string;
|
||||
}): Promise<void> {
|
||||
await super.uninstall({ item, progressId });
|
||||
this.messageService.info(`Successfully uninstalled library ${item.name}:${item.installedVersion}`, { timeout: 3000 });
|
||||
this.messageService.info(
|
||||
`Successfully uninstalled library ${item.name}:${item.installedVersion}`,
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
|
||||
|
||||
protected response: number;
|
||||
|
||||
constructor(protected readonly options: MessageBoxDialog.Options) {
|
||||
super(options);
|
||||
this.contentNode.appendChild(this.createMessageNode(this.options.message));
|
||||
this.contentNode.appendChild(
|
||||
this.createMessageNode(this.options.message)
|
||||
);
|
||||
(options.buttons || ['OK']).forEach((text, index) => {
|
||||
const button = this.createButton(text);
|
||||
button.classList.add(index === 0 ? 'main' : 'secondary');
|
||||
this.controlPanel.appendChild(button);
|
||||
this.toDisposeOnDetach.push(addEventListener(button, 'click', () => {
|
||||
this.toDisposeOnDetach.push(
|
||||
addEventListener(button, 'click', () => {
|
||||
this.response = index;
|
||||
this.accept();
|
||||
}));
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -137,7 +183,6 @@ class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
|
||||
this.response = 0;
|
||||
super.handleEnter(event);
|
||||
}
|
||||
|
||||
}
|
||||
export namespace MessageBoxDialog {
|
||||
export interface Options extends DialogProps {
|
||||
|
@ -6,18 +6,20 @@ import { LibraryListWidget } from './library-list-widget';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class LibraryListWidgetFrontendContribution extends AbstractViewContribution<LibraryListWidget> implements FrontendApplicationContribution {
|
||||
|
||||
export class LibraryListWidgetFrontendContribution
|
||||
extends AbstractViewContribution<LibraryListWidget>
|
||||
implements FrontendApplicationContribution
|
||||
{
|
||||
constructor() {
|
||||
super({
|
||||
widgetId: LibraryListWidget.WIDGET_ID,
|
||||
widgetName: LibraryListWidget.WIDGET_LABEL,
|
||||
defaultWidgetOptions: {
|
||||
area: 'left',
|
||||
rank: 3
|
||||
rank: 3,
|
||||
},
|
||||
toggleCommandId: `${LibraryListWidget.WIDGET_ID}:toggle`,
|
||||
toggleKeybinding: 'CtrlCmd+Shift+I'
|
||||
toggleKeybinding: 'CtrlCmd+Shift+I',
|
||||
});
|
||||
}
|
||||
|
||||
@ -30,9 +32,8 @@ export class LibraryListWidgetFrontendContribution extends AbstractViewContribut
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: this.toggleCommand.id,
|
||||
label: 'Manage Libraries...',
|
||||
order: '3'
|
||||
order: '3',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,41 +1,82 @@
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { MAIN_MENU_BAR, MenuModelRegistry, MenuNode, MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu';
|
||||
import {
|
||||
MAIN_MENU_BAR,
|
||||
MenuModelRegistry,
|
||||
MenuNode,
|
||||
MenuPath,
|
||||
SubMenuOptions,
|
||||
} from '@theia/core/lib/common/menu';
|
||||
|
||||
export namespace ArduinoMenus {
|
||||
|
||||
// Main menu
|
||||
// -- File
|
||||
export const FILE__SKETCH_GROUP = [...CommonMenus.FILE, '0_sketch'];
|
||||
export const FILE__PRINT_GROUP = [...CommonMenus.FILE, '1_print'];
|
||||
// XXX: on macOS, the "Preferences" and "Advanced" group is not under `File`
|
||||
// The empty path ensures no top level menu is created for the preferences, even if they contains sub menus
|
||||
export const FILE__PREFERENCES_GROUP = [...(isOSX ? [''] : CommonMenus.FILE), '2_preferences'];
|
||||
export const FILE__ADVANCED_GROUP = [...(isOSX ? [''] : CommonMenus.FILE), '3_advanced'];
|
||||
export const FILE__ADVANCED_SUBMENU = [...FILE__ADVANCED_GROUP, '0_advanced_sub'];
|
||||
export const FILE__PREFERENCES_GROUP = [
|
||||
...(isOSX ? [''] : CommonMenus.FILE),
|
||||
'2_preferences',
|
||||
];
|
||||
export const FILE__ADVANCED_GROUP = [
|
||||
...(isOSX ? [''] : CommonMenus.FILE),
|
||||
'3_advanced',
|
||||
];
|
||||
export const FILE__ADVANCED_SUBMENU = [
|
||||
...FILE__ADVANCED_GROUP,
|
||||
'0_advanced_sub',
|
||||
];
|
||||
|
||||
export const FILE__QUIT_GROUP = [...CommonMenus.FILE, '3_quit'];
|
||||
|
||||
// -- File / Open Recent
|
||||
export const FILE__OPEN_RECENT_SUBMENU = [...FILE__SKETCH_GROUP, '0_open_recent'];
|
||||
export const FILE__OPEN_RECENT_SUBMENU = [
|
||||
...FILE__SKETCH_GROUP,
|
||||
'0_open_recent',
|
||||
];
|
||||
|
||||
// -- File / Sketchbook
|
||||
export const FILE__SKETCHBOOK_SUBMENU = [...FILE__SKETCH_GROUP, '1_sketchbook'];
|
||||
export const FILE__SKETCHBOOK_SUBMENU = [
|
||||
...FILE__SKETCH_GROUP,
|
||||
'1_sketchbook',
|
||||
];
|
||||
|
||||
// -- File / Examples
|
||||
export const FILE__EXAMPLES_SUBMENU = [...FILE__SKETCH_GROUP, '2_examples'];
|
||||
export const EXAMPLES__BUILT_IN_GROUP = [...FILE__EXAMPLES_SUBMENU, '0_built_ins'];
|
||||
export const EXAMPLES__ANY_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '1_any_board'];
|
||||
export const EXAMPLES__CURRENT_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '2_current_board'];
|
||||
export const EXAMPLES__USER_LIBS_GROUP = [...FILE__EXAMPLES_SUBMENU, '3_user_libs'];
|
||||
export const EXAMPLES__BUILT_IN_GROUP = [
|
||||
...FILE__EXAMPLES_SUBMENU,
|
||||
'0_built_ins',
|
||||
];
|
||||
export const EXAMPLES__ANY_BOARD_GROUP = [
|
||||
...FILE__EXAMPLES_SUBMENU,
|
||||
'1_any_board',
|
||||
];
|
||||
export const EXAMPLES__CURRENT_BOARD_GROUP = [
|
||||
...FILE__EXAMPLES_SUBMENU,
|
||||
'2_current_board',
|
||||
];
|
||||
export const EXAMPLES__USER_LIBS_GROUP = [
|
||||
...FILE__EXAMPLES_SUBMENU,
|
||||
'3_user_libs',
|
||||
];
|
||||
|
||||
// -- Edit
|
||||
// `Copy`, `Copy to Forum`, `Paste`, etc.
|
||||
// Note: `1_undo` is the first group from Theia, we start with `2`
|
||||
export const EDIT__TEXT_CONTROL_GROUP = [...CommonMenus.EDIT, '2_text_control'];
|
||||
export const EDIT__TEXT_CONTROL_GROUP = [
|
||||
...CommonMenus.EDIT,
|
||||
'2_text_control',
|
||||
];
|
||||
// `Comment/Uncomment`, etc.
|
||||
export const EDIT__CODE_CONTROL_GROUP = [...CommonMenus.EDIT, '3_code_control'];
|
||||
export const EDIT__FONT_CONTROL_GROUP = [...CommonMenus.EDIT, '4_font_control'];
|
||||
export const EDIT__CODE_CONTROL_GROUP = [
|
||||
...CommonMenus.EDIT,
|
||||
'3_code_control',
|
||||
];
|
||||
export const EDIT__FONT_CONTROL_GROUP = [
|
||||
...CommonMenus.EDIT,
|
||||
'4_font_control',
|
||||
];
|
||||
export const EDIT__FIND_GROUP = [...CommonMenus.EDIT, '5_find'];
|
||||
|
||||
// -- Sketch
|
||||
@ -62,35 +103,62 @@ export namespace ArduinoMenus {
|
||||
export const HELP__CONTROL_GROUP = [...CommonMenus.HELP, '2_control'];
|
||||
// `About` group
|
||||
// XXX: on macOS, the about group is not under `Help`
|
||||
export const HELP__ABOUT_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.HELP), '999_about'];
|
||||
export const HELP__ABOUT_GROUP = [
|
||||
...(isOSX ? MAIN_MENU_BAR : CommonMenus.HELP),
|
||||
'999_about',
|
||||
];
|
||||
|
||||
// ------------
|
||||
|
||||
// Context menus
|
||||
// -- Open
|
||||
export const OPEN_SKETCH__CONTEXT = ['arduino-open-sketch--context'];
|
||||
export const OPEN_SKETCH__CONTEXT__OPEN_GROUP = [...OPEN_SKETCH__CONTEXT, '0_open'];
|
||||
export const OPEN_SKETCH__CONTEXT__RECENT_GROUP = [...OPEN_SKETCH__CONTEXT, '1_recent'];
|
||||
export const OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP = [...OPEN_SKETCH__CONTEXT, '2_examples'];
|
||||
export const OPEN_SKETCH__CONTEXT__OPEN_GROUP = [
|
||||
...OPEN_SKETCH__CONTEXT,
|
||||
'0_open',
|
||||
];
|
||||
export const OPEN_SKETCH__CONTEXT__RECENT_GROUP = [
|
||||
...OPEN_SKETCH__CONTEXT,
|
||||
'1_recent',
|
||||
];
|
||||
export const OPEN_SKETCH__CONTEXT__EXAMPLES_GROUP = [
|
||||
...OPEN_SKETCH__CONTEXT,
|
||||
'2_examples',
|
||||
];
|
||||
|
||||
// -- Sketch control
|
||||
export const SKETCH_CONTROL__CONTEXT = ['arduino-sketch-control--context'];
|
||||
// `New Tab`, `Rename`, `Delete`
|
||||
export const SKETCH_CONTROL__CONTEXT__MAIN_GROUP = [...SKETCH_CONTROL__CONTEXT, '0_main'];
|
||||
export const SKETCH_CONTROL__CONTEXT__MAIN_GROUP = [
|
||||
...SKETCH_CONTROL__CONTEXT,
|
||||
'0_main',
|
||||
];
|
||||
// `Previous Tab`, `Next Tab`
|
||||
export const SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP = [...SKETCH_CONTROL__CONTEXT, '1_navigation'];
|
||||
export const SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP = [
|
||||
...SKETCH_CONTROL__CONTEXT,
|
||||
'1_navigation',
|
||||
];
|
||||
// Sketch files opened in editors
|
||||
export const SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP = [...SKETCH_CONTROL__CONTEXT, '2_resources'];
|
||||
|
||||
export const SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP = [
|
||||
...SKETCH_CONTROL__CONTEXT,
|
||||
'2_resources',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hack. It removes a submenu with all its children if any.
|
||||
* Theia cannot dispose submenu entries with a proper API: https://github.com/eclipse-theia/theia/issues/7299
|
||||
*/
|
||||
export function unregisterSubmenu(menuPath: string[], menuRegistry: MenuModelRegistry): void {
|
||||
export function unregisterSubmenu(
|
||||
menuPath: string[],
|
||||
menuRegistry: MenuModelRegistry
|
||||
): void {
|
||||
if (menuPath.length < 2) {
|
||||
throw new Error(`Expected at least two item as a menu-path. Got ${JSON.stringify(menuPath)} instead.`);
|
||||
throw new Error(
|
||||
`Expected at least two item as a menu-path. Got ${JSON.stringify(
|
||||
menuPath
|
||||
)} instead.`
|
||||
);
|
||||
}
|
||||
const toRemove = menuPath[menuPath.length - 1];
|
||||
const parentMenuPath = menuPath.slice(0, menuPath.length - 1);
|
||||
@ -99,7 +167,9 @@ export function unregisterSubmenu(menuPath: string[], menuRegistry: MenuModelReg
|
||||
const parent = menuRegistry.getMenu(parentMenuPath);
|
||||
const index = parent.children.findIndex(({ id }) => id === toRemove);
|
||||
if (index === -1) {
|
||||
throw new Error(`Could not find menu with menu-path: ${JSON.stringify(menuPath)}.`);
|
||||
throw new Error(
|
||||
`Could not find menu with menu-path: ${JSON.stringify(menuPath)}.`
|
||||
);
|
||||
}
|
||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||
}
|
||||
@ -108,8 +178,11 @@ export function unregisterSubmenu(menuPath: string[], menuRegistry: MenuModelReg
|
||||
* Special menu node that is not backed by any commands and is always disabled.
|
||||
*/
|
||||
export class PlaceholderMenuNode implements MenuNode {
|
||||
|
||||
constructor(protected readonly menuPath: MenuPath, readonly label: string, protected options: SubMenuOptions = { order: '0' }) { }
|
||||
constructor(
|
||||
protected readonly menuPath: MenuPath,
|
||||
readonly label: string,
|
||||
protected options: SubMenuOptions = { order: '0' }
|
||||
) {}
|
||||
|
||||
get icon(): string | undefined {
|
||||
return this.options?.iconClass;
|
||||
@ -122,5 +195,4 @@ export class PlaceholderMenuNode implements MenuNode {
|
||||
get id(): string {
|
||||
return [...this.menuPath, 'placeholder'].join('-');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,9 +3,19 @@ import { deepClone } from '@theia/core/lib/common/objects';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { MonitorService, MonitorConfig, MonitorError, Status } from '../../common/protocol/monitor-service';
|
||||
import {
|
||||
MonitorService,
|
||||
MonitorConfig,
|
||||
MonitorError,
|
||||
Status,
|
||||
} from '../../common/protocol/monitor-service';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
import { Port, Board, BoardsService, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||
import {
|
||||
Port,
|
||||
Board,
|
||||
BoardsService,
|
||||
AttachedBoardsChangeEvent,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||
import { BoardsConfig } from '../boards/boards-config';
|
||||
import { MonitorModel } from './monitor-model';
|
||||
@ -13,7 +23,6 @@ import { NotificationCenter } from '../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class MonitorConnection {
|
||||
|
||||
@inject(MonitorModel)
|
||||
protected readonly monitorModel: MonitorModel;
|
||||
|
||||
@ -43,8 +52,10 @@ export class MonitorConnection {
|
||||
* Note: The idea is to toggle this property from the UI (`Monitor` view)
|
||||
* and the boards config and the boards attachment/detachment logic can be at on place, here.
|
||||
*/
|
||||
protected _autoConnect: boolean = false;
|
||||
protected readonly onConnectionChangedEmitter = new Emitter<MonitorConnection.State | undefined>();
|
||||
protected _autoConnect = false;
|
||||
protected readonly onConnectionChangedEmitter = new Emitter<
|
||||
MonitorConnection.State | undefined
|
||||
>();
|
||||
/**
|
||||
* This emitter forwards all read events **iff** the connection is established.
|
||||
*/
|
||||
@ -60,7 +71,7 @@ export class MonitorConnection {
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.monitorServiceClient.onError(async error => {
|
||||
this.monitorServiceClient.onError(async (error) => {
|
||||
let shouldReconnect = false;
|
||||
if (this.state) {
|
||||
const { code, config } = error;
|
||||
@ -68,21 +79,40 @@ export class MonitorConnection {
|
||||
const options = { timeout: 3000 };
|
||||
switch (code) {
|
||||
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
|
||||
console.debug(`Connection was canceled by client: ${MonitorConnection.State.toString(this.state)}.`);
|
||||
console.debug(
|
||||
`Connection was canceled by client: ${MonitorConnection.State.toString(
|
||||
this.state
|
||||
)}.`
|
||||
);
|
||||
break;
|
||||
}
|
||||
case MonitorError.ErrorCodes.DEVICE_BUSY: {
|
||||
this.messageService.warn(`Connection failed. Serial port is busy: ${Port.toString(port)}.`, options);
|
||||
this.messageService.warn(
|
||||
`Connection failed. Serial port is busy: ${Port.toString(
|
||||
port
|
||||
)}.`,
|
||||
options
|
||||
);
|
||||
shouldReconnect = this.autoConnect;
|
||||
this.monitorErrors.push(error);
|
||||
break;
|
||||
}
|
||||
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
|
||||
this.messageService.info(`Disconnected ${Board.toString(board, { useFqbn: false })} from ${Port.toString(port)}.`, options);
|
||||
this.messageService.info(
|
||||
`Disconnected ${Board.toString(board, {
|
||||
useFqbn: false,
|
||||
})} from ${Port.toString(port)}.`,
|
||||
options
|
||||
);
|
||||
break;
|
||||
}
|
||||
case undefined: {
|
||||
this.messageService.error(`Unexpected error. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`, options);
|
||||
this.messageService.error(
|
||||
`Unexpected error. Reconnecting ${Board.toString(
|
||||
board
|
||||
)} on port ${Port.toString(port)}.`,
|
||||
options
|
||||
);
|
||||
console.error(JSON.stringify(error));
|
||||
shouldReconnect = this.connected && this.autoConnect;
|
||||
break;
|
||||
@ -93,32 +123,62 @@ export class MonitorConnection {
|
||||
this.onConnectionChangedEmitter.fire(this.state);
|
||||
if (shouldReconnect) {
|
||||
if (this.monitorErrors.length >= 10) {
|
||||
this.messageService.warn(`Failed to reconnect ${Board.toString(board, { useFqbn: false })} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(port)} serial port is busy. after 10 consecutive attempts.`);
|
||||
this.messageService.warn(
|
||||
`Failed to reconnect ${Board.toString(board, {
|
||||
useFqbn: false,
|
||||
})} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(
|
||||
port
|
||||
)} serial port is busy. after 10 consecutive attempts.`
|
||||
);
|
||||
this.monitorErrors.length = 0;
|
||||
} else {
|
||||
const attempts = (this.monitorErrors.length || 1);
|
||||
const attempts = this.monitorErrors.length || 1;
|
||||
if (this.reconnectTimeout !== undefined) {
|
||||
// Clear the previous timer.
|
||||
window.clearTimeout(this.reconnectTimeout);
|
||||
}
|
||||
const timeout = attempts * 1000;
|
||||
this.messageService.warn(`Reconnecting ${Board.toString(board, { useFqbn: false })} to ${Port.toString(port)} in ${attempts} seconds...`, { timeout });
|
||||
this.reconnectTimeout = window.setTimeout(() => this.connect(oldState.config), timeout);
|
||||
this.messageService.warn(
|
||||
`Reconnecting ${Board.toString(board, {
|
||||
useFqbn: false,
|
||||
})} to ${Port.toString(
|
||||
port
|
||||
)} in ${attempts} seconds...`,
|
||||
{ timeout }
|
||||
);
|
||||
this.reconnectTimeout = window.setTimeout(
|
||||
() => this.connect(oldState.config),
|
||||
timeout
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
|
||||
this.notificationCenter.onAttachedBoardsChanged(event => {
|
||||
this.boardsServiceProvider.onBoardsConfigChanged(
|
||||
this.handleBoardConfigChange.bind(this)
|
||||
);
|
||||
this.notificationCenter.onAttachedBoardsChanged((event) => {
|
||||
if (this.autoConnect && this.connected) {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
if (this.boardsServiceProvider.canUploadTo(boardsConfig, { silent: false })) {
|
||||
if (
|
||||
this.boardsServiceProvider.canUploadTo(boardsConfig, {
|
||||
silent: false,
|
||||
})
|
||||
) {
|
||||
const { attached } = AttachedBoardsChangeEvent.diff(event);
|
||||
if (attached.boards.some(board => !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
||||
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||
if (
|
||||
attached.boards.some(
|
||||
(board) =>
|
||||
!!board.port &&
|
||||
BoardsConfig.Config.sameAs(boardsConfig, board)
|
||||
)
|
||||
) {
|
||||
const { selectedBoard: board, selectedPort: port } =
|
||||
boardsConfig;
|
||||
const { baudRate } = this.monitorModel;
|
||||
this.disconnect()
|
||||
.then(() => this.connect({ board, port, baudRate }));
|
||||
this.disconnect().then(() =>
|
||||
this.connect({ board, port, baudRate })
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,7 +211,9 @@ export class MonitorConnection {
|
||||
if (!oldValue && value) {
|
||||
// We have to make sure the previous boards config has been restored.
|
||||
// Otherwise, we might start the auto-connection without configured boards.
|
||||
this.applicationState.reachedState('started_contributions').then(() => {
|
||||
this.applicationState
|
||||
.reachedState('started_contributions')
|
||||
.then(() => {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
this.handleBoardConfigChange(boardsConfig);
|
||||
});
|
||||
@ -170,7 +232,11 @@ export class MonitorConnection {
|
||||
return disconnectStatus;
|
||||
}
|
||||
}
|
||||
console.info(`>>> Creating serial monitor connection for ${Board.toString(config.board)} on port ${Port.toString(config.port)}...`);
|
||||
console.info(
|
||||
`>>> Creating serial monitor connection for ${Board.toString(
|
||||
config.board
|
||||
)} on port ${Port.toString(config.port)}...`
|
||||
);
|
||||
const connectStatus = await this.monitorService.connect(config);
|
||||
if (Status.isOK(connectStatus)) {
|
||||
const requestMessage = () => {
|
||||
@ -180,10 +246,15 @@ export class MonitorConnection {
|
||||
requestMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
requestMessage();
|
||||
this.state = { config };
|
||||
console.info(`<<< Serial monitor connection created for ${Board.toString(config.board, { useFqbn: false })} on port ${Port.toString(config.port)}.`);
|
||||
console.info(
|
||||
`<<< Serial monitor connection created for ${Board.toString(
|
||||
config.board,
|
||||
{ useFqbn: false }
|
||||
)} on port ${Port.toString(config.port)}.`
|
||||
);
|
||||
}
|
||||
this.onConnectionChangedEmitter.fire(this.state);
|
||||
return Status.isOK(connectStatus);
|
||||
@ -200,9 +271,17 @@ export class MonitorConnection {
|
||||
console.log('>>> Disposing existing monitor connection...');
|
||||
const status = await this.monitorService.disconnect();
|
||||
if (Status.isOK(status)) {
|
||||
console.log(`<<< Disposed connection. Was: ${MonitorConnection.State.toString(stateCopy)}`);
|
||||
console.log(
|
||||
`<<< Disposed connection. Was: ${MonitorConnection.State.toString(
|
||||
stateCopy
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
console.warn(`<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString(stateCopy)}`);
|
||||
console.warn(
|
||||
`<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString(
|
||||
stateCopy
|
||||
)}`
|
||||
);
|
||||
}
|
||||
this.state = undefined;
|
||||
this.onConnectionChangedEmitter.fire(this.state);
|
||||
@ -218,8 +297,9 @@ export class MonitorConnection {
|
||||
if (!this.connected) {
|
||||
return Status.NOT_CONNECTED;
|
||||
}
|
||||
return new Promise<Status>(resolve => {
|
||||
this.monitorService.send(data + this.monitorModel.lineEnding)
|
||||
return new Promise<Status>((resolve) => {
|
||||
this.monitorService
|
||||
.send(data + this.monitorModel.lineEnding)
|
||||
.then(() => resolve(Status.OK));
|
||||
});
|
||||
}
|
||||
@ -232,14 +312,24 @@ export class MonitorConnection {
|
||||
return this.onReadEmitter.event;
|
||||
}
|
||||
|
||||
protected async handleBoardConfigChange(boardsConfig: BoardsConfig.Config): Promise<void> {
|
||||
protected async handleBoardConfigChange(
|
||||
boardsConfig: BoardsConfig.Config
|
||||
): Promise<void> {
|
||||
if (this.autoConnect) {
|
||||
if (this.boardsServiceProvider.canUploadTo(boardsConfig, { silent: false })) {
|
||||
if (
|
||||
this.boardsServiceProvider.canUploadTo(boardsConfig, {
|
||||
silent: false,
|
||||
})
|
||||
) {
|
||||
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
|
||||
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
|
||||
this.boardsService.getAvailablePorts().then(ports => {
|
||||
if (ports.some(port => Port.equals(port, boardsConfig.selectedPort))) {
|
||||
new Promise<void>(resolve => {
|
||||
this.boardsService.getAvailablePorts().then((ports) => {
|
||||
if (
|
||||
ports.some((port) =>
|
||||
Port.equals(port, boardsConfig.selectedPort)
|
||||
)
|
||||
) {
|
||||
new Promise<void>((resolve) => {
|
||||
// First, disconnect if connected.
|
||||
if (this.connected) {
|
||||
this.disconnect().then(() => resolve());
|
||||
@ -248,7 +338,8 @@ export class MonitorConnection {
|
||||
resolve();
|
||||
}).then(() => {
|
||||
// Then (re-)connect.
|
||||
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||
const { selectedBoard: board, selectedPort: port } =
|
||||
boardsConfig;
|
||||
const { baudRate } = this.monitorModel;
|
||||
this.connect({ board, port, baudRate });
|
||||
});
|
||||
@ -257,11 +348,9 @@ export class MonitorConnection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace MonitorConnection {
|
||||
|
||||
export interface State {
|
||||
readonly config: MonitorConfig;
|
||||
}
|
||||
@ -273,5 +362,4 @@ export namespace MonitorConnection {
|
||||
return `${Board.toString(board)} ${Port.toString(port)}`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||
import {
|
||||
FrontendApplicationContribution,
|
||||
LocalStorageService,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { BoardsServiceProvider } from '../boards/boards-service-provider';
|
||||
|
||||
@injectable()
|
||||
export class MonitorModel implements FrontendApplicationContribution {
|
||||
|
||||
protected static STORAGE_ID = 'arduino-monitor-model';
|
||||
|
||||
@inject(LocalStorageService)
|
||||
@ -15,7 +17,9 @@ export class MonitorModel implements FrontendApplicationContribution {
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceClient: BoardsServiceProvider;
|
||||
|
||||
protected readonly onChangeEmitter: Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>;
|
||||
protected readonly onChangeEmitter: Emitter<
|
||||
MonitorModel.State.Change<keyof MonitorModel.State>
|
||||
>;
|
||||
protected _autoscroll: boolean;
|
||||
protected _timestamp: boolean;
|
||||
protected _baudRate: MonitorConfig.BaudRate;
|
||||
@ -26,11 +30,15 @@ export class MonitorModel implements FrontendApplicationContribution {
|
||||
this._timestamp = false;
|
||||
this._baudRate = MonitorConfig.BaudRate.DEFAULT;
|
||||
this._lineEnding = MonitorModel.EOL.DEFAULT;
|
||||
this.onChangeEmitter = new Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>();
|
||||
this.onChangeEmitter = new Emitter<
|
||||
MonitorModel.State.Change<keyof MonitorModel.State>
|
||||
>();
|
||||
}
|
||||
|
||||
onStart(): void {
|
||||
this.localStorageService.getData<MonitorModel.State>(MonitorModel.STORAGE_ID).then(state => {
|
||||
this.localStorageService
|
||||
.getData<MonitorModel.State>(MonitorModel.STORAGE_ID)
|
||||
.then((state) => {
|
||||
if (state) {
|
||||
this.restoreState(state);
|
||||
}
|
||||
@ -48,7 +56,12 @@ export class MonitorModel implements FrontendApplicationContribution {
|
||||
toggleAutoscroll(): void {
|
||||
this._autoscroll = !this._autoscroll;
|
||||
this.storeState();
|
||||
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'autoscroll', value: this._autoscroll }));
|
||||
this.storeState().then(() =>
|
||||
this.onChangeEmitter.fire({
|
||||
property: 'autoscroll',
|
||||
value: this._autoscroll,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get timestamp(): boolean {
|
||||
@ -57,7 +70,12 @@ export class MonitorModel implements FrontendApplicationContribution {
|
||||
|
||||
toggleTimestamp(): void {
|
||||
this._timestamp = !this._timestamp;
|
||||
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'timestamp', value: this._timestamp }));
|
||||
this.storeState().then(() =>
|
||||
this.onChangeEmitter.fire({
|
||||
property: 'timestamp',
|
||||
value: this._timestamp,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get baudRate(): MonitorConfig.BaudRate {
|
||||
@ -66,7 +84,12 @@ export class MonitorModel implements FrontendApplicationContribution {
|
||||
|
||||
set baudRate(baudRate: MonitorConfig.BaudRate) {
|
||||
this._baudRate = baudRate;
|
||||
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'baudRate', value: this._baudRate }));
|
||||
this.storeState().then(() =>
|
||||
this.onChangeEmitter.fire({
|
||||
property: 'baudRate',
|
||||
value: this._baudRate,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get lineEnding(): MonitorModel.EOL {
|
||||
@ -75,7 +98,12 @@ export class MonitorModel implements FrontendApplicationContribution {
|
||||
|
||||
set lineEnding(lineEnding: MonitorModel.EOL) {
|
||||
this._lineEnding = lineEnding;
|
||||
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'lineEnding', value: this._lineEnding }));
|
||||
this.storeState().then(() =>
|
||||
this.onChangeEmitter.fire({
|
||||
property: 'lineEnding',
|
||||
value: this._lineEnding,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected restoreState(state: MonitorModel.State): void {
|
||||
@ -90,14 +118,12 @@ export class MonitorModel implements FrontendApplicationContribution {
|
||||
autoscroll: this._autoscroll,
|
||||
timestamp: this._timestamp,
|
||||
baudRate: this._baudRate,
|
||||
lineEnding: this._lineEnding
|
||||
lineEnding: this._lineEnding,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace MonitorModel {
|
||||
|
||||
export interface State {
|
||||
autoscroll: boolean;
|
||||
timestamp: boolean;
|
||||
@ -115,5 +141,4 @@ export namespace MonitorModel {
|
||||
export namespace EOL {
|
||||
export const DEFAULT: EOL = '\n';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { MonitorServiceClient, MonitorError } from '../../common/protocol/monitor-service';
|
||||
import {
|
||||
MonitorServiceClient,
|
||||
MonitorError,
|
||||
} from '../../common/protocol/monitor-service';
|
||||
|
||||
@injectable()
|
||||
export class MonitorServiceClientImpl implements MonitorServiceClient {
|
||||
|
||||
protected readonly onErrorEmitter = new Emitter<MonitorError>();
|
||||
readonly onError = this.onErrorEmitter.event;
|
||||
|
||||
notifyError(error: MonitorError): void {
|
||||
this.onErrorEmitter.fire(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ import { injectable, inject } from 'inversify';
|
||||
import { AbstractViewContribution } from '@theia/core/lib/browser';
|
||||
import { MonitorWidget } from './monitor-widget';
|
||||
import { MenuModelRegistry, Command, CommandRegistry } from '@theia/core';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import {
|
||||
TabBarToolbarContribution,
|
||||
TabBarToolbarRegistry,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { MonitorModel } from './monitor-model';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
@ -12,25 +15,28 @@ export namespace SerialMonitor {
|
||||
export namespace Commands {
|
||||
export const AUTOSCROLL: Command = {
|
||||
id: 'serial-monitor-autoscroll',
|
||||
label: 'Autoscroll'
|
||||
}
|
||||
label: 'Autoscroll',
|
||||
};
|
||||
export const TIMESTAMP: Command = {
|
||||
id: 'serial-monitor-timestamp',
|
||||
label: 'Timestamp'
|
||||
}
|
||||
label: 'Timestamp',
|
||||
};
|
||||
export const CLEAR_OUTPUT: Command = {
|
||||
id: 'serial-monitor-clear-output',
|
||||
label: 'Clear Output',
|
||||
iconClass: 'clear-all'
|
||||
}
|
||||
iconClass: 'clear-all',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonitorViewContribution extends AbstractViewContribution<MonitorWidget> implements TabBarToolbarContribution {
|
||||
|
||||
export class MonitorViewContribution
|
||||
extends AbstractViewContribution<MonitorWidget>
|
||||
implements TabBarToolbarContribution
|
||||
{
|
||||
static readonly TOGGLE_SERIAL_MONITOR = MonitorWidget.ID + ':toggle';
|
||||
static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR = MonitorWidget.ID + ':toggle-toolbar';
|
||||
static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR =
|
||||
MonitorWidget.ID + ':toggle-toolbar';
|
||||
|
||||
@inject(MonitorModel) protected readonly model: MonitorModel;
|
||||
|
||||
@ -39,11 +45,11 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
|
||||
widgetId: MonitorWidget.ID,
|
||||
widgetName: 'Serial Monitor',
|
||||
defaultWidgetOptions: {
|
||||
area: 'bottom'
|
||||
area: 'bottom',
|
||||
},
|
||||
toggleCommandId: MonitorViewContribution.TOGGLE_SERIAL_MONITOR,
|
||||
toggleKeybinding: 'CtrlCmd+Shift+M'
|
||||
})
|
||||
toggleKeybinding: 'CtrlCmd+Shift+M',
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
@ -51,7 +57,7 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: this.toggleCommand.id,
|
||||
label: 'Serial Monitor',
|
||||
order: '5'
|
||||
order: '5',
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -60,38 +66,44 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
|
||||
registry.registerItem({
|
||||
id: 'monitor-autoscroll',
|
||||
render: () => this.renderAutoScrollButton(),
|
||||
isVisible: widget => widget instanceof MonitorWidget,
|
||||
onDidChange: this.model.onChange as any // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||
isVisible: (widget) => widget instanceof MonitorWidget,
|
||||
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||
});
|
||||
registry.registerItem({
|
||||
id: 'monitor-timestamp',
|
||||
render: () => this.renderTimestampButton(),
|
||||
isVisible: widget => widget instanceof MonitorWidget,
|
||||
onDidChange: this.model.onChange as any // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||
isVisible: (widget) => widget instanceof MonitorWidget,
|
||||
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||
});
|
||||
registry.registerItem({
|
||||
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
|
||||
command: SerialMonitor.Commands.CLEAR_OUTPUT.id,
|
||||
tooltip: 'Clear Output'
|
||||
tooltip: 'Clear Output',
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(commands: CommandRegistry): void {
|
||||
commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, {
|
||||
isEnabled: widget => widget instanceof MonitorWidget,
|
||||
isVisible: widget => widget instanceof MonitorWidget,
|
||||
execute: widget => {
|
||||
isEnabled: (widget) => widget instanceof MonitorWidget,
|
||||
isVisible: (widget) => widget instanceof MonitorWidget,
|
||||
execute: (widget) => {
|
||||
if (widget instanceof MonitorWidget) {
|
||||
widget.clearConsole();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
if (this.toggleCommand) {
|
||||
commands.registerCommand(this.toggleCommand, { execute: () => this.toggle() });
|
||||
commands.registerCommand({ id: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR }, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right',
|
||||
execute: () => this.toggle()
|
||||
commands.registerCommand(this.toggleCommand, {
|
||||
execute: () => this.toggle(),
|
||||
});
|
||||
commands.registerCommand(
|
||||
{ id: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR },
|
||||
{
|
||||
isVisible: (widget) =>
|
||||
ArduinoToolbar.is(widget) && widget.side === 'right',
|
||||
execute: () => this.toggle(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,13 +117,17 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
|
||||
}
|
||||
|
||||
protected renderAutoScrollButton(): React.ReactNode {
|
||||
return <React.Fragment key='autoscroll-toolbar-item'>
|
||||
return (
|
||||
<React.Fragment key="autoscroll-toolbar-item">
|
||||
<div
|
||||
title='Toggle Autoscroll'
|
||||
className={`item enabled fa fa-angle-double-down arduino-monitor ${this.model.autoscroll ? 'toggled' : ''}`}
|
||||
title="Toggle Autoscroll"
|
||||
className={`item enabled fa fa-angle-double-down arduino-monitor ${
|
||||
this.model.autoscroll ? 'toggled' : ''
|
||||
}`}
|
||||
onClick={this.toggleAutoScroll}
|
||||
></div>
|
||||
</React.Fragment>;
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
protected readonly toggleAutoScroll = () => this.doToggleAutoScroll();
|
||||
@ -120,18 +136,21 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
|
||||
}
|
||||
|
||||
protected renderTimestampButton(): React.ReactNode {
|
||||
return <React.Fragment key='line-ending-toolbar-item'>
|
||||
return (
|
||||
<React.Fragment key="line-ending-toolbar-item">
|
||||
<div
|
||||
title='Toggle Timestamp'
|
||||
className={`item enabled fa fa-clock-o arduino-monitor ${this.model.timestamp ? 'toggled' : ''}`}
|
||||
title="Toggle Timestamp"
|
||||
className={`item enabled fa fa-clock-o arduino-monitor ${
|
||||
this.model.timestamp ? 'toggled' : ''
|
||||
}`}
|
||||
onClick={this.toggleTimestamp}
|
||||
></div>
|
||||
</React.Fragment>;
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
protected readonly toggleTimestamp = () => this.doToggleTimestamp();
|
||||
protected async doToggleTimestamp(): Promise<void> {
|
||||
this.model.toggleTimestamp();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,8 +5,16 @@ import { OptionsType } from 'react-select/src/types';
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
|
||||
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable'
|
||||
import { ReactWidget, Message, Widget, MessageLoop } from '@theia/core/lib/browser/widgets';
|
||||
import {
|
||||
DisposableCollection,
|
||||
Disposable,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
ReactWidget,
|
||||
Message,
|
||||
Widget,
|
||||
MessageLoop,
|
||||
} from '@theia/core/lib/browser/widgets';
|
||||
import { Board, Port } from '../../common/protocol/boards-service';
|
||||
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||
import { ArduinoSelect } from '../widgets/arduino-select';
|
||||
@ -16,7 +24,6 @@ import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class MonitorWidget extends ReactWidget {
|
||||
|
||||
static readonly ID = 'serial-monitor';
|
||||
|
||||
@inject(MonitorModel)
|
||||
@ -49,18 +56,24 @@ export class MonitorWidget extends ReactWidget {
|
||||
this.title.closable = true;
|
||||
this.scrollOptions = undefined;
|
||||
this.toDispose.push(this.clearOutputEmitter);
|
||||
this.toDispose.push(Disposable.create(() => {
|
||||
this.toDispose.push(
|
||||
Disposable.create(() => {
|
||||
this.monitorConnection.autoConnect = false;
|
||||
if (this.monitorConnection.connected) {
|
||||
this.monitorConnection.disconnect();
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.update();
|
||||
this.toDispose.push(this.monitorConnection.onConnectionChanged(() => this.clearConsole()));
|
||||
this.toDispose.push(
|
||||
this.monitorConnection.onConnectionChanged(() =>
|
||||
this.clearConsole()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
clearConsole(): void {
|
||||
@ -106,72 +119,93 @@ export class MonitorWidget extends ReactWidget {
|
||||
return;
|
||||
}
|
||||
this.focusNode = element;
|
||||
requestAnimationFrame(() => MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest));
|
||||
}
|
||||
requestAnimationFrame(() =>
|
||||
MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest)
|
||||
);
|
||||
};
|
||||
|
||||
protected get lineEndings(): OptionsType<SelectOption<MonitorModel.EOL>> {
|
||||
return [
|
||||
{
|
||||
label: 'No Line Ending',
|
||||
value: ''
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'New Line',
|
||||
value: '\n'
|
||||
value: '\n',
|
||||
},
|
||||
{
|
||||
label: 'Carriage Return',
|
||||
value: '\r'
|
||||
value: '\r',
|
||||
},
|
||||
{
|
||||
label: 'Both NL & CR',
|
||||
value: '\r\n'
|
||||
}
|
||||
value: '\r\n',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected get baudRates(): OptionsType<SelectOption<MonitorConfig.BaudRate>> {
|
||||
const baudRates: Array<MonitorConfig.BaudRate> = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
|
||||
return baudRates.map(baudRate => ({ label: baudRate + ' baud', value: baudRate }));
|
||||
protected get baudRates(): OptionsType<
|
||||
SelectOption<MonitorConfig.BaudRate>
|
||||
> {
|
||||
const baudRates: Array<MonitorConfig.BaudRate> = [
|
||||
300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200,
|
||||
];
|
||||
return baudRates.map((baudRate) => ({
|
||||
label: baudRate + ' baud',
|
||||
value: baudRate,
|
||||
}));
|
||||
}
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
const { baudRates, lineEndings } = this;
|
||||
const lineEnding = lineEndings.find(item => item.value === this.monitorModel.lineEnding) || lineEndings[1]; // Defaults to `\n`.
|
||||
const baudRate = baudRates.find(item => item.value === this.monitorModel.baudRate) || baudRates[4]; // Defaults to `9600`.
|
||||
return <div className='serial-monitor'>
|
||||
<div className='head'>
|
||||
<div className='send'>
|
||||
const lineEnding =
|
||||
lineEndings.find(
|
||||
(item) => item.value === this.monitorModel.lineEnding
|
||||
) || lineEndings[1]; // Defaults to `\n`.
|
||||
const baudRate =
|
||||
baudRates.find(
|
||||
(item) => item.value === this.monitorModel.baudRate
|
||||
) || baudRates[4]; // Defaults to `9600`.
|
||||
return (
|
||||
<div className="serial-monitor">
|
||||
<div className="head">
|
||||
<div className="send">
|
||||
<SerialMonitorSendInput
|
||||
monitorConfig={this.monitorConnection.monitorConfig}
|
||||
resolveFocus={this.onFocusResolved}
|
||||
onSend={this.onSend} />
|
||||
onSend={this.onSend}
|
||||
/>
|
||||
</div>
|
||||
<div className='config'>
|
||||
<div className='select'>
|
||||
<div className="config">
|
||||
<div className="select">
|
||||
<ArduinoSelect
|
||||
maxMenuHeight={this.widgetHeight - 40}
|
||||
options={lineEndings}
|
||||
defaultValue={lineEnding}
|
||||
onChange={this.onChangeLineEnding} />
|
||||
onChange={this.onChangeLineEnding}
|
||||
/>
|
||||
</div>
|
||||
<div className='select'>
|
||||
<div className="select">
|
||||
<ArduinoSelect
|
||||
className='select'
|
||||
className="select"
|
||||
maxMenuHeight={this.widgetHeight - 40}
|
||||
options={baudRates}
|
||||
defaultValue={baudRate}
|
||||
onChange={this.onChangeBaudRate} />
|
||||
onChange={this.onChangeBaudRate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='body'>
|
||||
<div className="body">
|
||||
<SerialMonitorOutput
|
||||
monitorModel={this.monitorModel}
|
||||
monitorConnection={this.monitorConnection}
|
||||
clearConsoleEvent={this.clearOutputEmitter.event} />
|
||||
clearConsoleEvent={this.clearOutputEmitter.event}
|
||||
/>
|
||||
</div>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected readonly onSend = (value: string) => this.doSend(value);
|
||||
@ -179,14 +213,17 @@ export class MonitorWidget extends ReactWidget {
|
||||
this.monitorConnection.send(value);
|
||||
}
|
||||
|
||||
protected readonly onChangeLineEnding = (option: SelectOption<MonitorModel.EOL>) => {
|
||||
protected readonly onChangeLineEnding = (
|
||||
option: SelectOption<MonitorModel.EOL>
|
||||
) => {
|
||||
this.monitorModel.lineEnding = option.value;
|
||||
}
|
||||
};
|
||||
|
||||
protected readonly onChangeBaudRate = (option: SelectOption<MonitorConfig.BaudRate>) => {
|
||||
protected readonly onChangeBaudRate = (
|
||||
option: SelectOption<MonitorConfig.BaudRate>
|
||||
) => {
|
||||
this.monitorModel.baudRate = option.value;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export namespace SerialMonitorSendInput {
|
||||
@ -200,8 +237,10 @@ export namespace SerialMonitorSendInput {
|
||||
}
|
||||
}
|
||||
|
||||
export class SerialMonitorSendInput extends React.Component<SerialMonitorSendInput.Props, SerialMonitorSendInput.State> {
|
||||
|
||||
export class SerialMonitorSendInput extends React.Component<
|
||||
SerialMonitorSendInput.Props,
|
||||
SerialMonitorSendInput.State
|
||||
> {
|
||||
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
|
||||
super(props);
|
||||
this.state = { text: '' };
|
||||
@ -211,30 +250,39 @@ export class SerialMonitorSendInput extends React.Component<SerialMonitorSendInp
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return <input
|
||||
return (
|
||||
<input
|
||||
ref={this.setRef}
|
||||
type='text'
|
||||
className={`theia-input ${this.props.monitorConfig ? '' : 'warning'}`}
|
||||
type="text"
|
||||
className={`theia-input ${
|
||||
this.props.monitorConfig ? '' : 'warning'
|
||||
}`}
|
||||
placeholder={this.placeholder}
|
||||
value={this.state.text}
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown} />
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
protected get placeholder(): string {
|
||||
const { monitorConfig } = this.props;
|
||||
if (!monitorConfig) {
|
||||
return 'Not connected. Select a board and a port to connect automatically.'
|
||||
return 'Not connected. Select a board and a port to connect automatically.';
|
||||
}
|
||||
const { board, port } = monitorConfig;
|
||||
return `Message (${isOSX ? '⌘' : 'Ctrl'}+Enter to send message to '${Board.toString(board, { useFqbn: false })}' on '${Port.toString(port)}')`;
|
||||
return `Message (${
|
||||
isOSX ? '⌘' : 'Ctrl'
|
||||
}+Enter to send message to '${Board.toString(board, {
|
||||
useFqbn: false,
|
||||
})}' on '${Port.toString(port)}')`;
|
||||
}
|
||||
|
||||
protected setRef = (element: HTMLElement | null) => {
|
||||
if (this.props.resolveFocus) {
|
||||
this.props.resolveFocus(element || undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
this.setState({ text: event.target.value });
|
||||
@ -254,7 +302,6 @@ export class SerialMonitorSendInput extends React.Component<SerialMonitorSendInp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace SerialMonitorOutput {
|
||||
@ -269,8 +316,10 @@ export namespace SerialMonitorOutput {
|
||||
}
|
||||
}
|
||||
|
||||
export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Props, SerialMonitorOutput.State> {
|
||||
|
||||
export class SerialMonitorOutput extends React.Component<
|
||||
SerialMonitorOutput.Props,
|
||||
SerialMonitorOutput.State
|
||||
> {
|
||||
/**
|
||||
* Do not touch it. It is used to be able to "follow" the serial monitor log.
|
||||
*/
|
||||
@ -279,16 +328,26 @@ export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Pro
|
||||
|
||||
constructor(props: Readonly<SerialMonitorOutput.Props>) {
|
||||
super(props);
|
||||
this.state = { content: '', timestamp: this.props.monitorModel.timestamp };
|
||||
this.state = {
|
||||
content: '',
|
||||
timestamp: this.props.monitorModel.timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return <React.Fragment>
|
||||
<div style={({ whiteSpace: 'pre', fontFamily: 'monospace' })}>
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ whiteSpace: 'pre', fontFamily: 'monospace' }}>
|
||||
{this.state.content}
|
||||
</div>
|
||||
<div style={{ float: 'left', clear: 'both' }} ref={element => { this.anchor = element; }} />
|
||||
</React.Fragment>;
|
||||
<div
|
||||
style={{ float: 'left', clear: 'both' }}
|
||||
ref={(element) => {
|
||||
this.anchor = element;
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
@ -296,8 +355,11 @@ export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Pro
|
||||
this.toDisposeBeforeUnmount.pushAll([
|
||||
this.props.monitorConnection.onRead(({ message }) => {
|
||||
const rawLines = message.split('\n');
|
||||
const lines: string[] = []
|
||||
const timestamp = () => this.state.timestamp ? `${dateFormat(new Date(), 'H:M:ss.l')} -> ` : '';
|
||||
const lines: string[] = [];
|
||||
const timestamp = () =>
|
||||
this.state.timestamp
|
||||
? `${dateFormat(new Date(), 'H:M:ss.l')} -> `
|
||||
: '';
|
||||
for (let i = 0; i < rawLines.length; i++) {
|
||||
if (i === 0 && this.state.content.length !== 0) {
|
||||
lines.push(rawLines[i]);
|
||||
@ -314,7 +376,7 @@ export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Pro
|
||||
const { timestamp } = this.props.monitorModel;
|
||||
this.setState({ timestamp });
|
||||
}
|
||||
})
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -332,7 +394,6 @@ export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Pro
|
||||
this.anchor.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface SelectOption<T> {
|
||||
|
@ -3,25 +3,48 @@ import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { NotificationServiceClient, NotificationServiceServer } from '../common/protocol/notification-service';
|
||||
import { AttachedBoardsChangeEvent, BoardsPackage, LibraryPackage, Config, Sketch } from '../common/protocol';
|
||||
import {
|
||||
NotificationServiceClient,
|
||||
NotificationServiceServer,
|
||||
} from '../common/protocol/notification-service';
|
||||
import {
|
||||
AttachedBoardsChangeEvent,
|
||||
BoardsPackage,
|
||||
LibraryPackage,
|
||||
Config,
|
||||
Sketch,
|
||||
} from '../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class NotificationCenter implements NotificationServiceClient, FrontendApplicationContribution {
|
||||
|
||||
export class NotificationCenter
|
||||
implements NotificationServiceClient, FrontendApplicationContribution
|
||||
{
|
||||
@inject(NotificationServiceServer)
|
||||
protected readonly server: JsonRpcProxy<NotificationServiceServer>;
|
||||
|
||||
protected readonly indexUpdatedEmitter = new Emitter<void>();
|
||||
protected readonly daemonStartedEmitter = new Emitter<void>();
|
||||
protected readonly daemonStoppedEmitter = new Emitter<void>();
|
||||
protected readonly configChangedEmitter = new Emitter<{ config: Config | undefined }>();
|
||||
protected readonly platformInstalledEmitter = new Emitter<{ item: BoardsPackage }>();
|
||||
protected readonly platformUninstalledEmitter = new Emitter<{ item: BoardsPackage }>();
|
||||
protected readonly libraryInstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
||||
protected readonly libraryUninstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
||||
protected readonly attachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
||||
protected readonly recentSketchesChangedEmitter = new Emitter<{ sketches: Sketch[] }>();
|
||||
protected readonly configChangedEmitter = new Emitter<{
|
||||
config: Config | undefined;
|
||||
}>();
|
||||
protected readonly platformInstalledEmitter = new Emitter<{
|
||||
item: BoardsPackage;
|
||||
}>();
|
||||
protected readonly platformUninstalledEmitter = new Emitter<{
|
||||
item: BoardsPackage;
|
||||
}>();
|
||||
protected readonly libraryInstalledEmitter = new Emitter<{
|
||||
item: LibraryPackage;
|
||||
}>();
|
||||
protected readonly libraryUninstalledEmitter = new Emitter<{
|
||||
item: LibraryPackage;
|
||||
}>();
|
||||
protected readonly attachedBoardsChangedEmitter =
|
||||
new Emitter<AttachedBoardsChangeEvent>();
|
||||
protected readonly recentSketchesChangedEmitter = new Emitter<{
|
||||
sketches: Sketch[];
|
||||
}>();
|
||||
|
||||
protected readonly toDispose = new DisposableCollection(
|
||||
this.indexUpdatedEmitter,
|
||||
@ -94,5 +117,4 @@ export class NotificationCenter implements NotificationServiceClient, FrontendAp
|
||||
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void {
|
||||
this.recentSketchesChangedEmitter.fire(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,18 +2,22 @@ import { inject, injectable } from 'inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
|
||||
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||
import { ResponseService, OutputMessage, ProgressMessage } from '../common/protocol/response-service';
|
||||
import {
|
||||
ResponseService,
|
||||
OutputMessage,
|
||||
ProgressMessage,
|
||||
} from '../common/protocol/response-service';
|
||||
|
||||
@injectable()
|
||||
export class ResponseServiceImpl implements ResponseService {
|
||||
|
||||
@inject(OutputContribution)
|
||||
protected outputContribution: OutputContribution;
|
||||
|
||||
@inject(OutputChannelManager)
|
||||
protected outputChannelManager: OutputChannelManager;
|
||||
|
||||
protected readonly progressDidChangeEmitter = new Emitter<ProgressMessage>();
|
||||
protected readonly progressDidChangeEmitter =
|
||||
new Emitter<ProgressMessage>();
|
||||
readonly onProgressDidChange = this.progressDidChangeEmitter.event;
|
||||
|
||||
appendToOutput(message: OutputMessage): void {
|
||||
@ -30,5 +34,4 @@ export class ResponseServiceImpl implements ResponseService {
|
||||
reportProgress(progress: ProgressMessage): void {
|
||||
this.progressDidChangeEmitter.fire(progress);
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,37 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import {
|
||||
Command,
|
||||
CommandContribution,
|
||||
CommandRegistry,
|
||||
} from '@theia/core/lib/common/command';
|
||||
|
||||
/**
|
||||
* This is a workaround to break cycles in the dependency injection. Provides commands for `setData` and `getData`.
|
||||
*/
|
||||
@injectable()
|
||||
export class StorageWrapper implements CommandContribution {
|
||||
|
||||
@inject(StorageService)
|
||||
protected storageService: StorageService;
|
||||
|
||||
registerCommands(commands: CommandRegistry): void {
|
||||
commands.registerCommand(StorageWrapper.Commands.GET_DATA, {
|
||||
execute: (key: string, defaultValue?: any) => this.storageService.getData(key, defaultValue)
|
||||
execute: (key: string, defaultValue?: any) =>
|
||||
this.storageService.getData(key, defaultValue),
|
||||
});
|
||||
commands.registerCommand(StorageWrapper.Commands.SET_DATA, {
|
||||
execute: (key: string, value: any) => this.storageService.setData(key, value)
|
||||
execute: (key: string, value: any) =>
|
||||
this.storageService.setData(key, value),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
export namespace StorageWrapper {
|
||||
export namespace Commands {
|
||||
export const SET_DATA: Command = {
|
||||
id: 'arduino-store-wrapper-set'
|
||||
id: 'arduino-store-wrapper-set',
|
||||
};
|
||||
export const GET_DATA: Command = {
|
||||
id: 'arduino-store-wrapper-get'
|
||||
id: 'arduino-store-wrapper-get',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { ConnectionStatusService, ConnectionStatus } from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ApplicationShell as TheiaApplicationShell, Widget } from '@theia/core/lib/browser';
|
||||
import {
|
||||
ConnectionStatusService,
|
||||
ConnectionStatus,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import {
|
||||
ApplicationShell as TheiaApplicationShell,
|
||||
Widget,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { Sketch } from '../../../common/protocol';
|
||||
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class ApplicationShell extends TheiaApplicationShell {
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@ -32,7 +36,7 @@ export class ApplicationShell extends TheiaApplicationShell {
|
||||
}
|
||||
if (widget instanceof EditorWidget) {
|
||||
// Make the editor un-closeable asynchronously.
|
||||
this.sketchesServiceClient.currentSketch().then(sketch => {
|
||||
this.sketchesServiceClient.currentSketch().then((sketch) => {
|
||||
if (sketch) {
|
||||
if (Sketch.isInSketch(widget.editor.uri, sketch)) {
|
||||
widget.title.closable = false;
|
||||
@ -42,15 +46,20 @@ export class ApplicationShell extends TheiaApplicationShell {
|
||||
}
|
||||
}
|
||||
|
||||
async addWidget(widget: Widget, options: Readonly<TheiaApplicationShell.WidgetOptions> = {}): Promise<void> {
|
||||
async addWidget(
|
||||
widget: Widget,
|
||||
options: Readonly<TheiaApplicationShell.WidgetOptions> = {}
|
||||
): Promise<void> {
|
||||
// By default, Theia open a widget **next** to the currently active in the target area.
|
||||
// Instead of this logic, we want to open the new widget after the last of the target area.
|
||||
if (!widget.id) {
|
||||
console.error('Widgets added to the application shell must have a unique id property.');
|
||||
console.error(
|
||||
'Widgets added to the application shell must have a unique id property.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
let ref: Widget | undefined = options.ref;
|
||||
let area: TheiaApplicationShell.Area = options.area || 'main';
|
||||
const area: TheiaApplicationShell.Area = options.area || 'main';
|
||||
if (!ref && (area === 'main' || area === 'bottom')) {
|
||||
const tabBar = this.getTabBarFor(area);
|
||||
if (tabBar) {
|
||||
@ -64,13 +73,20 @@ export class ApplicationShell extends TheiaApplicationShell {
|
||||
}
|
||||
|
||||
async saveAll(): Promise<void> {
|
||||
if (this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE) {
|
||||
this.messageService.error('Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.');
|
||||
if (
|
||||
this.connectionStatusService.currentStatus ===
|
||||
ConnectionStatus.OFFLINE
|
||||
) {
|
||||
this.messageService.error(
|
||||
'Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.'
|
||||
);
|
||||
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
|
||||
}
|
||||
await super.saveAll();
|
||||
const options = { execOnlyIfTemp: true, openAfterMove: true };
|
||||
await this.commandService.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, options);
|
||||
await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { BrowserMainMenuFactory as TheiaBrowserMainMenuFactory, MenuBarWidget } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import {
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
MenuBarWidget,
|
||||
} from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class BrowserMainMenuFactory extends TheiaBrowserMainMenuFactory implements MainMenuManager {
|
||||
|
||||
export class BrowserMainMenuFactory
|
||||
extends TheiaBrowserMainMenuFactory
|
||||
implements MainMenuManager
|
||||
{
|
||||
protected menuBar: MenuBarWidget | undefined;
|
||||
|
||||
createMenuBar(): MenuBarWidget {
|
||||
@ -18,5 +23,4 @@ export class BrowserMainMenuFactory extends TheiaBrowserMainMenuFactory implemen
|
||||
this.fillMenuBar(this.menuBar);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
import '../../../../src/browser/style/browser-menu.css';
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { BrowserMenuBarContribution, BrowserMainMenuFactory as TheiaBrowserMainMenuFactory } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import {
|
||||
BrowserMenuBarContribution,
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
} from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ArduinoMenuContribution } from './browser-menu-plugin';
|
||||
import { BrowserMainMenuFactory } from './browser-main-menu-factory';
|
||||
@ -9,5 +12,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(BrowserMainMenuFactory).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(BrowserMainMenuFactory);
|
||||
rebind(TheiaBrowserMainMenuFactory).toService(BrowserMainMenuFactory);
|
||||
rebind(BrowserMenuBarContribution).to(ArduinoMenuContribution).inSingletonScope();
|
||||
rebind(BrowserMenuBarContribution)
|
||||
.to(ArduinoMenuContribution)
|
||||
.inSingletonScope();
|
||||
});
|
||||
|
@ -4,10 +4,8 @@ import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser
|
||||
|
||||
@injectable()
|
||||
export class ArduinoMenuContribution extends BrowserMenuBarContribution {
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
const menu = this.factory.createMenuBar();
|
||||
app.shell.addWidget(menu, { area: 'top' });
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { CommonFrontendContribution as TheiaCommonFrontendContribution, CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import {
|
||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||
CommonCommands,
|
||||
} from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
for (const command of [
|
||||
@ -21,10 +23,9 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
|
||||
CommonCommands.SELECT_ICON_THEME,
|
||||
CommonCommands.SELECT_COLOR_THEME,
|
||||
CommonCommands.ABOUT_COMMAND,
|
||||
CommonCommands.SAVE_WITHOUT_FORMATTING // Patched for https://github.com/eclipse-theia/theia/pull/8877
|
||||
CommonCommands.SAVE_WITHOUT_FORMATTING, // Patched for https://github.com/eclipse-theia/theia/pull/8877
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,14 +4,13 @@ import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-ba
|
||||
import {
|
||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||
ConnectionStatus
|
||||
ConnectionStatus,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ArduinoDaemon } from '../../../common/protocol';
|
||||
import { NotificationCenter } from '../../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
||||
|
||||
@inject(ArduinoDaemon)
|
||||
protected readonly daemon: ArduinoDaemon;
|
||||
|
||||
@ -26,19 +25,17 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
||||
try {
|
||||
this.isRunning = await this.daemon.isRunning();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(() => this.isRunning = true);
|
||||
this.notificationCenter.onDaemonStopped(() => this.isRunning = false);
|
||||
this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
|
||||
this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
|
||||
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
||||
this.updateStatus(this.isRunning);
|
||||
this.schedulePing();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ApplicationConnectionStatusContribution extends TheiaApplicationConnectionStatusContribution {
|
||||
|
||||
@inject(ArduinoDaemon)
|
||||
protected readonly daemon: ArduinoDaemon;
|
||||
|
||||
@ -52,8 +49,8 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
||||
try {
|
||||
this.isRunning = await this.daemon.isRunning();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(() => this.isRunning = true);
|
||||
this.notificationCenter.onDaemonStopped(() => this.isRunning = false);
|
||||
this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
|
||||
this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
|
||||
}
|
||||
|
||||
protected onStateChange(state: ConnectionStatus): void {
|
||||
@ -67,12 +64,21 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
||||
this.statusBar.setElement('connection-status', {
|
||||
alignment: StatusBarAlignment.LEFT,
|
||||
text: this.isRunning ? 'Offline' : '$(bolt) CLI Daemon Offline',
|
||||
tooltip: this.isRunning ? 'Cannot connect to the backend.' : 'Cannot connect to the CLI daemon.',
|
||||
priority: 5000
|
||||
tooltip: this.isRunning
|
||||
? 'Cannot connect to the backend.'
|
||||
: 'Cannot connect to the CLI daemon.',
|
||||
priority: 5000,
|
||||
});
|
||||
this.toDisposeOnOnline.push(Disposable.create(() => this.statusBar.removeElement('connection-status')));
|
||||
this.toDisposeOnOnline.push(
|
||||
Disposable.create(() =>
|
||||
this.statusBar.removeElement('connection-status')
|
||||
)
|
||||
);
|
||||
document.body.classList.add('theia-mod-offline');
|
||||
this.toDisposeOnOnline.push(Disposable.create(() => document.body.classList.remove('theia-mod-offline')));
|
||||
this.toDisposeOnOnline.push(
|
||||
Disposable.create(() =>
|
||||
document.body.classList.remove('theia-mod-offline')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { ArduinoCommands } from '../../arduino-commands';
|
||||
|
||||
@injectable()
|
||||
export class FrontendApplication extends TheiaFrontendApplication {
|
||||
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@ -27,10 +26,14 @@ export class FrontendApplication extends TheiaFrontendApplication {
|
||||
for (const root of roots) {
|
||||
const exists = await this.fileService.exists(root.resource);
|
||||
if (exists) {
|
||||
this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
|
||||
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.resource);
|
||||
this.sketchesService.markAsRecentlyOpened(
|
||||
root.resource.toString()
|
||||
); // no await, will get the notification later and rebuild the menu
|
||||
await this.commandService.executeCommand(
|
||||
ArduinoCommands.OPEN_SKETCH_FILES.id,
|
||||
root.resource
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { Command } from '@theia/core/lib/common/command';
|
||||
import { Keybinding } from '@theia/core/lib/common/keybinding';
|
||||
import { KeybindingRegistry as TheiaKeybindingRegistry, KeybindingScope } from '@theia/core/lib/browser/keybinding';
|
||||
import {
|
||||
KeybindingRegistry as TheiaKeybindingRegistry,
|
||||
KeybindingScope,
|
||||
} from '@theia/core/lib/browser/keybinding';
|
||||
|
||||
@injectable()
|
||||
export class KeybindingRegistry extends TheiaKeybindingRegistry {
|
||||
|
||||
// https://github.com/eclipse-theia/theia/issues/8209
|
||||
unregisterKeybinding(key: string): void;
|
||||
unregisterKeybinding(keybinding: Keybinding): void;
|
||||
@ -14,7 +16,8 @@ export class KeybindingRegistry extends TheiaKeybindingRegistry {
|
||||
const keymap = this.keymaps[KeybindingScope.DEFAULT];
|
||||
const filter = Command.is(arg)
|
||||
? ({ command }: Keybinding) => command === arg.id
|
||||
: ({ keybinding }: Keybinding) => Keybinding.is(arg)
|
||||
: ({ keybinding }: Keybinding) =>
|
||||
Keybinding.is(arg)
|
||||
? keybinding === arg.keybinding
|
||||
: keybinding === arg;
|
||||
for (const binding of keymap.filter(filter)) {
|
||||
@ -24,5 +27,4 @@ export class KeybindingRegistry extends TheiaKeybindingRegistry {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { ShellLayoutRestorer as TheiaShellLayoutRestorer } from '@theia/core/lib
|
||||
|
||||
@injectable()
|
||||
export class ShellLayoutRestorer extends TheiaShellLayoutRestorer {
|
||||
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
|
||||
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
|
||||
if (this.shouldStoreLayout) {
|
||||
@ -12,13 +11,20 @@ export class ShellLayoutRestorer extends TheiaShellLayoutRestorer {
|
||||
this.logger.info('>>> Storing the layout...');
|
||||
const layoutData = app.shell.getLayoutData();
|
||||
const serializedLayoutData = this.deflate(layoutData);
|
||||
await this.storageService.setData(this.storageKey, serializedLayoutData);
|
||||
this.logger.info('<<< The layout has been successfully stored.');
|
||||
await this.storageService.setData(
|
||||
this.storageKey,
|
||||
serializedLayoutData
|
||||
);
|
||||
this.logger.info(
|
||||
'<<< The layout has been successfully stored.'
|
||||
);
|
||||
} catch (error) {
|
||||
await this.storageService.setData(this.storageKey, undefined);
|
||||
this.logger.error('Error during serialization of layout data', error);
|
||||
this.logger.error(
|
||||
'Error during serialization of layout data',
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import { ConfigService } from '../../../common/protocol/config-service';
|
||||
|
||||
@injectable()
|
||||
export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@ -20,19 +19,26 @@ export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.configService.getConfiguration()
|
||||
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
||||
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
||||
this.configService
|
||||
.getConfiguration()
|
||||
.then(({ dataDirUri }) => (this.dataDirUri = new URI(dataDirUri)))
|
||||
.catch((err) =>
|
||||
this.logger.error(
|
||||
`Failed to determine the data directory: ${err}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getDecorations(title: Title<Widget>): WidgetDecoration.Data[] {
|
||||
if (title.owner instanceof EditorWidget) {
|
||||
const editor = title.owner.editor;
|
||||
if (this.dataDirUri && this.dataDirUri.isEqualOrParent(editor.uri)) {
|
||||
if (
|
||||
this.dataDirUri &&
|
||||
this.dataDirUri.isEqualOrParent(editor.uri)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return super.getDecorations(title);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { injectable } from 'inversify';
|
||||
import { LabelIcon } from '@theia/core/lib/browser/label-parser';
|
||||
import { TabBarToolbar as TheiaTabBarToolbar, TabBarToolbarItem } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import {
|
||||
TabBarToolbar as TheiaTabBarToolbar,
|
||||
TabBarToolbarItem,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
|
||||
@injectable()
|
||||
export class TabBarToolbar extends TheiaTabBarToolbar {
|
||||
|
||||
/**
|
||||
* Copied over from Theia. Added an ID to the parent of the toolbar item (`--container`).
|
||||
* CSS3 does not support parent selectors but we want to style the parent of the toolbar item.
|
||||
@ -16,7 +18,9 @@ export class TabBarToolbar extends TheiaTabBarToolbar {
|
||||
if (item.text) {
|
||||
for (const labelPart of this.labelParser.parse(item.text)) {
|
||||
if (typeof labelPart !== 'string' && LabelIcon.is(labelPart)) {
|
||||
const className = `fa fa-${labelPart.name}${labelPart.animation ? ' fa-' + labelPart.animation : ''}`;
|
||||
const className = `fa fa-${labelPart.name}${
|
||||
labelPart.animation ? ' fa-' + labelPart.animation : ''
|
||||
}`;
|
||||
classNames.push(...className.split(' '));
|
||||
} else {
|
||||
innerText = labelPart;
|
||||
@ -24,15 +28,36 @@ export class TabBarToolbar extends TheiaTabBarToolbar {
|
||||
}
|
||||
}
|
||||
const command = this.commands.getCommand(item.command);
|
||||
const iconClass = (typeof item.icon === 'function' && item.icon()) || item.icon || (command && command.iconClass);
|
||||
const iconClass =
|
||||
(typeof item.icon === 'function' && item.icon()) ||
|
||||
item.icon ||
|
||||
(command && command.iconClass);
|
||||
if (iconClass) {
|
||||
classNames.push(iconClass);
|
||||
}
|
||||
const tooltip = item.tooltip || (command && command.label);
|
||||
return <div id={`${item.id}--container`} key={item.id} className={`${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM}${command && this.commandIsEnabled(command.id) ? ' enabled' : ''}`}
|
||||
onMouseDown={this.onMouseDownEvent} onMouseUp={this.onMouseUpEvent} onMouseOut={this.onMouseUpEvent} >
|
||||
<div id={item.id} className={classNames.join(' ')} onClick={this.executeCommand} title={tooltip}>{innerText}</div>
|
||||
</div>;
|
||||
return (
|
||||
<div
|
||||
id={`${item.id}--container`}
|
||||
key={item.id}
|
||||
className={`${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM}${
|
||||
command && this.commandIsEnabled(command.id)
|
||||
? ' enabled'
|
||||
: ''
|
||||
}`}
|
||||
onMouseDown={this.onMouseDownEvent}
|
||||
onMouseUp={this.onMouseUpEvent}
|
||||
onMouseOut={this.onMouseUpEvent}
|
||||
>
|
||||
<div
|
||||
id={item.id}
|
||||
className={classNames.join(' ')}
|
||||
onClick={this.executeCommand}
|
||||
title={tooltip}
|
||||
>
|
||||
{innerText}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||
import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars';
|
||||
|
||||
export class TabBarRenderer extends TheiaTabBarRenderer {
|
||||
|
||||
createTabClass(data: TabBar.IRenderData<any>): string {
|
||||
let className = super.createTabClass(data);
|
||||
if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
|
||||
@ -11,5 +10,4 @@ export class TabBarRenderer extends TheiaTabBarRenderer {
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,11 +9,13 @@ import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@th
|
||||
import { SketchesService } from '../../../common/protocol';
|
||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||
import { DebugConfigurationModel } from './debug-configuration-model';
|
||||
import { FileOperationError, FileOperationResult } from '@theia/filesystem/lib/common/files';
|
||||
import {
|
||||
FileOperationError,
|
||||
FileOperationResult,
|
||||
} from '@theia/filesystem/lib/common/files';
|
||||
|
||||
@injectable()
|
||||
export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
@ -23,7 +25,8 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
protected onTempContentDidChangeEmitter = new Emitter<TheiaDebugConfigurationModel.JsonContent>();
|
||||
protected onTempContentDidChangeEmitter =
|
||||
new Emitter<TheiaDebugConfigurationModel.JsonContent>();
|
||||
get onTempContentDidChange(): Event<TheiaDebugConfigurationModel.JsonContent> {
|
||||
return this.onTempContentDidChangeEmitter.event;
|
||||
}
|
||||
@ -38,15 +41,25 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
return;
|
||||
}
|
||||
// Watch the file of the container folder.
|
||||
this.fileService.watch(tempContent instanceof URI ? tempContent : tempContent.uri);
|
||||
this.fileService.watch(
|
||||
tempContent instanceof URI ? tempContent : tempContent.uri
|
||||
);
|
||||
// Use the normalized temp folder name. We cannot compare Theia URIs here.
|
||||
// /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-a0337d47f86b24a51df3dbcf2cc17925/launch.json
|
||||
// /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925/launch.json
|
||||
const tempFolderName = (tempContent instanceof URI ? tempContent : tempContent.uri.parent).path.base.toLowerCase();
|
||||
this.fileService.onDidFilesChange(event => {
|
||||
const tempFolderName = (
|
||||
tempContent instanceof URI
|
||||
? tempContent
|
||||
: tempContent.uri.parent
|
||||
).path.base.toLowerCase();
|
||||
this.fileService.onDidFilesChange((event) => {
|
||||
for (const { resource } of event.changes) {
|
||||
if (resource.path.base === 'launch.json' && resource.parent.path.base.toLowerCase() === tempFolderName) {
|
||||
this.getTempLaunchJsonContent().then(config => {
|
||||
if (
|
||||
resource.path.base === 'launch.json' &&
|
||||
resource.parent.path.base.toLowerCase() ===
|
||||
tempFolderName
|
||||
) {
|
||||
this.getTempLaunchJsonContent().then((config) => {
|
||||
if (config && !(config instanceof URI)) {
|
||||
this.onTempContentDidChangeEmitter.fire(config);
|
||||
}
|
||||
@ -71,9 +84,19 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
if (!tempContent) {
|
||||
continue;
|
||||
}
|
||||
const configurations: DebugConfiguration[] = tempContent instanceof URI ? [] : tempContent.configurations;
|
||||
const uri = tempContent instanceof URI ? undefined : tempContent.uri;
|
||||
const model = new DebugConfigurationModel(key, this.preferences, configurations, uri, this.onTempContentDidChange);
|
||||
const configurations: DebugConfiguration[] =
|
||||
tempContent instanceof URI
|
||||
? []
|
||||
: tempContent.configurations;
|
||||
const uri =
|
||||
tempContent instanceof URI ? undefined : tempContent.uri;
|
||||
const model = new DebugConfigurationModel(
|
||||
key,
|
||||
this.preferences,
|
||||
configurations,
|
||||
uri,
|
||||
this.onTempContentDidChange
|
||||
);
|
||||
model.onDidChange(() => this.updateCurrent());
|
||||
model.onDispose(() => this.models.delete(key));
|
||||
this.models.set(key, model);
|
||||
@ -88,7 +111,11 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
this.updateCurrent();
|
||||
}, 500);
|
||||
|
||||
protected async getTempLaunchJsonContent(): Promise<TheiaDebugConfigurationModel.JsonContent & { uri: URI } | URI | undefined> {
|
||||
protected async getTempLaunchJsonContent(): Promise<
|
||||
| (TheiaDebugConfigurationModel.JsonContent & { uri: URI })
|
||||
| URI
|
||||
| undefined
|
||||
> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return undefined;
|
||||
@ -99,15 +126,22 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
try {
|
||||
const uri = tempFolderUri.resolve('launch.json');
|
||||
const { value } = await this.fileService.read(uri);
|
||||
const configurations = DebugConfigurationModel.parse(JSON.parse(value));
|
||||
const configurations = DebugConfigurationModel.parse(
|
||||
JSON.parse(value)
|
||||
);
|
||||
return { uri, configurations };
|
||||
} catch (err) {
|
||||
if (err instanceof FileOperationError && err.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
|
||||
if (
|
||||
err instanceof FileOperationError &&
|
||||
err.fileOperationResult === FileOperationResult.FILE_NOT_FOUND
|
||||
) {
|
||||
return tempFolderUri;
|
||||
}
|
||||
console.error('Could not load debug configuration from IDE2 temp folder.', err);
|
||||
console.error(
|
||||
'Could not load debug configuration from IDE2 temp folder.',
|
||||
err
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,38 +5,42 @@ import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
|
||||
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
|
||||
|
||||
export class DebugConfigurationModel extends TheiaDebugConfigurationModel {
|
||||
|
||||
constructor(
|
||||
readonly workspaceFolderUri: string,
|
||||
protected readonly preferences: PreferenceService,
|
||||
protected readonly config: DebugConfiguration[],
|
||||
protected configUri: URI | undefined,
|
||||
protected readonly onConfigDidChange: Event<TheiaDebugConfigurationModel.JsonContent>) {
|
||||
|
||||
protected readonly onConfigDidChange: Event<TheiaDebugConfigurationModel.JsonContent>
|
||||
) {
|
||||
super(workspaceFolderUri, preferences);
|
||||
this.toDispose.push(onConfigDidChange(content => {
|
||||
this.toDispose.push(
|
||||
onConfigDidChange((content) => {
|
||||
const { uri, configurations } = content;
|
||||
this.configUri = uri;
|
||||
this.config.length = 0;
|
||||
this.config.push(...configurations);
|
||||
this.reconcile();
|
||||
}));
|
||||
})
|
||||
);
|
||||
this.reconcile();
|
||||
}
|
||||
|
||||
protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent {
|
||||
return {
|
||||
uri: this.configUri,
|
||||
configurations: this.config
|
||||
configurations: this.config,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace DebugConfigurationModel {
|
||||
export function parse(launchConfig: any): DebugConfiguration[] {
|
||||
const configurations: DebugConfiguration[] = [];
|
||||
if (launchConfig && typeof launchConfig === 'object' && 'configurations' in launchConfig) {
|
||||
if (
|
||||
launchConfig &&
|
||||
typeof launchConfig === 'object' &&
|
||||
'configurations' in launchConfig
|
||||
) {
|
||||
if (Array.isArray(launchConfig.configurations)) {
|
||||
for (const configuration of launchConfig.configurations) {
|
||||
if (DebugConfiguration.is(configuration)) {
|
||||
|
@ -1,20 +1,31 @@
|
||||
import debounce from 'p-debounce';
|
||||
import { inject, injectable, postConstruct, interfaces, Container } from 'inversify';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
interfaces,
|
||||
Container,
|
||||
} from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { MonacoConfigurationService } from '@theia/monaco/lib/browser/monaco-frontend-module';
|
||||
import { INLINE_VALUE_DECORATION_KEY } from '@theia/debug/lib/browser/editor//debug-inline-value-decorator';
|
||||
import { DebugEditor } from '@theia/debug/lib/browser/editor/debug-editor';
|
||||
import { DebugExceptionWidget } from '@theia/debug/lib/browser/editor/debug-exception-widget';
|
||||
import { DebugBreakpointWidget } from '@theia/debug/lib/browser/editor/debug-breakpoint-widget';
|
||||
import { DebugEditorModel as TheiaDebugEditorModel } from '@theia/debug/lib/browser/editor/debug-editor-model';
|
||||
import { createDebugHoverWidgetContainer } from './debug-hover-widget'
|
||||
import { createDebugHoverWidgetContainer } from './debug-hover-widget';
|
||||
|
||||
// TODO: Remove after https://github.com/eclipse-theia/theia/pull/9256/
|
||||
@injectable()
|
||||
export class DebugEditorModel extends TheiaDebugEditorModel {
|
||||
|
||||
static createContainer(parent: interfaces.Container, editor: DebugEditor): Container {
|
||||
static createContainer(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): Container {
|
||||
const child = createDebugHoverWidgetContainer(parent, editor);
|
||||
child.bind(DebugEditorModel).toSelf();
|
||||
child.bind(DebugBreakpointWidget).toSelf();
|
||||
@ -22,8 +33,13 @@ export class DebugEditorModel extends TheiaDebugEditorModel {
|
||||
return child;
|
||||
}
|
||||
|
||||
static createModel(parent: interfaces.Container, editor: DebugEditor): DebugEditorModel {
|
||||
return DebugEditorModel.createContainer(parent, editor).get(DebugEditorModel);
|
||||
static createModel(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): DebugEditorModel {
|
||||
return DebugEditorModel.createContainer(parent, editor).get(
|
||||
DebugEditorModel
|
||||
);
|
||||
}
|
||||
|
||||
@inject(MonacoConfigurationService)
|
||||
@ -41,26 +57,38 @@ export class DebugEditorModel extends TheiaDebugEditorModel {
|
||||
if (this.isCurrentEditorFrame(this.uri)) {
|
||||
const codeEditor = this.editor.getControl();
|
||||
codeEditor.updateOptions({ hover: { enabled: false } });
|
||||
this.toDisposeOnRenderFrames.push(Disposable.create(() => {
|
||||
this.toDisposeOnRenderFrames.push(
|
||||
Disposable.create(() => {
|
||||
const model = codeEditor.getModel()!;
|
||||
const overrides = {
|
||||
resource: model.uri,
|
||||
overrideIdentifier: (model as any).getLanguageIdentifier().language,
|
||||
overrideIdentifier: (
|
||||
model as any
|
||||
).getLanguageIdentifier().language,
|
||||
};
|
||||
const { enabled, delay, sticky } = this.configurationService._configuration.getValue('editor.hover', overrides, undefined);
|
||||
const { enabled, delay, sticky } =
|
||||
this.configurationService._configuration.getValue(
|
||||
'editor.hover',
|
||||
overrides,
|
||||
undefined
|
||||
);
|
||||
codeEditor.updateOptions({
|
||||
hover: {
|
||||
enabled,
|
||||
delay,
|
||||
sticky
|
||||
}
|
||||
sticky,
|
||||
},
|
||||
});
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private isCurrentEditorFrame(uri: URI): boolean {
|
||||
return this.sessions.currentFrame?.source?.uri.toString() === uri.toString();
|
||||
return (
|
||||
this.sessions.currentFrame?.source?.uri.toString() ===
|
||||
uri.toString()
|
||||
);
|
||||
}
|
||||
|
||||
protected readonly renderFrames = debounce(async () => {
|
||||
@ -70,15 +98,19 @@ export class DebugEditorModel extends TheiaDebugEditorModel {
|
||||
this.toDisposeOnRenderFrames.dispose();
|
||||
|
||||
this.toggleExceptionWidget();
|
||||
const [newFrameDecorations, inlineValueDecorations] = await Promise.all([
|
||||
this.createFrameDecorations(),
|
||||
this.createInlineValueDecorations()
|
||||
]);
|
||||
const [newFrameDecorations, inlineValueDecorations] = await Promise.all(
|
||||
[this.createFrameDecorations(), this.createInlineValueDecorations()]
|
||||
);
|
||||
const codeEditor = this.editor.getControl();
|
||||
codeEditor.removeDecorations(INLINE_VALUE_DECORATION_KEY);
|
||||
codeEditor.setDecorations(INLINE_VALUE_DECORATION_KEY, inlineValueDecorations);
|
||||
this.frameDecorations = this.deltaDecorations(this.frameDecorations, newFrameDecorations);
|
||||
codeEditor.setDecorations(
|
||||
INLINE_VALUE_DECORATION_KEY,
|
||||
inlineValueDecorations
|
||||
);
|
||||
this.frameDecorations = this.deltaDecorations(
|
||||
this.frameDecorations,
|
||||
newFrameDecorations
|
||||
);
|
||||
this.updateEditorHover();
|
||||
}, 100);
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution, DebugMenus } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
import {
|
||||
DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution,
|
||||
DebugMenus,
|
||||
} from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
import { unregisterSubmenu } from '../../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class DebugFrontendApplicationContribution extends TheiaDebugFrontendApplicationContribution {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
super();
|
||||
this.options.defaultWidgetOptions.rank = 4;
|
||||
}
|
||||
|
||||
@ -15,5 +17,4 @@ export class DebugFrontendApplicationContribution extends TheiaDebugFrontendAppl
|
||||
super.registerMenus(registry);
|
||||
unregisterSubmenu(DebugMenus.DEBUG, registry);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ExpressionItem, DebugVariable } from '@theia/debug/lib/browser/console/debug-console-items';
|
||||
import {
|
||||
ExpressionItem,
|
||||
DebugVariable,
|
||||
} from '@theia/debug/lib/browser/console/debug-console-items';
|
||||
import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source';
|
||||
|
||||
// TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/.
|
||||
@injectable()
|
||||
export class DebugHoverSource extends TheiaDebugHoverSource {
|
||||
|
||||
async evaluate2(expression: string): Promise<ExpressionItem | DebugVariable | undefined> {
|
||||
async evaluate2(
|
||||
expression: string
|
||||
): Promise<ExpressionItem | DebugVariable | undefined> {
|
||||
const evaluated = await this.doEvaluate(expression);
|
||||
const elements = evaluated && await evaluated.getElements();
|
||||
const elements = evaluated && (await evaluated.getElements());
|
||||
this._expression = evaluated;
|
||||
this.elements = elements ? [...elements] : [];
|
||||
this.fireDidChange();
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,12 +6,18 @@ import { DebugEditor } from '@theia/debug/lib/browser/editor/debug-editor';
|
||||
import { DebugVariable } from '@theia/debug/lib/browser/console/debug-console-items';
|
||||
import { DebugExpressionProvider } from '@theia/debug/lib/browser/editor/debug-expression-provider';
|
||||
import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source';
|
||||
import { DebugHoverWidget as TheiaDebugHoverWidget, ShowDebugHoverOptions } from '@theia/debug/lib/browser/editor/debug-hover-widget';
|
||||
import {
|
||||
DebugHoverWidget as TheiaDebugHoverWidget,
|
||||
ShowDebugHoverOptions,
|
||||
} from '@theia/debug/lib/browser/editor/debug-hover-widget';
|
||||
import { DebugHoverSource } from './debug-hover-source';
|
||||
|
||||
export function createDebugHoverWidgetContainer(parent: interfaces.Container, editor: DebugEditor): Container {
|
||||
export function createDebugHoverWidgetContainer(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): Container {
|
||||
const child = SourceTreeWidget.createContainer(parent, {
|
||||
virtualized: false
|
||||
virtualized: false,
|
||||
});
|
||||
child.bind(DebugEditor).toConstantValue(editor);
|
||||
child.bind(TheiaDebugHoverSource).toSelf();
|
||||
@ -28,8 +34,9 @@ export function createDebugHoverWidgetContainer(parent: interfaces.Container, ed
|
||||
// TODO: remove patch after https://github.com/eclipse-theia/theia/pull/9256/
|
||||
@injectable()
|
||||
export class DebugHoverWidget extends TheiaDebugHoverWidget {
|
||||
|
||||
protected async doShow(options: ShowDebugHoverOptions | undefined = this.options): Promise<void> {
|
||||
protected async doShow(
|
||||
options: ShowDebugHoverOptions | undefined = this.options
|
||||
): Promise<void> {
|
||||
if (!this.isEditorFrame()) {
|
||||
this.hide();
|
||||
return;
|
||||
@ -38,7 +45,10 @@ export class DebugHoverWidget extends TheiaDebugHoverWidget {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
if (this.options && this.options.selection.equalsRange(options.selection)) {
|
||||
if (
|
||||
this.options &&
|
||||
this.options.selection.equalsRange(options.selection)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!this.isAttached) {
|
||||
@ -46,19 +56,26 @@ export class DebugHoverWidget extends TheiaDebugHoverWidget {
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
const matchingExpression = this.expressionProvider.get(this.editor.getControl().getModel()!, options.selection);
|
||||
const matchingExpression = this.expressionProvider.get(
|
||||
this.editor.getControl().getModel()!,
|
||||
options.selection
|
||||
);
|
||||
if (!matchingExpression) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
const toFocus = new DisposableCollection();
|
||||
if (this.options.focus === true) {
|
||||
toFocus.push(this.model.onNodeRefreshed(() => {
|
||||
toFocus.push(
|
||||
this.model.onNodeRefreshed(() => {
|
||||
toFocus.dispose();
|
||||
this.activate();
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
const expression = await (this.hoverSource as DebugHoverSource).evaluate2(matchingExpression);
|
||||
const expression = await (
|
||||
this.hoverSource as DebugHoverSource
|
||||
).evaluate2(matchingExpression);
|
||||
if (!expression || !expression.value) {
|
||||
toFocus.dispose();
|
||||
this.hide();
|
||||
@ -66,13 +83,19 @@ export class DebugHoverWidget extends TheiaDebugHoverWidget {
|
||||
}
|
||||
|
||||
this.contentNode.hidden = false;
|
||||
['number', 'boolean', 'string'].forEach(token => this.titleNode.classList.remove(token));
|
||||
['number', 'boolean', 'string'].forEach((token) =>
|
||||
this.titleNode.classList.remove(token)
|
||||
);
|
||||
this.domNode.classList.remove('complex-value');
|
||||
if (expression.hasElements) {
|
||||
this.domNode.classList.add('complex-value');
|
||||
} else {
|
||||
this.contentNode.hidden = true;
|
||||
if (expression.type === 'number' || expression.type === 'boolean' || expression.type === 'string') {
|
||||
if (
|
||||
expression.type === 'number' ||
|
||||
expression.type === 'boolean' ||
|
||||
expression.type === 'string'
|
||||
) {
|
||||
this.titleNode.classList.add(expression.type);
|
||||
} else if (!isNaN(+expression.value)) {
|
||||
this.titleNode.classList.add('number');
|
||||
@ -85,12 +108,15 @@ export class DebugHoverWidget extends TheiaDebugHoverWidget {
|
||||
|
||||
// super.show(); // Here we cannot call `super.show()` but have to call `show` on the `Widget` prototype.
|
||||
Widget.prototype.show.call(this);
|
||||
await new Promise<void>(resolve => {
|
||||
setTimeout(() => window.requestAnimationFrame(() => {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(
|
||||
() =>
|
||||
window.requestAnimationFrame(() => {
|
||||
this.editor.getControl().layoutContentWidget(this);
|
||||
resolve();
|
||||
}), 0);
|
||||
}),
|
||||
0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,9 +6,13 @@ import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/li
|
||||
|
||||
@injectable()
|
||||
export class DebugSessionManager extends TheiaDebugSessionManager {
|
||||
|
||||
async start(options: DebugSessionOptions): Promise<DebugSession | undefined> {
|
||||
return this.progressService.withProgress('Start...', 'debug', async () => {
|
||||
async start(
|
||||
options: DebugSessionOptions
|
||||
): Promise<DebugSession | undefined> {
|
||||
return this.progressService.withProgress(
|
||||
'Start...',
|
||||
'debug',
|
||||
async () => {
|
||||
try {
|
||||
// Only save when dirty. To avoid saving temporary sketches.
|
||||
// This is a quick fix for not saving the editor when there are no dirty editors.
|
||||
@ -21,25 +25,35 @@ export class DebugSessionManager extends TheiaDebugSessionManager {
|
||||
|
||||
// preLaunchTask isn't run in case of auto restart as well as postDebugTask
|
||||
if (!options.configuration.__restart) {
|
||||
const taskRun = await this.runTask(options.workspaceFolderUri, resolved.configuration.preLaunchTask, true);
|
||||
const taskRun = await this.runTask(
|
||||
options.workspaceFolderUri,
|
||||
resolved.configuration.preLaunchTask,
|
||||
true
|
||||
);
|
||||
if (!taskRun) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const sessionId = await this.debug.createDebugSession(resolved.configuration);
|
||||
const sessionId = await this.debug.createDebugSession(
|
||||
resolved.configuration
|
||||
);
|
||||
return this.doStart(sessionId, resolved);
|
||||
} catch (e) {
|
||||
if (DebugError.NotFound.is(e)) {
|
||||
this.messageService.error(`The debug session type "${e.data.type}" is not supported.`);
|
||||
this.messageService.error(
|
||||
`The debug session type "${e.data.type}" is not supported.`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.messageService.error('There was an error starting the debug session, check the logs for more details.');
|
||||
this.messageService.error(
|
||||
'There was an error starting the debug session, check the logs for more details.'
|
||||
);
|
||||
console.error('Error starting the debug session', e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,19 +3,21 @@ import { EditorCommandContribution as TheiaEditorCommandContribution } from '@th
|
||||
|
||||
@injectable()
|
||||
export class EditorCommandContribution extends TheiaEditorCommandContribution {
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/8722.
|
||||
this.editorPreferences.onPreferenceChanged(({ preferenceName, newValue, oldValue }) => {
|
||||
this.editorPreferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
if (preferenceName === 'editor.autoSave') {
|
||||
const autoSaveWasOnBeforeChange = !oldValue || oldValue === 'on';
|
||||
const autoSaveIsOnAfterChange = !newValue || newValue === 'on';
|
||||
const autoSaveWasOnBeforeChange =
|
||||
!oldValue || oldValue === 'on';
|
||||
const autoSaveIsOnAfterChange =
|
||||
!newValue || newValue === 'on';
|
||||
if (!autoSaveWasOnBeforeChange && autoSaveIsOnAfterChange) {
|
||||
this.shell.saveAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,7 @@ import { StatusBarAlignment } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class EditorContribution extends TheiaEditorContribution {
|
||||
|
||||
protected updateLanguageStatus(editor: TextEditor | undefined): void {
|
||||
}
|
||||
protected updateLanguageStatus(editor: TextEditor | undefined): void {}
|
||||
|
||||
protected setCursorPositionStatus(editor: TextEditor | undefined): void {
|
||||
if (!editor) {
|
||||
@ -18,8 +16,7 @@ export class EditorContribution extends TheiaEditorContribution {
|
||||
this.statusBar.setElement('editor-status-cursor-position', {
|
||||
text: `${cursor.line + 1}`,
|
||||
alignment: StatusBarAlignment.LEFT,
|
||||
priority: 100
|
||||
priority: 100,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { SketchesService, Sketch } from '../../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
@ -23,16 +22,19 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
|
||||
return this.maybeUpdateCaption(widget);
|
||||
}
|
||||
|
||||
protected async maybeUpdateCaption(widget: EditorWidget): Promise<EditorWidget> {
|
||||
protected async maybeUpdateCaption(
|
||||
widget: EditorWidget
|
||||
): Promise<EditorWidget> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
const { uri } = widget.editor;
|
||||
if (sketch && Sketch.isInSketch(uri, sketch)) {
|
||||
const isTemp = await this.sketchesService.isTemp(sketch);
|
||||
if (isTemp) {
|
||||
widget.title.caption = `Unsaved – ${this.labelProvider.getName(uri)}`;
|
||||
widget.title.caption = `Unsaved – ${this.labelProvider.getName(
|
||||
uri
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
import { KeymapsFrontendContribution as TheiaKeymapsFrontendContribution, KeymapsCommands } from '@theia/keymaps/lib/browser/keymaps-frontend-contribution';
|
||||
import {
|
||||
KeymapsFrontendContribution as TheiaKeymapsFrontendContribution,
|
||||
KeymapsCommands,
|
||||
} from '@theia/keymaps/lib/browser/keymaps-frontend-contribution';
|
||||
import { ArduinoMenus } from '../../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class KeymapsFrontendContribution extends TheiaKeymapsFrontendContribution {
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, {
|
||||
commandId: KeymapsCommands.OPEN_KEYMAPS.id,
|
||||
label: 'Keyboard Shortcuts',
|
||||
order: '1'
|
||||
order: '1',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/
|
||||
|
||||
@injectable()
|
||||
export class ProblemContribution extends TheiaProblemContribution {
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
@ -19,9 +18,8 @@ export class ProblemContribution extends TheiaProblemContribution {
|
||||
if (this.toggleCommand) {
|
||||
keybindings.registerKeybinding({
|
||||
command: this.toggleCommand.id,
|
||||
keybinding: 'ctrlcmd+alt+shift+m'
|
||||
keybinding: 'ctrlcmd+alt+shift+m',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { ConfigService } from '../../../common/protocol/config-service';
|
||||
|
||||
@injectable()
|
||||
export class ProblemManager extends TheiaProblemManager {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@ -20,16 +19,24 @@ export class ProblemManager extends TheiaProblemManager {
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.configService.getConfiguration()
|
||||
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
||||
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
||||
this.configService
|
||||
.getConfiguration()
|
||||
.then(({ dataDirUri }) => (this.dataDirUri = new URI(dataDirUri)))
|
||||
.catch((err) =>
|
||||
this.logger.error(
|
||||
`Failed to determine the data directory: ${err}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setMarkers(uri: URI, owner: string, data: Diagnostic[]): Marker<Diagnostic>[] {
|
||||
setMarkers(
|
||||
uri: URI,
|
||||
owner: string,
|
||||
data: Diagnostic[]
|
||||
): Marker<Diagnostic>[] {
|
||||
if (this.dataDirUri && this.dataDirUri.isEqualOrParent(uri)) {
|
||||
return [];
|
||||
}
|
||||
return super.setMarkers(uri, owner, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,34 +1,50 @@
|
||||
import * as React from 'react';
|
||||
import { NotificationComponent } from './notification-component';
|
||||
import { NotificationCenterComponent as TheiaNotificationCenterComponent } from '@theia/messages/lib/browser/notification-center-component'
|
||||
import { NotificationCenterComponent as TheiaNotificationCenterComponent } from '@theia/messages/lib/browser/notification-center-component';
|
||||
|
||||
const PerfectScrollbar = require('react-perfect-scrollbar');
|
||||
|
||||
export class NotificationCenterComponent extends TheiaNotificationCenterComponent {
|
||||
|
||||
render(): React.ReactNode {
|
||||
const empty = this.state.notifications.length === 0;
|
||||
const title = empty ? 'NO NEW NOTIFICATIONS' : 'NOTIFICATIONS';
|
||||
return (
|
||||
<div className={`theia-notifications-container theia-notification-center ${this.state.visibilityState === 'center' ? 'open' : 'closed'}`}>
|
||||
<div className='theia-notification-center-header'>
|
||||
<div className='theia-notification-center-header-title'>{title}</div>
|
||||
<div className='theia-notification-center-header-actions'>
|
||||
<ul className='theia-notification-actions'>
|
||||
<li className='collapse' title='Hide Notification Center' onClick={this.onHide} />
|
||||
<li className='clear-all' title='Clear All' onClick={this.onClearAll} />
|
||||
<div
|
||||
className={`theia-notifications-container theia-notification-center ${
|
||||
this.state.visibilityState === 'center' ? 'open' : 'closed'
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-center-header">
|
||||
<div className="theia-notification-center-header-title">
|
||||
{title}
|
||||
</div>
|
||||
<div className="theia-notification-center-header-actions">
|
||||
<ul className="theia-notification-actions">
|
||||
<li
|
||||
className="collapse"
|
||||
title="Hide Notification Center"
|
||||
onClick={this.onHide}
|
||||
/>
|
||||
<li
|
||||
className="clear-all"
|
||||
title="Clear All"
|
||||
onClick={this.onClearAll}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<PerfectScrollbar className='theia-notification-list-scroll-container'>
|
||||
<div className='theia-notification-list'>
|
||||
{this.state.notifications.map(notification =>
|
||||
<NotificationComponent key={notification.messageId} notification={notification} manager={this.props.manager} />
|
||||
)}
|
||||
<PerfectScrollbar className="theia-notification-list-scroll-container">
|
||||
<div className="theia-notification-list">
|
||||
{this.state.notifications.map((notification) => (
|
||||
<NotificationComponent
|
||||
key={notification.messageId}
|
||||
notification={notification}
|
||||
manager={this.props.manager}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PerfectScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,35 +1,70 @@
|
||||
import * as React from 'react';
|
||||
import { NotificationComponent as TheiaNotificationComponent } from '@theia/messages/lib/browser/notification-component'
|
||||
import { NotificationComponent as TheiaNotificationComponent } from '@theia/messages/lib/browser/notification-component';
|
||||
|
||||
export class NotificationComponent extends TheiaNotificationComponent {
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { messageId, message, type, collapsed, expandable, source, actions } = this.props.notification;
|
||||
return (<div key={messageId} className='theia-notification-list-item'>
|
||||
<div className={`theia-notification-list-item-content ${collapsed ? 'collapsed' : ''}`}>
|
||||
<div className='theia-notification-list-item-content-main'>
|
||||
<div className={`theia-notification-icon theia-notification-icon-${type}`} />
|
||||
<div className='theia-notification-message'>
|
||||
<span dangerouslySetInnerHTML={{ __html: message }} onClick={this.onMessageClick} />
|
||||
const {
|
||||
messageId,
|
||||
message,
|
||||
type,
|
||||
collapsed,
|
||||
expandable,
|
||||
source,
|
||||
actions,
|
||||
} = this.props.notification;
|
||||
return (
|
||||
<div key={messageId} className="theia-notification-list-item">
|
||||
<div
|
||||
className={`theia-notification-list-item-content ${
|
||||
collapsed ? 'collapsed' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-list-item-content-main">
|
||||
<div
|
||||
className={`theia-notification-icon theia-notification-icon-${type}`}
|
||||
/>
|
||||
<div className="theia-notification-message">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: message }}
|
||||
onClick={this.onMessageClick}
|
||||
/>
|
||||
</div>
|
||||
<ul className='theia-notification-actions'>
|
||||
<ul className="theia-notification-actions">
|
||||
{expandable && (
|
||||
<li className={collapsed ? 'expand' : 'collapse'} title={collapsed ? 'Expand' : 'Collapse'}
|
||||
data-message-id={messageId} onClick={this.onToggleExpansion} />
|
||||
<li
|
||||
className={
|
||||
collapsed ? 'expand' : 'collapse'
|
||||
}
|
||||
title={collapsed ? 'Expand' : 'Collapse'}
|
||||
data-message-id={messageId}
|
||||
onClick={this.onToggleExpansion}
|
||||
/>
|
||||
)}
|
||||
{!this.isProgress && (
|
||||
<li
|
||||
className="clear"
|
||||
title="Clear"
|
||||
data-message-id={messageId}
|
||||
onClick={this.onClear}
|
||||
/>
|
||||
)}
|
||||
{!this.isProgress && (<li className='clear' title='Clear' data-message-id={messageId} onClick={this.onClear} />)}
|
||||
</ul>
|
||||
</div>
|
||||
{(source || !!actions.length) && (
|
||||
<div className='theia-notification-list-item-content-bottom'>
|
||||
<div className='theia-notification-source'>
|
||||
{source && (<span>{source}</span>)}
|
||||
<div className="theia-notification-list-item-content-bottom">
|
||||
<div className="theia-notification-source">
|
||||
{source && <span>{source}</span>}
|
||||
</div>
|
||||
<div className='theia-notification-buttons'>
|
||||
{actions && actions.map((action, index) => (
|
||||
<button key={messageId + `-action-${index}`} className='theia-button'
|
||||
data-message-id={messageId} data-action={action}
|
||||
onClick={this.onAction}>
|
||||
<div className="theia-notification-buttons">
|
||||
{actions &&
|
||||
actions.map((action, index) => (
|
||||
<button
|
||||
key={messageId + `-action-${index}`}
|
||||
className="theia-button"
|
||||
data-message-id={messageId}
|
||||
data-action={action}
|
||||
onClick={this.onAction}
|
||||
>
|
||||
{action}
|
||||
</button>
|
||||
))}
|
||||
@ -38,7 +73,8 @@ export class NotificationComponent extends TheiaNotificationComponent {
|
||||
)}
|
||||
</div>
|
||||
{this.renderProgressBar()}
|
||||
</div>);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderProgressBar(): React.ReactNode {
|
||||
@ -46,17 +82,25 @@ export class NotificationComponent extends TheiaNotificationComponent {
|
||||
return undefined;
|
||||
}
|
||||
if (!Number.isNaN(this.props.notification.progress)) {
|
||||
return <div className='theia-notification-item-progress'>
|
||||
<div className='theia-notification-item-progressbar' style={{ width: `${this.props.notification.progress}%` }} />
|
||||
</div>;
|
||||
}
|
||||
return <div className='theia-progress-bar-container'>
|
||||
<div className='theia-progress-bar' />
|
||||
return (
|
||||
<div className="theia-notification-item-progress">
|
||||
<div
|
||||
className="theia-notification-item-progressbar"
|
||||
style={{
|
||||
width: `${this.props.notification.progress}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="theia-progress-bar-container">
|
||||
<div className="theia-progress-bar" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private get isProgress(): boolean {
|
||||
return typeof this.props.notification.progress === 'number';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,25 @@
|
||||
import * as React from 'react';
|
||||
import { NotificationComponent } from './notification-component';
|
||||
import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component'
|
||||
import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component';
|
||||
|
||||
export class NotificationToastsComponent extends TheiaNotificationToastsComponent {
|
||||
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<div className={`theia-notifications-container theia-notification-toasts ${this.state.visibilityState === 'toasts' ? 'open' : 'closed'}`}>
|
||||
<div className='theia-notification-list'>
|
||||
{this.state.toasts.map(notification => <NotificationComponent key={notification.messageId} notification={notification} manager={this.props.manager} />)}
|
||||
<div
|
||||
className={`theia-notifications-container theia-notification-toasts ${
|
||||
this.state.visibilityState === 'toasts' ? 'open' : 'closed'
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-list">
|
||||
{this.state.toasts.map((notification) => (
|
||||
<NotificationComponent
|
||||
key={notification.messageId}
|
||||
notification={notification}
|
||||
manager={this.props.manager}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,19 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import { ProgressMessage, ProgressUpdate } from '@theia/core/lib/common/message-service-protocol';
|
||||
import {
|
||||
ProgressMessage,
|
||||
ProgressUpdate,
|
||||
} from '@theia/core/lib/common/message-service-protocol';
|
||||
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||
|
||||
@injectable()
|
||||
export class NotificationManager extends TheiaNotificationManager {
|
||||
|
||||
async reportProgress(messageId: string, update: ProgressUpdate, originalMessage: ProgressMessage, cancellationToken: CancellationToken): Promise<void> {
|
||||
async reportProgress(
|
||||
messageId: string,
|
||||
update: ProgressUpdate,
|
||||
originalMessage: ProgressMessage,
|
||||
cancellationToken: CancellationToken
|
||||
): Promise<void> {
|
||||
const notification = this.find(messageId);
|
||||
if (!notification) {
|
||||
return;
|
||||
@ -14,12 +21,19 @@ export class NotificationManager extends TheiaNotificationManager {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
this.clear(messageId);
|
||||
} else {
|
||||
notification.message = originalMessage.text && update.message ? `${originalMessage.text}: ${update.message}` :
|
||||
originalMessage.text || update?.message || notification.message;
|
||||
notification.message =
|
||||
originalMessage.text && update.message
|
||||
? `${originalMessage.text}: ${update.message}`
|
||||
: originalMessage.text ||
|
||||
update?.message ||
|
||||
notification.message;
|
||||
|
||||
// Unlike in Theia, we allow resetting the progress monitor to NaN to enforce unknown progress.
|
||||
const candidate = this.toPlainProgress(update);
|
||||
notification.progress = typeof candidate === 'number' ? candidate : notification.progress;
|
||||
notification.progress =
|
||||
typeof candidate === 'number'
|
||||
? candidate
|
||||
: notification.progress;
|
||||
}
|
||||
this.fireUpdatedEvent();
|
||||
}
|
||||
@ -31,7 +45,6 @@ export class NotificationManager extends TheiaNotificationManager {
|
||||
if (Number.isNaN(update.work.done) || Number.isNaN(update.work.total)) {
|
||||
return Number.NaN; // This should trigger the unknown monitor.
|
||||
}
|
||||
return Math.min(update.work.done / update.work.total * 100, 100);
|
||||
return Math.min((update.work.done / update.work.total) * 100, 100);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,12 +7,16 @@ import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/mess
|
||||
|
||||
@injectable()
|
||||
export class NotificationsRenderer extends TheiaNotificationsRenderer {
|
||||
|
||||
protected render(): void {
|
||||
ReactDOM.render(<div>
|
||||
<NotificationToastsComponent manager={this.manager} corePreferences={this.corePreferences} />
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<NotificationToastsComponent
|
||||
manager={this.manager}
|
||||
corePreferences={this.corePreferences}
|
||||
/>
|
||||
<NotificationCenterComponent manager={this.manager} />
|
||||
</div>, this.container);
|
||||
</div>,
|
||||
this.container
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,22 +1,32 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||
|
||||
type CancelablePromise = Promise<monaco.referenceSearch.ReferencesModel> & { cancel: () => void };
|
||||
type CancelablePromise = Promise<monaco.referenceSearch.ReferencesModel> & {
|
||||
cancel: () => void;
|
||||
};
|
||||
interface EditorFactory {
|
||||
(override: monaco.editor.IEditorOverrideServices, toDispose: DisposableCollection): Promise<MonacoEditor>;
|
||||
(
|
||||
override: monaco.editor.IEditorOverrideServices,
|
||||
toDispose: DisposableCollection
|
||||
): Promise<MonacoEditor>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
protected async doCreateEditor(uri: URI, factory: EditorFactory): Promise<MonacoEditor> {
|
||||
protected async doCreateEditor(
|
||||
uri: URI,
|
||||
factory: EditorFactory
|
||||
): Promise<MonacoEditor> {
|
||||
const editor = await super.doCreateEditor(uri, factory);
|
||||
const toDispose = new DisposableCollection();
|
||||
toDispose.push(this.installCustomReferencesController(editor));
|
||||
@ -24,32 +34,60 @@ export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
|
||||
return editor;
|
||||
}
|
||||
|
||||
private installCustomReferencesController(editor: MonacoEditor): Disposable {
|
||||
private installCustomReferencesController(
|
||||
editor: MonacoEditor
|
||||
): Disposable {
|
||||
const control = editor.getControl();
|
||||
const referencesController = control._contributions['editor.contrib.referencesController'];
|
||||
const referencesController =
|
||||
control._contributions['editor.contrib.referencesController'];
|
||||
const originalToggleWidget = referencesController.toggleWidget;
|
||||
const toDispose = new DisposableCollection();
|
||||
const toDisposeBeforeToggleWidget = new DisposableCollection();
|
||||
referencesController.toggleWidget = (range: monaco.Range, modelPromise: CancelablePromise, peekMode: boolean) => {
|
||||
referencesController.toggleWidget = (
|
||||
range: monaco.Range,
|
||||
modelPromise: CancelablePromise,
|
||||
peekMode: boolean
|
||||
) => {
|
||||
toDisposeBeforeToggleWidget.dispose();
|
||||
originalToggleWidget.bind(referencesController)(range, modelPromise, peekMode);
|
||||
originalToggleWidget.bind(referencesController)(
|
||||
range,
|
||||
modelPromise,
|
||||
peekMode
|
||||
);
|
||||
if (referencesController._widget) {
|
||||
if ('onDidClose' in referencesController._widget) {
|
||||
toDisposeBeforeToggleWidget.push((referencesController._widget as any).onDidClose(() => toDisposeBeforeToggleWidget.dispose()));
|
||||
toDisposeBeforeToggleWidget.push(
|
||||
(referencesController._widget as any).onDidClose(() =>
|
||||
toDisposeBeforeToggleWidget.dispose()
|
||||
)
|
||||
);
|
||||
}
|
||||
const preview = (referencesController._widget as any)._preview as monaco.editor.ICodeEditor;
|
||||
const preview = (referencesController._widget as any)
|
||||
._preview as monaco.editor.ICodeEditor;
|
||||
if (preview) {
|
||||
toDisposeBeforeToggleWidget.push(preview.onDidChangeModel(() => this.updateReadOnlyState(preview)))
|
||||
toDisposeBeforeToggleWidget.push(
|
||||
preview.onDidChangeModel(() =>
|
||||
this.updateReadOnlyState(preview)
|
||||
)
|
||||
);
|
||||
this.updateReadOnlyState(preview);
|
||||
}
|
||||
}
|
||||
};
|
||||
toDispose.push(Disposable.create(() => toDisposeBeforeToggleWidget.dispose()));
|
||||
toDispose.push(Disposable.create(() => referencesController.toggleWidget = originalToggleWidget));
|
||||
toDispose.push(
|
||||
Disposable.create(() => toDisposeBeforeToggleWidget.dispose())
|
||||
);
|
||||
toDispose.push(
|
||||
Disposable.create(
|
||||
() => (referencesController.toggleWidget = originalToggleWidget)
|
||||
)
|
||||
);
|
||||
return toDispose;
|
||||
}
|
||||
|
||||
private updateReadOnlyState(editor: monaco.editor.ICodeEditor | undefined): void {
|
||||
private updateReadOnlyState(
|
||||
editor: monaco.editor.ICodeEditor | undefined
|
||||
): void {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
@ -60,5 +98,4 @@ export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
|
||||
const readOnly = this.sketchesServiceClient.isReadOnly(model.uri);
|
||||
editor.updateOptions({ readOnly });
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,11 +3,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from
|
||||
|
||||
@injectable()
|
||||
export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution {
|
||||
protected setConfigTabSizeWidget() {}
|
||||
|
||||
protected setConfigTabSizeWidget() {
|
||||
}
|
||||
|
||||
protected setLineEndingWidget() {
|
||||
}
|
||||
|
||||
protected setLineEndingWidget() {}
|
||||
}
|
||||
|
@ -10,42 +10,52 @@ import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-ser
|
||||
|
||||
@injectable()
|
||||
export class MonacoTextModelService extends TheiaMonacoTextModelService {
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
protected async createModel(resource: Resource): Promise<MonacoEditorModel> {
|
||||
const factory = this.factories.getContributions().find(({ scheme }) => resource.uri.scheme === scheme);
|
||||
protected async createModel(
|
||||
resource: Resource
|
||||
): Promise<MonacoEditorModel> {
|
||||
const factory = this.factories
|
||||
.getContributions()
|
||||
.find(({ scheme }) => resource.uri.scheme === scheme);
|
||||
const readOnly = this.sketchesServiceClient.isReadOnly(resource.uri);
|
||||
return factory ? factory.createModel(resource) : new MaybeReadonlyMonacoEditorModel(resource, this.m2p, this.p2m, this.logger, undefined, readOnly);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// https://github.com/eclipse-theia/theia/pull/8491
|
||||
class SilentMonacoEditorModel extends MonacoEditorModel {
|
||||
|
||||
protected trace(loggable: Loggable): void {
|
||||
if (this.logger) {
|
||||
this.logger.trace((log: Log) =>
|
||||
loggable((message, ...params) => log(message, ...params, this.resource.uri.toString(true)))
|
||||
return factory
|
||||
? factory.createModel(resource)
|
||||
: new MaybeReadonlyMonacoEditorModel(
|
||||
resource,
|
||||
this.m2p,
|
||||
this.p2m,
|
||||
this.logger,
|
||||
undefined,
|
||||
readOnly
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/eclipse-theia/theia/pull/8491
|
||||
class SilentMonacoEditorModel extends MonacoEditorModel {
|
||||
protected trace(loggable: Loggable): void {
|
||||
if (this.logger) {
|
||||
this.logger.trace((log: Log) =>
|
||||
loggable((message, ...params) =>
|
||||
log(message, ...params, this.resource.uri.toString(true))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel {
|
||||
|
||||
constructor(
|
||||
protected readonly resource: Resource,
|
||||
protected readonly m2p: MonacoToProtocolConverter,
|
||||
protected readonly p2m: ProtocolToMonacoConverter,
|
||||
protected readonly logger?: ILogger,
|
||||
protected readonly editorPreferences?: EditorPreferences,
|
||||
protected readonly _readOnly?: boolean,
|
||||
protected readonly _readOnly?: boolean
|
||||
) {
|
||||
super(resource, m2p, p2m, logger, editorPreferences)
|
||||
super(resource, m2p, p2m, logger, editorPreferences);
|
||||
}
|
||||
|
||||
get readOnly(): boolean {
|
||||
@ -69,6 +79,4 @@ class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel {
|
||||
}
|
||||
this.onDirtyChangedEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -11,15 +11,24 @@ import { WorkspacePreferences } from '@theia/workspace/lib/browser/workspace-pre
|
||||
|
||||
@injectable()
|
||||
export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
|
||||
|
||||
constructor(
|
||||
@inject(FileNavigatorPreferences) protected readonly fileNavigatorPreferences: FileNavigatorPreferences,
|
||||
@inject(FileNavigatorPreferences)
|
||||
protected readonly fileNavigatorPreferences: FileNavigatorPreferences,
|
||||
@inject(OpenerService) protected readonly openerService: OpenerService,
|
||||
@inject(FileNavigatorFilter) protected readonly fileNavigatorFilter: FileNavigatorFilter,
|
||||
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService,
|
||||
@inject(WorkspacePreferences) protected readonly workspacePreferences: WorkspacePreferences
|
||||
@inject(FileNavigatorFilter)
|
||||
protected readonly fileNavigatorFilter: FileNavigatorFilter,
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService,
|
||||
@inject(WorkspacePreferences)
|
||||
protected readonly workspacePreferences: WorkspacePreferences
|
||||
) {
|
||||
super(fileNavigatorPreferences, openerService, fileNavigatorFilter, workspaceService, workspacePreferences);
|
||||
super(
|
||||
fileNavigatorPreferences,
|
||||
openerService,
|
||||
fileNavigatorFilter,
|
||||
workspaceService,
|
||||
workspacePreferences
|
||||
);
|
||||
this.options.defaultWidgetOptions.rank = 1;
|
||||
}
|
||||
|
||||
@ -29,10 +38,8 @@ export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
[
|
||||
WorkspaceCommands.FILE_RENAME,
|
||||
WorkspaceCommands.FILE_DELETE
|
||||
].forEach(registry.unregisterKeybinding.bind(registry));
|
||||
[WorkspaceCommands.FILE_RENAME, WorkspaceCommands.FILE_DELETE].forEach(
|
||||
registry.unregisterKeybinding.bind(registry)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@thei
|
||||
*/
|
||||
@injectable()
|
||||
export class NavigatorTabBarDecorator extends TheiaNavigatorTabBarDecorator {
|
||||
|
||||
onStart(): void {
|
||||
// NOOP
|
||||
}
|
||||
@ -17,5 +16,4 @@ export class NavigatorTabBarDecorator extends TheiaNavigatorTabBarDecorator {
|
||||
// Does not decorate anything.
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,18 +4,15 @@ import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/
|
||||
|
||||
@injectable()
|
||||
export class OutlineViewContribution extends TheiaOutlineViewContribution {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions = {
|
||||
area: 'left',
|
||||
rank: 500
|
||||
rank: 500,
|
||||
};
|
||||
}
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,13 @@ import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { OutputUri } from '@theia/output/lib/common/output-uri';
|
||||
import { IReference } from '@theia/monaco/lib/browser/monaco-text-model-service';
|
||||
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
|
||||
import { OutputChannelManager as TheiaOutputChannelManager, OutputChannel as TheiaOutputChannel } from '@theia/output/lib/common/output-channel';
|
||||
import {
|
||||
OutputChannelManager as TheiaOutputChannelManager,
|
||||
OutputChannel as TheiaOutputChannel,
|
||||
} from '@theia/output/lib/common/output-channel';
|
||||
|
||||
@injectable()
|
||||
export class OutputChannelManager extends TheiaOutputChannelManager {
|
||||
|
||||
getChannel(name: string): TheiaOutputChannel {
|
||||
const existing = this.channels.get(name);
|
||||
if (existing) {
|
||||
@ -21,26 +23,31 @@ export class OutputChannelManager extends TheiaOutputChannelManager {
|
||||
let resource = this.resources.get(name);
|
||||
if (!resource) {
|
||||
const uri = OutputUri.create(name);
|
||||
const editorModelRef = new Deferred<IReference<MonacoEditorModel>>();
|
||||
const editorModelRef = new Deferred<
|
||||
IReference<MonacoEditorModel>
|
||||
>();
|
||||
resource = this.createResource({ uri, editorModelRef });
|
||||
this.resources.set(name, resource);
|
||||
this.textModelService.createModelReference(uri).then(ref => editorModelRef.resolve(ref));
|
||||
this.textModelService
|
||||
.createModelReference(uri)
|
||||
.then((ref) => editorModelRef.resolve(ref));
|
||||
}
|
||||
|
||||
const channel = new OutputChannel(resource, this.preferences);
|
||||
this.channels.set(name, channel);
|
||||
this.toDisposeOnChannelDeletion.set(name, this.registerListeners(channel));
|
||||
this.toDisposeOnChannelDeletion.set(
|
||||
name,
|
||||
this.registerListeners(channel)
|
||||
);
|
||||
this.channelAddedEmitter.fire(channel);
|
||||
if (!this.selectedChannel) {
|
||||
this.selectedChannel = channel;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class OutputChannel extends TheiaOutputChannel {
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
if ((this as any).disposed) {
|
||||
@ -49,5 +56,4 @@ export class OutputChannel extends TheiaOutputChannel {
|
||||
textModifyQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,15 +1,22 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ReactTabBarToolbarItem, TabBarToolbarItem, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import {
|
||||
ReactTabBarToolbarItem,
|
||||
TabBarToolbarItem,
|
||||
TabBarToolbarRegistry,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
|
||||
@injectable()
|
||||
export class OutputToolbarContribution extends TheiaOutputToolbarContribution {
|
||||
|
||||
async registerToolbarItems(registry: TabBarToolbarRegistry): Promise<void> {
|
||||
await super.registerToolbarItems(registry); // Why is it async?
|
||||
// It's a hack. Currently, it's not possible to unregister a toolbar contribution via API.
|
||||
((registry as any).items as Map<string, TabBarToolbarItem | ReactTabBarToolbarItem>).delete('channels');
|
||||
(
|
||||
(registry as any).items as Map<
|
||||
string,
|
||||
TabBarToolbarItem | ReactTabBarToolbarItem
|
||||
>
|
||||
).delete('channels');
|
||||
(registry as any).fireOnDidChange();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,10 +6,8 @@ import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/out
|
||||
// Remove this module after ATL-222 and the Theia update.
|
||||
@injectable()
|
||||
export class OutputWidget extends TheiaOutputWidget {
|
||||
|
||||
protected onAfterShow(msg: Message): void {
|
||||
super.onAfterShow(msg);
|
||||
this.onResize(Widget.ResizeMessage.UnknownSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,12 +6,18 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl } f
|
||||
|
||||
@injectable()
|
||||
export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMainImpl {
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
$append(name: string, text: string, pluginInfo: PluginInfo): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.APPEND.id, { name, text });
|
||||
$append(
|
||||
name: string,
|
||||
text: string,
|
||||
pluginInfo: PluginInfo
|
||||
): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.APPEND.id, {
|
||||
name,
|
||||
text,
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@ -27,12 +33,14 @@ export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMai
|
||||
|
||||
async $reveal(name: string, preserveFocus: boolean): Promise<void> {
|
||||
const options = { preserveFocus };
|
||||
this.commandService.executeCommand(OutputCommands.SHOW.id, { name, options });
|
||||
this.commandService.executeCommand(OutputCommands.SHOW.id, {
|
||||
name,
|
||||
options,
|
||||
});
|
||||
}
|
||||
|
||||
$close(name: string): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.HIDE.id, { name });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,16 +6,17 @@ import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/
|
||||
|
||||
@injectable()
|
||||
export class PreferencesContribution extends TheiaPreferencesContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
// The settings group: preferences, CLI config is not part of the `File` menu on macOS.
|
||||
// On Windows and Linux, we rebind it to `Preferences...`. It is safe to remove here.
|
||||
registry.unregisterMenuAction(CommonCommands.OPEN_PREFERENCES.id, CommonMenus.FILE_SETTINGS_SUBMENU_OPEN);
|
||||
registry.unregisterMenuAction(
|
||||
CommonCommands.OPEN_PREFERENCES.id,
|
||||
CommonMenus.FILE_SETTINGS_SUBMENU_OPEN
|
||||
);
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
|
||||
@injectable()
|
||||
export class ScmContribution extends TheiaScmContribution {
|
||||
|
||||
async initializeLayout(): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
@ -12,5 +11,4 @@ export class ScmContribution extends TheiaScmContribution {
|
||||
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution, SearchInWorkspaceCommands } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import {
|
||||
SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution,
|
||||
SearchInWorkspaceCommands,
|
||||
} from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspaceFrontendContribution {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions.rank = 5;
|
||||
@ -13,12 +15,15 @@ export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspac
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
|
||||
registry.unregisterMenuAction(
|
||||
SearchInWorkspaceCommands.OPEN_SIW_WIDGET
|
||||
);
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
super.registerKeybindings(keybindings);
|
||||
keybindings.unregisterKeybinding(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
|
||||
keybindings.unregisterKeybinding(
|
||||
SearchInWorkspaceCommands.OPEN_SIW_WIDGET
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user