diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 00b21f99..22e10781 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -1,5 +1,5 @@ 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'; @@ -101,6 +101,7 @@ 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-contributions'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -111,7 +112,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(); @@ -329,4 +330,5 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un Contribution.configure(bind, OpenSketchExternal); Contribution.configure(bind, EditContributions); Contribution.configure(bind, QuitApp); + Contribution.configure(bind, SketchControl); }); diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch.ts b/arduino-ide-extension/src/browser/contributions/open-sketch.ts index 644b0bd9..fb471f9c 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch.ts @@ -1,9 +1,8 @@ import { inject, injectable } from 'inversify'; import { remote } from 'electron'; -import { Disposable } from '@theia/languages/lib/browser'; import { MaybePromise } from '@theia/core/lib/common/types'; -import { DisposableCollection } from '@theia/core/lib/common/disposable'; 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'; @@ -28,37 +27,40 @@ export class OpenSketch extends SketchContribution { if (!sketches.length) { this.openSketch(); } else { + if (!(target instanceof HTMLElement)) { return; } - const toDisposeOnClose = new DisposableCollection(); + const { parentElement } = target; + if (!parentElement) { + return; + } + + const toDisposeOnHide = new DisposableCollection(); this.menuRegistry.registerMenuAction(ArduinoMenus.OPEN_SKETCH__CONTEXT__OPEN_GROUP, { commandId: OpenSketch.Commands.OPEN_SKETCH.id, label: 'Open...' }); - toDisposeOnClose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(OpenSketch.Commands.OPEN_SKETCH))); + toDisposeOnHide.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) }; - toDisposeOnClose.push(registry.registerCommand(command, handler)); + toDisposeOnHide.push(registry.registerCommand(command, handler)); this.menuRegistry.registerMenuAction(ArduinoMenus.OPEN_SKETCH__CONTEXT__RECENT_GROUP, { commandId: command.id, label: sketch.name }); - toDisposeOnClose.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))); + toDisposeOnHide.push(Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))); } - const { parentElement } = target; - if (parentElement) { - const options = { - menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT, - anchor: { - x: parentElement.getBoundingClientRect().left, - y: parentElement.getBoundingClientRect().top + parentElement.offsetHeight - }, - onHide: () => toDisposeOnClose.dispose() - } - this.contextMenuRenderer.render(options); + const options = { + menuPath: ArduinoMenus.OPEN_SKETCH__CONTEXT, + anchor: { + x: parentElement.getBoundingClientRect().left, + y: parentElement.getBoundingClientRect().top + parentElement.offsetHeight + }, + onHide: () => toDisposeOnHide.dispose() } + this.contextMenuRenderer.render(options); } } }); diff --git a/arduino-ide-extension/src/browser/contributions/sketch-control-contributions.ts b/arduino-ide-extension/src/browser/contributions/sketch-control-contributions.ts new file mode 100644 index 00000000..5251ad00 --- /dev/null +++ b/arduino-ide-extension/src/browser/contributions/sketch-control-contributions.ts @@ -0,0 +1,138 @@ +import { inject, injectable } from 'inversify'; +import { EditorManager } from '@theia/editor/lib/browser/editor-manager'; +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 } from './contribution'; +import { ArduinoMenus } from '../menu/arduino-menus'; + +@injectable() +export class SketchControl extends SketchContribution { + + @inject(ApplicationShell) + protected readonly shell: ApplicationShell; + + @inject(EditorManager) + protected readonly editorManager: EditorManager; + + @inject(MenuModelRegistry) + protected readonly menuRegistry: MenuModelRegistry; + + @inject(ContextMenuRenderer) + protected readonly contextMenuRenderer: ContextMenuRenderer; + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(SketchControl.Commands.OPEN_SKETCH_CONTROL__TOOLBAR, { + isVisible: widget => this.shell.getWidgets('main').indexOf(widget) !== -1, + execute: async () => { + const toDisposeOnHide = new DisposableCollection(); + 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: () => { + console.log('bar'); + this.editorManager.open(uri, { mode: 'activate' }); + console.log('foo'); + } + }; + toDisposeOnHide.push(registry.registerCommand(command, handler)); + this.menuRegistry.registerMenuAction(ArduinoMenus.SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP, { + commandId: command.id, + label: uri.path.base, + order: `${i}` + }); + toDisposeOnHide.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 + }, + onHide: () => toDisposeOnHide.dispose() + } + 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' + }; + } +} diff --git a/arduino-ide-extension/src/browser/menu/arduino-menus.ts b/arduino-ide-extension/src/browser/menu/arduino-menus.ts index 0685de28..a0606ea8 100644 --- a/arduino-ide-extension/src/browser/menu/arduino-menus.ts +++ b/arduino-ide-extension/src/browser/menu/arduino-menus.ts @@ -32,4 +32,10 @@ export namespace ArduinoMenus { 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 (such as `New Tab`, `Rename`, `Delete`, etc.) + export const SKETCH_CONTROL__CONTEXT = ['arduino-sketch-control--context']; + export const SKETCH_CONTROL__CONTEXT__MAIN_GROUP = [...SKETCH_CONTROL__CONTEXT, '0_main']; + export const SKETCH_CONTROL__CONTEXT__NAVIGATION_GROUP = [...SKETCH_CONTROL__CONTEXT, '1_navigation']; + export const SKETCH_CONTROL__CONTEXT__RESOURCES_GROUP = [...SKETCH_CONTROL__CONTEXT, '2_resources']; + } diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts index 03e64712..b1409761 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-frontend-contribution.ts @@ -1,6 +1,7 @@ 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'; @@ -26,6 +27,15 @@ export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContrib 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() diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts new file mode 100644 index 00000000..e952c991 --- /dev/null +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-main-menu-factory.ts @@ -0,0 +1,17 @@ +import { injectable } from 'inversify' +import { Keybinding } from '@theia/core/lib/common/keybinding'; +import { ElectronMainMenuFactory as TheiaElectronMainMenuFactory } from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory'; + +@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'); + } + +} diff --git a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts index ac7acde6..b39dee8c 100644 --- a/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts +++ b/arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts @@ -2,9 +2,13 @@ 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); });