mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-06 04:06:32 +00:00
Merge pull request #128 from bcmi-labs/external-programmer
[ATL-119]: Rework the menu organization for IDE 2.0, [ATL-75]: External programmer
This commit is contained in:
commit
1b8c510f33
@ -3,9 +3,6 @@
|
||||
"version": "0.0.7",
|
||||
"description": "An extension for debugging Arduino programs",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.11.0 <12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@theia/debug": "next",
|
||||
"arduino-ide-extension": "0.0.7",
|
||||
|
@ -114,7 +114,7 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp
|
||||
id: ArduinoDebugCommands.START_DEBUG.id,
|
||||
command: ArduinoDebugCommands.START_DEBUG.id,
|
||||
tooltip: 'Start Debugging',
|
||||
priority: 1
|
||||
priority: 3
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,6 @@
|
||||
"version": "0.0.7",
|
||||
"description": "An extension for Theia building the Arduino IDE",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.11.0 <12"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "yarn download-cli && yarn download-ls && yarn run clean && yarn run build",
|
||||
"clean": "rimraf lib",
|
||||
@ -22,7 +19,6 @@
|
||||
"@grpc/grpc-js": "^1.1.1",
|
||||
"@theia/application-package": "next",
|
||||
"@theia/core": "next",
|
||||
"@theia/cpp": "next",
|
||||
"@theia/editor": "next",
|
||||
"@theia/filesystem": "next",
|
||||
"@theia/git": "next",
|
||||
@ -31,6 +27,8 @@
|
||||
"@theia/monaco": "next",
|
||||
"@theia/navigator": "next",
|
||||
"@theia/outline-view": "next",
|
||||
"@theia/preferences": "next",
|
||||
"@theia/output": "next",
|
||||
"@theia/search-in-workspace": "next",
|
||||
"@theia/terminal": "next",
|
||||
"@theia/workspace": "next",
|
||||
@ -40,9 +38,11 @@
|
||||
"@types/google-protobuf": "^3.7.2",
|
||||
"@types/js-yaml": "^3.12.2",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/ncp": "^2.0.4",
|
||||
"@types/ps-tree": "^1.1.0",
|
||||
"@types/react-select": "^3.0.0",
|
||||
"@types/sinon": "^7.5.2",
|
||||
"@types/temp": "^0.8.34",
|
||||
"@types/which": "^1.3.1",
|
||||
"ajv": "^6.5.3",
|
||||
"css-element-queries": "^1.2.0",
|
||||
@ -53,11 +53,13 @@
|
||||
"google-protobuf": "^3.11.4",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"js-yaml": "^3.13.1",
|
||||
"ncp": "^2.0.0",
|
||||
"p-queue": "^5.0.0",
|
||||
"ps-tree": "^1.2.0",
|
||||
"react-select": "^3.0.4",
|
||||
"semver": "^6.3.0",
|
||||
"string-natural-compare": "^2.0.3",
|
||||
"temp": "^0.9.1",
|
||||
"tree-kill": "^1.2.1",
|
||||
"upath": "^1.1.2",
|
||||
"which": "^1.3.1"
|
||||
@ -66,7 +68,6 @@
|
||||
"@types/chai": "^4.2.7",
|
||||
"@types/chai-string": "^1.4.2",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/temp": "^0.8.34",
|
||||
"chai": "^4.2.0",
|
||||
"chai-string": "^1.5.0",
|
||||
"decompress": "^4.2.0",
|
||||
@ -76,11 +77,9 @@
|
||||
"grpc_tools_node_protoc_ts": "^4.1.0",
|
||||
"mocha": "^7.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"ncp": "^2.0.0",
|
||||
"protoc": "^1.0.4",
|
||||
"shelljs": "^0.8.3",
|
||||
"sinon": "^9.0.1",
|
||||
"temp": "^0.9.1",
|
||||
"uuid": "^3.2.1",
|
||||
"yargs": "^11.1.0"
|
||||
},
|
||||
@ -108,8 +107,8 @@
|
||||
"frontend": "lib/browser/arduino-ide-frontend-module"
|
||||
},
|
||||
{
|
||||
"frontend": "lib/browser/menu/browser-arduino-menu-module",
|
||||
"frontendElectron": "lib/electron-browser/menu/electron-arduino-menu-module"
|
||||
"frontend": "lib/browser/theia/core/browser-menu-module",
|
||||
"frontendElectron": "lib/electron-browser/theia/core/electron-menu-module"
|
||||
},
|
||||
{
|
||||
"frontend": "lib/browser/boards/quick-open/boards-quick-open-module"
|
||||
|
@ -1,75 +1,30 @@
|
||||
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 {
|
||||
|
||||
const category = 'Arduino';
|
||||
|
||||
export const VERIFY: Command = {
|
||||
id: 'arduino-verify',
|
||||
label: 'Verify Sketch'
|
||||
}
|
||||
export const VERIFY_TOOLBAR: Command = {
|
||||
id: 'arduino-verify-toolbar',
|
||||
}
|
||||
|
||||
export const UPLOAD: Command = {
|
||||
id: 'arduino-upload',
|
||||
label: 'Upload Sketch'
|
||||
}
|
||||
export const UPLOAD_TOOLBAR: Command = {
|
||||
id: 'arduino-upload-toolbar',
|
||||
}
|
||||
|
||||
export const TOGGLE_COMPILE_FOR_DEBUG: Command = {
|
||||
id: "arduino-toggle-compile-for-debug"
|
||||
}
|
||||
|
||||
export const SHOW_OPEN_CONTEXT_MENU: Command = {
|
||||
id: 'arduino-show-open-context-menu',
|
||||
label: 'Open Sketch',
|
||||
category
|
||||
id: 'arduino-toggle-compile-for-debug'
|
||||
};
|
||||
|
||||
export const OPEN_FILE_NAVIGATOR: Command = {
|
||||
id: 'arduino-open-file-navigator'
|
||||
}
|
||||
|
||||
export const OPEN_SKETCH: Command = {
|
||||
id: 'arduino-open-file'
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlike `OPEN_SKETCH`, it opens all files from a sketch folder. (ino, cpp, etc...)
|
||||
*/
|
||||
export const OPEN_SKETCH_FILES: Command = {
|
||||
id: 'arduino-open-sketch-files'
|
||||
}
|
||||
|
||||
export const SAVE_SKETCH: Command = {
|
||||
id: 'arduino-save-file'
|
||||
}
|
||||
|
||||
export const NEW_SKETCH: Command = {
|
||||
id: 'arduino-new-sketch',
|
||||
label: 'New Sketch',
|
||||
category
|
||||
}
|
||||
};
|
||||
|
||||
export const OPEN_BOARDS_DIALOG: Command = {
|
||||
id: 'arduino-open-boards-dialog'
|
||||
}
|
||||
};
|
||||
|
||||
export const TOGGLE_ADVANCED_MODE: Command = {
|
||||
id: 'arduino-toggle-advanced-mode'
|
||||
}
|
||||
};
|
||||
export const TOGGLE_ADVANCED_MODE_TOOLBAR: Command = {
|
||||
id: "arduino-toggle-advanced-mode-toolbar"
|
||||
}
|
||||
|
||||
export const OPEN_CLI_CONFIG: Command = {
|
||||
id: 'arduino-open-cli-config',
|
||||
label: 'Open CLI Configuration',
|
||||
category
|
||||
id: 'arduino-toggle-advanced-mode-toolbar'
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,61 +1,45 @@
|
||||
import * as React from 'react';
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { CommandContribution, CommandRegistry, Command, CommandHandler } from '@theia/core/lib/common/command';
|
||||
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { BoardsService, BoardsServiceClient, CoreService, Sketch, SketchesService, ToolOutputServiceClient } from '../common/protocol';
|
||||
import { BoardsService, BoardsServiceClient, CoreService, SketchesService, ToolOutputServiceClient, Port } from '../common/protocol';
|
||||
import { ArduinoCommands } from './arduino-commands';
|
||||
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
|
||||
import { WorkspaceRootUriAwareCommandHandler, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import { SelectionService, MenuContribution, MenuModelRegistry, MAIN_MENU_BAR, MenuPath } from '@theia/core';
|
||||
import { SelectionService, MenuContribution, MenuModelRegistry, MAIN_MENU_BAR } from '@theia/core';
|
||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||
import { EditorManager, EditorMainMenu } from '@theia/editor/lib/browser';
|
||||
import {
|
||||
ContextMenuRenderer, Widget, StatusBar, StatusBarAlignment, FrontendApplicationContribution,
|
||||
FrontendApplication, KeybindingContribution, KeybindingRegistry, OpenerService, open
|
||||
ContextMenuRenderer, StatusBar, StatusBarAlignment, FrontendApplicationContribution,
|
||||
FrontendApplication, KeybindingContribution, KeybindingRegistry, OpenerService
|
||||
} from '@theia/core/lib/browser';
|
||||
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
||||
import { FileSystem, FileStat } from '@theia/filesystem/lib/common';
|
||||
import { CommonCommands, CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { FileSystemCommands } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
|
||||
import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution';
|
||||
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { BoardsConfigDialog } from './boards/boards-config-dialog';
|
||||
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
||||
import { BoardsConfig } from './boards/boards-config';
|
||||
import { MonitorConnection } from './monitor/monitor-connection';
|
||||
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||
import { ArduinoWorkspaceService } from './arduino-workspace-service';
|
||||
import { WorkspaceService } from './theia/workspace/workspace-service';
|
||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
|
||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { EditorMode } from './editor-mode';
|
||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
|
||||
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
import { BoardsConfigStore } from './boards/boards-config-store';
|
||||
import { MainMenuManager } from './menu/main-menu-manager';
|
||||
|
||||
export namespace ArduinoMenus {
|
||||
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
||||
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
|
||||
}
|
||||
|
||||
export namespace ArduinoToolbarContextMenu {
|
||||
export const OPEN_SKETCH_PATH: MenuPath = ['arduino-open-sketch-context-menu'];
|
||||
export const OPEN_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '1_open'];
|
||||
export const WS_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '2_sketches'];
|
||||
export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '3_examples'];
|
||||
}
|
||||
import { BoardsDataStore } from './boards/boards-data-store';
|
||||
import { MainMenuManager } from '../common/main-menu-manager';
|
||||
import { FileSystemExt } from '../common/protocol/filesystem-ext';
|
||||
import { ArduinoMenus } from './menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
|
||||
@ -110,8 +94,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
@inject(StatusBar)
|
||||
protected readonly statusBar: StatusBar;
|
||||
|
||||
@inject(ArduinoWorkspaceService)
|
||||
protected readonly workspaceService: ArduinoWorkspaceService;
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(MonitorConnection)
|
||||
protected readonly monitorConnection: MonitorConnection;
|
||||
@ -146,14 +130,14 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(BoardsConfigStore)
|
||||
protected readonly boardsConfigStore: BoardsConfigStore;
|
||||
@inject(BoardsDataStore)
|
||||
protected readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
protected application: FrontendApplication;
|
||||
protected wsSketchCount: number = 0; // TODO: this does not belong here, does it?
|
||||
@inject(FileSystemExt)
|
||||
protected readonly fileSystemExt: FileSystemExt;
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
@ -161,20 +145,25 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
// 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.');
|
||||
}
|
||||
const updateStatusBar = (config: BoardsConfig.Config) => {
|
||||
const updateStatusBar = ({ selectedBoard, selectedPort }: BoardsConfig.Config) => {
|
||||
this.statusBar.setElement('arduino-selected-board', {
|
||||
alignment: StatusBarAlignment.RIGHT,
|
||||
text: BoardsConfig.Config.toString(config)
|
||||
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'
|
||||
});
|
||||
}
|
||||
}
|
||||
this.boardsServiceClientImpl.onBoardsConfigChanged(updateStatusBar);
|
||||
updateStatusBar(this.boardsServiceClientImpl.boardsConfig);
|
||||
|
||||
this.registerSketchesInMenu(this.menuRegistry);
|
||||
}
|
||||
|
||||
onStart(app: FrontendApplication): void {
|
||||
this.application = app;
|
||||
// Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
|
||||
for (const viewContribution of [
|
||||
this.fileNavigatorContributions,
|
||||
@ -183,36 +172,13 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
this.problemContribution,
|
||||
this.scmContribution,
|
||||
this.siwContribution] as Array<FrontendApplicationContribution>) {
|
||||
|
||||
if (viewContribution.initializeLayout) {
|
||||
viewContribution.initializeLayout(this.application);
|
||||
viewContribution.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: ArduinoCommands.VERIFY.id,
|
||||
command: ArduinoCommands.VERIFY_TOOLBAR.id,
|
||||
tooltip: 'Verify'
|
||||
});
|
||||
registry.registerItem({
|
||||
id: ArduinoCommands.UPLOAD.id,
|
||||
command: ArduinoCommands.UPLOAD_TOOLBAR.id,
|
||||
tooltip: 'Upload'
|
||||
});
|
||||
registry.registerItem({
|
||||
id: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
|
||||
command: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
|
||||
tooltip: 'Open',
|
||||
priority: 2
|
||||
});
|
||||
registry.registerItem({
|
||||
id: ArduinoCommands.SAVE_SKETCH.id,
|
||||
command: ArduinoCommands.SAVE_SKETCH.id,
|
||||
tooltip: 'Save',
|
||||
priority: 2
|
||||
});
|
||||
registry.registerItem({
|
||||
id: BoardsToolBarItem.TOOLBAR_ID,
|
||||
render: () => <BoardsToolBarItem
|
||||
@ -220,137 +186,31 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
commands={this.commandRegistry}
|
||||
boardsServiceClient={this.boardsServiceClientImpl} />,
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
priority: 2
|
||||
priority: 7
|
||||
});
|
||||
registry.registerItem({
|
||||
id: 'toggle-serial-monitor',
|
||||
command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR,
|
||||
tooltip: 'Toggle Serial Monitor'
|
||||
tooltip: 'Serial Monitor'
|
||||
});
|
||||
|
||||
registry.registerItem({
|
||||
id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||
command: ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR.id,
|
||||
tooltip: 'Toggle Advanced Mode',
|
||||
text: (this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)')
|
||||
tooltip: this.editorMode.proMode ? 'Switch to Classic Mode' : 'Switch to Advanced Mode',
|
||||
text: this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)'
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
// TODO: use proper API https://github.com/eclipse-theia/theia/pull/6599
|
||||
const allHandlers: { [id: string]: CommandHandler[] } = (registry as any)._handlers;
|
||||
|
||||
// Make sure to reveal the `Explorer` before executing `New File` and `New Folder`.
|
||||
for (const command of [WorkspaceCommands.NEW_FILE, WorkspaceCommands.NEW_FOLDER]) {
|
||||
const { id } = command;
|
||||
const handlers = allHandlers[id].slice();
|
||||
registry.unregisterCommand(id);
|
||||
registry.registerCommand(command);
|
||||
for (const handler of handlers) {
|
||||
const wrapper: CommandHandler = {
|
||||
execute: (...args: any[]) => {
|
||||
this.fileNavigatorContributions.openView({ reveal: true }).then(() => handler.execute(args));
|
||||
},
|
||||
isVisible: (...args: any[]) => {
|
||||
return handler.isVisible!(args);
|
||||
},
|
||||
isEnabled: (args: any[]) => {
|
||||
return handler.isEnabled!(args);
|
||||
},
|
||||
isToggled: (args: any[]) => {
|
||||
return handler.isToggled!(args);
|
||||
}
|
||||
};
|
||||
if (!handler.isEnabled) {
|
||||
delete wrapper.isEnabled;
|
||||
}
|
||||
if (!handler.isToggled) {
|
||||
delete wrapper.isToggled;
|
||||
}
|
||||
if (!handler.isVisible) {
|
||||
delete wrapper.isVisible;
|
||||
}
|
||||
registry.registerHandler(id, wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerCommand(ArduinoCommands.VERIFY, {
|
||||
execute: this.verify.bind(this)
|
||||
});
|
||||
registry.registerCommand(ArduinoCommands.VERIFY_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: this.verify.bind(this)
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, {
|
||||
execute: () => this.editorMode.toggleCompileForDebug(),
|
||||
isToggled: () => this.editorMode.compileForDebug
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.UPLOAD, {
|
||||
execute: this.upload.bind(this)
|
||||
});
|
||||
registry.registerCommand(ArduinoCommands.UPLOAD_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: this.upload.bind(this)
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: async (widget: Widget, target: EventTarget) => {
|
||||
if (this.wsSketchCount) {
|
||||
const el = (target as HTMLElement).parentElement;
|
||||
if (el) {
|
||||
this.contextMenuRenderer.render(ArduinoToolbarContextMenu.OPEN_SKETCH_PATH, {
|
||||
x: el.getBoundingClientRect().left,
|
||||
y: el.getBoundingClientRect().top + el.offsetHeight
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.commandRegistry.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR, {
|
||||
execute: () => this.doOpenFile()
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.OPEN_SKETCH, {
|
||||
execute: async (sketch: Sketch) => {
|
||||
this.workspaceService.open(new URI(sketch.uri));
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.OPEN_SKETCH_FILES, {
|
||||
execute: async (uri: string) => {
|
||||
this.openSketchFiles(uri);
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.SAVE_SKETCH, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: (sketch: Sketch) => {
|
||||
registry.executeCommand(CommonCommands.SAVE_ALL.id);
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.NEW_SKETCH, new WorkspaceRootUriAwareCommandHandler(this.workspaceService, this.selectionService, {
|
||||
execute: async uri => {
|
||||
try {
|
||||
// hack: sometimes we don't get the workspace root, but the currently active file: correct for that
|
||||
if (uri.path.ext !== "") {
|
||||
uri = uri.withPath(uri.path.dir.dir);
|
||||
}
|
||||
|
||||
const sketch = await this.sketchService.createNewSketch(uri.toString());
|
||||
this.workspaceService.open(new URI(sketch.uri));
|
||||
} catch (e) {
|
||||
await this.messageService.error(e.toString());
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, {
|
||||
execute: async () => {
|
||||
const boardsConfig = await this.boardsConfigDialog.open();
|
||||
@ -359,7 +219,6 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, {
|
||||
isToggled: () => this.editorMode.proMode,
|
||||
execute: () => this.editorMode.toggleProMode()
|
||||
@ -369,189 +228,35 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
isToggled: () => this.editorMode.proMode,
|
||||
execute: () => this.editorMode.toggleProMode()
|
||||
});
|
||||
|
||||
registry.registerCommand(ArduinoCommands.OPEN_CLI_CONFIG, {
|
||||
execute: () => this.configService.getCliConfigFileUri().then(uri => open(this.openerService, new URI(uri)))
|
||||
});
|
||||
}
|
||||
|
||||
protected async verify() {
|
||||
const widget = this.getCurrentWidget();
|
||||
if (widget instanceof EditorWidget) {
|
||||
await widget.saveable.save();
|
||||
}
|
||||
|
||||
const uri = this.toUri(widget);
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||
throw new Error('No boards selected. Please select a board.');
|
||||
}
|
||||
if (!boardsConfig.selectedBoard.fqbn) {
|
||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
||||
}
|
||||
const fqbn = await this.boardsConfigStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn);
|
||||
this.outputContribution.openView({ reveal: true });
|
||||
await this.coreService.compile({
|
||||
sketchUri: uri.toString(),
|
||||
fqbn,
|
||||
optimizeForDebug: this.editorMode.compileForDebug
|
||||
});
|
||||
} catch (e) {
|
||||
await this.messageService.error(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
protected async upload() {
|
||||
const widget = this.getCurrentWidget();
|
||||
if (widget instanceof EditorWidget) {
|
||||
await widget.saveable.save();
|
||||
}
|
||||
|
||||
const uri = this.toUri(widget);
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
const monitorConfig = this.monitorConnection.monitorConfig;
|
||||
if (monitorConfig) {
|
||||
await this.monitorConnection.disconnect();
|
||||
}
|
||||
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||
throw new Error('No boards selected. Please select a board.');
|
||||
}
|
||||
const { selectedPort } = boardsConfig;
|
||||
if (!selectedPort) {
|
||||
throw new Error('No ports selected. Please select a port.');
|
||||
}
|
||||
if (!boardsConfig.selectedBoard.fqbn) {
|
||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
||||
}
|
||||
this.outputContribution.openView({ reveal: true });
|
||||
const fqbn = await this.boardsConfigStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn);
|
||||
await this.coreService.upload({
|
||||
sketchUri: uri.toString(),
|
||||
fqbn,
|
||||
port: selectedPort.address,
|
||||
optimizeForDebug: this.editorMode.compileForDebug
|
||||
});
|
||||
} catch (e) {
|
||||
await this.messageService.error(e.toString());
|
||||
} finally {
|
||||
if (monitorConfig) {
|
||||
await this.monitorConnection.connect(monitorConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry) {
|
||||
if (!this.editorMode.proMode) {
|
||||
// If are not in pro-mode, we have to disable the context menu for the tabs.
|
||||
// Such as `Close`, `Close All`, etc.
|
||||
for (const command of [
|
||||
CommonCommands.CLOSE_TAB,
|
||||
CommonCommands.CLOSE_OTHER_TABS,
|
||||
CommonCommands.CLOSE_RIGHT_TABS,
|
||||
CommonCommands.CLOSE_ALL_TABS,
|
||||
CommonCommands.COLLAPSE_PANEL,
|
||||
CommonCommands.TOGGLE_MAXIMIZED,
|
||||
FileNavigatorCommands.REVEAL_IN_NAVIGATOR
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
const menuId = (menuPath: string[]): string => {
|
||||
const index = menuPath.length - 1;
|
||||
const menuId = menuPath[index];
|
||||
return menuId;
|
||||
}
|
||||
|
||||
registry.unregisterMenuAction(FileSystemCommands.UPLOAD);
|
||||
registry.unregisterMenuAction(FileDownloadCommands.DOWNLOAD);
|
||||
|
||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_FOLDER);
|
||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_WORKSPACE);
|
||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_RECENT_WORKSPACE);
|
||||
registry.unregisterMenuAction(WorkspaceCommands.SAVE_WORKSPACE_AS);
|
||||
registry.unregisterMenuAction(WorkspaceCommands.CLOSE);
|
||||
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(MonacoMenus.SELECTION));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(EditorMainMenu.GO));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(TerminalMenus.TERMINAL));
|
||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(CommonMenus.VIEW));
|
||||
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(CommonMenus.VIEW));
|
||||
}
|
||||
|
||||
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
|
||||
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH, {
|
||||
commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id,
|
||||
label: 'Optimize for Debugging',
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH, {
|
||||
commandId: ArduinoCommands.VERIFY.id,
|
||||
label: 'Verify/Compile',
|
||||
order: '2'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH, {
|
||||
commandId: ArduinoCommands.UPLOAD.id,
|
||||
label: 'Upload',
|
||||
order: '3'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoToolbarContextMenu.OPEN_GROUP, {
|
||||
commandId: ArduinoCommands.OPEN_FILE_NAVIGATOR.id,
|
||||
label: 'Open...'
|
||||
});
|
||||
|
||||
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
|
||||
|
||||
registry.registerMenuAction(CommonMenus.HELP, {
|
||||
commandId: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||
label: 'Advanced Mode'
|
||||
});
|
||||
|
||||
registry.registerMenuAction([...CommonMenus.FILE, '0_new_sketch'], {
|
||||
commandId: ArduinoCommands.NEW_SKETCH.id
|
||||
});
|
||||
|
||||
registry.registerMenuAction([...CommonMenus.FILE_SETTINGS_SUBMENU, '3_settings_cli'], {
|
||||
commandId: ArduinoCommands.OPEN_CLI_CONFIG.id
|
||||
});
|
||||
}
|
||||
|
||||
protected getMenuId(menuPath: string[]): string {
|
||||
const index = menuPath.length - 1;
|
||||
const menuId = menuPath[index];
|
||||
return menuId;
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
keybindings.registerKeybinding({
|
||||
command: ArduinoCommands.VERIFY.id,
|
||||
keybinding: 'ctrlcmd+alt+v'
|
||||
});
|
||||
keybindings.registerKeybinding({
|
||||
command: ArduinoCommands.UPLOAD.id,
|
||||
keybinding: 'ctrlcmd+alt+u'
|
||||
});
|
||||
}
|
||||
|
||||
protected async registerSketchesInMenu(registry: MenuModelRegistry): Promise<void> {
|
||||
const sketches = await this.sketchService.getSketches();
|
||||
this.wsSketchCount = sketches.length;
|
||||
sketches.forEach(sketch => {
|
||||
const command: Command = {
|
||||
id: 'openSketch' + sketch.name
|
||||
}
|
||||
this.commandRegistry.registerCommand(command, {
|
||||
execute: () => this.commandRegistry.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoToolbarContextMenu.WS_SKETCHES_GROUP, {
|
||||
commandId: command.id,
|
||||
label: sketch.name
|
||||
});
|
||||
});
|
||||
keybindings.unregisterKeybinding('ctrlcmd+n'); // Unregister the keybinding for `New File`, will be used by `New Sketch`. (eclipse-theia/theia#8170)
|
||||
}
|
||||
|
||||
protected async openSketchFiles(uri: string): Promise<void> {
|
||||
@ -559,82 +264,9 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
for (const uri of uris) {
|
||||
await this.editorManager.open(new URI(uri));
|
||||
}
|
||||
if (uris.length) {
|
||||
await this.editorManager.open(new URI(uris[0])); // Make sure the sketch file has the focus.
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file after prompting the `Open File` dialog. Shows a warning message if
|
||||
* - the file to open does not exist,
|
||||
* - it was not a file, but a directory, or
|
||||
* - the file does not pass validation.
|
||||
*
|
||||
* Otherwise, resolves to the URI of the file.
|
||||
*/
|
||||
protected async doOpenFile(): Promise<void> {
|
||||
const props: OpenFileDialogProps = {
|
||||
title: WorkspaceCommands.OPEN_FILE.dialogLabel,
|
||||
canSelectFolders: false,
|
||||
canSelectFiles: true
|
||||
};
|
||||
const [rootStat] = await this.workspaceService.roots;
|
||||
const destinationFileUri = await this.fileDialogService.showOpenDialog(props, rootStat);
|
||||
if (!destinationFileUri) {
|
||||
return;
|
||||
}
|
||||
const destinationFile = await this.fileSystem.getFileStat(destinationFileUri.toString());
|
||||
if (!destinationFile) {
|
||||
this.messageService.warn(`File does not exist: ${this.fileSystem.getFsPath(destinationFileUri.toString())}`)
|
||||
return;
|
||||
}
|
||||
if (destinationFile.isDirectory) {
|
||||
this.messageService.warn('Please select a sketch file, not a directory.')
|
||||
return;
|
||||
}
|
||||
const message = await this.validate(destinationFile);
|
||||
if (message) {
|
||||
this.messageService.warn(message);
|
||||
return;
|
||||
}
|
||||
this.workspaceService.open(destinationFileUri.parent);
|
||||
}
|
||||
|
||||
protected getCurrentWidget(): EditorWidget | undefined {
|
||||
let widget = this.editorManager.currentEditor;
|
||||
if (!widget) {
|
||||
const visibleWidgets = this.editorManager.all.filter(w => w.isVisible);
|
||||
if (visibleWidgets.length > 0) {
|
||||
widget = visibleWidgets[0];
|
||||
}
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `undefined` if the `file` is valid. Otherwise, returns with the validation error message.
|
||||
*/
|
||||
protected validate(file: FileStat): MaybePromise<string | undefined> {
|
||||
const uri = new URI(file.uri);
|
||||
const path = uri.path;
|
||||
const { name, ext, dir } = path;
|
||||
if (ext !== '.ino') {
|
||||
return "Only sketches with '.ino' extension can be opened.";
|
||||
}
|
||||
if (name !== dir.name) {
|
||||
return `The file "${name}${ext}" needs to be inside a sketch folder named "${name}".`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private toUri(arg: any): URI | undefined {
|
||||
if (arg instanceof URI) {
|
||||
return arg;
|
||||
}
|
||||
if (typeof arg === 'string') {
|
||||
return new URI(arg);
|
||||
}
|
||||
if (arg instanceof EditorWidget) {
|
||||
return arg.editor.uri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerColors(colors: ColorRegistry): void {
|
||||
@ -663,6 +295,24 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
|
||||
hc: 'editorWidget.background'
|
||||
},
|
||||
description: 'Color of the Arduino Pro 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'
|
||||
},
|
||||
description: 'Background color of the toolbar items. Such as Upload, Verify, etc.'
|
||||
},
|
||||
{
|
||||
id: 'arduino.toolbar.hoverBackground',
|
||||
defaults: {
|
||||
dark: 'button.hoverBackground',
|
||||
light: 'button.hoverBackground',
|
||||
hc: 'activityBar.inactiveForeground'
|
||||
},
|
||||
description: 'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import '../../src/browser/style/index.css';
|
||||
import { ContainerModule, interfaces } from 'inversify';
|
||||
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 } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
|
||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application'
|
||||
import { FrontendApplicationContribution, FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application'
|
||||
import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser/textmate';
|
||||
import { LanguageClientContribution } from '@theia/languages/lib/browser';
|
||||
import { ArduinoLanguageClientContribution } from './language/arduino-language-client-contribution';
|
||||
@ -22,31 +22,35 @@ import { ToolOutputServiceClient } from '../common/protocol/tool-output-service'
|
||||
import { ToolOutputService } from '../common/protocol/tool-output-service';
|
||||
import { ToolOutputServiceClientImpl } from './tool-output/client-service-impl';
|
||||
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { ArduinoWorkspaceService } from './arduino-workspace-service';
|
||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { ArduinoOutlineViewContribution } from './customization/arduino-outline-contribution';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { ArduinoProblemContribution } from './customization/arduino-problem-contribution';
|
||||
import { ArduinoNavigatorContribution } from './customization/arduino-navigator-contribution';
|
||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { WorkspaceService } from './theia/workspace/workspace-service';
|
||||
import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { OutlineViewContribution } from './theia/outline/outline-contribution';
|
||||
import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { ProblemContribution } from './theia/markers/problem-contribution';
|
||||
import { FileNavigatorContribution } from './theia/navigator/navigator-contribution';
|
||||
import { FileNavigatorContribution as TheiaFileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution';
|
||||
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
import { ArduinoOutputToolContribution } from './customization/arduino-output-tool-contribution';
|
||||
import { EditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
||||
import { ArduinoEditorContribution } from './customization/arduino-editor-contribution';
|
||||
import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
||||
import { ArduinoMonacoStatusBarContribution } from './customization/arduino-monaco-status-bar-contribution';
|
||||
import { ApplicationShell, ShellLayoutRestorer, KeybindingContribution } from '@theia/core/lib/browser';
|
||||
import { EditorContribution as TheiaEditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
||||
import { EditorContribution } from './theia/editor/editor-contribution';
|
||||
import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
||||
import { MonacoStatusBarContribution } from './theia/monaco/monaco-status-bar-contribution';
|
||||
import {
|
||||
ApplicationShell as TheiaApplicationShell,
|
||||
ShellLayoutRestorer as TheiaShellLayoutRestorer,
|
||||
KeybindingContribution,
|
||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||
KeybindingRegistry as TheiaKeybindingRegistry
|
||||
} from '@theia/core/lib/browser';
|
||||
import { MenuContribution } from '@theia/core/lib/common/menu';
|
||||
import { ArduinoApplicationShell } from './customization/arduino-application-shell';
|
||||
import { ArduinoFrontendApplication } from './customization/arduino-frontend-application';
|
||||
import { ApplicationShell } from './theia/core/application-shell';
|
||||
import { FrontendApplication } from './theia/core/frontend-application';
|
||||
import { BoardsConfigDialog, BoardsConfigDialogProps } from './boards/boards-config-dialog';
|
||||
import { BoardsConfigDialogWidget } from './boards/boards-config-dialog-widget';
|
||||
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { ArduinoScmContribution } from './customization/arduino-scm-contribution';
|
||||
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { ArduinoSearchInWorkspaceContribution } from './customization/arduino-search-in-workspace-contribution';
|
||||
import { ScmContribution as TheiaScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { ScmContribution } from './theia/scm/scm-contribution';
|
||||
import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
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';
|
||||
@ -55,29 +59,52 @@ import { MonitorWidget } from './monitor/monitor-widget';
|
||||
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||
import { MonitorConnection } from './monitor/monitor-connection';
|
||||
import { MonitorModel } from './monitor/monitor-model';
|
||||
import { TabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||
import { ArduinoTabBarDecoratorService } from './shell/arduino-tab-bar-decorator';
|
||||
import { ProblemManager } from '@theia/markers/lib/browser';
|
||||
import { ArduinoProblemManager } from './markers/arduino-problem-manager';
|
||||
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||
import { TabBarDecoratorService } from './theia/core/tab-bar-decorator';
|
||||
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser';
|
||||
import { ProblemManager } from './theia/markers/problem-manager';
|
||||
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
||||
import { AboutDialog } from '@theia/core/lib/browser/about-dialog';
|
||||
import { ArduinoAboutDialog } from './customization/arduino-about-dialog';
|
||||
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||
import { AboutDialog as TheiaAboutDialog } from '@theia/core/lib/browser/about-dialog';
|
||||
import { AboutDialog } from './theia/core/about-dialog';
|
||||
import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer';
|
||||
import { EditorMode } from './editor-mode';
|
||||
import { ListItemRenderer } from './components/component-list/list-item-renderer';
|
||||
import { ListItemRenderer } from './widgets/component-list/list-item-renderer';
|
||||
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
|
||||
import { MonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service';
|
||||
import { ArduinoDaemonClientImpl } from './arduino-daemon-client-impl';
|
||||
import { ArduinoDaemonClient, ArduinoDaemonPath, ArduinoDaemon } from '../common/protocol/arduino-daemon';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { ArduinoEditorManager } from './editor/arduino-editor-manager';
|
||||
import { ArduinoFrontendConnectionStatusService, ArduinoApplicationConnectionStatusContribution } from './customization/arduino-connection-status-service';
|
||||
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from '@theia/core/lib/browser/connection-status-service';
|
||||
import { EditorManager as TheiaEditorManager } from '@theia/editor/lib/browser';
|
||||
import { EditorManager } from './theia/editor/editor-manager';
|
||||
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution } from './theia/core/connection-status-service';
|
||||
import {
|
||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ConfigServiceClientImpl } from './config-service-client-impl';
|
||||
import { CoreServiceClientImpl } from './core-service-client-impl';
|
||||
import { BoardsDetailsMenuUpdater } from './boards/boards-details-menu-updater';
|
||||
import { BoardsConfigStore } from './boards/boards-config-store';
|
||||
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 { WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution, FileMenuContribution as TheiaFileMenuContribution } from '@theia/workspace/lib/browser';
|
||||
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';
|
||||
import { CloseSketch } from './contributions/close-sketch';
|
||||
import { SaveAsSketch } from './contributions/save-as-sketch';
|
||||
import { SaveSketch } from './contributions/save-sketch';
|
||||
import { VerifySketch } from './contributions/verify-sketch';
|
||||
import { UploadSketch } from './contributions/upload-sketch';
|
||||
import { CommonFrontendContribution } from './theia/core/common-frontend-contribution';
|
||||
import { EditContributions } from './contributions/edit-contributions';
|
||||
import { OpenSketchExternal } from './contributions/open-sketch-external';
|
||||
import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/preferences/lib/browser/preference-contribution';
|
||||
import { PreferencesContribution } from './theia/preferences/preference-contribution';
|
||||
import { QuitApp } from './contributions/quit-app';
|
||||
import { SketchControl } from './contributions/sketch-control';
|
||||
import { Settings } from './contributions/settings';
|
||||
import { KeybindingRegistry } from './theia/core/keybindings';
|
||||
|
||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||
|
||||
@ -88,7 +115,7 @@ MonacoThemingService.register({
|
||||
json: require('../../src/browser/data/arduino.color-theme.json')
|
||||
});
|
||||
|
||||
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
ElementQueries.listen();
|
||||
ElementQueries.init();
|
||||
|
||||
@ -150,19 +177,15 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
bind(BoardsServiceClient).toDynamicValue(async context => {
|
||||
const client = context.container.get(BoardsServiceClientImpl);
|
||||
const service = context.container.get<BoardsService>(BoardsService);
|
||||
const [attachedBoards, availablePorts] = await Promise.all([
|
||||
service.getAttachedBoards(),
|
||||
service.getAvailablePorts()
|
||||
]);
|
||||
client.init({ attachedBoards, availablePorts });
|
||||
await client.init(service);
|
||||
WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath, client);
|
||||
return client;
|
||||
}).inSingletonScope();
|
||||
|
||||
// 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(BoardsDetailsMenuUpdater).inSingletonScope();
|
||||
bind(BoardsConfigStore).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsConfigStore);
|
||||
bind(FrontendApplicationContribution).to(BoardsDataMenuUpdater).inSingletonScope();
|
||||
bind(BoardsDataStore).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsDataStore);
|
||||
// Logger for the Arduino daemon
|
||||
bind(ILogger).toDynamicValue(ctx => {
|
||||
const parentLogger = ctx.container.get<ILogger>(ILogger);
|
||||
@ -236,50 +259,54 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
return client;
|
||||
}).inSingletonScope();
|
||||
|
||||
bind(ArduinoWorkspaceService).toSelf().inSingletonScope();
|
||||
rebind(WorkspaceService).toService(ArduinoWorkspaceService);
|
||||
bind(WorkspaceService).toSelf().inSingletonScope();
|
||||
rebind(TheiaWorkspaceService).toService(WorkspaceService);
|
||||
|
||||
// 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(OutlineViewContribution).to(ArduinoOutlineViewContribution).inSingletonScope();
|
||||
rebind(ProblemContribution).to(ArduinoProblemContribution).inSingletonScope();
|
||||
rebind(FileNavigatorContribution).to(ArduinoNavigatorContribution).inSingletonScope();
|
||||
rebind(OutputToolbarContribution).to(ArduinoOutputToolContribution).inSingletonScope();
|
||||
rebind(EditorContribution).to(ArduinoEditorContribution).inSingletonScope();
|
||||
rebind(MonacoStatusBarContribution).to(ArduinoMonacoStatusBarContribution).inSingletonScope();
|
||||
rebind(ApplicationShell).to(ArduinoApplicationShell).inSingletonScope();
|
||||
rebind(ScmContribution).to(ArduinoScmContribution).inSingletonScope();
|
||||
rebind(SearchInWorkspaceFrontendContribution).to(ArduinoSearchInWorkspaceContribution).inSingletonScope();
|
||||
rebind(FrontendApplication).to(ArduinoFrontendApplication).inSingletonScope();
|
||||
rebind(TheiaOutlineViewContribution).to(OutlineViewContribution).inSingletonScope();
|
||||
rebind(TheiaProblemContribution).to(ProblemContribution).inSingletonScope();
|
||||
rebind(TheiaFileNavigatorContribution).to(FileNavigatorContribution).inSingletonScope();
|
||||
rebind(TheiaEditorContribution).to(EditorContribution).inSingletonScope();
|
||||
rebind(TheiaMonacoStatusBarContribution).to(MonacoStatusBarContribution).inSingletonScope();
|
||||
rebind(TheiaApplicationShell).to(ApplicationShell).inSingletonScope();
|
||||
rebind(TheiaScmContribution).to(ScmContribution).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(TheiaKeybindingRegistry).to(KeybindingRegistry).inSingletonScope();
|
||||
|
||||
// Show a disconnected status bar, when the daemon is not available
|
||||
bind(ArduinoApplicationConnectionStatusContribution).toSelf().inSingletonScope();
|
||||
rebind(ApplicationConnectionStatusContribution).toService(ArduinoApplicationConnectionStatusContribution);
|
||||
bind(ArduinoFrontendConnectionStatusService).toSelf().inSingletonScope();
|
||||
rebind(FrontendConnectionStatusService).toService(ArduinoFrontendConnectionStatusService);
|
||||
bind(ApplicationConnectionStatusContribution).toSelf().inSingletonScope();
|
||||
rebind(TheiaApplicationConnectionStatusContribution).toService(ApplicationConnectionStatusContribution);
|
||||
bind(FrontendConnectionStatusService).toSelf().inSingletonScope();
|
||||
rebind(TheiaFrontendConnectionStatusService).toService(FrontendConnectionStatusService);
|
||||
|
||||
// Editor customizations. Sets the editor to `readOnly` if under the data dir.
|
||||
bind(ArduinoEditorManager).toSelf().inSingletonScope();
|
||||
rebind(EditorManager).toService(ArduinoEditorManager);
|
||||
bind(EditorManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaEditorManager).toService(EditorManager);
|
||||
|
||||
// Decorator customizations
|
||||
bind(ArduinoTabBarDecoratorService).toSelf().inSingletonScope();
|
||||
rebind(TabBarDecoratorService).toService(ArduinoTabBarDecoratorService);
|
||||
bind(TabBarDecoratorService).toSelf().inSingletonScope();
|
||||
rebind(TheiaTabBarDecoratorService).toService(TabBarDecoratorService);
|
||||
|
||||
// Problem markers
|
||||
bind(ArduinoProblemManager).toSelf().inSingletonScope();
|
||||
rebind(ProblemManager).toService(ArduinoProblemManager);
|
||||
bind(ProblemManager).toSelf().inSingletonScope();
|
||||
rebind(TheiaProblemManager).toService(ProblemManager);
|
||||
|
||||
// About dialog to show the CLI version
|
||||
bind(ArduinoAboutDialog).toSelf().inSingletonScope();
|
||||
rebind(AboutDialog).toService(ArduinoAboutDialog);
|
||||
bind(AboutDialog).toSelf().inSingletonScope();
|
||||
rebind(TheiaAboutDialog).toService(AboutDialog);
|
||||
|
||||
// Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
|
||||
bind(ArduinoShellLayoutRestorer).toSelf().inSingletonScope();
|
||||
rebind(ShellLayoutRestorer).toService(ArduinoShellLayoutRestorer);
|
||||
bind(ShellLayoutRestorer).toSelf().inSingletonScope();
|
||||
rebind(TheiaShellLayoutRestorer).toService(ShellLayoutRestorer);
|
||||
|
||||
// Arduino daemon client. Receives notifications from the backend if the CLI daemon process has been restarted.
|
||||
bind(ArduinoDaemon).toDynamicValue(context => {
|
||||
@ -293,4 +320,20 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
||||
WebSocketConnectionProvider.createProxy(context.container, ArduinoDaemonPath, client);
|
||||
return client;
|
||||
}).inSingletonScope();
|
||||
|
||||
// File-system extension
|
||||
bind(FileSystemExt).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, FileSystemExtPath)).inSingletonScope();
|
||||
|
||||
Contribution.configure(bind, NewSketch);
|
||||
Contribution.configure(bind, OpenSketch);
|
||||
Contribution.configure(bind, CloseSketch);
|
||||
Contribution.configure(bind, SaveSketch);
|
||||
Contribution.configure(bind, SaveAsSketch);
|
||||
Contribution.configure(bind, VerifySketch);
|
||||
Contribution.configure(bind, UploadSketch);
|
||||
Contribution.configure(bind, OpenSketchExternal);
|
||||
Contribution.configure(bind, EditContributions);
|
||||
Contribution.configure(bind, QuitApp);
|
||||
Contribution.configure(bind, SketchControl);
|
||||
Contribution.configure(bind, Settings);
|
||||
});
|
||||
|
@ -4,10 +4,9 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
|
||||
import { BoardsService, Board } from '../../common/protocol/boards-service';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||
import { InstallationProgressDialog } from '../components/progress-dialog';
|
||||
import { InstallationProgressDialog } from '../widgets/progress-dialog';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
|
||||
|
||||
/**
|
||||
* Listens on `BoardsConfig.Config` changes, if a board is selected which does not
|
||||
* have the corresponding core installed, it proposes the user to install the core.
|
||||
|
@ -127,7 +127,7 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
}
|
||||
|
||||
protected queryBoards = (options: { query?: string } = {}): Promise<Array<Board & { packageName: string }>> => {
|
||||
return this.props.boardsService.searchBoards(options);
|
||||
return this.props.boardsServiceClient.searchBoards(options);
|
||||
}
|
||||
|
||||
protected get availablePorts(): Promise<Port[]> {
|
||||
@ -178,13 +178,24 @@ export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConf
|
||||
|
||||
protected renderBoards(): React.ReactNode {
|
||||
const { selectedBoard, searchResults } = this.state;
|
||||
// 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 key = toKey(board);
|
||||
if (!distinctBoards.has(key)) {
|
||||
distinctBoards.set(key, board);
|
||||
}
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className='search'>
|
||||
<input type='search' className='theia-input' placeholder='SEARCH BOARD' onChange={this.updateBoards} ref={this.focusNodeSet} />
|
||||
<i className='fa fa-search'></i>
|
||||
</div>
|
||||
<div className='boards list'>
|
||||
{Board.decorateBoards(selectedBoard, searchResults).map(board => <Item<Board & { packageName: string }>
|
||||
{Array.from(distinctBoards.values()).map(board => <Item<Board & { packageName: string }>
|
||||
key={`${board.name}-${board.packageName}`}
|
||||
item={board}
|
||||
label={board.name}
|
||||
|
@ -0,0 +1,113 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry, MenuNode } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { Board, ConfigOption, Programmer } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { BoardsDataStore } from './boards-data-store';
|
||||
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class BoardsDataMenuUpdater implements FrontendApplicationContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(BoardsDataStore)
|
||||
protected readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
|
||||
protected readonly toDisposeOnBoardChange = new DisposableCollection();
|
||||
|
||||
async onStart(): Promise<void> {
|
||||
await this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard);
|
||||
this.boardsDataStore.onChanged(async () => await this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(async ({ selectedBoard }) => await this.updateMenuActions(selectedBoard));
|
||||
}
|
||||
|
||||
protected async updateMenuActions(selectedBoard: Board | undefined): Promise<void> {
|
||||
if (selectedBoard) {
|
||||
this.toDisposeOnBoardChange.dispose();
|
||||
this.mainMenuManager.update();
|
||||
const { fqbn } = selectedBoard;
|
||||
if (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 }>()
|
||||
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
|
||||
};
|
||||
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(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
||||
...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(() => this.unregisterSubmenu(programmersMenuPath)));
|
||||
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)
|
||||
};
|
||||
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))
|
||||
]);
|
||||
}
|
||||
}
|
||||
this.mainMenuManager.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected unregisterSubmenu(menuPath: string[]): void {
|
||||
if (menuPath.length < 2) {
|
||||
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);
|
||||
// This is unsafe. Calling `getMenu` with a non-existing menu-path will result in a new menu creation.
|
||||
// https://github.com/eclipse-theia/theia/issues/7300
|
||||
const parent = this.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)}.`);
|
||||
}
|
||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
import { injectable, inject, named } from 'inversify';
|
||||
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 { deepClone, notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||
import { BoardsService, ConfigOption, Installable, BoardDetails } from '../../common/protocol';
|
||||
import { notEmpty } from '../../common/utils';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { BoardsService, ConfigOption, Installable, BoardDetails, Programmer } from '../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class BoardsConfigStore implements FrontendApplicationContribution {
|
||||
export class BoardsDataStore implements FrontendApplicationContribution {
|
||||
|
||||
@inject(ILogger)
|
||||
@named('store')
|
||||
@ -60,39 +61,62 @@ export class BoardsConfigStore implements FrontendApplicationContribution {
|
||||
fqbn: string,
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<string> {
|
||||
|
||||
const configOptions = await this.getConfig(fqbn, boardsPackageVersion);
|
||||
const { configOptions } = await this.getData(fqbn, boardsPackageVersion);
|
||||
return ConfigOption.decorate(fqbn, configOptions);
|
||||
}
|
||||
|
||||
async getConfig(
|
||||
async getData(
|
||||
fqbn: string,
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<ConfigOption[]> {
|
||||
boardsPackageVersion: MaybePromise<Installable.Version | undefined> = this.getBoardsPackageVersion(fqbn)): Promise<BoardsDataStore.Data> {
|
||||
|
||||
const version = await boardsPackageVersion;
|
||||
if (!version) {
|
||||
return [];
|
||||
return BoardsDataStore.Data.EMPTY;
|
||||
}
|
||||
const key = this.getStorageKey(fqbn, version);
|
||||
let configOptions = await this.storageService.getData<ConfigOption[] | undefined>(key, undefined);
|
||||
if (configOptions) {
|
||||
return configOptions;
|
||||
let data = await this.storageService.getData<BoardsDataStore.Data | undefined>(key, undefined);
|
||||
if (data) {
|
||||
if (data.programmers !== undefined) { // to be backward compatible. We did not save the `programmers` into the `localStorage`.
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
const details = await this.getBoardDetailsSafe(fqbn);
|
||||
if (!details) {
|
||||
return [];
|
||||
const boardDetails = await this.getBoardDetailsSafe(fqbn);
|
||||
if (!boardDetails) {
|
||||
return BoardsDataStore.Data.EMPTY;
|
||||
}
|
||||
|
||||
configOptions = details.configOptions;
|
||||
await this.storageService.setData(key, configOptions);
|
||||
return configOptions;
|
||||
data = { configOptions: boardDetails.configOptions, programmers: boardDetails.programmers };
|
||||
await this.storageService.setData(key, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async setSelected(
|
||||
async selectProgrammer(
|
||||
{ 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))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const version = await boardsPackageVersion;
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
const configOptions = deepClone(await this.getConfig(fqbn, boardsPackageVersion));
|
||||
const data = deepClone(await this.getData(fqbn, boardsPackageVersion));
|
||||
const { configOptions } = data;
|
||||
const configOption = configOptions.find(c => c.option === option);
|
||||
if (!configOption) {
|
||||
return false;
|
||||
@ -113,16 +137,17 @@ export class BoardsConfigStore implements FrontendApplicationContribution {
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
await this.setConfig({ fqbn, configOptions, version });
|
||||
|
||||
await this.setData({ fqbn, data, version });
|
||||
this.fireChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async setConfig(
|
||||
{ fqbn, configOptions, version }: { fqbn: string, configOptions: ConfigOption[], 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, configOptions);
|
||||
return this.storageService.setData(key, data);
|
||||
}
|
||||
|
||||
protected getStorageKey(fqbn: string, version: Installable.Version): string {
|
||||
@ -159,3 +184,17 @@ export class BoardsConfigStore implements FrontendApplicationContribution {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace BoardsDataStore {
|
||||
export interface Data {
|
||||
readonly configOptions: ConfigOption[];
|
||||
readonly programmers: Programmer[];
|
||||
readonly selectedProgrammer?: Programmer;
|
||||
}
|
||||
export namespace Data {
|
||||
export const EMPTY: Data = {
|
||||
configOptions: [],
|
||||
programmers: []
|
||||
};
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry, MenuNode } from '@theia/core/lib/common/menu';
|
||||
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||
import { Board, ConfigOption } from '../../common/protocol';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
||||
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
||||
import { BoardsConfigStore } from './boards-config-store';
|
||||
import { MainMenuManager } from '../menu/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class BoardsDetailsMenuUpdater implements FrontendApplicationContribution {
|
||||
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(MainMenuManager)
|
||||
protected readonly mainMenuManager: MainMenuManager;
|
||||
|
||||
@inject(BoardsConfigStore)
|
||||
protected readonly boardsConfigStore: BoardsConfigStore;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
|
||||
protected readonly toDisposeOnBoardChange = new DisposableCollection();
|
||||
|
||||
onStart(): void {
|
||||
this.boardsConfigStore.onChanged(() => this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard));
|
||||
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) => this.updateMenuActions(selectedBoard));
|
||||
this.updateMenuActions(this.boardsServiceClient.boardsConfig.selectedBoard);
|
||||
}
|
||||
|
||||
protected async updateMenuActions(selectedBoard: Board | undefined): Promise<void> {
|
||||
if (selectedBoard) {
|
||||
this.toDisposeOnBoardChange.dispose();
|
||||
this.mainMenuManager.update();
|
||||
const { fqbn } = selectedBoard;
|
||||
if (fqbn) {
|
||||
const configOptions = await this.boardsConfigStore.getConfig(fqbn);
|
||||
const boardsConfigMenuPath = [...ArduinoMenus.TOOLS, 'z_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.boardsConfigStore.setSelected({ fqbn, option, selectedValue }),
|
||||
isToggled: () => value.selected
|
||||
};
|
||||
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(() => this.unregisterSubmenu(menuPath)), // We cannot dispose submenu entries: https://github.com/eclipse-theia/theia/issues/7299
|
||||
...Array.from(commands.keys()).map((commandId, index) => {
|
||||
const { label } = commands.get(commandId)!;
|
||||
this.menuRegistry.registerMenuAction(menuPath, { commandId, order: String(index), label });
|
||||
return Disposable.create(() => this.menuRegistry.unregisterMenuAction(commandId));
|
||||
})
|
||||
]);
|
||||
}
|
||||
this.mainMenuManager.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected unregisterSubmenu(menuPath: string[]): void {
|
||||
if (menuPath.length < 2) {
|
||||
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);
|
||||
// This is unsafe. Calling `getMenu` with a non-existing menu-path will result in a new menu creation.
|
||||
// https://github.com/eclipse-theia/theia/issues/7300
|
||||
const parent = this.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)}.`);
|
||||
}
|
||||
(parent.children as Array<MenuNode>).splice(index, 1);
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { BoardsPackage, BoardsService } from '../../common/protocol/boards-service';
|
||||
import { ListWidget } from '../components/component-list/list-widget';
|
||||
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidget extends ListWidget<BoardsPackage> {
|
||||
|
@ -5,9 +5,15 @@ import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { StorageService } from '@theia/core/lib/browser/storage-service';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { RecursiveRequired } from '../../common/types';
|
||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent } from '../../common/protocol';
|
||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, Board, Port, BoardUninstalledEvent, BoardsService } from '../../common/protocol';
|
||||
import { BoardsConfig } from './boards-config';
|
||||
import { naturalCompare } from '../../common/utils';
|
||||
import { compareAnything } from '../theia/monaco/comparers';
|
||||
|
||||
interface BoardMatch {
|
||||
readonly board: Board & Readonly<{ packageName: string }>;
|
||||
readonly matches: monaco.filters.IMatch[] | undefined;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
|
||||
@ -40,6 +46,7 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
protected _attachedBoards: Board[] = []; // This does not contain the `Unknown` boards. They're visible from the available ports only.
|
||||
protected _availablePorts: Port[] = [];
|
||||
protected _availableBoards: AvailableBoard[] = [];
|
||||
protected boardsService: BoardsService;
|
||||
|
||||
/**
|
||||
* Event when the state of the attached/detached boards has changed. For instance, the user have detached a physical board.
|
||||
@ -65,7 +72,12 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
* When the FE connects to the BE, the BE stets the known boards and ports.\
|
||||
* This is a DI workaround for not being able to inject the service into the client.
|
||||
*/
|
||||
init({ attachedBoards, availablePorts }: { attachedBoards: Board[], availablePorts: Port[] }): void {
|
||||
async init(boardsService: BoardsService): Promise<void> {
|
||||
this.boardsService = boardsService;
|
||||
const [attachedBoards, availablePorts] = await Promise.all([
|
||||
this.boardsService.getAttachedBoards(),
|
||||
this.boardsService.getAvailablePorts()
|
||||
]);
|
||||
this._attachedBoards = attachedBoards;
|
||||
this._availablePorts = availablePorts;
|
||||
this.reconcileAvailableBoards().then(() => this.tryReconnect());
|
||||
@ -157,6 +169,40 @@ export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApp
|
||||
}
|
||||
}
|
||||
|
||||
async searchBoards({ query, cores }: { query?: string, cores?: string[] }): Promise<Array<Board & { packageName: string }>> {
|
||||
const boards = await this.boardsService.allBoards({});
|
||||
const coresFilter = !!cores && cores.length
|
||||
? ((toFilter: { packageName: string }) => cores.some(core => core === toFilter.packageName))
|
||||
: () => true;
|
||||
if (!query) {
|
||||
return boards.filter(coresFilter).sort(Board.compare);
|
||||
}
|
||||
const toMatch = ((toFilter: Board & { packageName: string }) => (({ board: toFilter, matches: monaco.filters.matchesFuzzy(query, toFilter.name, true) })));
|
||||
const compareEntries = (left: BoardMatch, right: BoardMatch, lookFor: string) => {
|
||||
const leftMatches = left.matches || [];
|
||||
const rightMatches = right.matches || [];
|
||||
if (leftMatches.length && !rightMatches.length) {
|
||||
return -1;
|
||||
}
|
||||
if (!leftMatches.length && rightMatches.length) {
|
||||
return 1;
|
||||
}
|
||||
if (leftMatches.length === 0 && rightMatches.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const leftLabel = left.board.name.replace(/\r?\n/g, ' ');
|
||||
const rightLabel = right.board.name.replace(/\r?\n/g, ' ');
|
||||
return compareAnything(leftLabel, rightLabel, lookFor);
|
||||
}
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
return boards
|
||||
.filter(coresFilter)
|
||||
.map(toMatch)
|
||||
.filter(({ matches }) => !!matches)
|
||||
.sort((left, right) => compareEntries(left, right, normalizedQuery))
|
||||
.map(({ board }) => board);
|
||||
}
|
||||
|
||||
get boardsConfig(): BoardsConfig.Config {
|
||||
return this._boardsConfig;
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
import { BoardsListWidget } from './boards-list-widget';
|
||||
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
||||
import { BoardsPackage } from '../../common/protocol/boards-service';
|
||||
import { ListWidgetFrontendContribution } from '../components/component-list/list-widget-frontend-contribution';
|
||||
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
|
||||
@ -19,7 +19,7 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
|
||||
rank: 600
|
||||
},
|
||||
toggleCommandId: BoardsListWidgetFrontendContribution.OPEN_MANAGER,
|
||||
toggleKeybinding: 'ctrlcmd+shift+b'
|
||||
toggleKeybinding: 'CtrlCmd+Shift+B'
|
||||
});
|
||||
}
|
||||
|
||||
@ -29,9 +29,10 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
if (this.toggleCommand) {
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS, {
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: this.toggleCommand.id,
|
||||
label: 'Boards Manager...'
|
||||
label: 'Boards Manager...',
|
||||
order: '4'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
import { naturalCompare } from '../../../common/utils';
|
||||
import { BoardsService, Port, Board, ConfigOption, ConfigValue } from '../../../common/protocol';
|
||||
import { CoreServiceClientImpl } from '../../core-service-client-impl';
|
||||
import { BoardsConfigStore } from '../boards-config-store';
|
||||
import { BoardsDataStore } from '../boards-data-store';
|
||||
import { BoardsServiceClientImpl, AvailableBoard } from '../boards-service-client-impl';
|
||||
|
||||
@injectable()
|
||||
@ -41,8 +41,8 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||
|
||||
@inject(BoardsConfigStore)
|
||||
protected readonly configStore: BoardsConfigStore;
|
||||
@inject(BoardsDataStore)
|
||||
protected readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
@inject(CoreServiceClientImpl)
|
||||
protected coreServiceClient: CoreServiceClientImpl;
|
||||
@ -52,7 +52,7 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
||||
// Attached boards plus the user's config.
|
||||
protected availableBoards: AvailableBoard[] = [];
|
||||
// Only for the `selected` one from the `availableBoards`. Note: the `port` of the `selected` is optional.
|
||||
protected boardConfigs: ConfigOption[] = [];
|
||||
protected data: BoardsDataStore.Data = BoardsDataStore.Data.EMPTY;
|
||||
protected allBoards: Board.Detailed[] = []
|
||||
protected selectedBoard?: (AvailableBoard & { port: Port });
|
||||
|
||||
@ -86,7 +86,7 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
||||
placeholder += 'No board selected.';
|
||||
}
|
||||
placeholder += 'Type to filter boards';
|
||||
if (this.boardConfigs.length) {
|
||||
if (this.data.configOptions.length) {
|
||||
placeholder += ' or use the ↓↑ keys to adjust the board settings...';
|
||||
} else {
|
||||
placeholder += '...';
|
||||
@ -129,7 +129,7 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
||||
|
||||
// Show the config only if the `input` is empty.
|
||||
if (!lookFor.trim().length) {
|
||||
toAccept.push(...this.boardConfigs.map((config, i) => {
|
||||
toAccept.push(...this.data.configOptions.map((config, i) => {
|
||||
let group: QuickOpenGroupItemOptions | undefined = undefined;
|
||||
if (i === 0) {
|
||||
group = { groupLabel: 'Board Settings', showBorder: true };
|
||||
@ -157,14 +157,14 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
||||
protected async update(availableBoards: AvailableBoard[]): Promise<void> {
|
||||
// `selectedBoard` is not an attached board, we need to show the board settings for it (TODO: clarify!)
|
||||
const selectedBoard = availableBoards.filter(AvailableBoard.hasPort).find(({ selected }) => selected);
|
||||
const [configs, boards] = await Promise.all([
|
||||
selectedBoard && selectedBoard.fqbn ? this.configStore.getConfig(selectedBoard.fqbn) : Promise.resolve([]),
|
||||
this.boardsService.searchBoards({})
|
||||
const [data, boards] = await Promise.all([
|
||||
selectedBoard && selectedBoard.fqbn ? this.boardsDataStore.getData(selectedBoard.fqbn) : Promise.resolve(BoardsDataStore.Data.EMPTY),
|
||||
this.boardsService.allBoards({})
|
||||
]);
|
||||
this.allBoards = Board.decorateBoards(selectedBoard, boards)
|
||||
.filter(board => !availableBoards.some(availableBoard => Board.sameAs(availableBoard, board)));
|
||||
this.availableBoards = availableBoards;
|
||||
this.boardConfigs = configs;
|
||||
this.data = data;
|
||||
this.selectedBoard = selectedBoard;
|
||||
|
||||
if (this.isOpen) {
|
||||
@ -280,7 +280,7 @@ export class BoardsQuickOpenService implements QuickOpenContribution, QuickOpenM
|
||||
return;
|
||||
}
|
||||
const { fqbn } = this.selectedBoard;
|
||||
this.configStore.setSelected({
|
||||
this.boardsDataStore.selectConfigOption({
|
||||
fqbn,
|
||||
option: config.option,
|
||||
selectedValue: value.value
|
||||
|
@ -4,7 +4,7 @@ import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { ConfigServiceClient, Config } from '../common/protocol';
|
||||
import { ArduinoCommands } from './arduino-commands';
|
||||
import { Settings } from './contributions/settings';
|
||||
|
||||
@injectable()
|
||||
export class ConfigServiceClientImpl implements ConfigServiceClient {
|
||||
@ -32,7 +32,7 @@ export class ConfigServiceClientImpl implements ConfigServiceClient {
|
||||
this.invalidConfigPopup = this.messageService.error(`Your CLI configuration is invalid. Do you want to correct it now?`, 'No', 'Yes')
|
||||
.then(answer => {
|
||||
if (answer === 'Yes') {
|
||||
this.commandService.executeCommand(ArduinoCommands.OPEN_CLI_CONFIG.id)
|
||||
this.commandService.executeCommand(Settings.Commands.OPEN_CLI_CONFIG.id)
|
||||
}
|
||||
this.invalidConfigPopup = undefined;
|
||||
})
|
||||
|
@ -0,0 +1,89 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, URI } from './contribution';
|
||||
import { SaveAsSketch } from './save-as-sketch';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
|
||||
@injectable()
|
||||
export class CloseSketch extends SketchContribution {
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(CloseSketch.Commands.CLOSE_SKETCH, {
|
||||
execute: async () => {
|
||||
const sketch = await this.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
const isTemp = await this.sketchService.isTemp(sketch);
|
||||
const uri = await this.currentSketchFile();
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
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."
|
||||
});
|
||||
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.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: CloseSketch.Commands.CLOSE_SKETCH.id,
|
||||
label: 'Close',
|
||||
order: '5'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: CloseSketch.Commands.CLOSE_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+W' // TODO: Windows binding?
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the file was ever touched/modified. We get this based on the `version` of the monaco model.
|
||||
*/
|
||||
protected async wasTouched(uri: string): Promise<boolean> {
|
||||
const editorWidget = await this.editorManager.getByUri(new URI(uri));
|
||||
if (editorWidget) {
|
||||
const { editor } = editorWidget;
|
||||
if (editor instanceof MonacoEditor) {
|
||||
const versionId = editor.getControl().getModel()?.getVersionId();
|
||||
if (Number.isInteger(versionId) && versionId! > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace CloseSketch {
|
||||
export namespace Commands {
|
||||
export const CLOSE_SKETCH: Command = {
|
||||
id: 'arduino-close-sketch'
|
||||
};
|
||||
}
|
||||
}
|
107
arduino-ide-extension/src/browser/contributions/contribution.ts
Normal file
107
arduino-ide-extension/src/browser/contributions/contribution.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { inject, injectable, interfaces } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
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 { 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 { Command, CommandRegistry, CommandContribution, CommandService } from '@theia/core/lib/common/command';
|
||||
import { SketchesService, ConfigService, FileSystemExt, Sketch } from '../../common/protocol';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
export { Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry, URI, Sketch, open };
|
||||
|
||||
@injectable()
|
||||
export abstract class Contribution implements CommandContribution, MenuContribution, KeybindingContribution, TabBarToolbarContribution {
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class SketchContribution extends Contribution {
|
||||
|
||||
@inject(FileSystem)
|
||||
protected readonly fileSystem: FileSystem;
|
||||
|
||||
@inject(FileSystemExt)
|
||||
protected readonly fileSystemExt: FileSystemExt;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchService: SketchesService;
|
||||
|
||||
@inject(OpenerService)
|
||||
protected readonly openerService: OpenerService;
|
||||
|
||||
protected async currentSketch(): Promise<Sketch | undefined> {
|
||||
const sketches = (await Promise.all(this.workspaceService.tryGetRoots().map(({ uri }) => this.sketchService.getSketchFolder(uri)))).filter(notEmpty);
|
||||
if (!sketches.length) {
|
||||
return;
|
||||
}
|
||||
if (sketches.length > 1) {
|
||||
console.log(`Multiple sketch folders were found in the workspace. Falling back to the first one. Sketch folders: ${JSON.stringify(sketches)}`);
|
||||
}
|
||||
return sketches[0];
|
||||
}
|
||||
|
||||
protected async currentSketchFile(): Promise<string | undefined> {
|
||||
const sketch = await this.currentSketch();
|
||||
if (sketch) {
|
||||
const uri = new URI(sketch.uri).resolve(`${sketch.name}.ino`).toString();
|
||||
const exists = await this.fileSystem.exists(uri);
|
||||
if (!exists) {
|
||||
this.messageService.warn(`Could not find sketch file: ${uri}`);
|
||||
return undefined;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Contribution {
|
||||
export function configure<T>(bind: interfaces.Bind, serviceIdentifier: interfaces.ServiceIdentifier<T>): void {
|
||||
bind(serviceIdentifier).toSelf().inSingletonScope();
|
||||
bind(CommandContribution).toService(serviceIdentifier);
|
||||
bind(MenuContribution).toService(serviceIdentifier);
|
||||
bind(KeybindingContribution).toService(serviceIdentifier);
|
||||
bind(TabBarToolbarContribution).toService(serviceIdentifier);
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
||||
import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service';
|
||||
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
||||
import { EDITOR_FONT_DEFAULTS } from '@theia/editor/lib/browser/editor-preferences';
|
||||
import { Contribution, Command, MenuModelRegistry, KeybindingRegistry, CommandRegistry } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
// 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;
|
||||
|
||||
@inject(ClipboardService)
|
||||
protected readonly clipboardService: ClipboardService;
|
||||
|
||||
@inject(PreferenceService)
|
||||
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, {
|
||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) + 1)
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.DECREASE_FONT_SIZE, {
|
||||
execute: () => this.preferences.set('editor.fontSize', this.preferences.get('editor.fontSize', EDITOR_FONT_DEFAULTS.fontSize) - 1)
|
||||
});
|
||||
/* 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]`)
|
||||
}
|
||||
}
|
||||
});
|
||||
registry.registerCommand(EditContributions.Commands.COPY_FOR_GITHUB, {
|
||||
execute: async () => {
|
||||
const value = await this.currentValue();
|
||||
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'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: CommonCommands.COPY.id,
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.COPY_FOR_FORUM.id,
|
||||
label: 'Copy for Forum',
|
||||
order: '2'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.COPY_FOR_GITHUB.id,
|
||||
label: 'Copy for GitHub',
|
||||
order: '3'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: CommonCommands.PASTE.id,
|
||||
order: '4'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: CommonCommands.SELECT_ALL.id,
|
||||
order: '5'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__TEXT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.GO_TO_LINE.id,
|
||||
label: 'Go to Line...',
|
||||
order: '6'
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.TOGGLE_COMMENT.id,
|
||||
label: 'Comment/Uncomment',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.INDENT_LINES.id,
|
||||
label: 'Increase Indent',
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__CODE_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.OUTDENT_LINES.id,
|
||||
label: 'Decrease Indent',
|
||||
order: '2'
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.INCREASE_FONT_SIZE.id,
|
||||
label: 'Increase Font Size',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FONT_CONTROL_GROUP, {
|
||||
commandId: EditContributions.Commands.DECREASE_FONT_SIZE.id,
|
||||
label: 'Decrease Font Size',
|
||||
order: '1'
|
||||
});
|
||||
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
|
||||
commandId: EditContributions.Commands.FIND.id,
|
||||
label: 'Find',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
|
||||
commandId: EditContributions.Commands.FIND_NEXT.id,
|
||||
label: 'Find Next',
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.EDIT__FIND_GROUP, {
|
||||
commandId: EditContributions.Commands.FIND_PREVIOUS.id,
|
||||
label: 'Find Previous',
|
||||
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'
|
||||
});
|
||||
|
||||
// `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'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.COPY_FOR_FORUM.id,
|
||||
keybinding: 'CtrlCmd+Shift+C'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.COPY_FOR_GITHUB.id,
|
||||
keybinding: 'CtrlCmd+Alt+C'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.GO_TO_LINE.id,
|
||||
keybinding: 'CtrlCmd+L'
|
||||
});
|
||||
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.TOGGLE_COMMENT.id,
|
||||
keybinding: 'CtrlCmd+/'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.INDENT_LINES.id,
|
||||
keybinding: 'Tab'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.OUTDENT_LINES.id,
|
||||
keybinding: 'Shift+Tab'
|
||||
});
|
||||
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.INCREASE_FONT_SIZE.id,
|
||||
keybinding: 'CtrlCmd+=' // TODO: compare with the Java IDE. It uses `⌘+`. There is no `+` on EN_US.
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.DECREASE_FONT_SIZE.id,
|
||||
keybinding: 'CtrlCmd+-'
|
||||
});
|
||||
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.FIND.id,
|
||||
keybinding: 'CtrlCmd+F'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.FIND_NEXT.id,
|
||||
keybinding: 'CtrlCmd+G'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.FIND_PREVIOUS.id,
|
||||
keybinding: 'CtrlCmd+Shift+G'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.USE_FOR_FIND.id,
|
||||
keybinding: 'CtrlCmd+E'
|
||||
});
|
||||
|
||||
// `Tools`
|
||||
registry.registerKeybinding({
|
||||
command: EditContributions.Commands.AUTO_FORMAT.id,
|
||||
keybinding: 'CtrlCmd+T'
|
||||
});
|
||||
}
|
||||
|
||||
protected async current(): Promise<monaco.editor.ICodeEditor | undefined> {
|
||||
return this.codeEditorService.getFocusedCodeEditor() || this.codeEditorService.getActiveCodeEditor();
|
||||
}
|
||||
|
||||
protected async currentValue(): Promise<string | undefined> {
|
||||
const currentEditor = await this.current()
|
||||
return currentEditor?.getValue();
|
||||
}
|
||||
|
||||
protected async run(commandId: string): Promise<any> {
|
||||
const editor = await this.current();
|
||||
if (editor) {
|
||||
const action = editor.getAction(commandId);
|
||||
if (action) {
|
||||
return action.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace EditContributions {
|
||||
export namespace Commands {
|
||||
export const COPY_FOR_FORUM: Command = {
|
||||
id: 'arduino-copy-for-forum'
|
||||
};
|
||||
export const COPY_FOR_GITHUB: Command = {
|
||||
id: 'arduino-copy-for-github'
|
||||
};
|
||||
export const GO_TO_LINE: Command = {
|
||||
id: 'arduino-go-to-line'
|
||||
};
|
||||
export const TOGGLE_COMMENT: Command = {
|
||||
id: 'arduino-toggle-comment'
|
||||
};
|
||||
export const INDENT_LINES: Command = {
|
||||
id: 'arduino-indent-lines'
|
||||
};
|
||||
export const OUTDENT_LINES: Command = {
|
||||
id: 'arduino-outdent-lines'
|
||||
};
|
||||
export const FIND: Command = {
|
||||
id: 'arduino-find'
|
||||
};
|
||||
export const FIND_NEXT: Command = {
|
||||
id: 'arduino-find-next'
|
||||
};
|
||||
export const FIND_PREVIOUS: Command = {
|
||||
id: 'arduino-find-previous'
|
||||
};
|
||||
export const USE_FOR_FIND: Command = {
|
||||
id: 'arduino-for-find'
|
||||
};
|
||||
export const INCREASE_FONT_SIZE: Command = {
|
||||
id: 'arduino-increase-font-size'
|
||||
};
|
||||
export const DECREASE_FONT_SIZE: Command = {
|
||||
id: 'arduino-decrease-font-size'
|
||||
};
|
||||
export const AUTO_FORMAT: Command = {
|
||||
id: 'arduino-auto-format' // `Auto Format` should belong to `Tool`.
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
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';
|
||||
|
||||
@injectable()
|
||||
export class NewSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(NewSketch.Commands.NEW_SKETCH, {
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: NewSketch.Commands.NEW_SKETCH.id,
|
||||
label: 'New',
|
||||
order: '0'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: NewSketch.Commands.NEW_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+N'
|
||||
});
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
|
||||
command: NewSketch.Commands.NEW_SKETCH__TOOLBAR.id,
|
||||
tooltip: 'New',
|
||||
priority: 3
|
||||
});
|
||||
}
|
||||
|
||||
async newSketch(): Promise<void> {
|
||||
try {
|
||||
const sketch = await this.sketchService.createNewSketch();
|
||||
this.workspaceService.open(new URI(sketch.uri));
|
||||
} catch (e) {
|
||||
await this.messageService.error(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace NewSketch {
|
||||
export namespace Commands {
|
||||
export const NEW_SKETCH: Command = {
|
||||
id: 'arduino-new-sketch'
|
||||
};
|
||||
export const NEW_SKETCH__TOOLBAR: Command = {
|
||||
id: 'arduino-new-sketch--toolbar'
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
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()
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__UTILS_GROUP, {
|
||||
commandId: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
|
||||
label: 'Show Sketch Folder',
|
||||
order: '0'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: OpenSketchExternal.Commands.OPEN_EXTERNAL.id,
|
||||
keybinding: 'CtrlCmd+Alt+K'
|
||||
});
|
||||
}
|
||||
|
||||
protected async openExternal(): Promise<void> {
|
||||
const uri = await this.currentSketchFile();
|
||||
if (uri) {
|
||||
const exists = this.fileSystem.exists(uri);
|
||||
if (exists) {
|
||||
const fsPath = await this.fileSystem.getFsPath(uri);
|
||||
if (fsPath) {
|
||||
remote.shell.showItemInFolder(fsPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace OpenSketchExternal {
|
||||
export namespace Commands {
|
||||
export const OPEN_EXTERNAL: Command = {
|
||||
id: 'arduino-open-sketch-external'
|
||||
};
|
||||
}
|
||||
}
|
136
arduino-ide-extension/src/browser/contributions/open-sketch.ts
Normal file
136
arduino-ide-extension/src/browser/contributions/open-sketch.ts
Normal file
@ -0,0 +1,136 @@
|
||||
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 { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { SketchContribution, Sketch, URI, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class OpenSketch extends SketchContribution {
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(ContextMenuRenderer)
|
||||
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
||||
|
||||
protected readonly toDisposeBeforeCreateNewContextMenu = new DisposableCollection();
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(OpenSketch.Commands.OPEN_SKETCH, {
|
||||
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',
|
||||
execute: async (_: Widget, target: EventTarget) => {
|
||||
const sketches = await this.sketchService.getSketches();
|
||||
if (!sketches.length) {
|
||||
this.openSketch();
|
||||
} else {
|
||||
this.toDisposeBeforeCreateNewContextMenu.dispose();
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const { parentElement } = target;
|
||||
if (!parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.OPEN_SKETCH__CONTEXT__OPEN_GROUP, {
|
||||
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
label: 'Open...'
|
||||
});
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(OpenSketch.Commands.OPEN_SKETCH)));
|
||||
for (const sketch of sketches) {
|
||||
const command = { id: `arduino-open-sketch--${sketch.uri}` };
|
||||
const handler = { execute: () => this.openSketch(sketch) };
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(registry.registerCommand(command, handler));
|
||||
this.menuRegistry.registerMenuAction(ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP, {
|
||||
commandId: command.id,
|
||||
label: sketch.name
|
||||
});
|
||||
this.toDisposeBeforeCreateNewContextMenu.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command)));
|
||||
}
|
||||
const options = {
|
||||
menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT,
|
||||
anchor: {
|
||||
x: parentElement.getBoundingClientRect().left,
|
||||
y: parentElement.getBoundingClientRect().top + parentElement.offsetHeight
|
||||
}
|
||||
}
|
||||
this.contextMenuRenderer.render(options);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
label: 'Open...',
|
||||
order: '1'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: OpenSketch.Commands.OPEN_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+O'
|
||||
});
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
|
||||
command: OpenSketch.Commands.OPEN_SKETCH__TOOLBAR.id,
|
||||
tooltip: 'Open',
|
||||
priority: 4
|
||||
});
|
||||
}
|
||||
|
||||
async openSketch(toOpen: MaybePromise<Sketch | undefined> = this.selectSketch()): Promise<void> {
|
||||
const sketch = await toOpen;
|
||||
if (sketch) {
|
||||
this.workspaceService.open(new URI(sketch.uri));
|
||||
}
|
||||
}
|
||||
|
||||
protected async selectSketch(): Promise<Sketch | undefined> {
|
||||
const config = await this.configService.getConfiguration();
|
||||
const defaultPath = await this.fileSystem.getFsPath(config.sketchDirUri);
|
||||
const { filePaths } = await remote.dialog.showOpenDialog({
|
||||
defaultPath,
|
||||
properties: ['createDirectory', 'openFile'],
|
||||
filters: [
|
||||
{
|
||||
name: 'Sketch',
|
||||
extensions: ['ino']
|
||||
}
|
||||
]
|
||||
});
|
||||
if (!filePaths.length) {
|
||||
return undefined;
|
||||
}
|
||||
if (filePaths.length > 1) {
|
||||
this.logger.warn(`Multiple sketches were selected: ${filePaths}. Using the first one.`);
|
||||
}
|
||||
// TODO: validate sketch file name against the sketch folder. Move the file if required.
|
||||
const sketchFilePath = filePaths[0];
|
||||
const sketchFileUri = await this.fileSystemExt.getUri(sketchFilePath);
|
||||
return this.sketchService.getSketchFolder(sketchFileUri);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace OpenSketch {
|
||||
export namespace Commands {
|
||||
export const OPEN_SKETCH: Command = {
|
||||
id: 'arduino-open-sketch'
|
||||
};
|
||||
export const OPEN_SKETCH__TOOLBAR: Command = {
|
||||
id: 'arduino-open-sketch--toolbar'
|
||||
};
|
||||
}
|
||||
}
|
45
arduino-ide-extension/src/browser/contributions/quit-app.ts
Normal file
45
arduino-ide-extension/src/browser/contributions/quit-app.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { remote } from 'electron';
|
||||
import { isOSX, isWindows } from '@theia/core/lib/common/os';
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
if (!isOSX) {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__QUIT_GROUP, {
|
||||
commandId: QuitApp.Commands.QUIT_APP.id,
|
||||
label: 'Quit',
|
||||
order: '0'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
if (!isOSX) {
|
||||
registry.registerKeybinding({
|
||||
command: QuitApp.Commands.QUIT_APP.id,
|
||||
keybinding: isWindows ? 'Alt+F4' : 'Ctrl+Q'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace QuitApp {
|
||||
export namespace Commands {
|
||||
export const QUIT_APP: Command = {
|
||||
id: 'arduino-quit-app'
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
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';
|
||||
|
||||
@injectable()
|
||||
export class SaveAsSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, {
|
||||
execute: args => this.saveAs(args)
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
label: 'Save As...',
|
||||
order: '7'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+Shift+S'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves `true` if the sketch was successfully saved as something.
|
||||
*/
|
||||
async saveAs({ execOnlyIfTemp, openAfterMove }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT): Promise<boolean> {
|
||||
const sketch = await this.currentSketch();
|
||||
if (!sketch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isTemp = await this.sketchService.isTemp(sketch);
|
||||
if (!isTemp && !!execOnlyIfTemp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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.fileSystem.exists(sketchDirUri.resolve(sketch.name).toString());
|
||||
const defaultUri = exists
|
||||
? sketchDirUri.resolve(sketchDirUri.resolve(`${sketch.name}_copy_${dateFormat(new Date(), 'yyyymmddHHMMss')}`).toString())
|
||||
: sketchDirUri.resolve(sketch.name);
|
||||
const defaultPath = await this.fileSystem.getFsPath(defaultUri.toString())!;
|
||||
const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath });
|
||||
if (!filePath || canceled) {
|
||||
return false;
|
||||
}
|
||||
const destinationUri = await this.fileSystemExt.getUri(filePath);
|
||||
if (!destinationUri) {
|
||||
return false;
|
||||
}
|
||||
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
||||
if (workspaceUri && openAfterMove) {
|
||||
this.workspaceService.open(new URI(workspaceUri));
|
||||
}
|
||||
return !!workspaceUri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace SaveAsSketch {
|
||||
export namespace Commands {
|
||||
export const SAVE_AS_SKETCH: Command = {
|
||||
id: 'arduino-save-as-sketch'
|
||||
};
|
||||
}
|
||||
export interface Options {
|
||||
readonly execOnlyIfTemp?: boolean;
|
||||
readonly openAfterMove?: boolean;
|
||||
}
|
||||
export namespace Options {
|
||||
export const DEFAULT: Options = {
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
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';
|
||||
|
||||
@injectable()
|
||||
export class SaveSketch extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(SaveSketch.Commands.SAVE_SKETCH, {
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SKETCH_GROUP, {
|
||||
commandId: SaveSketch.Commands.SAVE_SKETCH.id,
|
||||
label: 'Save',
|
||||
order: '6'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: SaveSketch.Commands.SAVE_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+S'
|
||||
});
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
|
||||
command: SaveSketch.Commands.SAVE_SKETCH__TOOLBAR.id,
|
||||
tooltip: 'Save',
|
||||
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'
|
||||
};
|
||||
export const SAVE_SKETCH__TOOLBAR: Command = {
|
||||
id: 'arduino-save-sketch--toolbar'
|
||||
};
|
||||
}
|
||||
}
|
38
arduino-ide-extension/src/browser/contributions/settings.ts
Normal file
38
arduino-ide-extension/src/browser/contributions/settings.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { URI, Command, MenuModelRegistry, CommandRegistry, SketchContribution, open } from './contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class Settings extends SketchContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(Settings.Commands.OPEN_CLI_CONFIG, {
|
||||
execute: () => this.configService.getCliConfigFileUri().then(uri => open(this.openerService, new URI(uri)))
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
||||
commandId: CommonCommands.OPEN_PREFERENCES.id,
|
||||
label: 'Preferences...',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.FILE__SETTINGS_GROUP, {
|
||||
commandId: Settings.Commands.OPEN_CLI_CONFIG.id,
|
||||
label: 'Open CLI Configuration',
|
||||
order: '1',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Settings {
|
||||
export namespace Commands {
|
||||
export const OPEN_CLI_CONFIG: Command = {
|
||||
id: 'arduino-open-cli-config',
|
||||
label: 'Open CLI Configuration',
|
||||
category: 'Arduino'
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
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 { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class SketchControl extends SketchContribution {
|
||||
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
@inject(MenuModelRegistry)
|
||||
protected readonly menuRegistry: MenuModelRegistry;
|
||||
|
||||
@inject(ContextMenuRenderer)
|
||||
protected readonly contextMenuRenderer: ContextMenuRenderer;
|
||||
|
||||
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,
|
||||
execute: async () => {
|
||||
this.toDisposeBeforeCreateNewContextMenu.dispose();
|
||||
const sketch = await this.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = document.getElementById(SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR.id);
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const { parentElement } = target;
|
||||
if (!parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uris = await this.sketchService.getSketchFiles(sketch.uri);
|
||||
// TODO: order them! The Java IDE orders them by tab index. Use the shell and the editor manager to achieve it.
|
||||
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, {
|
||||
commandId: command.id,
|
||||
label: this.labelProvider.getName(uri),
|
||||
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
|
||||
}
|
||||
}
|
||||
this.contextMenuRenderer.render(options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
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, {
|
||||
commandId: WorkspaceCommands.FILE_RENAME.id,
|
||||
label: 'Rename',
|
||||
order: '1'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__MAIN_GROUP, {
|
||||
commandId: WorkspaceCommands.FILE_DELETE.id,
|
||||
label: 'Delete',
|
||||
order: '2'
|
||||
});
|
||||
|
||||
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, {
|
||||
commandId: CommonCommands.NEXT_TAB.id,
|
||||
label: 'Next Tab',
|
||||
order: '0'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: WorkspaceCommands.NEW_FILE.id,
|
||||
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.
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: CommonCommands.NEXT_TAB.id,
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace SketchControl {
|
||||
export namespace Commands {
|
||||
export const OPEN_SKETCH_CONTROL__TOOLBAR: Command = {
|
||||
id: 'arduino-open-sketch-control--toolbar',
|
||||
iconClass: 'fa fa-caret-down'
|
||||
};
|
||||
}
|
||||
}
|
136
arduino-ide-extension/src/browser/contributions/upload-sketch.ts
Normal file
136
arduino-ide-extension/src/browser/contributions/upload-sketch.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||
import { CoreService } from '../../common/protocol';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { MonitorConnection } from '../monitor/monitor-connection';
|
||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class UploadSketch extends SketchContribution {
|
||||
|
||||
@inject(CoreService)
|
||||
protected readonly coreService: CoreService;
|
||||
|
||||
@inject(MonitorConnection)
|
||||
protected readonly monitorConnection: MonitorConnection;
|
||||
|
||||
@inject(BoardsDataStore)
|
||||
protected readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
|
||||
|
||||
@inject(OutputChannelManager)
|
||||
protected readonly outputChannelManager: OutputChannelManager;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
|
||||
execute: () => this.uploadSketch()
|
||||
});
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER, {
|
||||
execute: () => this.uploadSketch(true)
|
||||
});
|
||||
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () => registry.executeCommand(UploadSketch.Commands.UPLOAD_SKETCH.id)
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||
label: 'Upload',
|
||||
order: '0'
|
||||
});
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||
label: 'Upload Using Programmer',
|
||||
order: '1'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: UploadSketch.Commands.UPLOAD_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+U'
|
||||
});
|
||||
registry.registerKeybinding({
|
||||
command: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
|
||||
keybinding: 'CtrlCmd+Shift+U'
|
||||
});
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
|
||||
command: UploadSketch.Commands.UPLOAD_SKETCH_TOOLBAR.id,
|
||||
tooltip: 'Upload',
|
||||
priority: 1
|
||||
});
|
||||
}
|
||||
|
||||
async uploadSketch(usingProgrammer: boolean = false): Promise<void> {
|
||||
const uri = await this.currentSketchFile();
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
const monitorConfig = this.monitorConnection.monitorConfig;
|
||||
if (monitorConfig) {
|
||||
await this.monitorConnection.disconnect();
|
||||
}
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||
throw new Error('No boards selected. Please select a board.');
|
||||
}
|
||||
const { selectedPort } = boardsConfig;
|
||||
if (!selectedPort) {
|
||||
throw new Error('No ports selected. Please select a port.');
|
||||
}
|
||||
if (!boardsConfig.selectedBoard.fqbn) {
|
||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
||||
}
|
||||
const [fqbn, data] = await Promise.all([
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn),
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard.fqbn)
|
||||
]);
|
||||
this.outputChannelManager.getChannel('Arduino: upload').clear();
|
||||
const programmer = usingProgrammer ? data.selectedProgrammer : undefined;
|
||||
if (usingProgrammer && !programmer) {
|
||||
this.messageService.warn('Programmer is not selected. Uploading without programmer.', { timeout: 2000 });
|
||||
}
|
||||
await this.coreService.upload({
|
||||
sketchUri: uri,
|
||||
fqbn,
|
||||
port: selectedPort.address,
|
||||
optimizeForDebug: this.editorMode.compileForDebug,
|
||||
programmer
|
||||
});
|
||||
this.messageService.info('Done uploading.', { timeout: 1000 });
|
||||
} catch (e) {
|
||||
this.messageService.error(e.toString());
|
||||
} finally {
|
||||
if (monitorConfig) {
|
||||
await this.monitorConnection.connect(monitorConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace UploadSketch {
|
||||
export namespace Commands {
|
||||
export const UPLOAD_SKETCH: Command = {
|
||||
id: 'arduino-upload-sketch'
|
||||
};
|
||||
export const UPLOAD_SKETCH_USING_PROGRAMMER: Command = {
|
||||
id: 'arduino-upload-sketch-using-programmer'
|
||||
};
|
||||
export const UPLOAD_SKETCH_TOOLBAR: Command = {
|
||||
id: 'arduino-upload-sketch--toolbar'
|
||||
};
|
||||
}
|
||||
}
|
100
arduino-ide-extension/src/browser/contributions/verify-sketch.ts
Normal file
100
arduino-ide-extension/src/browser/contributions/verify-sketch.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||
import { CoreService } from '../../common/protocol';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { BoardsDataStore } from '../boards/boards-data-store';
|
||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry, KeybindingRegistry, TabBarToolbarRegistry } from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class VerifySketch extends SketchContribution {
|
||||
|
||||
@inject(CoreService)
|
||||
protected readonly coreService: CoreService;
|
||||
|
||||
@inject(BoardsDataStore)
|
||||
protected readonly boardsDataStore: BoardsDataStore;
|
||||
|
||||
@inject(BoardsServiceClientImpl)
|
||||
protected readonly boardsServiceClientImpl: BoardsServiceClientImpl;
|
||||
|
||||
@inject(OutputChannelManager)
|
||||
protected readonly outputChannelManager: OutputChannelManager;
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH, {
|
||||
execute: () => this.verifySketch()
|
||||
});
|
||||
registry.registerCommand(VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR, {
|
||||
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||
execute: () => registry.executeCommand(VerifySketch.Commands.VERIFY_SKETCH.id)
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
|
||||
commandId: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||
label: 'Verify/Compile',
|
||||
order: '2'
|
||||
});
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.registerKeybinding({
|
||||
command: VerifySketch.Commands.VERIFY_SKETCH.id,
|
||||
keybinding: 'CtrlCmd+R'
|
||||
});
|
||||
}
|
||||
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem({
|
||||
id: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
|
||||
command: VerifySketch.Commands.VERIFY_SKETCH_TOOLBAR.id,
|
||||
tooltip: 'Verify',
|
||||
priority: 0
|
||||
});
|
||||
}
|
||||
|
||||
async verifySketch(): Promise<void> {
|
||||
const uri = await this.currentSketchFile();
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { boardsConfig } = this.boardsServiceClientImpl;
|
||||
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||
throw new Error('No boards selected. Please select a board.');
|
||||
}
|
||||
if (!boardsConfig.selectedBoard.fqbn) {
|
||||
throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`);
|
||||
}
|
||||
const [data, fqbn] = await Promise.all([
|
||||
this.boardsDataStore.getData(boardsConfig.selectedBoard.fqbn),
|
||||
this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn)
|
||||
]);
|
||||
this.outputChannelManager.getChannel('Arduino: compile').clear();
|
||||
await this.coreService.compile({
|
||||
sketchUri: uri,
|
||||
fqbn,
|
||||
optimizeForDebug: this.editorMode.compileForDebug,
|
||||
programmer: data.selectedProgrammer
|
||||
});
|
||||
this.messageService.info('Done compiling.', { timeout: 1000 });
|
||||
} catch (e) {
|
||||
this.messageService.error(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace VerifySketch {
|
||||
export namespace Commands {
|
||||
export const VERIFY_SKETCH: Command = {
|
||||
id: 'arduino-verify-sketch'
|
||||
};
|
||||
export const VERIFY_SKETCH_TOOLBAR: Command = {
|
||||
id: 'arduino-verify-sketch--toolbar'
|
||||
};
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
import { ApplicationShell, Widget, Saveable, FocusTracker, Message } from '@theia/core/lib/browser';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoApplicationShell extends ApplicationShell {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
protected refreshBottomPanelToggleButton() {
|
||||
if (this.editorMode.proMode) {
|
||||
super.refreshBottomPanelToggleButton();
|
||||
}
|
||||
}
|
||||
|
||||
protected async track(widget: Widget): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
super.track(widget);
|
||||
} else {
|
||||
const tracker = (this as any).tracker as FocusTracker<Widget>;
|
||||
tracker.add(widget);
|
||||
this.disableClose(Saveable.apply(widget));
|
||||
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
|
||||
for (const toTrack of await widget.getTrackableWidgets()) {
|
||||
tracker.add(toTrack);
|
||||
this.disableClose(Saveable.apply(toTrack));
|
||||
}
|
||||
if (widget.onDidChangeTrackableWidgets) {
|
||||
widget.onDidChangeTrackableWidgets(widgets => widgets.forEach(w => this.track(w)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private disableClose(widget: Widget | undefined): void {
|
||||
if (widget instanceof EditorWidget) {
|
||||
const onCloseRequest = (_: Message) => {
|
||||
// NOOP
|
||||
};
|
||||
(widget as any).onCloseRequest = onCloseRequest.bind(widget);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
import { ArduinoCommands } from '../arduino-commands';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendApplication extends FrontendApplication {
|
||||
|
||||
@inject(FileSystem)
|
||||
protected readonly fileSystem: FileSystem;
|
||||
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
protected async initializeLayout(): Promise<void> {
|
||||
await super.initializeLayout();
|
||||
// If not in PRO mode, we open the sketch file with all the related files.
|
||||
// Otherwise, we reuse the workbench's restore functionality and we do not open anything at all.
|
||||
// TODO: check `otherwise`. Also, what if we check for opened editors, instead of blindly opening them?
|
||||
if (!this.editorMode.proMode) {
|
||||
const roots = await this.workspaceService.roots;
|
||||
for (const root of roots) {
|
||||
const exists = await this.fileSystem.exists(root.uri);
|
||||
if (exists) {
|
||||
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
||||
|
||||
export class ArduinoMonacoStatusBarContribution extends MonacoStatusBarContribution {
|
||||
|
||||
protected setConfigTabSizeWidget() {
|
||||
}
|
||||
|
||||
protected setLineEndingWidget() {
|
||||
}
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoNavigatorContribution extends FileNavigatorContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoOutputToolContribution extends OutputToolbarContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
super.registerToolbarItems(toolbarRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoSearchInWorkspaceContribution extends SearchInWorkspaceFrontendContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,10 +2,9 @@ import { injectable, inject } from 'inversify';
|
||||
import { ApplicationShell, FrontendApplicationContribution, FrontendApplication, Widget } from '@theia/core/lib/browser';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import { MainMenuManager } from './menu/main-menu-manager';
|
||||
import { MainMenuManager } from '../common/main-menu-manager';
|
||||
import { BoardsListWidget } from './boards/boards-list-widget';
|
||||
import { LibraryListWidget } from './library/library-list-widget';
|
||||
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||
|
||||
@injectable()
|
||||
export class EditorMode implements FrontendApplicationContribution {
|
||||
@ -42,7 +41,7 @@ export class EditorMode implements FrontendApplicationContribution {
|
||||
// `storeLayout` has a sync API but the implementation is async, we store the layout manually before we reload the page.
|
||||
// See: https://github.com/eclipse-theia/theia/issues/6579
|
||||
// XXX: hack instead of injecting the `ArduinoShellLayoutRestorer` we have to retrieve it from the application to avoid DI cycle.
|
||||
const layoutRestorer = (this.app as any).layoutRestorer as ArduinoShellLayoutRestorer
|
||||
const layoutRestorer = (this.app as any).layoutRestorer as { storeLayoutAsync(app: FrontendApplication): Promise<void> };
|
||||
await layoutRestorer.storeLayoutAsync(this.app);
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { Library, LibraryService } from '../../common/protocol/library-service';
|
||||
import { ListWidget } from '../components/component-list/list-widget';
|
||||
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||
import { ListWidget } from '../widgets/component-list/list-widget';
|
||||
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
|
||||
|
||||
@injectable()
|
||||
export class LibraryListWidget extends ListWidget<Library> {
|
||||
|
@ -3,7 +3,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
|
||||
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
import { LibraryListWidget } from './library-list-widget';
|
||||
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class LibraryListWidgetFrontendContribution extends AbstractViewContribution<LibraryListWidget> implements FrontendApplicationContribution {
|
||||
@ -17,7 +17,7 @@ export class LibraryListWidgetFrontendContribution extends AbstractViewContribut
|
||||
rank: 700
|
||||
},
|
||||
toggleCommandId: `${LibraryListWidget.WIDGET_ID}:toggle`,
|
||||
toggleKeybinding: 'ctrlcmd+shift+l'
|
||||
toggleKeybinding: 'CtrlCmd+Shift+I'
|
||||
});
|
||||
}
|
||||
|
||||
@ -27,9 +27,10 @@ export class LibraryListWidgetFrontendContribution extends AbstractViewContribut
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
if (this.toggleCommand) {
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS, {
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: this.toggleCommand.id,
|
||||
label: 'Manage Libraries...'
|
||||
label: 'Manage Libraries...',
|
||||
order: '3'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
52
arduino-ide-extension/src/browser/menu/arduino-menus.ts
Normal file
52
arduino-ide-extension/src/browser/menu/arduino-menus.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { MAIN_MENU_BAR } from '@theia/core/lib/common/menu';
|
||||
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import { isOSX } from '@theia/core';
|
||||
|
||||
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 settings group is not under `File`
|
||||
export const FILE__SETTINGS_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.FILE), '2_settings'];
|
||||
export const FILE__QUIT_GROUP = [...CommonMenus.FILE, '3_quit'];
|
||||
|
||||
// -- 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'];
|
||||
// `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__FIND_GROUP = [...CommonMenus.EDIT, '5_find'];
|
||||
|
||||
// -- Sketch
|
||||
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
||||
export const SKETCH__MAIN_GROUP = [...SKETCH, '0_main'];
|
||||
export const SKETCH__UTILS_GROUP = [...SKETCH, '1_utils'];
|
||||
|
||||
// -- Tools
|
||||
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
|
||||
// `Auto Format`, `Library Manager...`, `Boards Manager...`
|
||||
export const TOOLS__MAIN_GROUP = [...TOOLS, '0_main'];
|
||||
// Core settings, such as `Processor` and `Programmers` for the board.
|
||||
export const TOOLS__BOARD_SETTINGS_GROUP = [...TOOLS, '1_board_settings'];
|
||||
|
||||
// Context menu
|
||||
// -- 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'];
|
||||
|
||||
// -- 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'];
|
||||
// `Previous Tab`, `Next Tab`
|
||||
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'];
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import '../../../src/browser/style/browser-menu.css'
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { BrowserMenuBarContribution, BrowserMainMenuFactory } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from './main-menu-manager';
|
||||
import { ArduinoMenuContribution } from './arduino-menu-contribution';
|
||||
import { ArduinoBrowserMainMenuFactory } from './arduino-browser-main-menu-factory';
|
||||
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ArduinoBrowserMainMenuFactory).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(ArduinoBrowserMainMenuFactory);
|
||||
rebind(BrowserMainMenuFactory).toService(ArduinoBrowserMainMenuFactory);
|
||||
rebind(BrowserMenuBarContribution).to(ArduinoMenuContribution).inSingletonScope();
|
||||
});
|
@ -1,12 +1,12 @@
|
||||
import * as React from 'react';
|
||||
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 { ArduinoMenus } from "../arduino-frontend-contribution";
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar";
|
||||
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 { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||
import { MonitorModel } from './monitor-model';
|
||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||
|
||||
export namespace SerialMonitor {
|
||||
export namespace Commands {
|
||||
@ -42,15 +42,16 @@ export class MonitorViewContribution extends AbstractViewContribution<MonitorWid
|
||||
area: 'bottom'
|
||||
},
|
||||
toggleCommandId: MonitorViewContribution.TOGGLE_SERIAL_MONITOR,
|
||||
toggleKeybinding: 'ctrlcmd+shift+m'
|
||||
toggleKeybinding: 'CtrlCmd+Shift+M'
|
||||
})
|
||||
}
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
if (this.toggleCommand) {
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS, {
|
||||
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
|
||||
commandId: this.toggleCommand.id,
|
||||
label: 'Serial Monitor'
|
||||
label: 'Serial Monitor',
|
||||
order: '5'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import { DisposableCollection } 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 '../components/arduino-select';
|
||||
import { ArduinoSelect } from '../widgets/arduino-select';
|
||||
import { MonitorModel } from './monitor-model';
|
||||
import { MonitorConnection } from './monitor-connection';
|
||||
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||
|
@ -1,13 +0,0 @@
|
||||
/* Do not show the `close` icon for editor, but show the dirty state if not in pro-mode. */
|
||||
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable:hover > .p-TabBar-tabCloseIcon,
|
||||
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-current > .p-TabBar-tabCloseIcon {
|
||||
background-image: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty:hover > .p-TabBar-tabCloseIcon,
|
||||
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty > .p-TabBar-tabCloseIcon:hover {
|
||||
background-size: 10px;
|
||||
background-image: var(--theia-icon-circle);
|
||||
cursor: pointer;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
@import './list-widget.css';
|
||||
@import './board-select-dialog.css';
|
||||
@import './boards-config-dialog.css';
|
||||
@import './main.css';
|
||||
@import './editor.css';
|
||||
@import './monitor.css';
|
||||
@import './arduino-select.css';
|
||||
@import './status-bar.css';
|
||||
@import './terminal.css';
|
||||
|
||||
.theia-input.warning:focus {
|
||||
outline-width: 1px;
|
||||
|
@ -2,15 +2,6 @@
|
||||
background: var(--theia-editorGroupHeader-tabsBackground);
|
||||
}
|
||||
|
||||
.theia-output {
|
||||
background: var(--theia-terminal-background);
|
||||
color: var(--theia-terminal-foreground);
|
||||
}
|
||||
|
||||
#outputView {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.p-TabBar-toolbar .item.arduino-tool-item {
|
||||
margin-left: 3px;
|
||||
}
|
||||
@ -21,14 +12,15 @@
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background: var(--theia-button-background);
|
||||
background: var(--theia-arduino-toolbar-background);
|
||||
}
|
||||
|
||||
.p-TabBar-toolbar .item.arduino-tool-item > div:hover {
|
||||
background: var(--theia-button-hoverBackground);
|
||||
background: (--theia-arduino-toolbar-hoverBackground);
|
||||
}
|
||||
|
||||
.arduino-verify, .arduino-upload {
|
||||
.arduino-verify-sketch--toolbar,
|
||||
.arduino-upload-sketch--toolbar {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@ -42,22 +34,27 @@
|
||||
mask-size: 800%;
|
||||
}
|
||||
|
||||
.arduino-save-file-icon {
|
||||
.arduino-save-sketch--toolbar-icon {
|
||||
-webkit-mask-position: 59px -4px;
|
||||
mask-position: 59px -4px;
|
||||
}
|
||||
|
||||
.arduino-verify-icon {
|
||||
.arduino-verify-sketch--toolbar-icon {
|
||||
-webkit-mask-position: 188px -4px;
|
||||
mask-position: 188px -4px;
|
||||
}
|
||||
|
||||
.arduino-upload-icon {
|
||||
.arduino-upload-sketch--toolbar-icon {
|
||||
-webkit-mask-position: 156px -4px;
|
||||
mask-position: 156px -4px;
|
||||
}
|
||||
|
||||
.arduino-show-open-context-menu-icon {
|
||||
.arduino-new-sketch--toolbar-icon {
|
||||
-webkit-mask-position: 124px -4px;
|
||||
mask-position: 124px -4px;
|
||||
}
|
||||
|
||||
.arduino-open-sketch--toolbar-icon {
|
||||
-webkit-mask-position: 92px -4px;
|
||||
mask-position: 92px -4px;
|
||||
}
|
||||
@ -117,7 +114,17 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--theia-titleBar-activeBackground);;
|
||||
color: var(--theia-titleBar-activeBackground);
|
||||
}
|
||||
|
||||
.arduino-open-boards-control-icon {
|
||||
mask: none;
|
||||
-webkit-mask: none;
|
||||
background: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--theia-titleBar-activeBackground);
|
||||
}
|
||||
|
||||
.monaco-editor .margin {
|
||||
@ -125,6 +132,10 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.theia-output .monaco-editor .margin {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.noWrapInfo {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@ -138,3 +149,9 @@
|
||||
.p-Widget.dialogOverlay .dialogBlock {
|
||||
background-color: var(--theia-arduino-foreground);
|
||||
}
|
||||
|
||||
#arduino-open-sketch-control--toolbar {
|
||||
background-color: var(--theia-tab-inactiveBackground);
|
||||
border: 1px solid var(--theia-arduino-toolbar-background);
|
||||
padding: 2px 0px 2px 9px;
|
||||
}
|
||||
|
7
arduino-ide-extension/src/browser/style/status-bar.css
Normal file
7
arduino-ide-extension/src/browser/style/status-bar.css
Normal file
@ -0,0 +1,7 @@
|
||||
#theia-statusBar .area .element.arduino-selected-port {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#theia-statusBar .area .element.arduino-selected-board > *:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
3
arduino-ide-extension/src/browser/style/terminal.css
Normal file
3
arduino-ide-extension/src/browser/style/terminal.css
Normal file
@ -0,0 +1,3 @@
|
||||
.terminal-container .xterm .xterm-helper-textarea {
|
||||
opacity: 0 !important; /* fix secondary cursor-like issue. See https://github.com/eclipse-theia/theia/issues/8158 */
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { injectable, inject, postConstruct } from 'inversify';
|
||||
import { AboutDialog, ABOUT_CONTENT_CLASS } from '@theia/core/lib/browser/about-dialog';
|
||||
import { ConfigService } from '../../common/protocol/config-service';
|
||||
import { AboutDialog as TheiaAboutDialog, ABOUT_CONTENT_CLASS } from '@theia/core/lib/browser/about-dialog';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoAboutDialog extends AboutDialog {
|
||||
export class AboutDialog extends TheiaAboutDialog {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
@ -0,0 +1,44 @@
|
||||
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { PreferencesWidget } from '@theia/preferences/lib/browser/views/preference-widget';
|
||||
import { ApplicationShell as TheiaApplicationShell, Widget } from '@theia/core/lib/browser';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
||||
|
||||
@injectable()
|
||||
export class ApplicationShell extends TheiaApplicationShell {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
protected track(widget: Widget): void {
|
||||
super.track(widget);
|
||||
if (!this.editorMode.proMode) {
|
||||
if (widget instanceof EditorWidget) {
|
||||
// Always allow closing the whitelisted files.
|
||||
// TODO: It would be better to blacklist the sketch files only.
|
||||
if (['tasks.json',
|
||||
'launch.json',
|
||||
'settings.json',
|
||||
'arduino-cli.yaml'].some(fileName => widget.editor.uri.toString().endsWith(fileName))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (widget instanceof PreferencesWidget) {
|
||||
return;
|
||||
}
|
||||
widget.title.closable = false;
|
||||
}
|
||||
}
|
||||
|
||||
async saveAll(): Promise<void> {
|
||||
await super.saveAll();
|
||||
await this.commandService.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { execOnlyIfTemp: true, openAfterMove: true });
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { BrowserMainMenuFactory, MenuBarWidget } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from './main-menu-manager';
|
||||
import { BrowserMainMenuFactory as TheiaBrowserMainMenuFactory, MenuBarWidget } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoBrowserMainMenuFactory extends BrowserMainMenuFactory implements MainMenuManager {
|
||||
export class BrowserMainMenuFactory extends TheiaBrowserMainMenuFactory implements MainMenuManager {
|
||||
|
||||
protected menuBar: MenuBarWidget | undefined;
|
||||
|
@ -0,0 +1,13 @@
|
||||
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 { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ArduinoMenuContribution } from './browser-menu-plugin';
|
||||
import { BrowserMainMenuFactory } from './browser-main-menu-factory';
|
||||
|
||||
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();
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
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';
|
||||
|
||||
@injectable()
|
||||
export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
for (const command of [
|
||||
CommonCommands.SAVE,
|
||||
CommonCommands.SAVE_ALL,
|
||||
CommonCommands.CUT,
|
||||
CommonCommands.COPY,
|
||||
CommonCommands.PASTE,
|
||||
CommonCommands.COPY_PATH,
|
||||
CommonCommands.FIND,
|
||||
CommonCommands.REPLACE,
|
||||
CommonCommands.AUTO_SAVE,
|
||||
CommonCommands.OPEN_PREFERENCES,
|
||||
CommonCommands.SELECT_ICON_THEME,
|
||||
CommonCommands.SELECT_COLOR_THEME,
|
||||
CommonCommands.OPEN_PREFERENCES
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
import { FrontendConnectionStatusService, ApplicationConnectionStatusContribution, ConnectionStatus } from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ArduinoDaemonClientImpl } from '../arduino-daemon-client-impl';
|
||||
import {
|
||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||
ConnectionStatus
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ArduinoDaemonClientImpl } from '../../arduino-daemon-client-impl';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFrontendConnectionStatusService extends FrontendConnectionStatusService {
|
||||
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
||||
|
||||
@inject(ArduinoDaemonClientImpl)
|
||||
protected readonly daemonClient: ArduinoDaemonClientImpl;
|
||||
@ -23,7 +27,7 @@ export class ArduinoFrontendConnectionStatusService extends FrontendConnectionSt
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ArduinoApplicationConnectionStatusContribution extends ApplicationConnectionStatusContribution {
|
||||
export class ApplicationConnectionStatusContribution extends TheiaApplicationConnectionStatusContribution {
|
||||
|
||||
@inject(ArduinoDaemonClientImpl)
|
||||
protected readonly daemonClient: ArduinoDaemonClientImpl;
|
@ -0,0 +1,31 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ArduinoCommands } from '../../arduino-commands';
|
||||
|
||||
@injectable()
|
||||
export class FrontendApplication extends TheiaFrontendApplication {
|
||||
|
||||
@inject(FileSystem)
|
||||
protected readonly fileSystem: FileSystem;
|
||||
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
protected async initializeLayout(): Promise<void> {
|
||||
await super.initializeLayout();
|
||||
const roots = await this.workspaceService.roots;
|
||||
for (const root of roots) {
|
||||
const exists = await this.fileSystem.exists(root.uri);
|
||||
if (exists) {
|
||||
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
28
arduino-ide-extension/src/browser/theia/core/keybindings.ts
Normal file
28
arduino-ide-extension/src/browser/theia/core/keybindings.ts
Normal file
@ -0,0 +1,28 @@
|
||||
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';
|
||||
|
||||
@injectable()
|
||||
export class KeybindingRegistry extends TheiaKeybindingRegistry {
|
||||
|
||||
// https://github.com/eclipse-theia/theia/issues/8209
|
||||
unregisterKeybinding(key: string): void;
|
||||
unregisterKeybinding(keybinding: Keybinding): void;
|
||||
unregisterKeybinding(command: Command): void;
|
||||
unregisterKeybinding(arg: string | Keybinding | Command): void {
|
||||
const keymap = this.keymaps[KeybindingScope.DEFAULT];
|
||||
const filter = Command.is(arg)
|
||||
? ({ command }: Keybinding) => command === arg.id
|
||||
: ({ keybinding }: Keybinding) => Keybinding.is(arg)
|
||||
? keybinding === arg.keybinding
|
||||
: keybinding === arg;
|
||||
for (const binding of keymap.filter(filter)) {
|
||||
const idx = keymap.indexOf(binding);
|
||||
if (idx !== -1) {
|
||||
keymap.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer';
|
||||
import { ShellLayoutRestorer as TheiaShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoShellLayoutRestorer extends ShellLayoutRestorer {
|
||||
export class ShellLayoutRestorer extends TheiaShellLayoutRestorer {
|
||||
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
|
||||
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
|
@ -3,12 +3,12 @@ import URI from '@theia/core/lib/common/uri';
|
||||
import { Title, Widget } from '@phosphor/widgets';
|
||||
import { ILogger } from '@theia/core';
|
||||
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';
|
||||
import { TabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||
import { ConfigService } from '../../common/protocol/config-service';
|
||||
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoTabBarDecoratorService extends TabBarDecoratorService {
|
||||
export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
@ -16,7 +16,6 @@ export class ArduinoTabBarDecoratorService extends TabBarDecoratorService {
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
|
||||
protected dataDirUri: URI | undefined;
|
||||
|
||||
@postConstruct()
|
@ -1,8 +1,10 @@
|
||||
import { EditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
||||
import { injectable } from 'inversify';
|
||||
import { EditorContribution as TheiaEditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
||||
import { TextEditor } from '@theia/editor/lib/browser';
|
||||
import { StatusBarAlignment } from '@theia/core/lib/browser';
|
||||
|
||||
export class ArduinoEditorContribution extends EditorContribution {
|
||||
@injectable()
|
||||
export class EditorContribution extends TheiaEditorContribution {
|
||||
|
||||
protected updateLanguageStatus(editor: TextEditor | undefined): void {
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorManager, EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { ConfigService } from '../../common/protocol/config-service';
|
||||
import { EditorManager as TheiaEditorManager, EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoEditorManager extends EditorManager {
|
||||
export class EditorManager extends TheiaEditorManager {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
@ -2,11 +2,11 @@ import { inject, injectable } from 'inversify';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser';
|
||||
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoProblemContribution extends ProblemContribution {
|
||||
export class ProblemContribution extends TheiaProblemContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
@ -3,11 +3,11 @@ import { Diagnostic } from 'vscode-languageserver-types';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ILogger } from '@theia/core';
|
||||
import { Marker } from '@theia/markers/lib/common/marker';
|
||||
import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import { ConfigService } from '../../common/protocol/config-service';
|
||||
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoProblemManager extends ProblemManager {
|
||||
export class ProblemManager extends TheiaProblemManager {
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
368
arduino-ide-extension/src/browser/theia/monaco/comparers.ts
Normal file
368
arduino-ide-extension/src/browser/theia/monaco/comparers.ts
Normal file
@ -0,0 +1,368 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// Copied from https://github.com/microsoft/vscode/blob/724c307bf35646ac549a8533a255c51b63fea5c7/src/vs/base/common/comparers.ts
|
||||
// We cannot customize the monaco loader for Theia: https://github.com/eclipse-theia/theia/issues/8220
|
||||
|
||||
import { isWindows } from '@theia/core/lib/common/os';
|
||||
|
||||
const sep = (isWindows ? '\\' : '/');
|
||||
interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
interface IdleDeadline {
|
||||
readonly didTimeout: boolean;
|
||||
timeRemaining(): number;
|
||||
}
|
||||
let runWhenIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable;
|
||||
declare function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number;
|
||||
declare function cancelIdleCallback(handle: number): void;
|
||||
|
||||
(function () {
|
||||
if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
|
||||
const dummyIdle: IdleDeadline = Object.freeze({
|
||||
didTimeout: true,
|
||||
timeRemaining() { return 15; }
|
||||
});
|
||||
runWhenIdle = (runner) => {
|
||||
const handle = setTimeout(() => runner(dummyIdle));
|
||||
let disposed = false;
|
||||
return {
|
||||
dispose() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
disposed = true;
|
||||
clearTimeout(handle);
|
||||
}
|
||||
};
|
||||
};
|
||||
} else {
|
||||
runWhenIdle = (runner, timeout?) => {
|
||||
const handle: number = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined);
|
||||
let disposed = false;
|
||||
return {
|
||||
dispose() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
disposed = true;
|
||||
cancelIdleCallback(handle);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* An implementation of the "idle-until-urgent"-strategy as introduced
|
||||
* here: https://philipwalton.com/articles/idle-until-urgent/
|
||||
*/
|
||||
class IdleValue<T> {
|
||||
|
||||
private readonly _executor: () => void;
|
||||
private readonly _handle: IDisposable;
|
||||
|
||||
private _didRun: boolean = false;
|
||||
private _value?: T;
|
||||
private _error: any;
|
||||
|
||||
constructor(executor: () => T) {
|
||||
this._executor = () => {
|
||||
try {
|
||||
this._value = executor();
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
} finally {
|
||||
this._didRun = true;
|
||||
}
|
||||
};
|
||||
this._handle = runWhenIdle(() => this._executor());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._handle.dispose();
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
if (!this._didRun) {
|
||||
this._handle.dispose();
|
||||
this._executor();
|
||||
}
|
||||
if (this._error) {
|
||||
throw this._error;
|
||||
}
|
||||
return this._value!;
|
||||
}
|
||||
}
|
||||
|
||||
// When comparing large numbers of strings, such as in sorting large arrays, is better for
|
||||
// performance to create an Intl.Collator object and use the function provided by its compare
|
||||
// property than it is to use String.prototype.localeCompare()
|
||||
|
||||
// A collator with numeric sorting enabled, and no sensitivity to case or to accents
|
||||
const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
||||
return {
|
||||
collator: collator,
|
||||
collatorIsNumeric: collator.resolvedOptions().numeric
|
||||
};
|
||||
});
|
||||
|
||||
// A collator with numeric sorting enabled.
|
||||
const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
||||
const collator = new Intl.Collator(undefined, { numeric: true });
|
||||
return {
|
||||
collator: collator
|
||||
};
|
||||
});
|
||||
|
||||
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
|
||||
const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
|
||||
return {
|
||||
collator: collator
|
||||
};
|
||||
});
|
||||
|
||||
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
const a = one || '';
|
||||
const b = other || '';
|
||||
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
|
||||
|
||||
// Using the numeric option in the collator will
|
||||
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */
|
||||
export function compareFileNamesNumeric(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
||||
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
|
||||
let result;
|
||||
|
||||
// Check for name differences, comparing numbers numerically instead of alphabetically.
|
||||
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically.
|
||||
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Disambiguate the extension case if needed.
|
||||
if (oneExtension !== otherExtension) {
|
||||
return collatorNumeric.compare(oneExtension, otherExtension);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||
|
||||
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
if (!caseSensitive) {
|
||||
one = one && one.toLowerCase();
|
||||
other = other && other.toLowerCase();
|
||||
}
|
||||
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other);
|
||||
|
||||
if (oneName !== otherName) {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
|
||||
if (oneExtension === otherExtension) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
}
|
||||
|
||||
export function compareFileExtensions(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other);
|
||||
|
||||
let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
|
||||
|
||||
if (result === 0) {
|
||||
// Using the numeric option in the collator will
|
||||
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
}
|
||||
|
||||
// Extensions are equal, compare filenames
|
||||
result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
|
||||
|
||||
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */
|
||||
export function compareFileExtensionsNumeric(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
||||
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
|
||||
let result;
|
||||
|
||||
// Check for extension differences, ignoring differences in case and comparing numbers numerically.
|
||||
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Compare names.
|
||||
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Disambiguate extension case if needed.
|
||||
if (oneExtension !== otherExtension) {
|
||||
return collatorNumeric.compare(oneExtension, otherExtension);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
|
||||
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
|
||||
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
|
||||
|
||||
let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
|
||||
|
||||
// if the dotfilesAsNames option is selected, treat an empty filename with an extension,
|
||||
// or a filename that starts with a dot, as a dotfile name
|
||||
if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
|
||||
result = [result[0] + '.' + result[1], ''];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
|
||||
// Check for differences
|
||||
let result = collator.compare(one, other);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
|
||||
// Disambiguate by sorting the shorter string first.
|
||||
if (one.length !== other.length) {
|
||||
return one.length < other.length ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function comparePathComponents(one: string, other: string, caseSensitive = false): number {
|
||||
if (!caseSensitive) {
|
||||
one = one && one.toLowerCase();
|
||||
other = other && other.toLowerCase();
|
||||
}
|
||||
|
||||
if (one === other) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return one < other ? -1 : 1;
|
||||
}
|
||||
|
||||
export function comparePaths(one: string, other: string, caseSensitive = false): number {
|
||||
const oneParts = one.split(sep);
|
||||
const otherParts = other.split(sep);
|
||||
|
||||
const lastOne = oneParts.length - 1;
|
||||
const lastOther = otherParts.length - 1;
|
||||
let endOne: boolean, endOther: boolean;
|
||||
|
||||
for (let i = 0; ; i++) {
|
||||
endOne = lastOne === i;
|
||||
endOther = lastOther === i;
|
||||
|
||||
if (endOne && endOther) {
|
||||
return compareFileNames(oneParts[i], otherParts[i], caseSensitive);
|
||||
} else if (endOne) {
|
||||
return -1;
|
||||
} else if (endOther) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const result = comparePathComponents(oneParts[i], otherParts[i], caseSensitive);
|
||||
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function compareAnything(one: string, other: string, lookFor: string): number {
|
||||
const elementAName = one.toLowerCase();
|
||||
const elementBName = other.toLowerCase();
|
||||
|
||||
// Sort prefix matches over non prefix matches
|
||||
const prefixCompare = compareByPrefix(one, other, lookFor);
|
||||
if (prefixCompare) {
|
||||
return prefixCompare;
|
||||
}
|
||||
|
||||
// Sort suffix matches over non suffix matches
|
||||
const elementASuffixMatch = elementAName.endsWith(lookFor);
|
||||
const elementBSuffixMatch = elementBName.endsWith(lookFor);
|
||||
if (elementASuffixMatch !== elementBSuffixMatch) {
|
||||
return elementASuffixMatch ? -1 : 1;
|
||||
}
|
||||
|
||||
// Understand file names
|
||||
const r = compareFileNames(elementAName, elementBName);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Compare by name
|
||||
return elementAName.localeCompare(elementBName);
|
||||
}
|
||||
|
||||
export function compareByPrefix(one: string, other: string, lookFor: string): number {
|
||||
const elementAName = one.toLowerCase();
|
||||
const elementBName = other.toLowerCase();
|
||||
|
||||
// Sort prefix matches over non prefix matches
|
||||
const elementAPrefixMatch = elementAName.startsWith(lookFor);
|
||||
const elementBPrefixMatch = elementBName.startsWith(lookFor);
|
||||
if (elementAPrefixMatch !== elementBPrefixMatch) {
|
||||
return elementAPrefixMatch ? -1 : 1;
|
||||
}
|
||||
|
||||
// Same prefix: Sort shorter matches to the top to have those on top that match more precisely
|
||||
// tslint:disable-next-line: one-line
|
||||
else if (elementAPrefixMatch && elementBPrefixMatch) {
|
||||
if (elementAName.length < elementBName.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (elementAName.length > elementBName.length) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
||||
|
||||
@injectable()
|
||||
export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution {
|
||||
|
||||
protected setConfigTabSizeWidget() {
|
||||
}
|
||||
|
||||
protected setLineEndingWidget() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FileNavigatorContribution as TheiaFileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
[
|
||||
WorkspaceCommands.FILE_RENAME,
|
||||
WorkspaceCommands.FILE_DELETE
|
||||
].forEach(registry.unregisterKeybinding.bind(registry));
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoOutlineViewContribution extends OutlineViewContribution {
|
||||
export class OutlineViewContribution extends TheiaOutlineViewContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
@ -0,0 +1,27 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { isOSX } from '@theia/core/lib/common/os';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { CommonCommands, CommonMenus } from '@theia/core/lib/browser';
|
||||
import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/preferences/lib/browser/preference-contribution';
|
||||
|
||||
@injectable()
|
||||
export class PreferencesContribution extends TheiaPreferencesContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
if (isOSX) {
|
||||
// The settings group: preferences, CLI config is not part of the `File` menu on macOS.
|
||||
registry.unregisterMenuAction(CommonCommands.OPEN_PREFERENCES.id, CommonMenus.FILE_SETTINGS_SUBMENU_OPEN);
|
||||
}
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
// https://github.com/eclipse-theia/theia/issues/8202
|
||||
registry.registerKeybinding({
|
||||
command: CommonCommands.OPEN_PREFERENCES.id,
|
||||
keybinding: 'CtrlCmd+,',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { ScmContribution as TheiaScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||
import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
import { EditorMode } from '../editor-mode';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoScmContribution extends ScmContribution {
|
||||
export class ScmContribution extends TheiaScmContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
@ -0,0 +1,24 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution, SearchInWorkspaceCommands } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
|
||||
@injectable()
|
||||
export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspaceFrontendContribution {
|
||||
|
||||
@inject(EditorMode)
|
||||
protected readonly editorMode: EditorMode;
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
if (this.editorMode.proMode) {
|
||||
return super.initializeLayout(app);
|
||||
}
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { WorkspaceCommands, FileMenuContribution } from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import { WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution } from '@theia/workspace/lib/browser/workspace-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContribution {
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
// TODO: instead of blacklisting commands to remove, it would be more robust to whitelist the ones we want to keep
|
||||
const commands = new Set(registry.commands);
|
||||
[
|
||||
WorkspaceCommands.OPEN,
|
||||
WorkspaceCommands.OPEN_FILE,
|
||||
WorkspaceCommands.OPEN_FOLDER,
|
||||
WorkspaceCommands.OPEN_WORKSPACE,
|
||||
WorkspaceCommands.OPEN_RECENT_WORKSPACE,
|
||||
WorkspaceCommands.SAVE_WORKSPACE_AS,
|
||||
WorkspaceCommands.SAVE_AS,
|
||||
WorkspaceCommands.CLOSE
|
||||
].filter(commands.has.bind(commands)).forEach(registry.unregisterCommand.bind(registry));
|
||||
}
|
||||
|
||||
registerMenus(_: MenuModelRegistry): void {
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
[
|
||||
WorkspaceCommands.NEW_FILE,
|
||||
WorkspaceCommands.FILE_RENAME,
|
||||
WorkspaceCommands.FILE_DELETE
|
||||
].map(({ id }) => id).forEach(registry.unregisterKeybinding.bind(registry));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFileMenuContribution extends FileMenuContribution {
|
||||
|
||||
registerMenus(_: MenuModelRegistry): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { MessageService } from '@theia/core';
|
||||
import { LabelProvider } from '@theia/core/lib/browser';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { ConfigService } from '../common/protocol/config-service';
|
||||
import { SketchesService } from '../common/protocol/sketches-service';
|
||||
import { ArduinoWorkspaceRootResolver } from './arduino-workspace-resolver';
|
||||
import { EditorMode } from './editor-mode';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FocusTracker, Widget } from '@theia/core/lib/browser';
|
||||
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { EditorMode } from '../../editor-mode';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
import { SketchesService } from '../../../common/protocol/sketches-service';
|
||||
import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver';
|
||||
|
||||
@injectable()
|
||||
export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
export class WorkspaceService extends TheiaWorkspaceService {
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchService: SketchesService;
|
||||
@ -25,7 +30,19 @@ export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
@inject(ApplicationServer)
|
||||
protected readonly applicationServer: ApplicationServer;
|
||||
|
||||
private workspaceUri?: Promise<string | undefined>;
|
||||
private version?: string
|
||||
|
||||
async onStart(application: FrontendApplication): Promise<void> {
|
||||
const info = await this.applicationServer.getApplicationInfo();
|
||||
this.version = info?.version;
|
||||
application.shell.onDidChangeCurrentWidget(this.onCurrentWidgetChange.bind(this));
|
||||
const newValue = application.shell.currentWidget ? application.shell.currentWidget : null;
|
||||
this.onCurrentWidgetChange({ newValue, oldValue: null });
|
||||
}
|
||||
|
||||
protected getDefaultWorkspaceUri(): Promise<string | undefined> {
|
||||
if (this.workspaceUri) {
|
||||
@ -47,9 +64,7 @@ export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
await this.server.setMostRecentlyUsedWorkspace(uri);
|
||||
return toOpen.uri;
|
||||
}
|
||||
const { sketchDirUri } = (await this.configService.getConfiguration());
|
||||
this.logger.info(`No valid workspace URI found. Creating new sketch in ${sketchDirUri}`)
|
||||
return (await this.sketchService.createNewSketch(sketchDirUri)).uri;
|
||||
return (await this.sketchService.createNewSketch()).uri;
|
||||
} catch (err) {
|
||||
this.logger.fatal(`Failed to determine the sketch directory: ${err}`)
|
||||
this.messageService.error(
|
||||
@ -76,4 +91,32 @@ export class ArduinoWorkspaceService extends WorkspaceService {
|
||||
return sketchFolder;
|
||||
}
|
||||
|
||||
protected onCurrentWidgetChange({ newValue }: FocusTracker.IChangedArgs<Widget>): void {
|
||||
if (newValue instanceof EditorWidget) {
|
||||
const { uri } = newValue.editor;
|
||||
if (uri.toString().endsWith('.ino')) {
|
||||
this.updateTitle();
|
||||
} else {
|
||||
const title = this.workspaceTitle;
|
||||
const fileName = this.labelProvider.getName(uri);
|
||||
document.title = this.formatTitle(title ? `${title} - ${fileName}` : fileName);
|
||||
}
|
||||
} else {
|
||||
this.updateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
protected formatTitle(title?: string): string {
|
||||
const version = this.version ? ` ${this.version}` : '';
|
||||
const name = `${this.applicationName} ${version}`;
|
||||
return title ? `${title} | ${name}` : name;
|
||||
}
|
||||
|
||||
protected get workspaceTitle(): string | undefined {
|
||||
if (this.workspace) {
|
||||
const uri = new URI(this.workspace.uri);
|
||||
return this.labelProvider.getName(uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { ToolOutputServiceClient } from "../../common/protocol/tool-output-service";
|
||||
import { injectable, inject } from "inversify";
|
||||
import { OutputChannelManager } from "@theia/output/lib/common/output-channel";
|
||||
import { OutputContribution } from "@theia/output/lib/browser/output-contribution";
|
||||
import { ToolOutputServiceClient } from '../../common/protocol/tool-output-service';
|
||||
import { injectable, inject } from 'inversify';
|
||||
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
|
||||
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
|
||||
|
||||
@injectable()
|
||||
export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
|
||||
@ -13,9 +13,9 @@ export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
|
||||
protected readonly outputContribution: OutputContribution;
|
||||
|
||||
onNewOutput(tool: string, chunk: string): void {
|
||||
this.outputContribution.openView({ reveal: true }).then(() => {
|
||||
this.outputContribution.openView().then(() => {
|
||||
const channel = this.outputChannelManager.getChannel(`Arduino: ${tool}`);
|
||||
channel.setVisibility(true);
|
||||
channel.show();
|
||||
channel.append(chunk);
|
||||
});
|
||||
}
|
||||
|
@ -68,7 +68,9 @@ export interface BoardsService extends Installable<BoardsPackage>, Searchable<Bo
|
||||
getBoardDetails(options: { fqbn: string }): Promise<BoardDetails>;
|
||||
getBoardPackage(options: { id: string }): Promise<BoardsPackage | undefined>;
|
||||
getContainerBoardPackage(options: { fqbn: string }): Promise<BoardsPackage | undefined>;
|
||||
searchBoards(options: { query?: string }): Promise<Array<Board & { packageName: string }>>;
|
||||
// The CLI cannot do fuzzy search. This method provides all boards and we do the fuzzy search (with monaco) on the frontend.
|
||||
// https://github.com/arduino/arduino-cli/issues/629
|
||||
allBoards(options: {}): Promise<Array<Board & { packageName: string }>>;
|
||||
}
|
||||
|
||||
export interface Port {
|
||||
@ -94,6 +96,10 @@ export namespace Port {
|
||||
}
|
||||
}
|
||||
|
||||
export function is(arg: any): arg is Port {
|
||||
return !!arg && 'address' in arg && typeof arg['address'] === 'string' && 'protocol' in arg && typeof arg['protocol'] === 'string';
|
||||
}
|
||||
|
||||
export function toString(port: Port, options: { useLabel: boolean } = { useLabel: false }): string {
|
||||
if (options.useLabel && port.label) {
|
||||
return `${port.address} ${port.label}`
|
||||
@ -199,6 +205,7 @@ export interface BoardDetails {
|
||||
readonly fqbn: string;
|
||||
readonly requiredTools: Tool[];
|
||||
readonly configOptions: ConfigOption[];
|
||||
readonly programmers: Programmer[];
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
@ -223,18 +230,8 @@ export namespace ConfigOption {
|
||||
* Appends the configuration options to the `fqbn` argument.
|
||||
* Throws an error if the `fqbn` does not have the `segment(':'segment)*` format.
|
||||
* The provided output format is always segment(':'segment)*(':'option'='value(','option'='value)*)?
|
||||
* Validation can be disabled with the `{ validation: false }` option.
|
||||
*/
|
||||
export function decorate(fqbn: string, configOptions: ConfigOption[], { validate } = { validate: true }): string {
|
||||
if (validate) {
|
||||
if (!isValidFqbn(fqbn)) {
|
||||
throw new ConfigOptionError(`${fqbn} is not a valid FQBN.`);
|
||||
}
|
||||
if (isValidFqbnWithOptions(fqbn)) {
|
||||
throw new ConfigOptionError(`${fqbn} is already decorated with the configuration options.`);
|
||||
}
|
||||
}
|
||||
|
||||
export function decorate(fqbn: string, configOptions: ConfigOption[]): string {
|
||||
if (!configOptions.length) {
|
||||
return fqbn;
|
||||
}
|
||||
@ -256,14 +253,6 @@ export namespace ConfigOption {
|
||||
return `${fqbn}:${options}`;
|
||||
}
|
||||
|
||||
export function isValidFqbn(fqbn: string): boolean {
|
||||
return /^\w+(:\w+)*$/.test(fqbn);
|
||||
}
|
||||
|
||||
export function isValidFqbnWithOptions(fqbn: string): boolean {
|
||||
return /^\w+(:\w+)*(:\w+=\w+(,\w+=\w+)*)$/.test(fqbn);
|
||||
}
|
||||
|
||||
export class ConfigOptionError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
@ -281,6 +270,25 @@ export interface ConfigValue {
|
||||
readonly selected: boolean;
|
||||
}
|
||||
|
||||
export interface Programmer {
|
||||
readonly name: string;
|
||||
readonly platform: string;
|
||||
readonly id: string;
|
||||
}
|
||||
export namespace Programmer {
|
||||
|
||||
export function equals(left: Programmer | undefined, right: Programmer | undefined): boolean {
|
||||
if (!left) {
|
||||
return !right;
|
||||
}
|
||||
if (!right) {
|
||||
return !left;
|
||||
}
|
||||
return left.id === right.id && left.name === right.name && left.platform === right.platform;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace Board {
|
||||
|
||||
export function is(board: any): board is Board {
|
||||
@ -351,5 +359,4 @@ export namespace Board {
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||
import { Programmer } from './boards-service';
|
||||
|
||||
export const CoreServiceClient = Symbol('CoreServiceClient');
|
||||
export interface CoreServiceClient {
|
||||
@ -19,6 +20,7 @@ export namespace CoreService {
|
||||
readonly sketchUri: string;
|
||||
readonly fqbn: string;
|
||||
readonly optimizeForDebug: boolean;
|
||||
readonly programmer?: Programmer | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
export const FileSystemExtPath = '/services/file-system-ext';
|
||||
export const FileSystemExt = Symbol('FileSystemExt');
|
||||
export interface FileSystemExt {
|
||||
getUri(fsPath: string): Promise<string>;
|
||||
}
|
@ -3,6 +3,7 @@ export * from './arduino-daemon';
|
||||
export * from './boards-service';
|
||||
export * from './config-service';
|
||||
export * from './core-service';
|
||||
export * from './filesystem-ext';
|
||||
export * from './installable';
|
||||
export * from './library-service';
|
||||
export * from './monitor-service';
|
||||
|
@ -1,22 +1,49 @@
|
||||
export const SketchesServicePath = '/services/sketches-service';
|
||||
export const SketchesService = Symbol('SketchesService');
|
||||
export interface SketchesService {
|
||||
|
||||
/**
|
||||
* Returns with the direct sketch folders from the location of the `fileStat`.
|
||||
* The sketches returns with inverse-chronological order, the first item is the most recent one.
|
||||
*/
|
||||
getSketches(uri?: string): Promise<Sketch[]>
|
||||
getSketchFiles(uri: string): Promise<string[]>
|
||||
getSketches(uri?: string): Promise<Sketch[]>;
|
||||
|
||||
getSketchFiles(uri: string): Promise<string[]>;
|
||||
|
||||
/**
|
||||
* Creates a new sketch folder in the `parentUri` location.
|
||||
* Normally, `parentUri` is the client's workspace root, or the default `sketchDirUri` from the CLI.
|
||||
* Note, `parentUri` and `sketchDirUri` can be the same.
|
||||
* Creates a new sketch folder in the temp location.
|
||||
*/
|
||||
createNewSketch(parentUri: string): Promise<Sketch>
|
||||
isSketchFolder(uri: string): Promise<boolean>
|
||||
createNewSketch(): Promise<Sketch>;
|
||||
|
||||
isSketchFolder(uri: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Sketches are created to the temp location by default and will be moved under `directories.user` on save.
|
||||
* This method resolves to `true` if the `sketch` is still in the temp location. Otherwise, `false`.
|
||||
*/
|
||||
isTemp(sketch: Sketch): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* If `isTemp` is `true` for the `sketch`, you can call this method to move the sketch from the temp
|
||||
* location to `directories.user`. Resolves with the URI of the sketch after the move. Rejects, when the sketch
|
||||
* was not in the temp folder. This method always overrides. It's the callers responsibility to ask the user whether
|
||||
* the files at the destination can be overwritten or not.
|
||||
*/
|
||||
copy(sketch: Sketch, options: { destinationUri: string }): Promise<string>;
|
||||
|
||||
/**
|
||||
* Returns with the container sketch for the input `uri`. If the `uri` is not in a sketch folder, resolved `undefined`.
|
||||
*/
|
||||
getSketchFolder(uri: string): Promise<Sketch | undefined>;
|
||||
|
||||
}
|
||||
|
||||
export interface Sketch {
|
||||
readonly name: string;
|
||||
readonly uri: string
|
||||
readonly uri: string;
|
||||
}
|
||||
export namespace Sketch {
|
||||
export function is(arg: any): arg is Sketch {
|
||||
return !!arg && 'name' in arg && 'uri' in arg && typeof arg.name === 'string' && typeof arg.uri === 'string';
|
||||
}
|
||||
}
|
@ -1 +1,5 @@
|
||||
export const naturalCompare: (left: string, right: string) => number = require('string-natural-compare').caseInsensitive;
|
||||
|
||||
export function notEmpty(arg: string | undefined | null): arg is string {
|
||||
return !!arg;
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { ElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
|
||||
import { MainMenuManager } from '../../browser/menu/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class ElectronArduinoMenuContribution extends ElectronMenuContribution implements MainMenuManager {
|
||||
|
||||
protected hideTopPanel(): void {
|
||||
// NOOP
|
||||
// We reuse the `div` for the Arduino toolbar.
|
||||
}
|
||||
|
||||
update(): void {
|
||||
(this as any).setMenu();
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { ElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'
|
||||
import { ElectronArduinoMenuContribution } from './electron-arduino-menu-contribution';
|
||||
import { MainMenuManager } from '../../browser/menu/main-menu-manager';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ElectronArduinoMenuContribution).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(ElectronArduinoMenuContribution);
|
||||
rebind(ElectronMenuContribution).to(ElectronArduinoMenuContribution);
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import { injectable } from 'inversify'
|
||||
import { remote } from 'electron';
|
||||
import { Keybinding } from '@theia/core/lib/common/keybinding';
|
||||
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
|
||||
import { ArduinoMenus } from '../../../browser/menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
|
||||
|
||||
protected acceleratorFor(keybinding: Keybinding): string {
|
||||
// TODO: https://github.com/eclipse-theia/theia/issues/8207
|
||||
return this.keybindingRegistry.resolveKeybinding(keybinding)
|
||||
.map(binding => this.keybindingRegistry.acceleratorForKeyCode(binding, '+'))
|
||||
.join('')
|
||||
.replace('←', 'Left')
|
||||
.replace('→', 'Right');
|
||||
}
|
||||
|
||||
protected createOSXMenu(): Electron.MenuItemConstructorOptions {
|
||||
const { submenu } = super.createOSXMenu();
|
||||
const label = 'Arduino Pro IDE';
|
||||
if (!!submenu && !(submenu instanceof remote.Menu)) {
|
||||
const [about, , ...rest] = submenu;
|
||||
const menuModel = this.menuProvider.getMenu(ArduinoMenus.FILE__SETTINGS_GROUP);
|
||||
const settings = this.fillMenuTemplate([], menuModel);
|
||||
return {
|
||||
label,
|
||||
submenu: [
|
||||
about, // TODO: we have two about dialogs! one from electron the other from Theia.
|
||||
{ type: 'separator' },
|
||||
...settings,
|
||||
{ type: 'separator' },
|
||||
...rest
|
||||
]
|
||||
};
|
||||
}
|
||||
return { label, submenu };
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import { ElectronMenuContribution as TheiaElectronMenuContribution, ElectronCommands } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class ElectronMenuContribution extends TheiaElectronMenuContribution implements MainMenuManager {
|
||||
|
||||
protected hideTopPanel(): void {
|
||||
// NOOP
|
||||
// We reuse the `div` for the Arduino toolbar.
|
||||
}
|
||||
|
||||
update(): void {
|
||||
(this as any).setMenu();
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
registry.unregisterCommand(ElectronCommands.CLOSE_WINDOW);
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
registry.unregisterMenuAction(ElectronCommands.CLOSE_WINDOW);
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
registry.unregisterKeybinding(ElectronCommands.CLOSE_WINDOW.id);
|
||||
registry.unregisterKeybinding(ElectronCommands.ZOOM_IN.id);
|
||||
registry.unregisterKeybinding(ElectronCommands.ZOOM_OUT.id);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { ContainerModule } from 'inversify';
|
||||
import { ElectronMenuContribution as TheiaElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'
|
||||
import { ElectronMenuContribution } from './electron-menu-contribution';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
|
||||
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(ElectronMenuContribution).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(ElectronMenuContribution);
|
||||
rebind(TheiaElectronMenuContribution).to(ElectronMenuContribution);
|
||||
bind(ElectronMainMenuFactory).toSelf().inRequestScope();
|
||||
rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory);
|
||||
});
|
@ -166,11 +166,13 @@ export class ArduinoDaemonImpl implements ArduinoDaemon, BackendApplicationContr
|
||||
if (error) {
|
||||
ready.reject(error);
|
||||
}
|
||||
if (message.includes('Daemon is listening on TCP port')) {
|
||||
for (const expected of ['Daemon is listening on TCP port', 'Daemon is now listening on 127.0.0.1']) {
|
||||
if (message.includes(expected)) {
|
||||
grpcServerIsReady = true;
|
||||
ready.resolve(daemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
daemon.stderr.on('data', data => {
|
||||
const message = data.toString();
|
||||
|
@ -8,6 +8,6 @@ import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-a
|
||||
@injectable()
|
||||
export class ArduinoEnvVariablesServer extends EnvVariablesServerImpl {
|
||||
|
||||
protected readonly configDirUri = FileUri.create(join(homedir(), BackendApplicationConfigProvider.get().configDirName)).toString();
|
||||
protected readonly configDirUri = Promise.resolve(FileUri.create(join(homedir(), BackendApplicationConfigProvider.get().configDirName)).toString());
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ import { HostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-rea
|
||||
import { ConfigFileValidator } from './config-file-validator';
|
||||
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||
import { ArduinoEnvVariablesServer } from './arduino-env-variables-server';
|
||||
import { NodeFileSystemExt } from './node-filesystem-ext';
|
||||
import { FileSystemExt, FileSystemExtPath } from '../common/protocol/filesystem-ext';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
rebind(EnvVariablesServer).to(ArduinoEnvVariablesServer).inSingletonScope();
|
||||
@ -185,4 +187,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
|
||||
bind(ArduinoHostedPluginReader).toSelf().inSingletonScope();
|
||||
rebind(HostedPluginReader).toService(ArduinoHostedPluginReader);
|
||||
|
||||
// File-system extension for mapping paths to URIs
|
||||
bind(NodeFileSystemExt).toSelf().inSingletonScope();
|
||||
bind(FileSystemExt).toDynamicValue(context => context.container.get(NodeFileSystemExt));
|
||||
bind(ConnectionHandler).toDynamicValue(context => new JsonRpcConnectionHandler(FileSystemExtPath, () => context.container.get(FileSystemExt))).inSingletonScope();
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { injectable, inject, postConstruct, named } from 'inversify';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { BoardsService, BoardsPackage, Board, BoardsServiceClient, Port, BoardDetails, Tool, ConfigOption, ConfigValue } from '../common/protocol';
|
||||
import { BoardsService, BoardsPackage, Board, BoardsServiceClient, Port, BoardDetails, Tool, ConfigOption, ConfigValue, Programmer } from '../common/protocol';
|
||||
import {
|
||||
PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq,
|
||||
PlatformListResp, Platform, PlatformUninstallResp, PlatformUninstallReq
|
||||
@ -10,6 +10,7 @@ import { CoreClientProvider } from './core-client-provider';
|
||||
import { BoardListReq, BoardListResp, BoardDetailsReq, BoardDetailsResp } from './cli-protocol/commands/board_pb';
|
||||
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
|
||||
import { Installable } from '../common/protocol/installable';
|
||||
import { ListProgrammersAvailableForUploadReq, ListProgrammersAvailableForUploadResp } from './cli-protocol/commands/upload_pb';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceImpl implements BoardsService {
|
||||
@ -209,10 +210,10 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
const { client, instance } = coreClient;
|
||||
|
||||
const { fqbn } = options;
|
||||
const req = new BoardDetailsReq();
|
||||
req.setInstance(instance);
|
||||
req.setFqbn(fqbn);
|
||||
const resp = await new Promise<BoardDetailsResp>((resolve, reject) => client.boardDetails(req, (err, resp) => {
|
||||
const detailsReq = new BoardDetailsReq();
|
||||
detailsReq.setInstance(instance);
|
||||
detailsReq.setFqbn(fqbn);
|
||||
const detailsResp = await new Promise<BoardDetailsResp>((resolve, reject) => client.boardDetails(detailsReq, (err, resp) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
@ -220,13 +221,13 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
resolve(resp);
|
||||
}));
|
||||
|
||||
const requiredTools = resp.getToolsdependenciesList().map(t => <Tool>{
|
||||
const requiredTools = detailsResp.getToolsdependenciesList().map(t => <Tool>{
|
||||
name: t.getName(),
|
||||
packager: t.getPackager(),
|
||||
version: t.getVersion()
|
||||
});
|
||||
|
||||
const configOptions = resp.getConfigOptionsList().map(c => <ConfigOption>{
|
||||
const configOptions = detailsResp.getConfigOptionsList().map(c => <ConfigOption>{
|
||||
label: c.getOptionLabel(),
|
||||
option: c.getOption(),
|
||||
values: c.getValuesList().map(v => <ConfigValue>{
|
||||
@ -236,10 +237,28 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
})
|
||||
});
|
||||
|
||||
const listReq = new ListProgrammersAvailableForUploadReq();
|
||||
listReq.setInstance(instance);
|
||||
listReq.setFqbn(fqbn);
|
||||
const listResp = await new Promise<ListProgrammersAvailableForUploadResp>((resolve, reject) => client.listProgrammersAvailableForUpload(listReq, (err, resp) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(resp);
|
||||
}));
|
||||
|
||||
const programmers = listResp.getProgrammersList().map(p => <Programmer>{
|
||||
id: p.getId(),
|
||||
name: p.getName(),
|
||||
platform: p.getPlatform()
|
||||
});
|
||||
|
||||
return {
|
||||
fqbn,
|
||||
requiredTools,
|
||||
configOptions
|
||||
configOptions,
|
||||
programmers
|
||||
};
|
||||
}
|
||||
|
||||
@ -261,13 +280,10 @@ export class BoardsServiceImpl implements BoardsService {
|
||||
return packages.find(({ boards }) => boards.some(({ fqbn }) => fqbn === expectedFqbn));
|
||||
}
|
||||
|
||||
async searchBoards(options: { query?: string }): Promise<Array<Board & { packageName: string }>> {
|
||||
const query = (options.query || '').toLocaleLowerCase();
|
||||
async allBoards(options: {}): Promise<Array<Board & { packageName: string }>> {
|
||||
const results = await this.search(options);
|
||||
return results.map(item => item.boards.map(board => ({ ...board, packageName: item.name })))
|
||||
.reduce((acc, curr) => acc.concat(curr), [])
|
||||
.filter(board => board.name.toLocaleLowerCase().indexOf(query) !== -1)
|
||||
.sort(Board.compare);
|
||||
.reduce((acc, curr) => acc.concat(curr), []);
|
||||
}
|
||||
|
||||
async search(options: { query?: string }): Promise<BoardsPackage[]> {
|
||||
|
@ -13,6 +13,7 @@ import * as fs from './fs-extra';
|
||||
import { spawnCommand } from './exec-util';
|
||||
import { RawData } from './cli-protocol/settings/settings_pb';
|
||||
import { SettingsClient } from './cli-protocol/settings/settings_grpc_pb';
|
||||
import * as serviceGrpcPb from './cli-protocol/settings/settings_grpc_pb';
|
||||
import { ConfigFileValidator } from './config-file-validator';
|
||||
import { ArduinoDaemonImpl } from './arduino-daemon-impl';
|
||||
import { DefaultCliConfig, CLI_CONFIG_SCHEMA_PATH, CLI_CONFIG } from './cli-config';
|
||||
@ -245,7 +246,10 @@ export class ConfigServiceImpl implements BackendApplicationContribution, Config
|
||||
}
|
||||
|
||||
protected async updateDaemon(port: string | number, config: DefaultCliConfig): Promise<void> {
|
||||
const client = new SettingsClient(`localhost:${port}`, grpc.credentials.createInsecure());
|
||||
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
|
||||
// @ts-ignore
|
||||
const SettingsClient = grpc.makeClientConstructor(serviceGrpcPb['cc.arduino.cli.settings.Settings'], 'SettingsService') as any;
|
||||
const client = new SettingsClient(`localhost:${port}`, grpc.credentials.createInsecure()) as SettingsClient;
|
||||
const data = new RawData();
|
||||
data.setJsondata(JSON.stringify(config, null, 2));
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
@ -35,7 +35,7 @@ export class CoreServiceImpl implements CoreService {
|
||||
}
|
||||
|
||||
async compile(options: CoreService.Compile.Options): Promise<void> {
|
||||
console.log('compile', options);
|
||||
this.toolOutputService.publishNewOutput('compile', 'Compiling...\n' + JSON.stringify(options, null, 2) + '\n');
|
||||
const { sketchUri, fqbn } = options;
|
||||
const sketchFilePath = await this.fileSystem.getFsPath(sketchUri);
|
||||
if (!sketchFilePath) {
|
||||
@ -61,27 +61,30 @@ export class CoreServiceImpl implements CoreService {
|
||||
compilerReq.setPreprocess(false);
|
||||
compilerReq.setVerbose(true);
|
||||
compilerReq.setQuiet(false);
|
||||
if (options.programmer) {
|
||||
compilerReq.setProgrammer(options.programmer.id);
|
||||
}
|
||||
|
||||
const result = client.compile(compilerReq);
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
result.on('data', (cr: CompileResp) => {
|
||||
this.toolOutputService.publishNewOutput("compile", Buffer.from(cr.getOutStream_asU8()).toString());
|
||||
this.toolOutputService.publishNewOutput("compile error", Buffer.from(cr.getErrStream_asU8()).toString());
|
||||
this.toolOutputService.publishNewOutput("compile", Buffer.from(cr.getErrStream_asU8()).toString());
|
||||
});
|
||||
result.on('error', error => reject(error));
|
||||
result.on('end', () => resolve());
|
||||
});
|
||||
this.toolOutputService.publishNewOutput("compile", "Compilation complete\n");
|
||||
this.toolOutputService.publishNewOutput("compile", "Compilation complete.\n");
|
||||
} catch (e) {
|
||||
this.toolOutputService.publishNewOutput("compile error", `Compilation error: ${e}\n`);
|
||||
this.toolOutputService.publishNewOutput("compile", `Compilation error: ${e}\n`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async upload(options: CoreService.Upload.Options): Promise<void> {
|
||||
await this.compile(options);
|
||||
console.log('upload', options);
|
||||
this.toolOutputService.publishNewOutput('upload', 'Uploading...\n' + JSON.stringify(options, null, 2) + '\n');
|
||||
const { sketchUri, fqbn } = options;
|
||||
const sketchFilePath = await this.fileSystem.getFsPath(sketchUri);
|
||||
if (!sketchFilePath) {
|
||||
@ -89,7 +92,6 @@ export class CoreServiceImpl implements CoreService {
|
||||
}
|
||||
const sketchpath = path.dirname(sketchFilePath);
|
||||
|
||||
|
||||
const coreClient = await this.coreClientProvider.client();
|
||||
if (!coreClient) {
|
||||
return;
|
||||
@ -100,25 +102,28 @@ export class CoreServiceImpl implements CoreService {
|
||||
throw new Error('The selected board has no FQBN.');
|
||||
}
|
||||
|
||||
const req = new UploadReq();
|
||||
req.setInstance(instance);
|
||||
req.setSketchPath(sketchpath);
|
||||
req.setFqbn(fqbn);
|
||||
req.setPort(options.port);
|
||||
const result = client.upload(req);
|
||||
const uploadReq = new UploadReq();
|
||||
uploadReq.setInstance(instance);
|
||||
uploadReq.setSketchPath(sketchpath);
|
||||
uploadReq.setFqbn(fqbn);
|
||||
uploadReq.setPort(options.port);
|
||||
if (options.programmer) {
|
||||
uploadReq.setProgrammer(options.programmer.id);
|
||||
}
|
||||
const result = client.upload(uploadReq);
|
||||
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
result.on('data', (cr: UploadResp) => {
|
||||
this.toolOutputService.publishNewOutput("upload", Buffer.from(cr.getOutStream_asU8()).toString());
|
||||
this.toolOutputService.publishNewOutput("upload error", Buffer.from(cr.getErrStream_asU8()).toString());
|
||||
this.toolOutputService.publishNewOutput("upload", Buffer.from(cr.getErrStream_asU8()).toString());
|
||||
});
|
||||
result.on('error', error => reject(error));
|
||||
result.on('end', () => resolve());
|
||||
});
|
||||
this.toolOutputService.publishNewOutput("upload", "Upload complete\n");
|
||||
this.toolOutputService.publishNewOutput("upload", "Upload complete.\n");
|
||||
} catch (e) {
|
||||
this.toolOutputService.publishNewOutput("upload error", `Upload error: ${e}\n`);
|
||||
this.toolOutputService.publishNewOutput("upload", `Upload error: ${e}\n`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
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