chore: updated to Theia 1.37.0

- Updated `@theia/*` to `1.37.0`.
 - Fixed all `yarn audit` security vulnerabilities.
 - Updated to `electron@23.2.4`:
   - `contextIsolation` is `true`,
   - `nodeIntegration` is `false`, and the
   - `webpack` target is moved from `electron-renderer` to `web`.
 - Updated to `typescript@4.9.3`.
 - Updated the `eslint` plugins.
 - Added the new `Light High Contrast` theme to the IDE2.
 - High contrast themes use Theia APIs for style adjustments.
 - Support for ESM modules: `"moduleResolution": "node16"`.
 - Node.js >= 16.14 is required.
 - VISX langage packs were bumped to `1.70.0`.
 - Removed undesired editor context menu items. (Closes #1394)

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2023-03-13 14:40:31 +01:00 committed by Akos Kitta
parent 964ea3bc0c
commit 192aac5a81
167 changed files with 6173 additions and 6467 deletions

View File

@ -16,6 +16,7 @@ module.exports = {
'docs/*',
'scripts/*',
'electron-app/*',
'!electron-app/webpack.config.js',
'plugins/*',
'arduino-ide-extension/src/node/cli-protocol',
],

View File

@ -42,7 +42,7 @@ jobs:
- os: windows-2019
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX # Name of the secret that contains the certificate.
certificate-password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD # Name of the secret that contains the certificate password.
certificate-extension: pfx # File extension for the certificate.
certificate-extension: pfx # File extension for the certificate.
- os: ubuntu-20.04
- os: macos-latest
# APPLE_SIGNING_CERTIFICATE_P12 secret was produced by following the procedure from:
@ -57,10 +57,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node.js 16.x
- name: Install Node.js 16.14
uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '16.14'
registry-url: 'https://registry.npmjs.org'
- name: Install Python 3.x

View File

@ -29,10 +29,10 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Node.js 16.x
- name: Install Node.js 16.14
uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '16.14'
registry-url: 'https://registry.npmjs.org'
- name: Install Go

View File

@ -16,10 +16,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node.js 16.x
- name: Install Node.js 16.14
uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '16.14'
registry-url: 'https://registry.npmjs.org'
- name: Install Go

View File

@ -16,10 +16,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install Node.js 16.x
- name: Install Node.js 16.14
uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '16.14'
registry-url: 'https://registry.npmjs.org'
- name: Install Go

2
.gitignore vendored
View File

@ -7,7 +7,7 @@ build/
arduino-ide-extension/Examples/
!electron/build/
src-gen/
webpack.config.js
electron/build/webpack.config.js
gen-webpack.config.js
.DS_Store
# switching from `electron` to `browser` in dev mode.

View File

@ -9,7 +9,7 @@
"compose-changelog": "node ./scripts/compose-changelog.js",
"download-cli": "node ./scripts/download-cli.js",
"download-fwuploader": "node ./scripts/download-fwuploader.js",
"copy-i18n": "npx ncp ../i18n ./build/i18n",
"copy-i18n": "ncp ../i18n ./build/i18n",
"download-ls": "node ./scripts/download-ls.js",
"download-examples": "node ./scripts/download-examples.js",
"generate-protocol": "node ./scripts/generate-protocol.js",
@ -21,28 +21,28 @@
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
},
"dependencies": {
"@grpc/grpc-js": "^1.6.7",
"@theia/application-package": "1.31.1",
"@theia/core": "1.31.1",
"@theia/debug": "1.31.1",
"@theia/editor": "1.31.1",
"@theia/electron": "1.31.1",
"@theia/filesystem": "1.31.1",
"@theia/keymaps": "1.31.1",
"@theia/markers": "1.31.1",
"@theia/messages": "1.31.1",
"@theia/monaco": "1.31.1",
"@theia/monaco-editor-core": "1.67.2",
"@theia/navigator": "1.31.1",
"@theia/outline-view": "1.31.1",
"@theia/output": "1.31.1",
"@theia/plugin-ext": "1.31.1",
"@theia/preferences": "1.31.1",
"@theia/scm": "1.31.1",
"@theia/search-in-workspace": "1.31.1",
"@theia/terminal": "1.31.1",
"@theia/typehierarchy": "1.31.1",
"@theia/workspace": "1.31.1",
"@grpc/grpc-js": "^1.8.14",
"@theia/application-package": "1.37.0",
"@theia/core": "1.37.0",
"@theia/debug": "1.37.0",
"@theia/editor": "1.37.0",
"@theia/electron": "1.37.0",
"@theia/filesystem": "1.37.0",
"@theia/keymaps": "1.37.0",
"@theia/markers": "1.37.0",
"@theia/messages": "1.37.0",
"@theia/monaco": "1.37.0",
"@theia/monaco-editor-core": "1.72.3",
"@theia/navigator": "1.37.0",
"@theia/outline-view": "1.37.0",
"@theia/output": "1.37.0",
"@theia/plugin-ext": "1.37.0",
"@theia/preferences": "1.37.0",
"@theia/scm": "1.37.0",
"@theia/search-in-workspace": "1.37.0",
"@theia/terminal": "1.37.0",
"@theia/typehierarchy": "1.37.0",
"@theia/workspace": "1.37.0",
"@tippyjs/react": "^4.2.5",
"@types/auth0-js": "^9.14.0",
"@types/btoa": "^1.2.3",
@ -51,6 +51,7 @@
"@types/glob": "^7.2.0",
"@types/google-protobuf": "^3.7.2",
"@types/js-yaml": "^3.12.2",
"@types/jsdom": "^21.1.1",
"@types/keytar": "^4.4.0",
"@types/lodash.debounce": "^4.0.6",
"@types/node-fetch": "^2.5.7",
@ -65,7 +66,7 @@
"auth0-js": "^9.14.0",
"btoa": "^1.2.1",
"classnames": "^2.3.1",
"cpy": "^8.1.2",
"cpy": "^10.0.0",
"cross-fetch": "^3.1.5",
"dateformat": "^3.0.3",
"deepmerge": "^4.2.2",
@ -76,8 +77,9 @@
"glob": "^7.1.6",
"google-protobuf": "^3.20.1",
"hash.js": "^1.1.7",
"is-online": "^9.0.1",
"is-online": "^10.0.0",
"js-yaml": "^3.13.1",
"jsdom": "^21.1.1",
"jsonc-parser": "^2.2.0",
"just-diff": "^5.1.1",
"jwt-decode": "^3.1.2",
@ -88,6 +90,7 @@
"open": "^8.0.6",
"p-debounce": "^2.1.0",
"p-queue": "^2.4.2",
"process": "^0.11.10",
"ps-tree": "^1.2.0",
"query-string": "^7.0.1",
"react-disable": "^0.1.1",
@ -101,6 +104,7 @@
"temp": "^0.9.1",
"temp-dir": "^2.0.0",
"tree-kill": "^1.2.1",
"util": "^0.12.5",
"which": "^1.3.1"
},
"devDependencies": {
@ -147,6 +151,9 @@
"examples"
],
"theiaExtensions": [
{
"preload": "lib/electron-browser/preload"
},
{
"backend": "lib/node/arduino-ide-backend-module",
"frontend": "lib/browser/arduino-ide-frontend-module"
@ -157,6 +164,9 @@
{
"frontendElectron": "lib/electron-browser/theia/core/electron-window-module"
},
{
"frontendElectron": "lib/electron-browser/electron-arduino-module"
},
{
"electronMain": "lib/electron-main/arduino-electron-main-module"
}

View File

@ -0,0 +1,13 @@
import type { Disposable } from '@theia/core/lib/common/disposable';
import type { StartupTasks } from '../electron-common/startup-task';
import type { Sketch } from './contributions/contribution';
export const AppService = Symbol('AppService');
export interface AppService {
quit(): void;
version(): Promise<string>;
registerStartupTasksHandler(
handler: (tasks: StartupTasks) => void
): Disposable;
scheduleDeletion(sketch: Sketch): void; // TODO: find a better place
}

View File

@ -1,41 +1,47 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import {
TabBarToolbarContribution,
TabBarToolbarRegistry,
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import {
ColorTheme,
CssStyleCollector,
StylingParticipant,
} from '@theia/core/lib/browser/styling-service';
import {
CommandContribution,
CommandRegistry,
} from '@theia/core/lib/common/command';
import {
MAIN_MENU_BAR,
MenuContribution,
MenuModelRegistry,
} from '@theia/core/lib/common/menu';
import { MessageService } from '@theia/core/lib/common/message-service';
import { nls } from '@theia/core/lib/common/nls';
import { isHighContrast } from '@theia/core/lib/common/theme';
import { ElectronWindowPreferences } from '@theia/core/lib/electron-browser/window/electron-window-preferences';
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
import {
MAIN_MENU_BAR,
MenuContribution,
MenuModelRegistry,
} from '@theia/core';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
import {
TabBarToolbarContribution,
TabBarToolbarRegistry,
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { nls } from '@theia/core/lib/common';
import {
CommandContribution,
CommandRegistry,
} from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service';
import { EditorCommands, EditorMainMenu } from '@theia/editor/lib/browser';
import { EditorCommands } from '@theia/editor/lib/browser/editor-command';
import { EditorMainMenu } from '@theia/editor/lib/browser/editor-menu';
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
import { ElectronWindowPreferences } from '@theia/core/lib/electron-browser/window/electron-window-preferences';
import { BoardsServiceProvider } from './boards/boards-service-provider';
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
import { ArduinoMenus } from './menu/arduino-menus';
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { SerialPlotterContribution } from './serial/plotter/plotter-frontend-contribution';
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
@injectable()
export class ArduinoFrontendContribution
@ -44,7 +50,8 @@ export class ArduinoFrontendContribution
TabBarToolbarContribution,
CommandContribution,
MenuContribution,
ColorContribution
ColorContribution,
StylingParticipant
{
@inject(MessageService)
private readonly messageService: MessageService;
@ -80,8 +87,7 @@ export class ArduinoFrontendContribution
switch (event.preferenceName) {
case 'window.zoomLevel':
if (typeof event.newValue === 'number') {
const webContents = remote.getCurrentWebContents();
webContents.setZoomLevel(event.newValue || 0);
window.electronTheiaCore.setZoomLevel(event.newValue || 0);
}
break;
}
@ -89,10 +95,9 @@ export class ArduinoFrontendContribution
});
this.appStateService.reachedState('ready').then(() =>
this.electronWindowPreferences.ready.then(() => {
const webContents = remote.getCurrentWebContents();
const zoomLevel =
this.electronWindowPreferences.get('window.zoomLevel');
webContents.setZoomLevel(zoomLevel);
window.electronTheiaCore.setZoomLevel(zoomLevel);
})
);
}
@ -168,7 +173,8 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'button.background',
light: 'button.background',
hc: 'activityBar.inactiveForeground',
hcDark: 'activityBar.inactiveForeground',
hcLight: 'activityBar.inactiveForeground',
},
description:
'Background color of the toolbar items. Such as Upload, Verify, etc.',
@ -178,7 +184,8 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'button.hoverBackground',
light: 'button.hoverBackground',
hc: 'button.background',
hcDark: 'button.background',
hcLight: 'button.background',
},
description:
'Background color of the toolbar items when hovering over them. Such as Upload, Verify, etc.',
@ -188,7 +195,8 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'secondaryButton.foreground',
light: 'button.foreground',
hc: 'activityBar.inactiveForeground',
hcDark: 'activityBar.inactiveForeground',
hcLight: 'activityBar.inactiveForeground',
},
description:
'Foreground color of the toolbar items. Such as Serial Monitor and Serial Plotter',
@ -198,7 +206,8 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'secondaryButton.hoverBackground',
light: 'button.hoverBackground',
hc: 'textLink.foreground',
hcDark: 'textLink.foreground',
hcLight: 'textLink.foreground',
},
description:
'Background color of the toolbar items when hovering over them, such as "Serial Monitor" and "Serial Plotter"',
@ -208,7 +217,8 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'editor.selectionBackground',
light: 'editor.selectionBackground',
hc: 'textPreformat.foreground',
hcDark: 'textPreformat.foreground',
hcLight: 'textPreformat.foreground',
},
description:
'Toggle color of the toolbar items when they are currently toggled (the command is in progress)',
@ -218,37 +228,38 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'dropdown.border',
light: 'dropdown.border',
hc: 'dropdown.border',
hcDark: 'dropdown.border',
hcLight: 'dropdown.border',
},
description: 'Border color of the Board Selector.',
},
{
id: 'arduino.toolbar.dropdown.borderActive',
defaults: {
dark: 'focusBorder',
light: 'focusBorder',
hc: 'focusBorder',
hcDark: 'focusBorder',
hcLight: 'focusBorder',
},
description: "Border color of the Board Selector when it's active",
},
{
id: 'arduino.toolbar.dropdown.background',
defaults: {
dark: 'tab.unfocusedActiveBackground',
light: 'dropdown.background',
hc: 'dropdown.background',
hcDark: 'dropdown.background',
hcLight: 'dropdown.background',
},
description: 'Background color of the Board Selector.',
},
{
id: 'arduino.toolbar.dropdown.label',
defaults: {
dark: 'dropdown.foreground',
light: 'dropdown.foreground',
hc: 'dropdown.foreground',
hcDark: 'dropdown.foreground',
hcLight: 'dropdown.foreground',
},
description: 'Font color of the Board Selector.',
},
@ -257,7 +268,8 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'list.activeSelectionIconForeground',
light: 'list.activeSelectionIconForeground',
hc: 'list.activeSelectionIconForeground',
hcDark: 'list.activeSelectionIconForeground',
hcLight: 'list.activeSelectionIconForeground',
},
description:
'Color of the selected protocol icon in the Board Selector.',
@ -267,7 +279,8 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'list.hoverBackground',
light: 'list.hoverBackground',
hc: 'list.hoverBackground',
hcDark: 'list.hoverBackground',
hcLight: 'list.hoverBackground',
},
description: 'Background color on hover of the Board Selector options.',
},
@ -276,11 +289,191 @@ export class ArduinoFrontendContribution
defaults: {
dark: 'list.activeSelectionBackground',
light: 'list.activeSelectionBackground',
hc: 'list.activeSelectionBackground',
hcDark: 'list.activeSelectionBackground',
hcLight: 'list.activeSelectionBackground',
},
description:
'Background color of the selected board in the Board Selector.',
}
);
}
registerThemeStyle(theme: ColorTheme, collector: CssStyleCollector): void {
const warningForeground = theme.getColor('warningForeground');
const warningBackground = theme.getColor('warningBackground');
const focusBorder = theme.getColor('focusBorder');
const contrastBorder = theme.getColor('contrastBorder');
const notificationsBackground = theme.getColor('notifications.background');
const buttonBorder = theme.getColor('button.border');
const buttonBackground = theme.getColor('button.background') || 'none';
const dropdownBackground = theme.getColor('dropdown.background');
const arduinoToolbarButtonBackground = theme.getColor(
'arduino.toolbar.button.background'
);
if (isHighContrast(theme.type)) {
// toolbar items
collector.addRule(`
.p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-monitor,
.p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-plotter {
background: transparent;
}
`);
if (contrastBorder) {
collector.addRule(`
.quick-input-widget {
outline: 1px solid ${contrastBorder};
outline-offset: -1px;
}
`);
}
if (focusBorder) {
// customized react-select widget
collector.addRule(`
.arduino-select__option--is-selected {
outline: 1px solid ${focusBorder};
}
`);
collector.addRule(`
.arduino-select__option--is-focused {
outline: 1px dashed ${focusBorder};
}
`);
// boards selector dropdown
collector.addRule(`
#select-board-dialog .selectBoardContainer .list .item:hover {
outline: 1px dashed ${focusBorder};
}
`);
// button hover
collector.addRule(`
.theia-button:hover,
button.theia-button:hover {
outline: 1px dashed ${focusBorder};
}
`);
collector.addRule(`
.theia-button {
border: 1px solid ${focusBorder};
}
`);
collector.addRule(`
.component-list-item .header .installed-version:hover:before {
background-color: transparent;
outline: 1px dashed ${focusBorder};
}
`);
// tree node
collector.addRule(`
.theia-TreeNode:hover {
outline: 1px dashed ${focusBorder};
}
`);
collector.addRule(`
.quick-input-list .monaco-list-row.focused,
.theia-Tree .theia-TreeNode.theia-mod-selected {
outline: 1px dotted ${focusBorder};
}
`);
collector.addRule(`
div#select-board-dialog .selectBoardContainer .list .item.selected,
.theia-Tree:focus .theia-TreeNode.theia-mod-selected,
.theia-Tree .ReactVirtualized__List:focus .theia-TreeNode.theia-mod-selected {
outline: 1px solid ${focusBorder};
}
`);
// quick input
collector.addRule(`
.quick-input-list .monaco-list-row:hover {
outline: 1px dashed ${focusBorder};
}
`);
// editor tab-bar
collector.addRule(`
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon:hover {
outline: 1px dashed ${focusBorder};
}
`);
collector.addRule(`
#theia-main-content-panel .p-TabBar .p-TabBar-tab:hover {
outline: 1px dashed ${focusBorder};
outline-offset: -4px;
}
`);
collector.addRule(`
#theia-main-content-panel .p-TabBar .p-TabBar-tab.p-mod-current {
outline: 1px solid ${focusBorder};
outline-offset: -4px;
}
`);
// boards selector dropdown
collector.addRule(`
.arduino-boards-dropdown-item:hover {
outline: 1px dashed ${focusBorder};
outline-offset: -2px;
}
`);
if (notificationsBackground) {
// notification
collector.addRule(`
.theia-notification-list-item:hover:not(:focus) {
background-color: ${notificationsBackground};
outline: 1px dashed ${focusBorder};
outline-offset: -2px;
}
`);
}
if (arduinoToolbarButtonBackground) {
// toolbar item
collector.addRule(`
.item.arduino-tool-item.toggled .arduino-upload-sketch--toolbar,
.item.arduino-tool-item.toggled .arduino-verify-sketch--toolbar {
background-color: ${arduinoToolbarButtonBackground} !important;
outline: 1px solid ${focusBorder};
}
`);
collector.addRule(`
.p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div {
background: ${arduinoToolbarButtonBackground};
outline: 1px dashed ${focusBorder};
}
`);
}
}
if (dropdownBackground) {
// boards selector dropdown
collector.addRule(`
.arduino-boards-dropdown-item:hover {
background: ${dropdownBackground};
}
`);
}
if (warningForeground && warningBackground) {
// <input> widget with inverted foreground and background colors
collector.addRule(`
.theia-input.warning:focus,
.theia-input.warning::placeholder,
.theia-input.warning {
color: ${warningBackground};
background-color: ${warningForeground};
}
`);
}
if (buttonBorder) {
collector.addRule(`
button.theia-button,
button.theia-button.secondary,
.component-list-item .theia-button.secondary.no-border,
.component-list-item .theia-button.secondary.no-border:hover {
border: 1px solid ${buttonBorder};
}
`);
collector.addRule(`
.component-list-item .header .installed-version:before {
color: ${buttonBackground};
border: 1px solid ${buttonBorder};
}
`);
}
}
}
}

View File

@ -1,5 +1,5 @@
import '../../src/browser/style/index.css';
import { Container, ContainerModule } from '@theia/core/shared/inversify';
import { ContainerModule } from '@theia/core/shared/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';
@ -295,7 +295,7 @@ import { CoreErrorHandler } from './contributions/core-error-handler';
import { CompilerErrors } from './contributions/compiler-errors';
import { WidgetManager } from './theia/core/widget-manager';
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
import { StartupTasks } from './contributions/startup-task';
import { StartupTasksExecutor } from './contributions/startup-tasks-executor';
import { IndexesUpdateProgress } from './contributions/indexes-update-progress';
import { Daemon } from './contributions/daemon';
import { FirstStartupInstaller } from './contributions/first-startup-installer';
@ -341,16 +341,6 @@ import { TypeHierarchyContribution } from './theia/typehierarchy/type-hierarchy-
import { TypeHierarchyContribution as TheiaTypeHierarchyContribution } from '@theia/typehierarchy/lib/browser/typehierarchy-contribution';
import { DefaultDebugSessionFactory } from './theia/debug/debug-session-contribution';
import { DebugSessionFactory } from '@theia/debug/lib/browser/debug-session-contribution';
import { DebugToolbar } from './theia/debug/debug-toolbar-widget';
import { DebugToolBar as TheiaDebugToolbar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
import { PluginMenuCommandAdapter } from './theia/plugin-ext/plugin-menu-command-adapter';
import { PluginMenuCommandAdapter as TheiaPluginMenuCommandAdapter } from '@theia/plugin-ext/lib/main/browser/menus/plugin-menu-command-adapter';
import { DebugSessionManager } from './theia/debug/debug-session-manager';
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
import { DebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
import { DebugViewModel } from '@theia/debug/lib/browser/view/debug-view-model';
import { DebugSessionWidget } from '@theia/debug/lib/browser/view/debug-session-widget';
import { DebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget';
import { ConfigServiceClient } from './config/config-service-client';
import { ValidateSketch } from './contributions/validate-sketch';
import { RenameCloudSketch } from './contributions/rename-cloud-sketch';
@ -361,15 +351,29 @@ import { SidebarBottomMenuWidget as TheiaSidebarBottomMenuWidget } from '@theia/
import { CreateCloudCopy } from './contributions/create-cloud-copy';
import { FileResourceResolver } from './theia/filesystem/file-resource';
import { FileResourceResolver as TheiaFileResourceResolver } from '@theia/filesystem/lib/browser/file-resource';
import { StylingParticipant } from '@theia/core/lib/browser/styling-service';
import { MonacoEditorMenuContribution } from './theia/monaco/monaco-menu';
import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } from '@theia/monaco/lib/browser/monaco-menu';
// Hack to fix copy/cut/paste issue after electron version update in Theia.
// https://github.com/eclipse-theia/theia/issues/12487
import('@theia/core/lib/browser/common-frontend-contribution.js').then(
(theiaCommonContribution) => {
theiaCommonContribution['supportCopy'] = true;
theiaCommonContribution['supportCut'] = true;
theiaCommonContribution['supportPaste'] = true;
}
);
export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Commands and toolbar items
// Commands, colors, theme adjustments, and toolbar items
bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(ArduinoFrontendContribution);
bind(MenuContribution).toService(ArduinoFrontendContribution);
bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution);
bind(FrontendApplicationContribution).toService(ArduinoFrontendContribution);
bind(ColorContribution).toService(ArduinoFrontendContribution);
bind(StylingParticipant).toService(ArduinoFrontendContribution);
bind(ArduinoToolbarContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution);
@ -722,7 +726,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, PlotterFrontendContribution);
Contribution.configure(bind, Format);
Contribution.configure(bind, CompilerErrors);
Contribution.configure(bind, StartupTasks);
Contribution.configure(bind, StartupTasksExecutor);
Contribution.configure(bind, IndexesUpdateProgress);
Contribution.configure(bind, Daemon);
Contribution.configure(bind, FirstStartupInstaller);
@ -982,9 +986,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// workaround for themes cannot be removed after registration
// https://github.com/eclipse-theia/theia/issues/11151
bind(CleanupObsoleteThemes).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(
CleanupObsoleteThemes
);
bind(FrontendApplicationContribution).toService(CleanupObsoleteThemes);
bind(ThemesRegistrationSummary).toSelf().inSingletonScope();
bind(MonacoThemeRegistry).toSelf().inSingletonScope();
rebind(TheiaMonacoThemeRegistry).toService(MonacoThemeRegistry);
@ -998,37 +1000,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(TypeHierarchyContribution).toSelf().inSingletonScope();
rebind(TheiaTypeHierarchyContribution).toService(TypeHierarchyContribution);
// patched the debugger for `cortex-debug@1.5.1`
// https://github.com/eclipse-theia/theia/issues/11871
// https://github.com/eclipse-theia/theia/issues/11879
// https://github.com/eclipse-theia/theia/issues/11880
// https://github.com/eclipse-theia/theia/issues/11885
// https://github.com/eclipse-theia/theia/issues/11886
// https://github.com/eclipse-theia/theia/issues/11916
// based on: https://github.com/eclipse-theia/theia/compare/master...kittaakos:theia:%2311871
bind(DefaultDebugSessionFactory).toSelf().inSingletonScope();
rebind(DebugSessionFactory).toService(DefaultDebugSessionFactory);
bind(DebugSessionManager).toSelf().inSingletonScope();
rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
bind(DebugToolbar).toSelf().inSingletonScope();
rebind(TheiaDebugToolbar).toService(DebugToolbar);
bind(PluginMenuCommandAdapter).toSelf().inSingletonScope();
rebind(TheiaPluginMenuCommandAdapter).toService(PluginMenuCommandAdapter);
bind(WidgetFactory)
.toDynamicValue(({ container }) => ({
id: DebugWidget.ID,
createWidget: () => {
const child = new Container({ defaultScope: 'Singleton' });
child.parent = container;
child.bind(DebugViewModel).toSelf();
child.bind(DebugToolbar).toSelf(); // patched toolbar
child.bind(DebugSessionWidget).toSelf();
child.bind(DebugConfigurationWidget).toSelf();
child.bind(DebugWidget).toSelf();
return child.get(DebugWidget);
},
}))
.inSingletonScope();
bind(SidebarBottomMenuWidget).toSelf();
rebind(TheiaSidebarBottomMenuWidget).toService(SidebarBottomMenuWidget);
@ -1043,4 +1016,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// https://github.com/arduino/arduino-ide/issues/437
bind(FileResourceResolver).toSelf().inSingletonScope();
rebind(TheiaFileResourceResolver).toService(FileResourceResolver);
// Full control over the editor context menu to filter undesired menu items contributed by Theia.
// https://github.com/arduino/arduino-ide/issues/1394
// https://github.com/arduino/arduino-ide/pull/2027#pullrequestreview-1414246614
bind(MonacoEditorMenuContribution).toSelf().inSingletonScope();
rebind(TheiaMonacoEditorMenuContribution).toService(
MonacoEditorMenuContribution
);
});

View File

@ -9,13 +9,13 @@ import {
CommandContribution,
} from '@theia/core/lib/common/command';
import {
AuthOptions,
AuthenticationService,
AuthenticationServiceClient,
AuthenticationSession,
authServerPort,
} from '../../common/protocol/authentication-service';
import { CloudUserCommands } from './cloud-user-commands';
import { serverPort } from '../../node/auth/authentication-server';
import { AuthOptions } from '../../node/auth/types';
import { ArduinoPreferences } from '../arduino-preferences';
@injectable()
@ -61,7 +61,7 @@ export class AuthenticationClientService
setOptions(): Promise<void> {
return this.service.setOptions({
redirectUri: `http://localhost:${serverPort}/callback`,
redirectUri: `http://localhost:${authServerPort}/callback`,
responseType: 'code',
clientID: this.arduinoPreferences['arduino.auth.clientID'],
domain: this.arduinoPreferences['arduino.auth.domain'],

View File

@ -1,4 +1,4 @@
import * as PQueue from 'p-queue';
import PQueue from 'p-queue';
import { inject, injectable } from '@theia/core/shared/inversify';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';

View File

@ -1,26 +1,27 @@
import { inject, injectable } from '@theia/core/shared/inversify';
import * as moment from 'moment';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { isOSX, isWindows } from '@theia/core/lib/common/os';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
import {
Contribution,
Command,
MenuModelRegistry,
CommandRegistry,
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common/nls';
import { isOSX, isWindows } from '@theia/core/lib/common/os';
import { inject, injectable } from '@theia/core/shared/inversify';
import moment from 'moment';
import { ConfigService } from '../../common/protocol';
import { nls } from '@theia/core/lib/common';
import { AppService } from '../app-service';
import { ArduinoMenus } from '../menu/arduino-menus';
import {
Command,
CommandRegistry,
Contribution,
MenuModelRegistry,
} from './contribution';
@injectable()
export class About extends Contribution {
@inject(ClipboardService)
protected readonly clipboardService: ClipboardService;
private readonly clipboardService: ClipboardService;
@inject(ConfigService)
protected readonly configService: ConfigService;
private readonly configService: ConfigService;
@inject(AppService)
private readonly appService: AppService;
override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(About.Commands.ABOUT_APP, {
@ -40,17 +41,20 @@ export class About extends Contribution {
});
}
async showAbout(): Promise<void> {
const version = await this.configService.getVersion();
private async showAbout(): Promise<void> {
const [appVersion, cliVersion] = await Promise.all([
this.appService.version(),
this.configService.getVersion(),
]);
const buildDate = this.buildDate;
const detail = (showAll: boolean) =>
nls.localize(
'arduino/about/detail',
'Version: {0}\nDate: {1}{2}\nCLI Version: {3}\n\n{4}',
remote.app.getVersion(),
appVersion,
buildDate ? buildDate : nls.localize('', 'dev build'),
buildDate && showAll ? ` (${this.ago(buildDate)})` : '',
version,
cliVersion,
nls.localize(
'arduino/about/copyright',
'Copyright © {0} Arduino SA',
@ -60,34 +64,31 @@ export class About extends Contribution {
const ok = nls.localize('vscode/issueMainService/ok', 'OK');
const copy = nls.localize('vscode/textInputActions/copy', 'Copy');
const buttons = !isWindows && !isOSX ? [copy, ok] : [ok, copy];
const { response } = await remote.dialog.showMessageBox(
remote.getCurrentWindow(),
{
message: `${this.applicationName}`,
title: `${this.applicationName}`,
type: 'info',
detail: detail(true),
buttons,
noLink: true,
defaultId: buttons.indexOf(ok),
cancelId: buttons.indexOf(ok),
}
);
const { response } = await this.dialogService.showMessageBox({
message: `${this.applicationName}`,
title: `${this.applicationName}`,
type: 'info',
detail: detail(true),
buttons,
noLink: true,
defaultId: buttons.indexOf(ok),
cancelId: buttons.indexOf(ok),
});
if (buttons[response] === copy) {
await this.clipboardService.writeText(detail(false).trim());
}
}
protected get applicationName(): string {
private get applicationName(): string {
return FrontendApplicationConfigProvider.get().applicationName;
}
protected get buildDate(): string | undefined {
private get buildDate(): string | undefined {
return FrontendApplicationConfigProvider.get().buildDate;
}
protected ago(isoTime: string): string {
private ago(isoTime: string): string {
const now = moment(Date.now());
const other = moment(isoTime);
let result = now.diff(other, 'minute');

View File

@ -1,22 +1,21 @@
import { nls } from '@theia/core/lib/common/nls';
import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { FileDialogService } from '@theia/filesystem/lib/browser';
import { ArduinoMenus } from '../menu/arduino-menus';
import { CurrentSketch } from '../sketches-service-client-impl';
import {
SketchContribution,
Command,
CommandRegistry,
MenuModelRegistry,
URI,
Sketch,
SketchContribution,
URI,
} from './contribution';
import { FileDialogService } from '@theia/filesystem/lib/browser';
import { nls } from '@theia/core/lib/common';
import { CurrentSketch } from '../sketches-service-client-impl';
@injectable()
export class AddFile extends SketchContribution {
@inject(FileDialogService)
private readonly fileDialogService: FileDialogService;
private readonly fileDialogService: FileDialogService; // TODO: use dialogService
override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(AddFile.Commands.ADD_FILE, {
@ -50,7 +49,7 @@ export class AddFile extends SketchContribution {
const { uri: targetUri, filename } = this.resolveTarget(sketch, toAddUri);
const exists = await this.fileService.exists(targetUri);
if (exists) {
const { response } = await remote.dialog.showMessageBox({
const { response } = await this.dialogService.showMessageBox({
type: 'question',
title: nls.localize('arduino/contributions/replaceTitle', 'Replace'),
buttons: [

View File

@ -1,5 +1,4 @@
import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
import { ArduinoMenus } from '../menu/arduino-menus';
@ -42,23 +41,20 @@ export class AddZipLibrary extends SketchContribution {
private async addZipLibrary(): Promise<void> {
const homeUri = await this.envVariableServer.getHomeDirUri();
const defaultPath = await this.fileService.fsPath(new URI(homeUri));
const { canceled, filePaths } = await remote.dialog.showOpenDialog(
remote.getCurrentWindow(),
{
title: nls.localize(
'arduino/selectZip',
"Select a zip file containing the library you'd like to add"
),
defaultPath,
properties: ['openFile'],
filters: [
{
name: nls.localize('arduino/library/zipLibrary', 'Library'),
extensions: ['zip'],
},
],
}
);
const { canceled, filePaths } = await this.dialogService.showOpenDialog({
title: nls.localize(
'arduino/selectZip',
"Select a zip file containing the library you'd like to add"
),
defaultPath,
properties: ['openFile'],
filters: [
{
name: nls.localize('arduino/library/zipLibrary', 'Library'),
extensions: ['zip'],
},
],
});
if (!canceled && filePaths.length) {
const zipUri = await this.fileSystemExt.getUri(filePaths[0]);
try {

View File

@ -1,6 +1,5 @@
import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import * as dateFormat from 'dateformat';
import dateFormat from 'dateformat';
import { ArduinoMenus } from '../menu/arduino-menus';
import {
SketchContribution,
@ -39,16 +38,13 @@ export class ArchiveSketch extends SketchContribution {
const defaultContainerUri = await this.defaultUri();
const defaultUri = defaultContainerUri.resolve(archiveBasename);
const defaultPath = await this.fileService.fsPath(defaultUri);
const { filePath, canceled } = await remote.dialog.showSaveDialog(
remote.getCurrentWindow(),
{
title: nls.localize(
'arduino/sketch/saveSketchAs',
'Save sketch folder as...'
),
defaultPath,
}
);
const { filePath, canceled } = await this.dialogService.showSaveDialog({
title: nls.localize(
'arduino/sketch/saveSketchAs',
'Save sketch folder as...'
),
defaultPath,
});
if (!filePath || canceled) {
return;
}

View File

@ -1,5 +1,4 @@
import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import {
DisposableCollection,
@ -65,7 +64,7 @@ VID: ${VID}
PID: ${PID}
SN: ${SN}
`.trim();
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
await this.dialogService.showMessageBox({
message: nls.localize('arduino/board/boardInfo', 'Board Info'),
title: nls.localize('arduino/board/boardInfo', 'Board Info'),
type: 'info',

View File

@ -1,26 +1,26 @@
import { injectable } from '@theia/core/shared/inversify';
import { toArray } from '@theia/core/shared/@phosphor/algorithm';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import type { MaybePromise } from '@theia/core/lib/common/types';
import { Dialog } from '@theia/core/lib/browser/dialogs';
import type {
FrontendApplication,
OnWillStopAction,
} from '@theia/core/lib/browser/frontend-application';
import { nls } from '@theia/core/lib/common/nls';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { nls } from '@theia/core/lib/common/nls';
import type { MaybePromise } from '@theia/core/lib/common/types';
import { toArray } from '@theia/core/shared/@phosphor/algorithm';
import { inject, injectable } from '@theia/core/shared/inversify';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { ArduinoMenus } from '../menu/arduino-menus';
import { CurrentSketch } from '../sketches-service-client-impl';
import { WindowServiceExt } from '../theia/core/window-service-ext';
import {
SketchContribution,
Command,
CommandRegistry,
MenuModelRegistry,
KeybindingRegistry,
MenuModelRegistry,
Sketch,
SketchContribution,
URI,
} from './contribution';
import { Dialog } from '@theia/core/lib/browser/dialogs';
import { CurrentSketch } from '../sketches-service-client-impl';
import { SaveAsSketch } from './save-as-sketch';
/**
@ -28,6 +28,9 @@ import { SaveAsSketch } from './save-as-sketch';
*/
@injectable()
export class Close extends SketchContribution {
@inject(WindowServiceExt)
private readonly windowServiceExt: WindowServiceExt;
private shell: ApplicationShell | undefined;
override onStart(app: FrontendApplication): MaybePromise<void> {
@ -56,7 +59,7 @@ export class Close extends SketchContribution {
}
}
}
return remote.getCurrentWindow().close();
return this.windowServiceExt.close();
},
});
}
@ -150,26 +153,23 @@ export class Close extends SketchContribution {
}
private async prompt(isTemp: boolean): Promise<Prompt> {
const { response } = await remote.dialog.showMessageBox(
remote.getCurrentWindow(),
{
message: nls.localize(
'arduino/sketch/saveSketch',
'Save your sketch to open it again later.'
),
title: nls.localize(
'theia/core/quitTitle',
'Are you sure you want to quit?'
),
type: 'question',
buttons: [
nls.localizeByDefault("Don't Save"),
Dialog.CANCEL,
nls.localizeByDefault(isTemp ? 'Save As...' : 'Save'),
],
defaultId: 2, // `Save`/`Save As...` button index is the default.
}
);
const { response } = await this.dialogService.showMessageBox({
message: nls.localize(
'arduino/sketch/saveSketch',
'Save your sketch to open it again later.'
),
title: nls.localize(
'theia/core/quitTitle',
'Are you sure you want to quit?'
),
type: 'question',
buttons: [
nls.localizeByDefault("Don't Save"),
Dialog.CANCEL,
nls.localizeByDefault(isTemp ? 'Save As...' : 'Save'),
],
defaultId: 2, // `Save`/`Save As...` button index is the default.
});
switch (response) {
case 0:
return Prompt.DoNotSave;

View File

@ -67,6 +67,7 @@ import { WorkspaceService } from '../theia/workspace/workspace-service';
import { MainMenuManager } from '../../common/main-menu-manager';
import { ConfigServiceClient } from '../config/config-service-client';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { DialogService } from '../dialog-service';
export {
Command,
@ -115,6 +116,9 @@ export abstract class Contribution
@inject(MainMenuManager)
protected readonly menuManager: MainMenuManager;
@inject(DialogService)
protected readonly dialogService: DialogService;
@postConstruct()
protected init(): void {
this.appStateService.reachedState('ready').then(() => this.onReady());

View File

@ -1,5 +1,3 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { ipcRenderer } from '@theia/core/electron-shared/electron';
import { Dialog } from '@theia/core/lib/browser/dialogs';
import { NavigatableWidget } from '@theia/core/lib/browser/navigatable-types';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
@ -10,11 +8,11 @@ import URI from '@theia/core/lib/common/uri';
import type { Widget } from '@theia/core/shared/@phosphor/widgets';
import { inject, injectable } from '@theia/core/shared/inversify';
import { SketchesError } from '../../common/protocol';
import { SCHEDULE_DELETION_SIGNAL } from '../../electron-common/electron-messages';
import { Sketch } from '../contributions/contribution';
import { isNotFound } from '../create/typings';
import { Command, CommandRegistry } from './contribution';
import { CloudSketchContribution } from './cloud-contribution';
import { AppService } from '../app-service';
export interface DeleteSketchParams {
/**
@ -38,6 +36,8 @@ export class DeleteSketch extends CloudSketchContribution {
private readonly shell: ApplicationShell;
@inject(WindowService)
private readonly windowService: WindowService;
@inject(AppService)
private readonly appService: AppService;
override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(DeleteSketch.Commands.DELETE_SKETCH, {
@ -66,7 +66,7 @@ export class DeleteSketch extends CloudSketchContribution {
}
const cloudUri = this.createFeatures.cloudUri(sketch);
if (willNavigateAway !== 'force') {
const { response } = await remote.dialog.showMessageBox({
const { response } = await this.dialogService.showMessageBox({
title: nls.localizeByDefault('Delete'),
type: 'question',
buttons: [Dialog.CANCEL, Dialog.OK],
@ -120,7 +120,7 @@ export class DeleteSketch extends CloudSketchContribution {
}
private scheduleDeletion(sketch: Sketch): void {
ipcRenderer.send(SCHEDULE_DELETION_SIGNAL, sketch);
this.appService.scheduleDeletion(sketch);
}
private async loadSketch(uri: string): Promise<Sketch | undefined> {

View File

@ -1,11 +1,7 @@
import * as PQueue from 'p-queue';
import PQueue from 'p-queue';
import { inject, injectable } from '@theia/core/shared/inversify';
import { CommandHandler, CommandService } from '@theia/core/lib/common/command';
import {
MenuPath,
CompositeMenuNode,
SubMenuOptions,
} from '@theia/core/lib/common/menu';
import { MenuPath, SubMenuOptions } from '@theia/core/lib/common/menu';
import {
Disposable,
DisposableCollection,
@ -143,19 +139,6 @@ export abstract class Examples extends SketchContribution {
}): void;
override registerMenus(registry: MenuModelRegistry): void {
try {
// This is a hack the ensures the desired menu ordering! We cannot use https://github.com/eclipse-theia/theia/pull/8377 due to ATL-222.
const index = ArduinoMenus.FILE__EXAMPLES_SUBMENU.length - 1;
const menuId = ArduinoMenus.FILE__EXAMPLES_SUBMENU[index];
const groupPath =
index === 0 ? [] : ArduinoMenus.FILE__EXAMPLES_SUBMENU.slice(0, index);
const parent: CompositeMenuNode = (registry as any).findGroup(groupPath);
const examples = new CompositeMenuNode(menuId, '', { order: '4' });
parent.addNode(examples);
} catch (e) {
console.error(e);
console.warn('Could not patch menu ordering.');
}
// Registering the same submenu multiple times has no side-effect.
// TODO: unregister submenu? https://github.com/eclipse-theia/theia/issues/7300
registry.registerSubmenu(

View File

@ -1,4 +1,4 @@
import * as PQueue from 'p-queue';
import PQueue from 'p-queue';
import { inject, injectable } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';

View File

@ -1,4 +1,7 @@
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { inject, injectable } from '@theia/core/shared/inversify';
import { Mutex } from 'async-mutex';
import {
@ -120,6 +123,7 @@ export class InoLanguage extends SketchContribution {
return;
}
const release = await this.languageServerStartMutex.acquire();
const toDisposeOnRelease = new DisposableCollection();
try {
await this.hostedPluginEvents.didStart;
const details = await this.boardsService.getBoardDetails({ fqbn });
@ -179,12 +183,13 @@ export class InoLanguage extends SketchContribution {
]);
this.languageServerFqbn = await Promise.race([
new Promise<undefined>((_, reject) =>
setTimeout(
new Promise<undefined>((_, reject) => {
const timer = setTimeout(
() => reject(new Error(`Timeout after ${20_000} ms.`)),
20_000
)
),
);
toDisposeOnRelease.push(Disposable.create(() => clearTimeout(timer)));
}),
this.commandService.executeCommand<string>(
'arduino.languageserver.start',
{
@ -206,6 +211,7 @@ export class InoLanguage extends SketchContribution {
console.log(`Failed to start language server. Original FQBN: ${fqbn}`, e);
this.languageServerFqbn = undefined;
} finally {
toDisposeOnRelease.dispose();
release();
}
}

View File

@ -8,7 +8,7 @@ import {
import { ArduinoMenus } from '../menu/arduino-menus';
import { CommandRegistry, MaybePromise, nls } from '@theia/core/lib/common';
import { Settings } from '../dialogs/settings/settings';
import debounce = require('lodash.debounce');
import debounce from 'lodash.debounce';
@injectable()
export class InterfaceScale extends Contribution {

View File

@ -1,5 +1,4 @@
import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri';
import { ArduinoMenus } from '../menu/arduino-menus';
import {
@ -9,7 +8,7 @@ import {
MenuModelRegistry,
KeybindingRegistry,
} from './contribution';
import { nls } from '@theia/core/lib/common';
import { nls } from '@theia/core/lib/common/nls';
@injectable()
export class OpenSketchExternal extends SketchContribution {
@ -41,7 +40,7 @@ export class OpenSketchExternal extends SketchContribution {
if (exists) {
const fsPath = await this.fileService.fsPath(new URI(uri));
if (fsPath) {
remote.shell.showItemInFolder(fsPath);
window.electronTheiaCore.showItemInFolder(fsPath);
}
}
}

View File

@ -118,6 +118,7 @@ export class OpenSketchFiles extends SketchContribution {
fileService: this.fileService,
sketchesService: this.sketchesService,
labelProvider: this.labelProvider,
dialogService: this.dialogService,
});
if (movedSketch) {
this.workspaceService.open(new URI(movedSketch.uri), {

View File

@ -1,4 +1,3 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { nls } from '@theia/core/lib/common/nls';
import { injectable } from '@theia/core/shared/inversify';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
@ -18,6 +17,7 @@ import {
SketchContribution,
URI,
} from './contribution';
import { DialogService } from '../dialog-service';
export type SketchLocation = string | URI | SketchRef;
export namespace SketchLocation {
@ -83,19 +83,16 @@ export class OpenSketch extends SketchContribution {
private async selectSketch(): Promise<Sketch | undefined> {
const defaultPath = await this.defaultPath();
const { filePaths } = await remote.dialog.showOpenDialog(
remote.getCurrentWindow(),
{
defaultPath,
properties: ['createDirectory', 'openFile'],
filters: [
{
name: nls.localize('arduino/sketch/sketch', 'Sketch'),
extensions: ['ino', 'pde'],
},
],
}
);
const { filePaths } = await this.dialogService.showOpenDialog({
defaultPath,
properties: ['createDirectory', 'openFile'],
filters: [
{
name: nls.localize('arduino/sketch/sketch', 'Sketch'),
extensions: ['ino', 'pde'],
},
],
});
if (!filePaths.length) {
return undefined;
}
@ -115,6 +112,7 @@ export class OpenSketch extends SketchContribution {
fileService: this.fileService,
sketchesService: this.sketchesService,
labelProvider: this.labelProvider,
dialogService: this.dialogService,
});
}
}
@ -134,14 +132,16 @@ export async function promptMoveSketch(
fileService: FileService;
sketchesService: SketchesService;
labelProvider: LabelProvider;
dialogService: DialogService;
}
): Promise<Sketch | undefined> {
const { fileService, sketchesService, labelProvider } = options;
const { fileService, sketchesService, labelProvider, dialogService } =
options;
const uri =
sketchFileUri instanceof URI ? sketchFileUri : new URI(sketchFileUri);
const name = uri.path.name;
const nameWithExt = labelProvider.getName(uri);
const { response } = await remote.dialog.showMessageBox({
const { response } = await dialogService.showMessageBox({
title: nls.localize('arduino/sketch/moving', 'Moving'),
type: 'question',
buttons: [
@ -160,7 +160,7 @@ export async function promptMoveSketch(
const newSketchUri = uri.parent.resolve(name);
const exists = await fileService.exists(newSketchUri);
if (exists) {
await remote.dialog.showMessageBox({
await dialogService.showMessageBox({
type: 'error',
title: nls.localize('vscode/dialog/dialogErrorMessage', 'Error'),
message: nls.localize(

View File

@ -1,5 +1,4 @@
import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { inject, injectable } from '@theia/core/shared/inversify';
import { isOSX } from '@theia/core/lib/common/os';
import {
Contribution,
@ -9,14 +8,18 @@ import {
CommandRegistry,
} from './contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
import { nls } from '@theia/core/lib/common/nls';
import { AppService } from '../app-service';
@injectable()
export class QuitApp extends Contribution {
@inject(AppService)
private readonly appService: AppService;
override registerCommands(registry: CommandRegistry): void {
if (!isOSX) {
registry.registerCommand(QuitApp.Commands.QUIT_APP, {
execute: () => remote.app.quit(),
execute: () => this.appService.quit(),
});
}
}

View File

@ -1,4 +1,3 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { Dialog } from '@theia/core/lib/browser/dialogs';
import { NavigatableWidget } from '@theia/core/lib/browser/navigatable';
import { Saveable } from '@theia/core/lib/browser/saveable';
@ -8,7 +7,7 @@ import { nls } from '@theia/core/lib/common/nls';
import { inject, injectable } from '@theia/core/shared/inversify';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { WorkspaceInput } from '@theia/workspace/lib/browser/workspace-service';
import { StartupTask } from '../../electron-common/startup-task';
import { StartupTasks } from '../../electron-common/startup-task';
import { ArduinoMenus } from '../menu/arduino-menus';
import { CurrentSketch } from '../sketches-service-client-impl';
import { CloudSketchContribution } from './cloud-contribution';
@ -95,7 +94,7 @@ export class SaveAsSketch extends CloudSketchContribution {
if (markAsRecentlyOpened) {
this.sketchesService.markAsRecentlyOpened(newWorkspaceUri);
}
const options: WorkspaceInput & StartupTask.Owner = {
const options: WorkspaceInput & StartupTasks = {
preserveWindow: true,
tasks: [],
};
@ -165,16 +164,13 @@ export class SaveAsSketch extends CloudSketchContribution {
): Promise<string | undefined> {
let sketchFolderDestinationUri: string | undefined;
while (!sketchFolderDestinationUri) {
const { filePath } = await remote.dialog.showSaveDialog(
remote.getCurrentWindow(),
{
title: nls.localize(
'arduino/sketch/saveFolderAs',
'Save sketch folder as...'
),
defaultPath,
}
);
const { filePath } = await this.dialogService.showSaveDialog({
title: nls.localize(
'arduino/sketch/saveFolderAs',
'Save sketch folder as...'
),
defaultPath,
});
if (!filePath) {
return undefined;
}
@ -225,13 +221,10 @@ ${dialogContent.details}
${dialogContent.question}`.trim();
defaultPath = filePath;
const { response } = await remote.dialog.showMessageBox(
remote.getCurrentWindow(),
{
message,
buttons: [Dialog.CANCEL, Dialog.YES],
}
);
const { response } = await this.dialogService.showMessageBox({
message,
buttons: [Dialog.CANCEL, Dialog.YES],
});
// cancel
if (response === 0) {
return undefined;

View File

@ -1,52 +0,0 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import type { IpcRendererEvent } from '@theia/core/electron-shared/electron';
import { ipcRenderer } from '@theia/core/electron-shared/electron';
import { injectable } from '@theia/core/shared/inversify';
import { StartupTask } from '../../electron-common/startup-task';
import { Contribution } from './contribution';
@injectable()
export class StartupTasks extends Contribution {
override onReady(): void {
ipcRenderer.once(
StartupTask.Messaging.STARTUP_TASKS_SIGNAL,
(_: IpcRendererEvent, args: unknown) => {
console.debug(
`Received the startup tasks from the electron main process. Args: ${JSON.stringify(
args
)}`
);
if (!StartupTask.has(args)) {
console.warn(`Could not detect 'tasks' from the signal. Skipping.`);
return;
}
const tasks = args.tasks;
if (tasks.length) {
console.log(`Executing startup tasks:`);
tasks.forEach(({ command, args = [] }) => {
console.log(
` - '${command}' ${
args.length ? `, args: ${JSON.stringify(args)}` : ''
}`
);
this.commandService
.executeCommand(command, ...args)
.catch((err) =>
console.error(
`Error occurred when executing the startup task '${command}'${
args?.length ? ` with args: '${JSON.stringify(args)}` : ''
}.`,
err
)
);
});
}
}
);
const { id } = remote.getCurrentWindow();
console.debug(
`Signalling app ready event to the electron main process. Sender ID: ${id}.`
);
ipcRenderer.send(StartupTask.Messaging.APP_READY_SIGNAL(id));
}
}

View File

@ -0,0 +1,65 @@
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import {
hasStartupTasks,
StartupTasks,
} from '../../electron-common/startup-task';
import { AppService } from '../app-service';
import { Contribution } from './contribution';
@injectable()
export class StartupTasksExecutor extends Contribution {
@inject(AppService)
private readonly appService: AppService;
private readonly toDispose = new DisposableCollection();
@postConstruct()
protected override init(): void {
super.init();
this.toDispose.push(
this.appService.registerStartupTasksHandler((tasks) =>
this.handleStartupTasks(tasks)
)
);
}
onStop(): void {
this.toDispose.dispose();
}
private async handleStartupTasks(tasks: StartupTasks): Promise<void> {
console.debug(
`Received the startup tasks from the electron main process. Args: ${JSON.stringify(
tasks
)}`
);
if (!hasStartupTasks(tasks)) {
console.warn(`Could not detect 'tasks' from the signal. Skipping.`);
return;
}
await this.appStateService.reachedState('ready');
console.log(`Executing startup tasks:`);
tasks.tasks.forEach(({ command, args = [] }) => {
console.log(
` - '${command}' ${
args.length ? `, args: ${JSON.stringify(args)}` : ''
}`
);
this.commandService
.executeCommand(command, ...args)
.catch((err) =>
console.error(
`Error occurred when executing the startup task '${command}'${
args?.length ? ` with args: '${JSON.stringify(args)}` : ''
}.`,
err
)
);
});
}
}

View File

@ -1,4 +1,3 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { Dialog } from '@theia/core/lib/browser/dialogs';
import { nls } from '@theia/core/lib/common/nls';
import { Deferred, waitForEvent } from '@theia/core/lib/common/promise-util';
@ -180,15 +179,12 @@ export class ValidateSketch extends CloudSketchContribution {
message: string,
buttons: string[] = [Dialog.CANCEL, Dialog.OK]
): Promise<boolean> {
const { response } = await remote.dialog.showMessageBox(
remote.getCurrentWindow(),
{
title,
message,
type: 'warning',
buttons,
}
);
const { response } = await this.dialogService.showMessageBox({
title,
message,
type: 'warning',
buttons,
});
// cancel
if (response === 0) {
return false;

View File

@ -4,7 +4,7 @@ import { Emitter, Event } from '@theia/core/lib/common/event';
import URI from '@theia/core/lib/common/uri';
import { inject, injectable } from '@theia/core/shared/inversify';
import { Sketch } from '../../common/protocol';
import { AuthenticationSession } from '../../node/auth/types';
import { AuthenticationSession } from '../../common/protocol/authentication-service';
import { ArduinoPreferences } from '../arduino-preferences';
import { AuthenticationClientService } from '../auth/authentication-client-service';
import { LocalCacheFsProvider } from '../local-cache/local-cache-fs-provider';

View File

@ -0,0 +1,15 @@
import type {
MessageBoxOptions,
MessageBoxReturnValue,
OpenDialogOptions,
OpenDialogReturnValue,
SaveDialogOptions,
SaveDialogReturnValue,
} from '../electron-common/electron-arduino';
export const DialogService = Symbol('DialogService');
export interface DialogService {
showMessageBox(options: MessageBoxOptions): Promise<MessageBoxReturnValue>;
showOpenDialog(options: OpenDialogOptions): Promise<OpenDialogReturnValue>;
showSaveDialog(options: SaveDialogOptions): Promise<SaveDialogReturnValue>;
}

View File

@ -1,12 +1,14 @@
import * as React from '@theia/core/shared/react';
import { inject, injectable } from '@theia/core/shared/inversify';
import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { TreeNode } from '@theia/core/lib/browser/tree/tree';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { nls } from '@theia/core/lib/common/nls';
import { MaybePromise } from '@theia/core/lib/common/types';
import { Message } from '@theia/core/shared/@phosphor/messaging';
import { clipboard } from '@theia/core/electron-shared/@electron/remote';
import { ReactWidget, DialogProps } from '@theia/core/lib/browser';
import { AbstractDialog } from '../theia/dialogs/dialogs';
import { Widget } from '@theia/core/shared/@phosphor/widgets';
import * as React from '@theia/core/shared/react';
import { CreateApi } from '../create/create-api';
import { nls } from '@theia/core/lib/common';
import { AbstractDialog } from '../theia/dialogs/dialogs';
const RadioButton = (props: {
id: string;
@ -35,15 +37,18 @@ export const ShareSketchComponent = ({
treeNode,
createApi,
domain = 'https://create.arduino.cc',
writeClipboard,
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
treeNode: any;
createApi: CreateApi;
domain?: string;
writeClipboard: (text: string) => MaybePromise<void>;
}): React.ReactElement => {
const [loading, setloading] = React.useState<boolean>(false);
const [loading, setLoading] = React.useState<boolean>(false);
const radioChangeHandler = async (event: React.BaseSyntheticEvent) => {
setloading(true);
setLoading(true);
const sketch = await createApi.editSketch({
id: treeNode.sketchId,
params: {
@ -52,7 +57,7 @@ export const ShareSketchComponent = ({
});
// setPublicVisibility(sketch.is_public);
treeNode.isPublic = sketch.is_public;
setloading(false);
setLoading(false);
};
const sketchLink = `${domain}/editor/_/${treeNode.sketchId}/preview`;
@ -100,7 +105,7 @@ export const ShareSketchComponent = ({
className="theia-input"
/>
<button
onClick={() => clipboard.writeText(sketchLink)}
onClick={() => writeClipboard(sketchLink)}
value="copy"
className="theia-button secondary"
>
@ -121,44 +126,52 @@ export const ShareSketchComponent = ({
);
};
@injectable()
export class ShareSketchWidget extends ReactWidget {
constructor(private treeNode: any, private createApi: CreateApi) {
private readonly writeClipboard = (text: string) =>
this.clipboardService.writeText(text);
constructor(
private treeNode: TreeNode,
private createApi: CreateApi,
private clipboardService: ClipboardService
) {
super();
}
protected render(): React.ReactNode {
protected override render(): React.ReactNode {
return (
<ShareSketchComponent
treeNode={this.treeNode}
createApi={this.createApi}
writeClipboard={this.writeClipboard}
/>
);
}
}
@injectable()
export class ShareSketchDialogProps extends DialogProps {
readonly node: any;
readonly node: TreeNode;
readonly createApi: CreateApi;
readonly clipboardService: ClipboardService;
}
@injectable()
export class ShareSketchDialog extends AbstractDialog<void> {
protected widget: ShareSketchWidget;
constructor(
@inject(ShareSketchDialogProps)
protected override readonly props: ShareSketchDialogProps
) {
constructor(protected override readonly props: ShareSketchDialogProps) {
super({ title: props.title });
this.contentNode.classList.add('arduino-share-sketch-dialog');
this.widget = new ShareSketchWidget(props.node, props.createApi);
this.widget = new ShareSketchWidget(
props.node,
props.createApi,
props.clipboardService
);
}
get value(): void {
override get value(): void {
return;
}
protected override onAfterAttach(msg: Message): void {
if (this.widget.isAttached) {
Widget.detach(this.widget);

View File

@ -157,7 +157,7 @@ export const FirmwareUploaderComponent = ({
options={firmwareOptions}
value={selectedFirmware}
tabSelectsValue={false}
onChange={(value) => {
onChange={(value: FirmwareOption | null) => {
if (value) {
setInstallFeedback(null);
setSelectedFirmware(value);

View File

@ -1,10 +1,15 @@
import { nls } from '@theia/core/lib/common';
import { shell } from '@theia/core/electron-shared/@electron/remote';
import { nls } from '@theia/core/lib/common/nls';
import * as React from '@theia/core/shared/react';
import ReactMarkdown from 'react-markdown';
// @ts-expect-error see https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1319854183
import type { Options } from 'react-markdown';
import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater';
import ProgressBar from '../../components/ProgressBar';
const ReactMarkdown = React.lazy<React.ComponentType<Options>>(
// @ts-expect-error see above
() => import('react-markdown')
);
export interface UpdateProgress {
progressInfo?: ProgressInfo | undefined;
downloadFinished?: boolean;
@ -15,6 +20,7 @@ export interface UpdateProgress {
export interface IDEUpdaterComponentProps {
updateInfo: UpdateInfo;
updateProgress: UpdateProgress;
openExternal: (url: string) => undefined;
}
export const IDEUpdaterComponent = ({
@ -25,6 +31,7 @@ export const IDEUpdaterComponent = ({
progressInfo,
error,
},
openExternal,
}: IDEUpdaterComponentProps): React.ReactElement => {
const { version, releaseNotes } = updateInfo;
const [changelog, setChangelog] = React.useState<string>('');
@ -95,20 +102,26 @@ export const IDEUpdaterComponent = ({
{changelog && (
<div className="dialogRow changelog-container">
<div className="changelog">
<ReactMarkdown
components={{
a: ({ href, children, ...props }) => (
<a
onClick={() => href && shell.openExternal(href)}
{...props}
>
{children}
</a>
),
}}
<React.Suspense
fallback={
<div className="fallback">
<div className="spinner" />
</div>
}
>
{changelog}
</ReactMarkdown>
<ReactMarkdown
components={{
// @ts-expect-error see imports. There is no ESM type-only import in CommonJS modules.
a: ({ href, children, ...props }) => (
<a onClick={() => href && openExternal(href)} {...props}>
{children}
</a>
),
}}
>
{changelog}
</ReactMarkdown>
</React.Suspense>
</div>
</div>
)}

View File

@ -18,8 +18,6 @@ import {
import { LocalStorageService } from '@theia/core/lib/browser';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
const DOWNLOAD_PAGE_URL = 'https://www.arduino.cc/en/software';
@injectable()
export class IDEUpdaterDialogProps extends DialogProps {}
@ -76,11 +74,15 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
<IDEUpdaterComponent
updateInfo={this.updateInfo}
updateProgress={this.updateProgress}
openExternal={this.openExternal}
/>
)
);
}
private readonly openExternal = (url: string) =>
this.windowService.openNewWindow(url, { external: true });
get value(): UpdateInfo | undefined {
return this.updateInfo;
}
@ -164,7 +166,7 @@ export class IDEUpdaterDialog extends ReactDialog<UpdateInfo | undefined> {
}
private openDownloadPage(): void {
this.windowService.openNewWindow(DOWNLOAD_PAGE_URL, { external: true });
this.openExternal('https://www.arduino.cc/en/software');
this.close();
}

View File

@ -14,11 +14,9 @@ import {
Monitor,
MonitorManagerProxyClient,
MonitorManagerProxyFactory,
} from '../common/protocol/monitor-service';
import {
PluggableMonitorSettings,
MonitorSettings,
} from '../node/monitor-settings/monitor-settings-provider';
PluggableMonitorSettings,
} from '../common/protocol/monitor-service';
import { BoardsConfig } from './boards/boards-config';
import { BoardsServiceProvider } from './boards/boards-service-provider';

View File

@ -10,10 +10,10 @@ import {
monitorConnectionStatusEquals,
MonitorEOL,
MonitorManagerProxyClient,
MonitorSettings,
MonitorState,
} from '../common/protocol';
import { isNullOrUndefined } from '../common/utils';
import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider';
@injectable()
export class MonitorModel implements FrontendApplicationContribution {

View File

@ -23,9 +23,9 @@ import { nls } from '@theia/core/lib/common';
import {
MonitorEOL,
MonitorManagerProxyClient,
MonitorSettings,
} from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model';
import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
@injectable()

View File

@ -2,7 +2,7 @@ import * as React from '@theia/core/shared/react';
import { Event } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { areEqual, FixedSizeList as List } from 'react-window';
import dateFormat = require('dateformat');
import dateFormat from 'dateformat';
import { messagesToLines, truncateLines } from './monitor-utils';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model';

View File

@ -1,26 +1,16 @@
import { Endpoint } from '@theia/core/lib/browser/endpoint';
import { ThemeService } from '@theia/core/lib/browser/theming';
import { injectable, inject } from '@theia/core/shared/inversify';
import {
Command,
CommandRegistry,
MaybePromise,
MenuModelRegistry,
} from '@theia/core';
import { ArduinoMenus } from '../../menu/arduino-menus';
import { Contribution } from '../../contributions/contribution';
import { Endpoint, FrontendApplication } from '@theia/core/lib/browser';
import { ipcRenderer } from '@theia/electron/shared/electron';
import { Command, CommandRegistry } from '@theia/core/lib/common/command';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import type { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { nls } from '@theia/core/lib/common/nls';
import { inject, injectable } from '@theia/core/shared/inversify';
import queryString from 'query-string';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { Contribution } from '../../contributions/contribution';
import { ArduinoMenus } from '../../menu/arduino-menus';
import { MonitorModel } from '../../monitor-model';
import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
import {
CLOSE_PLOTTER_WINDOW,
SHOW_PLOTTER_WINDOW,
} from '../../../common/ipc-communication';
import { nls } from '@theia/core/lib/common/nls';
const queryString = require('query-string');
export namespace SerialPlotterContribution {
export namespace Commands {
@ -44,38 +34,31 @@ export namespace SerialPlotterContribution {
@injectable()
export class PlotterFrontendContribution extends Contribution {
protected window: Window | null;
protected url: string;
protected wsPort: number;
private readonly endpointUrl = new Endpoint({ path: '/plotter' })
.getRestUrl()
.toString();
private readonly toDispose = new DisposableCollection();
private _plotterUrl: string | undefined;
@inject(MonitorModel)
protected readonly model: MonitorModel;
private readonly model: MonitorModel;
@inject(ThemeService)
protected readonly themeService: ThemeService;
private readonly themeService: ThemeService;
@inject(MonitorManagerProxyClient)
protected readonly monitorManagerProxy: MonitorManagerProxyClient;
private readonly monitorManagerProxy: MonitorManagerProxyClient;
@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider;
override onStart(app: FrontendApplication): MaybePromise<void> {
this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString();
ipcRenderer.on(CLOSE_PLOTTER_WINDOW, async () => {
if (!!this.window) {
this.window = null;
}
});
override onStart(): void {
this.toDispose.push(
window.electronArduino.registerPlotterWindowCloseHandler(() => {
this._plotterUrl = undefined;
})
);
this.monitorManagerProxy.onMonitorShouldReset(() => this.reset());
return super.onStart(app);
}
override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(SerialPlotterContribution.Commands.OPEN, {
execute: this.startPlotter.bind(this),
execute: () => this.startPlotter(),
});
registry.registerCommand(SerialPlotterContribution.Commands.RESET, {
execute: () => this.reset(),
@ -85,7 +68,7 @@ export class PlotterFrontendContribution extends Contribution {
{
isVisible: (widget) =>
ArduinoToolbar.is(widget) && widget.side === 'right',
execute: this.startPlotter.bind(this),
execute: () => this.startPlotter(),
}
);
}
@ -98,10 +81,13 @@ export class PlotterFrontendContribution extends Contribution {
});
}
async startPlotter(): Promise<void> {
private async startPlotter(forceReload = false): Promise<void> {
await this.monitorManagerProxy.startMonitor();
if (!!this.window) {
ipcRenderer.send(SHOW_PLOTTER_WINDOW);
if (this._plotterUrl) {
window.electronArduino.showPlotterWindow({
url: this._plotterUrl,
forceReload,
});
return;
}
const wsPort = this.monitorManagerProxy.getWebSocketPort();
@ -117,26 +103,30 @@ export class PlotterFrontendContribution extends Contribution {
}
}
protected async open(wsPort: number): Promise<void> {
private open(wsPort: number): void {
const initConfig = {
darkTheme: this.themeService.getCurrentTheme().type === 'dark',
darkTheme: this.isDarkTheme,
wsPort,
serialPort: this.model.serialPort,
};
const urlWithParams = queryString.stringifyUrl(
this._plotterUrl = queryString.stringifyUrl(
{
url: this.url,
url: this.endpointUrl,
query: initConfig,
},
{ arrayFormat: 'comma' }
);
this.window = window.open(urlWithParams, 'serialPlotter');
window.electronArduino.showPlotterWindow({ url: this._plotterUrl });
}
protected async reset(): Promise<void> {
if (!!this.window) {
this.window.close();
await this.startPlotter();
private get isDarkTheme(): boolean {
const themeType = this.themeService.getCurrentTheme().type;
return themeType === 'dark' || themeType === 'hc';
}
private async reset(): Promise<void> {
if (this._plotterUrl) {
await this.startPlotter(true);
}
}
}

View File

@ -49,14 +49,3 @@
padding-top: 0 !important;
padding-bottom: 0 !important;
}
/* High Contrast Theme rules */
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
.hc-black.hc-theia.theia-hc .arduino-select__option--is-selected {
outline: 1px solid var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .arduino-select__option--is-focused {
outline: 1px dashed var(--theia-focusBorder);
}

View File

@ -276,16 +276,6 @@ div#select-board-dialog .selectBoardContainer .list .item.selected i {
border-top: 1px solid var(--theia-dropdown-border);
}
/* High Contrast Theme rules */
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
.hc-black.hc-theia.theia-hc #select-board-dialog .selectBoardContainer .list .item:hover {
outline: 1px dashed var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc div#select-board-dialog .selectBoardContainer .list .item.selected {
outline: 1px solid var(--theia-focusBorder);
}
@media only screen and (max-height: 400px) {
div.dialogContent.select-board-dialog > div.head {
display: none;

View File

@ -41,6 +41,17 @@
overflow: auto;
padding: 0 12px;
cursor: text;
width: 100%;
}
.ide-updater-dialog .changelog .fallback {
min-height: 180px;
width: 100%;
display: flex;
}
.ide-updater-dialog .changelog .fallback .spinner {
align-self: center;
}
.dialogContent.ide-updater-dialog
@ -104,3 +115,10 @@
margin-left: 79px;
margin-right: auto;
}
/* https://github.com/arduino/arduino-ide/pull/2027#issuecomment-1533174638 */
/* https://github.com/eclipse-theia/theia/commit/1b5ff9ee459df14cedc0e8266dd02dae93fcd1bf#diff-d8d45a890507a01141c010ad4a6891edf2ae727cfa6dfe604cebbd667812cccbR68 */
/* Use normal whitespace handling for the changelog content in the update dialog. */
.p-Widget.dialogOverlay .dialogContent.ide-updater-dialog {
white-space: normal;
}

View File

@ -54,12 +54,6 @@ body.theia-dark {
background-color: var(--theia-warningBackground);
}
.hc-black.hc-theia.theia-hc .theia-input.warning,
.hc-black.hc-theia.theia-hc .theia-input.warning::placeholder {
color: var(--theia-warningBackground);
background-color: var(--theia-warningForeground);
}
.theia-input.error:focus {
outline-width: 1px;
outline-style: solid;
@ -170,25 +164,6 @@ button.theia-button.message-box-dialog-button {
font-size: 14px;
}
/* High Contrast Theme rules */
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
.hc-black.hc-theia.theia-hc button.theia-button:hover,
.hc-black.hc-theia.theia-hc .theia-button:hover {
outline: 1px dashed var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc button.theia-button,
.hc-black.hc-theia.theia-hc .theia-button,
.hc-black.hc-theia.theia-hc button.theia-button.secondary {
border: 1px solid var(--theia-button-border);
}
.hc-black.hc-theia.theia-hc .theia-notification-list-item:hover:not(:focus) {
background-color: var(--theia-notifications-background);
outline: 1px dashed var(--theia-focusBorder);
outline-offset: -2px;
}
.debug-toolbar .debug-action>div {
font-family: var(--theia-ui-font-family);
font-size: var(--theia-ui-font-size0);

View File

@ -219,15 +219,3 @@ div.filterable-list-container > div > div > div > div:nth-child(1) > div.separat
width: 65px;
min-width: 65px;
}
/* High Contrast Theme rules */
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
.hc-black.hc-theia.theia-hc .component-list-item .header .installed-version:hover:before {
background-color: transparent;
outline: 1px dashed var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .component-list-item .header .installed-version:before {
color: var(--theia-button-background);
border: 1px solid var(--theia-button-border);
}

View File

@ -137,24 +137,6 @@
color: var(--theia-titleBar-activeForeground);
}
.p-TabBar-toolbar .item > div.arduino-toggle-advanced-mode {
display: flex;
width: 24px;
height: 24px;
justify-content: center;
align-items: center;
}
.arduino-toggle-advanced-mode-icon {
mask: none;
-webkit-mask: none;
background: none;
display: flex;
justify-content: center;
align-items: center;
color: var(--theia-titleBar-activeBackground);
}
.arduino-open-boards-control-icon {
mask: none;
-webkit-mask: none;
@ -202,61 +184,6 @@
background-color: var(--theia-terminal-background);
}
/* High Contrast Theme rules */
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
.hc-black.hc-theia.theia-hc .p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div {
background: var(--theia-arduino-toolbar-button-background);
outline: 1px dashed var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-plotter,
.hc-black.hc-theia.theia-hc .p-TabBar-toolbar .item.arduino-tool-item.enabled:hover > div.toggle-serial-monitor {
background: transparent;
}
.hc-black.hc-theia.theia-hc .item.arduino-tool-item.toggled .arduino-verify-sketch--toolbar,
.hc-black.hc-theia.theia-hc .item.arduino-tool-item.toggled .arduino-upload-sketch--toolbar {
background-color: var(--theia-arduino-toolbar-button-background) !important;
outline: 1px solid var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .arduino-boards-dropdown-item:hover {
background: var(--theia-dropdown-background);
}
.hc-black.hc-theia.theia-hc .arduino-boards-dropdown-item:hover {
outline: 1px dashed var(--theia-focusBorder);
outline-offset: -2px;
}
.hc-black.hc-theia.theia-hc #theia-main-content-panel .p-TabBar .p-TabBar-tab.p-mod-current {
outline: 1px solid var(--theia-focusBorder);
outline-offset: -4px;
}
.hc-black.hc-theia.theia-hc #theia-main-content-panel .p-TabBar .p-TabBar-tab:hover {
outline: 1px dashed var(--theia-focusBorder);
outline-offset: -4px;
}
.hc-black.hc-theia.theia-hc .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon:hover {
outline: 1px dashed var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .quick-input-list .monaco-list-row.focused {
outline: 1px dotted var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .quick-input-list .monaco-list-row:hover {
outline: 1px dashed var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .quick-input-widget {
outline: 1px solid var(--theia-contrastBorder);
outline-offset: -1px;
}
.monaco-hover .hover-row.markdown-hover:first-child p {
margin-top: 8px;
}

View File

@ -80,19 +80,3 @@ widget width.
background: var(--theia-list-inactiveSelectionBackground);
color: var(--theia-list-inactiveSelectionForeground) !important;
}
/* High Contrast Theme rules */
/* TODO: Remove it when the Theia version is upgraded to 1.27.0 and use Theia APIs to implement it*/
.hc-black.hc-theia.theia-hc .theia-TreeNode:hover {
outline: 1px dashed var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .theia-Tree .theia-TreeNode.theia-mod-selected {
outline: 1px dotted var(--theia-focusBorder);
}
.hc-black.hc-theia.theia-hc .theia-Tree:focus .theia-TreeNode.theia-mod-selected,
.hc-black.hc-theia.theia-hc .theia-Tree .ReactVirtualized__List:focus .theia-TreeNode.theia-mod-selected {
outline: 1px solid var(--theia-focusBorder);
}

View File

@ -20,8 +20,7 @@ import { ArduinoDaemon } from '../../../common/protocol';
import { assertUnreachable } from '../../../common/utils';
import { CreateFeatures } from '../../create/create-features';
import { NotificationCenter } from '../../notification-center';
import debounce = require('lodash.debounce');
import isOnline = require('is-online');
import debounce from 'lodash.debounce';
@injectable()
export class IsOnline implements FrontendApplicationContribution {
@ -30,17 +29,19 @@ export class IsOnline implements FrontendApplicationContribution {
private stopped = false;
onStart(): void {
const checkOnline = async () => {
if (!this.stopped) {
try {
const online = await isOnline();
this.setOnline(online);
} finally {
window.setTimeout(() => checkOnline(), 6_000); // 6 seconds poll interval
import('is-online').then((module) => {
const checkOnline = async () => {
if (!this.stopped) {
try {
const online = await module.default();
this.setOnline(online);
} finally {
window.setTimeout(() => checkOnline(), 6_000); // 6 seconds poll interval
}
}
}
};
checkOnline();
};
checkOnline();
});
}
onStop(): void {
@ -56,7 +57,7 @@ export class IsOnline implements FrontendApplicationContribution {
return this.onDidChangeOnlineEmitter.event;
}
private setOnline(online: boolean) {
private setOnline(online: boolean): void {
const oldOnline = this._online;
this._online = online;
if (!this.stopped && this._online !== oldOnline) {

View File

@ -4,7 +4,7 @@ import {
TabBarRenderer as TheiaTabBarRenderer,
ToolbarAwareTabBar as TheiaToolbarAwareTabBar,
} from '@theia/core/lib/browser/shell/tab-bars';
import debounce = require('lodash.debounce');
import debounce from 'lodash.debounce';
export class TabBarRenderer extends TheiaTabBarRenderer {
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -25,8 +25,8 @@ const builtInThemeIds = new Set(
[
ArduinoThemes.light,
ArduinoThemes.dark,
BuiltinThemeProvider.hcLightTheme,
BuiltinThemeProvider.hcTheme,
// TODO: add the HC light theme after Theia 1.36
].map(({ id }) => id)
);
const deprecatedThemeIds = new Set(
@ -37,7 +37,14 @@ const deprecatedThemeIds = new Set(
export const lightThemeLabel = nls.localize('arduino/theme/light', 'Light');
export const darkThemeLabel = nls.localize('arduino/theme/dark', 'Dark');
export const hcThemeLabel = nls.localize('arduino/theme/hc', 'High Contrast');
export const hcLightThemeLabel = nls.localize(
'arduino/theme/hcLight',
'Light High Contrast'
);
export const hcThemeLabel = nls.localize(
'arduino/theme/hc',
'Dark High Contrast'
);
export function userThemeLabel(theme: Theme): string {
return nls.localize('arduino/theme/user', '{0} (user)', theme.label);
}
@ -57,6 +64,8 @@ export function themeLabelForSettings(theme: Theme): string {
return darkThemeLabel;
case BuiltinThemeProvider.hcTheme.id:
return hcThemeLabel;
case BuiltinThemeProvider.hcLightTheme.id:
return hcLightThemeLabel;
case BuiltinThemeProvider.lightTheme.id: // fall-through
case BuiltinThemeProvider.darkTheme.id:
return deprecatedThemeLabel(theme);
@ -73,6 +82,8 @@ export function compatibleBuiltInTheme(theme: Theme): Theme {
return ArduinoThemes.dark;
case 'hc':
return BuiltinThemeProvider.hcTheme;
case 'hcLight':
return BuiltinThemeProvider.hcLightTheme;
default: {
console.warn(
`Unhandled theme type: ${theme.type}. Theme ID: ${theme.id}, label: ${theme.label}`
@ -91,7 +102,7 @@ interface ThemeProvider {
/**
* Returns with a list of built-in themes officially supported by IDE2 (https://github.com/arduino/arduino-ide/issues/1283).
* The themes in the array follow the following order:
* - built-in themes first (in `Light`, `Dark`, `High Contrast`), // TODO -> High Contrast will be split up to HC Dark and HC Light after the Theia version uplift
* - built-in themes first (in `Light`, `Dark`, `Light High Contrast`, and `Dark High Contrast`),
* - followed by user installed (VSIX) themes grouped by theme type, then alphabetical order,
* - if the `currentTheme` is either Light (Theia) or Dark (Theia), the last item of the array will be the selected theme with `(deprecated)` suffix.
*/
@ -171,7 +182,8 @@ const arduinoThemeTypeOrder: Record<ArduinoThemeType, number> = {
const themeTypeOrder: Record<ThemeType, number> = {
light: 0,
dark: 1,
hc: 2,
hcLight: 2,
hc: 3,
};
export function arduinoThemeTypeOf(theme: Theme | string): ArduinoThemeType {

View File

@ -1,4 +1,4 @@
import type { StartupTask } from '../../../electron-common/startup-task';
import type { StartupTasks } from '../../../electron-common/startup-task';
export const WindowServiceExt = Symbol('WindowServiceExt');
export interface WindowServiceExt {
@ -6,5 +6,6 @@ export interface WindowServiceExt {
* Returns with a promise that resolves to `true` if the current window is the first window.
*/
isFirstWindow(): Promise<boolean>;
reload(options?: StartupTask.Owner): void;
reload(tasks?: StartupTasks): void;
close(): void;
}

View File

@ -1,4 +1,3 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
import { NavigatableWidget } from '@theia/core/lib/browser/navigatable-types';
@ -118,10 +117,8 @@ export class WindowTitleUpdater extends TheiaWindowTitleUpdater {
if (widget instanceof EditorWidget) {
const { uri } = widget.editor;
const filename = uri.path.toString();
// Do not necessarily require the current window if not needed. It's a synchronous, blocking call.
if (this.previousRepresentedFilename !== filename) {
const currentWindow = remote.getCurrentWindow();
currentWindow.setRepresentedFilename(uri.path.toString());
window.electronArduino.setRepresentedFilename(uri.path.fsPath());
this.previousRepresentedFilename = filename;
}
}

View File

@ -1,29 +0,0 @@
import * as React from '@theia/core/shared/react';
import { DebugAction as TheiaDebugAction } from '@theia/debug/lib/browser/view/debug-action';
import {
codiconArray,
DISABLED_CLASS,
} from '@theia/core/lib/browser/widgets/widget';
// customized debug action to show the contributed command's label when there is no icon
export class DebugAction extends TheiaDebugAction {
override render(): React.ReactNode {
const { enabled, label, iconClass } = this.props;
const classNames = ['debug-action', ...codiconArray(iconClass, true)];
if (enabled === false) {
classNames.push(DISABLED_CLASS);
}
return (
<span
tabIndex={0}
className={classNames.join(' ')}
title={label}
onClick={this.props.run}
ref={this.setRef}
>
{!iconClass ||
(iconClass.match(/plugin-icon-\d+/) && <div>{label}</div>)}
</span>
);
}
}

View File

@ -1,4 +1,4 @@
import debounce = require('p-debounce');
import debounce from 'p-debounce';
import {
inject,
injectable,

View File

@ -43,7 +43,8 @@ export class DefaultDebugSessionFactory extends TheiaDefaultDebugSessionFactory
this.messages,
this.fileService,
this.debugContributionProvider,
this.workspaceService
this.workspaceService,
2_000
);
}
}

View File

@ -1,120 +0,0 @@
import type { ContextKey } from '@theia/core/lib/browser/context-key-service';
import { injectable, postConstruct } from '@theia/core/shared/inversify';
import {
DebugSession,
DebugState,
} from '@theia/debug/lib/browser/debug-session';
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
import type { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
function debugStateLabel(state: DebugState): string {
switch (state) {
case DebugState.Initializing:
return 'initializing';
case DebugState.Stopped:
return 'stopped';
case DebugState.Running:
return 'running';
default:
return 'inactive';
}
}
@injectable()
export class DebugSessionManager extends TheiaDebugSessionManager {
protected debugStateKey: ContextKey<string>;
@postConstruct()
protected override init(): void {
this.debugStateKey = this.contextKeyService.createKey<string>(
'debugState',
debugStateLabel(this.state)
);
super.init();
}
protected override fireDidChange(current: DebugSession | undefined): void {
this.debugTypeKey.set(current?.configuration.type);
this.inDebugModeKey.set(this.inDebugMode);
this.debugStateKey.set(debugStateLabel(this.state));
this.onDidChangeEmitter.fire(current);
}
protected override async doStart(
sessionId: string,
options: DebugConfigurationSessionOptions
): Promise<DebugSession> {
const parentSession =
options.configuration.parentSession &&
this._sessions.get(options.configuration.parentSession.id);
const contrib = this.sessionContributionRegistry.get(
options.configuration.type
);
const sessionFactory = contrib
? contrib.debugSessionFactory()
: this.debugSessionFactory;
const session = sessionFactory.get(sessionId, options, parentSession);
this._sessions.set(sessionId, session);
this.debugTypeKey.set(session.configuration.type);
// this.onDidCreateDebugSessionEmitter.fire(session); // defer the didCreate event after start https://github.com/eclipse-theia/theia/issues/11916
let state = DebugState.Inactive;
session.onDidChange(() => {
if (state !== session.state) {
state = session.state;
if (state === DebugState.Stopped) {
this.onDidStopDebugSessionEmitter.fire(session);
}
}
this.updateCurrentSession(session);
});
session.onDidChangeBreakpoints((uri) =>
this.fireDidChangeBreakpoints({ session, uri })
);
session.on('terminated', async (event) => {
const restart = event.body && event.body.restart;
if (restart) {
// postDebugTask isn't run in case of auto restart as well as preLaunchTask
this.doRestart(session, !!restart);
} else {
await session.disconnect(false, () =>
this.debug.terminateDebugSession(session.id)
);
await this.runTask(
session.options.workspaceFolderUri,
session.configuration.postDebugTask
);
}
});
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
session.on('exited', async (event) => {
await session.disconnect(false, () =>
this.debug.terminateDebugSession(session.id)
);
});
session.onDispose(() => this.cleanup(session));
session
.start()
.then(() => {
this.onDidCreateDebugSessionEmitter.fire(session); // now fire the didCreate event
this.onDidStartDebugSessionEmitter.fire(session);
})
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
.catch((e) => {
session.stop(false, () => {
this.debug.terminateDebugSession(session.id);
});
});
session.onDidCustomEvent(({ event, body }) =>
this.onDidReceiveDebugSessionCustomEventEmitter.fire({
event,
body,
session,
})
);
return session;
}
}

View File

@ -1,231 +1,12 @@
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { Mutable } from '@theia/core/lib/common/types';
import { URI } from '@theia/core/lib/common/uri';
import { DebugSession as TheiaDebugSession } from '@theia/debug/lib/browser/debug-session';
import { DebugFunctionBreakpoint } from '@theia/debug/lib/browser/model/debug-function-breakpoint';
import { DebugSourceBreakpoint } from '@theia/debug/lib/browser/model/debug-source-breakpoint';
import {
DebugThreadData,
StoppedDetails,
} from '@theia/debug/lib/browser/model/debug-thread';
import { DebugProtocol } from '@vscode/debugprotocol';
import { DebugThread } from './debug-thread';
export class DebugSession extends TheiaDebugSession {
/**
* The `send('initialize')` request resolves later than `on('initialized')` emits the event.
* Hence, the `configure` would use the empty object `capabilities`.
* Using the empty `capabilities` could result in missing exception breakpoint filters, as
* always `capabilities.exceptionBreakpointFilters` is falsy. This deferred promise works
* around this timing issue.
* See: https://github.com/eclipse-theia/theia/issues/11886.
*/
protected didReceiveCapabilities = new Deferred();
protected override async initialize(): Promise<void> {
const clientName = FrontendApplicationConfigProvider.get().applicationName;
try {
const response = await this.connection.sendRequest('initialize', {
clientID: clientName.toLocaleLowerCase().replace(/ /g, '_'),
clientName,
adapterID: this.configuration.type,
locale: 'en-US',
linesStartAt1: true,
columnsStartAt1: true,
pathFormat: 'path',
supportsVariableType: false,
supportsVariablePaging: false,
supportsRunInTerminalRequest: true,
});
this.updateCapabilities(response?.body || {});
this.didReceiveCapabilities.resolve();
} catch (err) {
this.didReceiveCapabilities.reject(err);
throw err;
}
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
protected override handleDisconnectError(err: unknown): void {
// NOOP
}
protected override async configure(): Promise<void> {
await this.didReceiveCapabilities.promise;
return super.configure();
}
override async stop(isRestart: boolean, callback: () => void): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _this = this as any;
if (!_this.isStopping) {
_this.isStopping = true;
if (this.configuration.lifecycleManagedByParent && this.parentSession) {
await this.parentSession.stop(isRestart, callback);
} else {
if (this.canTerminate()) {
const terminated = this.waitFor('terminated', 5000);
try {
await this.connection.sendRequest(
'terminate',
{ restart: isRestart },
5000
);
await terminated;
} catch (e) {
console.error('Did not receive terminated event in time', e);
}
} else {
const terminateDebuggee =
this.initialized && this.capabilities.supportTerminateDebuggee;
// Related https://github.com/microsoft/vscode/issues/165138
try {
await this.sendRequest(
'disconnect',
{ restart: isRestart, terminateDebuggee },
2000
);
} catch (err) {
if (
'message' in err &&
typeof err.message === 'string' &&
err.message.test(err.message)
) {
// VS Code ignores errors when sending the `disconnect` request.
// Debug adapter might not send the `disconnected` event as a response.
} else {
throw err;
}
}
}
callback();
}
}
}
protected override async sendFunctionBreakpoints(
affectedUri: URI
): Promise<void> {
const all = this.breakpoints
.getFunctionBreakpoints()
.map(
(origin) =>
new DebugFunctionBreakpoint(origin, this.asDebugBreakpointOptions())
);
const enabled = all.filter((b) => b.enabled);
if (this.capabilities.supportsFunctionBreakpoints) {
try {
const response = await this.sendRequest('setFunctionBreakpoints', {
breakpoints: enabled.map((b) => b.origin.raw),
});
// Apparently, `body` and `breakpoints` can be missing.
// https://github.com/eclipse-theia/theia/issues/11885
// https://github.com/microsoft/vscode/blob/80004351ccf0884b58359f7c8c801c91bb827d83/src/vs/workbench/contrib/debug/browser/debugSession.ts#L448-L449
if (response && response.body) {
response.body.breakpoints.forEach((raw, index) => {
// node debug adapter returns more breakpoints sometimes
if (enabled[index]) {
enabled[index].update({ raw });
}
});
}
} catch (error) {
// could be error or promise rejection of DebugProtocol.SetFunctionBreakpoints
if (error instanceof Error) {
console.error(`Error setting breakpoints: ${error.message}`);
} else {
// handle adapters that send failed DebugProtocol.SetFunctionBreakpoints for invalid breakpoints
const genericMessage =
'Function breakpoint not valid for current debug session';
const message = error.message ? `${error.message}` : genericMessage;
console.warn(
`Could not handle function breakpoints: ${message}, disabling...`
);
enabled.forEach((b) =>
b.update({
raw: {
verified: false,
message,
},
})
);
}
}
}
this.setBreakpoints(affectedUri, all);
}
protected override async sendSourceBreakpoints(
affectedUri: URI,
sourceModified?: boolean
): Promise<void> {
const source = await this.toSource(affectedUri);
const all = this.breakpoints
.findMarkers({ uri: affectedUri })
.map(
({ data }) =>
new DebugSourceBreakpoint(data, this.asDebugBreakpointOptions())
);
const enabled = all.filter((b) => b.enabled);
try {
const breakpoints = enabled.map(({ origin }) => origin.raw);
const response = await this.sendRequest('setBreakpoints', {
source: source.raw,
sourceModified,
breakpoints,
lines: breakpoints.map(({ line }) => line),
});
response.body.breakpoints.forEach((raw, index) => {
// node debug adapter returns more breakpoints sometimes
if (enabled[index]) {
enabled[index].update({ raw });
}
});
} catch (error) {
// could be error or promise rejection of DebugProtocol.SetBreakpointsResponse
if (error instanceof Error) {
console.error(`Error setting breakpoints: ${error.message}`);
} else {
// handle adapters that send failed DebugProtocol.SetBreakpointsResponse for invalid breakpoints
const genericMessage = 'Breakpoint not valid for current debug session';
const message = error.message ? `${error.message}` : genericMessage;
console.warn(
`Could not handle breakpoints for ${affectedUri}: ${message}, disabling...`
);
enabled.forEach((b) =>
b.update({
raw: {
verified: false,
message,
},
})
);
}
}
this.setSourceBreakpoints(affectedUri, all);
}
protected override doUpdateThreads(
threads: DebugProtocol.Thread[],
stoppedDetails?: StoppedDetails
): void {
const existing = this._threads;
this._threads = new Map();
for (const raw of threads) {
const id = raw.id;
const thread = existing.get(id) || new DebugThread(this); // patched debug thread
this._threads.set(id, thread);
const data: Partial<Mutable<DebugThreadData>> = { raw };
if (stoppedDetails) {
if (stoppedDetails.threadId === id) {
data.stoppedDetails = stoppedDetails;
} else if (stoppedDetails.allThreadsStopped) {
data.stoppedDetails = {
// When a debug adapter notifies us that all threads are stopped,
// we do not know why the others are stopped, so we should default
// to something generic.
reason: '',
};
}
}
thread.update(data);
}
this.updateCurrentThread(stoppedDetails);
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
protected override handleTerminateError(err: unknown): void {
// NOOP
}
}

View File

@ -1,32 +0,0 @@
import { WidgetOpenerOptions } from '@theia/core/lib/browser/widget-open-handler';
import { Range } from '@theia/core/shared/vscode-languageserver-types';
import { DebugStackFrame as TheiaDebugStackFrame } from '@theia/debug/lib/browser/model/debug-stack-frame';
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
export class DebugStackFrame extends TheiaDebugStackFrame {
override async open(
options: WidgetOpenerOptions = {
mode: 'reveal',
}
): Promise<EditorWidget | undefined> {
if (!this.source) {
return undefined;
}
const { line, column, endLine, endColumn, source } = this.raw;
if (!source) {
return undefined;
}
// create selection based on VS Code
// https://github.com/eclipse-theia/theia/issues/11880
const selection = Range.create(
line,
column,
endLine || line,
endColumn || column
);
this.source.open({
...options,
selection,
});
}
}

View File

@ -1,22 +0,0 @@
import { DebugStackFrame as TheiaDebugStackFrame } from '@theia/debug/lib/browser/model/debug-stack-frame';
import { DebugThread as TheiaDebugThread } from '@theia/debug/lib/browser/model/debug-thread';
import { DebugProtocol } from '@vscode/debugprotocol';
import { DebugStackFrame } from './debug-stack-frame';
export class DebugThread extends TheiaDebugThread {
protected override doUpdateFrames(
frames: DebugProtocol.StackFrame[]
): TheiaDebugStackFrame[] {
const result = new Set<TheiaDebugStackFrame>();
for (const raw of frames) {
const id = raw.id;
const frame =
this._frames.get(id) || new DebugStackFrame(this, this.session); // patched debug stack frame
this._frames.set(id, frame);
frame.update({ raw });
result.add(frame);
}
this.updateCurrentFrame();
return [...result.values()];
}
}

View File

@ -1,85 +0,0 @@
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
import { CommandRegistry } from '@theia/core/lib/common/command';
import {
ActionMenuNode,
CompositeMenuNode,
MenuModelRegistry,
} from '@theia/core/lib/common/menu';
import { nls } from '@theia/core/lib/common/nls';
import { inject, injectable } from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
import { DebugState } from '@theia/debug/lib/browser/debug-session';
import { DebugAction } from './debug-action';
import { DebugToolBar as TheiaDebugToolbar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
@injectable()
export class DebugToolbar extends TheiaDebugToolbar {
@inject(CommandRegistry) private readonly commandRegistry: CommandRegistry;
@inject(MenuModelRegistry)
private readonly menuModelRegistry: MenuModelRegistry;
@inject(ContextKeyService)
private readonly contextKeyService: ContextKeyService;
protected override render(): React.ReactNode {
const { state } = this.model;
return (
<React.Fragment>
{this.renderContributedCommands()}
{this.renderContinue()}
<DebugAction
enabled={state === DebugState.Stopped}
run={this.stepOver}
label={nls.localizeByDefault('Step Over')}
iconClass="debug-step-over"
ref={this.setStepRef}
/>
<DebugAction
enabled={state === DebugState.Stopped}
run={this.stepIn}
label={nls.localizeByDefault('Step Into')}
iconClass="debug-step-into"
/>
<DebugAction
enabled={state === DebugState.Stopped}
run={this.stepOut}
label={nls.localizeByDefault('Step Out')}
iconClass="debug-step-out"
/>
<DebugAction
enabled={state !== DebugState.Inactive}
run={this.restart}
label={nls.localizeByDefault('Restart')}
iconClass="debug-restart"
/>
{this.renderStart()}
</React.Fragment>
);
}
private renderContributedCommands(): React.ReactNode {
return this.menuModelRegistry
.getMenu(TheiaDebugToolbar.MENU)
.children.filter((node) => node instanceof CompositeMenuNode)
.map((node) => (node as CompositeMenuNode).children)
.reduce((acc, curr) => acc.concat(curr), [])
.filter((node) => node instanceof ActionMenuNode)
.map((node) => this.debugAction(node as ActionMenuNode));
}
private debugAction(node: ActionMenuNode): React.ReactNode {
const { label, command, when, icon: iconClass = '' } = node;
const run = () => this.commandRegistry.executeCommand(command);
const enabled = when ? this.contextKeyService.match(when) : true;
return (
enabled && (
<DebugAction
key={command}
enabled={enabled}
label={label}
iconClass={iconClass}
run={run}
/>
)
);
}
}

View File

@ -1,5 +1,3 @@
import { ResourceSaveOptions } from '@theia/core/lib/common/resource';
import { Readable } from '@theia/core/lib/common/stream';
import URI from '@theia/core/lib/common/uri';
import { injectable } from '@theia/core/shared/inversify';
import {
@ -11,14 +9,13 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service';
import {
FileOperationError,
FileOperationResult,
FileStat,
} from '@theia/filesystem/lib/common/files';
import * as PQueue from 'p-queue';
import PQueue from 'p-queue';
@injectable()
export class FileResourceResolver extends TheiaFileResourceResolver {
override async resolve(uri: URI): Promise<WriteQueuedFileResource> {
let stat: FileStat | undefined;
let stat;
try {
stat = await this.fileService.resolve(uri);
} catch (e) {
@ -37,6 +34,7 @@ export class FileResourceResolver extends TheiaFileResourceResolver {
);
}
return new WriteQueuedFileResource(uri, this.fileService, {
isReadonly: stat?.isReadonly ?? false,
shouldOverwrite: () => this.shouldOverwrite(uri),
shouldOpenAsText: (error) => this.shouldOpenAsText(uri, error),
});
@ -52,23 +50,32 @@ class WriteQueuedFileResource extends FileResource {
options: FileResourceOptions
) {
super(uri, fileService, options);
const originalDoWrite = this['doWrite'];
this['doWrite'] = (content, options) =>
this.writeQueue.add(() => originalDoWrite.bind(this)(content, options));
const originalSaveStream = this['saveStream'];
if (originalSaveStream) {
this['saveStream'] = (content, options) =>
this.writeQueue.add(() =>
originalSaveStream.bind(this)(content, options)
);
}
const originalSaveContents = this['saveContents'];
if (originalSaveContents) {
this['saveContents'] = (content, options) =>
this.writeQueue.add(() =>
originalSaveContents.bind(this)(content, options)
);
}
const originalSaveContentChanges = this['saveContentChanges'];
if (originalSaveContentChanges) {
this['saveContentChanges'] = (changes, options) => {
return this.writeQueue.add(() =>
this['saveContentChanges'] = (changes, options) =>
this.writeQueue.add(() =>
originalSaveContentChanges.bind(this)(changes, options)
);
};
}
}
protected override async doWrite(
content: string | Readable<string>,
options?: ResourceSaveOptions
): Promise<void> {
return this.writeQueue.add(() => super.doWrite(content, options));
}
protected override async isInSync(): Promise<boolean> {
// Let all the write operations finish to update the version (mtime) before checking whether the resource is in sync.
// https://github.com/eclipse-theia/theia/issues/12327

View File

@ -8,7 +8,7 @@ import URI from '@theia/core/lib/common/uri';
import { Marker } from '@theia/markers/lib/common/marker';
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
import { ConfigServiceClient } from '../../config/config-service-client';
import debounce = require('lodash.debounce');
import debounce from 'lodash.debounce';
import {
ARDUINO_CLOUD_FOLDER,
REMOTE_SKETCHBOOK_FOLDER,

View File

@ -0,0 +1,24 @@
import type { MenuModelRegistry } from '@theia/core/lib/common/menu/menu-model-registry';
import { injectable } from '@theia/core/shared/inversify';
import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } from '@theia/monaco/lib/browser/monaco-menu';
@injectable()
export class MonacoEditorMenuContribution extends TheiaMonacoEditorMenuContribution {
override registerMenus(registry: MenuModelRegistry): void {
super.registerMenus(registry);
// https://github.com/arduino/arduino-ide/issues/1394
registry.unregisterMenuAction('editor.action.refactor'); // Refactor...
registry.unregisterMenuAction('editor.action.sourceAction'); // Source Action...
// https://github.com/arduino/arduino-ide/pull/2027#pullrequestreview-1414246614
// Root editor context menu
registry.unregisterMenuAction('editor.action.revealDeclaration'); // Go to Declaration
registry.unregisterMenuAction('editor.action.goToTypeDefinition'); // Go to Type Definition
registry.unregisterMenuAction('editor.action.goToImplementation'); // Go to Implementations
registry.unregisterMenuAction('editor.action.goToReferences'); // Go to References
// Peek submenu
registry.unregisterMenuAction('editor.action.peekDeclaration'); // Peek Declaration
registry.unregisterMenuAction('editor.action.peekTypeDefinition'); // Peek Type Definition
registry.unregisterMenuAction('editor.action.peekImplementation'); // Peek Implementation
registry.unregisterMenuAction('editor.action.referenceSearch.trigger'); // Peek References
}
}

View File

@ -1,66 +0,0 @@
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { DebuggerDescription } from '@theia/debug/lib/common/debug-service';
import { DebugMainImpl as TheiaDebugMainImpl } from '@theia/plugin-ext/lib/main/browser/debug/debug-main';
import { PluginDebugAdapterContribution } from '@theia/plugin-ext/lib/main/browser/debug/plugin-debug-adapter-contribution';
import { PluginDebugSessionFactory } from './plugin-debug-session-factory';
export class DebugMainImpl extends TheiaDebugMainImpl {
override async $registerDebuggerContribution(
description: DebuggerDescription
): Promise<void> {
const debugType = description.type;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _this = <any>this;
const terminalOptionsExt = await _this.debugExt.$getTerminalCreationOptions(
debugType
);
if (_this.toDispose.disposed) {
return;
}
const debugSessionFactory = new PluginDebugSessionFactory(
_this.terminalService,
_this.editorManager,
_this.breakpointsManager,
_this.labelProvider,
_this.messages,
_this.outputChannelManager,
_this.debugPreferences,
async (sessionId: string) => {
const connection = await _this.connectionMain.ensureConnection(
sessionId
);
return connection;
},
_this.fileService,
terminalOptionsExt,
_this.debugContributionProvider,
_this.workspaceService
);
const toDispose = new DisposableCollection(
Disposable.create(() => _this.debuggerContributions.delete(debugType))
);
_this.debuggerContributions.set(debugType, toDispose);
toDispose.pushAll([
_this.pluginDebugService.registerDebugAdapterContribution(
new PluginDebugAdapterContribution(
description,
_this.debugExt,
_this.pluginService
)
),
_this.sessionContributionRegistrator.registerDebugSessionContribution({
debugType: description.type,
debugSessionFactory: () => debugSessionFactory,
}),
]);
_this.toDispose.push(
Disposable.create(() => this.$unregisterDebuggerConfiguration(debugType))
);
}
}

View File

@ -1,16 +1,7 @@
import { Emitter, Event, JsonRpcProxy } from '@theia/core';
import { injectable, interfaces } from '@theia/core/shared/inversify';
import { HostedPluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol';
import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
import {
HostedPluginSupport as TheiaHostedPluginSupport,
PluginHost,
} from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
import { PluginWorker } from '@theia/plugin-ext/lib/hosted/browser/plugin-worker';
import { setUpPluginApi } from '@theia/plugin-ext/lib/main/browser/main-context';
import { PLUGIN_RPC_CONTEXT } from '@theia/plugin-ext/lib/common/plugin-api-rpc';
import { DebugMainImpl } from './debug-main';
import { ConnectionImpl } from '@theia/plugin-ext/lib/common/connection';
import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
@injectable()
export class HostedPluginSupport extends TheiaHostedPluginSupport {
@ -41,26 +32,4 @@ export class HostedPluginSupport extends TheiaHostedPluginSupport {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (this as any).server;
}
// to patch the VS Code extension based debugger
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
protected override initRpc(host: PluginHost, pluginId: string): RPCProtocol {
const rpc =
host === 'frontend' ? new PluginWorker().rpc : this.createServerRpc(host);
setUpPluginApi(rpc, this.container);
this.patchDebugMain(rpc);
this.mainPluginApiProviders
.getContributions()
.forEach((p) => p.initialize(rpc, this.container));
return rpc;
}
private patchDebugMain(rpc: RPCProtocol): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const connectionMain = (rpc as any).locals.get(
PLUGIN_RPC_CONTEXT.CONNECTION_MAIN.id
) as ConnectionImpl;
const debugMain = new DebugMainImpl(rpc, connectionMain, this.container);
rpc.set(PLUGIN_RPC_CONTEXT.DEBUG_MAIN, debugMain);
}
}

View File

@ -1,37 +0,0 @@
import { DebugSession } from '@theia/debug/lib/browser/debug-session';
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
import { PluginDebugSessionFactory as TheiaPluginDebugSessionFactory } from '@theia/plugin-ext/lib/main/browser/debug/plugin-debug-session-factory';
import { PluginDebugSession } from './plugin-debug-session';
export class PluginDebugSessionFactory extends TheiaPluginDebugSessionFactory {
override get(
sessionId: string,
options: DebugConfigurationSessionOptions,
parentSession?: DebugSession
): DebugSession {
const connection = new DebugSessionConnection(
sessionId,
this.connectionFactory,
this.getTraceOutputChannel()
);
return new PluginDebugSession(
sessionId,
options,
parentSession,
connection,
this.terminalService,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.editorManager as any,
this.breakpoints,
this.labelProvider,
this.messages,
this.fileService,
this.terminalOptionsExt,
this.debugContributionProvider,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.workspaceService as any
);
}
}

View File

@ -1,62 +0,0 @@
import { ContributionProvider, MessageClient } from '@theia/core';
import { LabelProvider } from '@theia/core/lib/browser';
import { BreakpointManager } from '@theia/debug/lib/browser/breakpoint/breakpoint-manager';
import { DebugContribution } from '@theia/debug/lib/browser/debug-contribution';
import { DebugSession as TheiaDebugSession } from '@theia/debug/lib/browser/debug-session';
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { TerminalOptionsExt } from '@theia/plugin-ext';
import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
import {
TerminalWidget,
TerminalWidgetOptions,
} from '@theia/terminal/lib/browser/base/terminal-widget';
import { DebugSession } from '../debug/debug-session';
import { EditorManager } from '../editor/editor-manager';
import { WorkspaceService } from '../workspace/workspace-service';
// This class extends the patched debug session, and not the default debug session from Theia
export class PluginDebugSession extends DebugSession {
constructor(
override readonly id: string,
override readonly options: DebugConfigurationSessionOptions,
override readonly parentSession: TheiaDebugSession | undefined,
protected override readonly connection: DebugSessionConnection,
protected override readonly terminalServer: TerminalService,
protected override readonly editorManager: EditorManager,
protected override readonly breakpoints: BreakpointManager,
protected override readonly labelProvider: LabelProvider,
protected override readonly messages: MessageClient,
protected override readonly fileService: FileService,
protected readonly terminalOptionsExt: TerminalOptionsExt | undefined,
protected override readonly debugContributionProvider: ContributionProvider<DebugContribution>,
protected override readonly workspaceService: WorkspaceService
) {
super(
id,
options,
parentSession,
connection,
terminalServer,
editorManager,
breakpoints,
labelProvider,
messages,
fileService,
debugContributionProvider,
workspaceService
);
}
protected override async doCreateTerminal(
terminalWidgetOptions: TerminalWidgetOptions
): Promise<TerminalWidget> {
terminalWidgetOptions = Object.assign(
{},
terminalWidgetOptions,
this.terminalOptionsExt
);
return super.doCreateTerminal(terminalWidgetOptions);
}
}

View File

@ -1,73 +0,0 @@
import { MenuPath } from '@theia/core';
import { TAB_BAR_TOOLBAR_CONTEXT_MENU } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { injectable, postConstruct } from '@theia/core/shared/inversify';
import { DebugToolBar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
import { DebugVariablesWidget } from '@theia/debug/lib/browser/view/debug-variables-widget';
import {
ArgumentAdapter,
PluginMenuCommandAdapter as TheiaPluginMenuCommandAdapter,
} from '@theia/plugin-ext/lib/main/browser/menus/plugin-menu-command-adapter';
import {
codeToTheiaMappings,
ContributionPoint,
} from '@theia/plugin-ext/lib/main/browser/menus/vscode-theia-menu-mappings';
function patch(
toPatch: typeof codeToTheiaMappings,
key: string,
value: MenuPath[]
): void {
const loose = toPatch as Map<string, MenuPath[]>;
if (!loose.has(key)) {
loose.set(key, value);
}
}
// mappings is a const and cannot be customized with DI
patch(codeToTheiaMappings, 'debug/variables/context', [
DebugVariablesWidget.CONTEXT_MENU,
]);
patch(codeToTheiaMappings, 'debug/toolBar', [DebugToolBar.MENU]);
@injectable()
export class PluginMenuCommandAdapter extends TheiaPluginMenuCommandAdapter {
@postConstruct()
protected override init(): void {
const toCommentArgs: ArgumentAdapter = (...args) =>
this.toCommentArgs(...args);
const firstArgOnly: ArgumentAdapter = (...args) => [args[0]];
const noArgs: ArgumentAdapter = () => [];
const toScmArgs: ArgumentAdapter = (...args) => this.toScmArgs(...args);
const selectedResource = () => this.getSelectedResources();
const widgetURI: ArgumentAdapter = (widget) =>
this.codeEditorUtil.is(widget)
? [this.codeEditorUtil.getResourceUri(widget)]
: [];
(<Array<[ContributionPoint, ArgumentAdapter | undefined]>>[
['comments/comment/context', toCommentArgs],
['comments/comment/title', toCommentArgs],
['comments/commentThread/context', toCommentArgs],
['debug/callstack/context', firstArgOnly],
['debug/variables/context', firstArgOnly],
['debug/toolBar', noArgs],
['editor/context', selectedResource],
['editor/title', widgetURI],
['editor/title/context', selectedResource],
['explorer/context', selectedResource],
['scm/resourceFolder/context', toScmArgs],
['scm/resourceGroup/context', toScmArgs],
['scm/resourceState/context', toScmArgs],
['scm/title', () => this.toScmArg(this.scmService.selectedRepository)],
['timeline/item/context', (...args) => this.toTimelineArgs(...args)],
['view/item/context', (...args) => this.toTreeArgs(...args)],
['view/title', noArgs],
]).forEach(([contributionPoint, adapter]) => {
if (adapter) {
const paths = codeToTheiaMappings.get(contributionPoint);
if (paths) {
paths.forEach((path) => this.addArgumentAdapter(path, adapter));
}
}
});
this.addArgumentAdapter(TAB_BAR_TOOLBAR_CONTEXT_MENU, widgetURI);
}
}

View File

@ -15,8 +15,9 @@ import {
SketchesService,
} from '../../../common/protocol/sketches-service';
import {
StartupTask,
StartupTaskProvider,
hasStartupTasks,
StartupTask,
} from '../../../electron-common/startup-task';
import { WindowServiceExt } from '../core/window-service-ext';
@ -128,7 +129,7 @@ export class WorkspaceService extends TheiaWorkspaceService {
.getContributions()
.map((contribution) => contribution.tasks())
.reduce((prev, curr) => prev.concat(curr), []);
if (StartupTask.has(options)) {
if (hasStartupTasks(options)) {
tasks.push(...options.tasks);
}
return tasks;

View File

@ -1,4 +1,4 @@
import { notEmpty } from '@theia/core';
import { notEmpty } from '@theia/core/lib/common/objects';
/**
* Finds the closest child HTMLButtonElement representing a Theia button.

View File

@ -1,16 +1,9 @@
import * as React from '@theia/core/shared/react';
import type { Props, StylesConfig, ThemeConfig } from 'react-select';
import Select from 'react-select';
import type { StylesConfig } from 'react-select/dist/declarations/src/styles';
import type { ThemeConfig } from 'react-select/dist/declarations/src/theme';
import type { GroupBase } from 'react-select/dist/declarations/src/types';
import type { StateManagerProps } from 'react-select/dist/declarations/src/useStateManager';
export class ArduinoSelect<
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
> extends React.Component<StateManagerProps<Option, IsMulti, Group>> {
constructor(props: StateManagerProps<Option, IsMulti, Group>) {
export class ArduinoSelect extends React.Component<Props> {
constructor(props: Props) {
super(props);
}

View File

@ -12,8 +12,8 @@ import { AuthenticationClientService } from '../../auth/authentication-client-se
import { CloudSketchbookTreeModel } from './cloud-sketchbook-tree-model';
import { BaseSketchbookCompositeWidget } from '../sketchbook/sketchbook-composite-widget';
import { CreateNew } from '../sketchbook/create-new';
import { AuthenticationSession } from '../../../node/auth/types';
import { ApplicationConnectionStatusContribution } from '../../theia/core/connection-status-service';
import { AuthenticationSession } from '../../../common/protocol/authentication-service';
@injectable()
export class CloudSketchbookCompositeWidget extends BaseSketchbookCompositeWidget<CloudSketchbookTreeWidget> {

View File

@ -34,6 +34,7 @@ import { SketchbookCommands } from '../sketchbook/sketchbook-commands';
import { CloudSketchbookCommands } from './cloud-sketchbook-commands';
import { CloudSketchbookTree } from './cloud-sketchbook-tree';
import { CreateUri } from '../../create/create-uri';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
const SKETCHBOOKSYNC__CONTEXT = ['arduino-sketchbook-sync--context'];
@ -61,6 +62,8 @@ export class CloudSketchbookContribution extends CloudSketchContribution {
private readonly configServiceClient: ConfigServiceClient;
@inject(ApplicationConnectionStatusContribution)
private readonly connectionStatus: ApplicationConnectionStatusContribution;
@inject(ClipboardService)
private readonly clipboardService: ClipboardService;
private readonly onDidChangeToolbarEmitter = new Emitter<void>();
private readonly toDisposeBeforeNewContextMenu = new DisposableCollection();
@ -176,6 +179,7 @@ export class CloudSketchbookContribution extends CloudSketchContribution {
node: arg.node,
title: nls.localize('arduino/cloud/shareSketch', 'Share Sketch'),
createApi: this.createApi,
clipboardService: this.clipboardService,
}).open();
},
isEnabled: (arg) => this.isCloudSketchDirNodeCommandArg(arg),

View File

@ -35,6 +35,7 @@ function sketchBaseDir(sketch: Create.Sketch): FileStat {
isDirectory: true,
isFile: false,
isSymbolicLink: false,
isReadonly: false,
resource: createPath,
mtime,
ctime,

View File

@ -12,7 +12,6 @@ import {
import { NodeProps } from '@theia/core/lib/browser/tree/tree-widget';
import { TreeNode } from '@theia/core/lib/browser/tree';
import { CompositeTreeNode } from '@theia/core/lib/browser';
import { shell } from '@theia/core/electron-shared/@electron/remote';
import { SketchbookTreeWidget } from '../sketchbook/sketchbook-tree-widget';
import { nls } from '@theia/core/lib/common';
import { ApplicationConnectionStatusContribution } from '../../theia/core/connection-status-service';
@ -60,7 +59,12 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
</div>
<button
className="theia-button uppercase"
onClick={() => shell.openExternal('https://create.arduino.cc/editor')}
onClick={() =>
this.windowService.openNewWindow(
'https://create.arduino.cc/editor',
{ external: true }
)
}
>
{nls.localize('arduino/cloud/goToCloud', 'Go to Cloud')}
</button>
@ -81,7 +85,10 @@ export class CloudSketchbookTreeWidget extends SketchbookTreeWidget {
return CompositeTreeNode.is(node) && node.children.length === 0;
}
protected override createNodeClassNames(node: any, props: NodeProps): string[] {
protected override createNodeClassNames(
node: any,
props: NodeProps
): string[] {
const classNames = super.createNodeClassNames(node, props);
if (

View File

@ -1,5 +1,5 @@
import * as React from '@theia/core/shared/react';
import debounce = require('lodash.debounce');
import debounce from 'lodash.debounce';
import { Event } from '@theia/core/lib/common/event';
import { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service';

View File

@ -1,4 +1,3 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { inject, injectable } from '@theia/core/shared/inversify';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
@ -120,7 +119,7 @@ export class SketchbookWidgetContribution
if (exists) {
const fsPath = await this.fileService.fsPath(new URI(arg.node.uri));
if (fsPath) {
remote.shell.openPath(fsPath);
window.electronArduino.openPath(fsPath);
}
}
}

View File

@ -1,2 +0,0 @@
export const SHOW_PLOTTER_WINDOW = 'SHOW_PLOTTER_WINDOW';
export const CLOSE_PLOTTER_WINDOW = 'CLOSE_PLOTTER_WINDOW';

View File

@ -1,5 +1,15 @@
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { AuthOptions } from '../../node/auth/types';
export const authServerPort = 9876;
export interface AuthOptions {
redirectUri: string;
responseType: string;
clientID: string;
domain: string;
audience: string;
registerUri: string;
scopes: string[];
}
export interface AuthenticationSession {
readonly id: string;

View File

@ -1,8 +1,12 @@
import * as semver from 'semver';
import { ExecuteWithProgress } from './progressible';
import type { MessageService } from '@theia/core/lib/common/message-service';
import {
coerce as coerceSemver,
compare as compareSemver,
parse as parseSemver,
} from 'semver';
import { naturalCompare } from '../utils';
import type { ArduinoComponent } from './arduino-component';
import type { MessageService } from '@theia/core/lib/common/message-service';
import { ExecuteWithProgress } from './progressible';
import type { ResponseServiceClient } from './response-service';
export interface Installable<T extends ArduinoComponent> {
@ -35,16 +39,16 @@ export namespace Installable {
right: Version,
coerce = false
): number => {
const validLeft = semver.parse(left);
const validRight = semver.parse(right);
const validLeft = parseSemver(left);
const validRight = parseSemver(right);
if (validLeft && validRight) {
return semver.compare(validLeft, validRight);
return compareSemver(validLeft, validRight);
}
if (coerce) {
const coercedLeft = validLeft ?? semver.coerce(left);
const coercedRight = validRight ?? semver.coerce(right);
const coercedLeft = validLeft ?? coerceSemver(left);
const coercedRight = validRight ?? coerceSemver(right);
if (coercedLeft && coercedRight) {
return semver.compare(coercedLeft, coercedRight);
return compareSemver(coercedLeft, coercedRight);
}
}
return naturalCompare(left, right);

View File

@ -1,10 +1,12 @@
import { ApplicationError, Event, JsonRpcServer, nls } from '@theia/core';
import {
PluggableMonitorSettings,
MonitorSettings,
} from '../../node/monitor-settings/monitor-settings-provider';
import { Board, Port } from './boards-service';
export type PluggableMonitorSettings = Record<string, PluggableMonitorSetting>;
export interface MonitorSettings {
pluggableMonitorSettings?: PluggableMonitorSettings;
monitorUISettings?: Partial<MonitorState>;
}
export const MonitorManagerProxyFactory = Symbol('MonitorManagerProxyFactory');
export type MonitorManagerProxyFactory = () => MonitorManagerProxy;

View File

@ -1,7 +1,7 @@
import { ApplicationError } from '@theia/core/lib/common/application-error';
import { nls } from '@theia/core/lib/common/nls';
import URI from '@theia/core/lib/common/uri';
import * as dateFormat from 'dateformat';
import dateFormat from 'dateformat';
const filenameReservedRegex = require('filename-reserved-regex');
export namespace SketchesError {

View File

@ -0,0 +1,26 @@
import type { Disposable } from '@theia/core/lib/common/disposable';
import { injectable } from '@theia/core/shared/inversify';
import type { AppService } from '../browser/app-service';
import type { Sketch } from '../common/protocol/sketches-service';
import type { StartupTasks } from '../electron-common/startup-task';
@injectable()
export class ElectronAppService implements AppService {
quit(): void {
window.electronArduino.quitApp();
}
version(): Promise<string> {
return window.electronArduino.appVersion();
}
registerStartupTasksHandler(
handler: (tasks: StartupTasks) => void
): Disposable {
return window.electronArduino.registerStartupTasksHandler(handler);
}
scheduleDeletion(sketch: Sketch): void {
window.electronArduino.scheduleDeletion(sketch);
}
}

View File

@ -0,0 +1,12 @@
import { ContainerModule } from '@theia/core/shared/inversify';
import { AppService } from '../browser/app-service';
import { DialogService } from '../browser/dialog-service';
import { ElectronAppService } from './electron-app-service';
import { ElectronDialogService } from './electron-dialog-service';
export default new ContainerModule((bind) => {
bind(ElectronAppService).toSelf().inSingletonScope();
bind(AppService).toService(ElectronAppService);
bind(ElectronDialogService).toSelf().inSingletonScope();
bind(DialogService).toService(ElectronDialogService);
});

View File

@ -0,0 +1,25 @@
import { injectable } from '@theia/core/shared/inversify';
import type { DialogService } from '../browser/dialog-service';
import type {
MessageBoxOptions,
MessageBoxReturnValue,
OpenDialogOptions,
OpenDialogReturnValue,
SaveDialogOptions,
SaveDialogReturnValue,
} from '../electron-common/electron-arduino';
@injectable()
export class ElectronDialogService implements DialogService {
showMessageBox(options: MessageBoxOptions): Promise<MessageBoxReturnValue> {
return window.electronArduino.showMessageBox(options);
}
showOpenDialog(options: OpenDialogOptions): Promise<OpenDialogReturnValue> {
return window.electronArduino.showOpenDialog(options);
}
showSaveDialog(options: SaveDialogOptions): Promise<SaveDialogReturnValue> {
return window.electronArduino.showSaveDialog(options);
}
}

View File

@ -0,0 +1,131 @@
import {
contextBridge,
ipcRenderer,
} from '@theia/core/electron-shared/electron';
import { Disposable } from '@theia/core/lib/common/disposable';
import {
CHANNEL_REQUEST_RELOAD,
MenuDto,
} from '@theia/core/lib/electron-common/electron-api';
import { v4 } from 'uuid';
import type { Sketch } from '../common/protocol/sketches-service';
import {
CHANNEL_APP_VERSION,
CHANNEL_IS_FIRST_WINDOW,
CHANNEL_MAIN_MENU_ITEM_DID_CLICK,
CHANNEL_OPEN_PATH,
CHANNEL_PLOTTER_WINDOW_DID_CLOSE,
CHANNEL_QUIT_APP,
CHANNEL_SCHEDULE_DELETION,
CHANNEL_SEND_STARTUP_TASKS,
CHANNEL_SET_MENU_WITH_NODE_ID,
CHANNEL_SET_REPRESENTED_FILENAME,
CHANNEL_SHOW_MESSAGE_BOX,
CHANNEL_SHOW_OPEN_DIALOG,
CHANNEL_SHOW_PLOTTER_WINDOW,
CHANNEL_SHOW_SAVE_DIALOG,
ElectronArduino,
InternalMenuDto,
MessageBoxOptions,
OpenDialogOptions,
SaveDialogOptions,
} from '../electron-common/electron-arduino';
import { hasStartupTasks, StartupTasks } from '../electron-common/startup-task';
let mainMenuHandlers: Map<string, () => void> = new Map();
function convertMenu(
menu: MenuDto[] | undefined,
handlerMap: Map<string, () => void>
): InternalMenuDto[] | undefined {
if (!menu) {
return undefined;
}
return menu.map((item) => {
let nodeId = v4();
if (item.execute) {
if (!item.id) {
throw new Error(
"A menu item having the 'execute' property must have an 'id' too."
);
}
nodeId = item.id;
handlerMap.set(nodeId, item.execute);
}
return {
id: item.id,
submenu: convertMenu(item.submenu, handlerMap),
accelerator: item.accelerator,
label: item.label,
nodeId,
checked: item.checked,
enabled: item.enabled,
role: item.role,
type: item.type,
visible: item.visible,
};
});
}
const api: ElectronArduino = {
showMessageBox: (options: MessageBoxOptions) =>
ipcRenderer.invoke(CHANNEL_SHOW_MESSAGE_BOX, options),
showOpenDialog: (options: OpenDialogOptions) =>
ipcRenderer.invoke(CHANNEL_SHOW_OPEN_DIALOG, options),
showSaveDialog: (options: SaveDialogOptions) =>
ipcRenderer.invoke(CHANNEL_SHOW_SAVE_DIALOG, options),
appVersion: () => ipcRenderer.invoke(CHANNEL_APP_VERSION),
quitApp: () => ipcRenderer.send(CHANNEL_QUIT_APP),
isFirstWindow: () => ipcRenderer.invoke(CHANNEL_IS_FIRST_WINDOW),
requestReload: (options: StartupTasks) =>
ipcRenderer.send(CHANNEL_REQUEST_RELOAD, options),
registerStartupTasksHandler: (handler: (tasks: StartupTasks) => void) => {
const listener = (_: Electron.IpcRendererEvent, args: unknown) => {
if (hasStartupTasks(args)) {
handler(args);
} else {
console.warn(
`Events received on the ${CHANNEL_SEND_STARTUP_TASKS} channel expected to have a startup task argument, but it was: ${JSON.stringify(
args
)}`
);
}
};
ipcRenderer.on(CHANNEL_SEND_STARTUP_TASKS, listener);
return Disposable.create(() =>
ipcRenderer.removeListener(CHANNEL_SEND_STARTUP_TASKS, listener)
);
},
scheduleDeletion: (sketch: Sketch) =>
ipcRenderer.send(CHANNEL_SCHEDULE_DELETION, sketch),
setRepresentedFilename: (fsPath: string) =>
ipcRenderer.send(CHANNEL_SET_REPRESENTED_FILENAME, fsPath),
showPlotterWindow: (params: { url: string; forceReload?: boolean }) =>
ipcRenderer.send(CHANNEL_SHOW_PLOTTER_WINDOW, params),
registerPlotterWindowCloseHandler: (handler: () => void) => {
const listener = () => handler();
ipcRenderer.on(CHANNEL_PLOTTER_WINDOW_DID_CLOSE, listener);
return Disposable.create(() =>
ipcRenderer.removeListener(CHANNEL_PLOTTER_WINDOW_DID_CLOSE, listener)
);
},
openPath: (fsPath: string) => ipcRenderer.send(CHANNEL_OPEN_PATH, fsPath),
setMenu: (menu: MenuDto[] | undefined): void => {
mainMenuHandlers = new Map();
const internalMenu = convertMenu(menu, mainMenuHandlers);
ipcRenderer.send(CHANNEL_SET_MENU_WITH_NODE_ID, internalMenu);
},
};
export function preload(): void {
contextBridge.exposeInMainWorld('electronArduino', api);
ipcRenderer.on(CHANNEL_MAIN_MENU_ITEM_DID_CLICK, (_, nodeId: string) => {
const handler = mainMenuHandlers.get(nodeId);
if (handler) {
handler();
}
});
console.log('Exposed Arduino IDE electron API');
}

View File

@ -1,4 +1,3 @@
import { webFrame } from '@theia/core/electron-shared/electron/';
import {
ContextMenuAccess,
coordinateFromAnchor,
@ -16,28 +15,24 @@ export class ElectronContextMenuRenderer extends TheiaElectronContextMenuRendere
options: RenderContextMenuOptions
): ContextMenuAccess {
if (this.useNativeStyle) {
const { menuPath, anchor, args, onHide, context } = options;
const { menuPath, anchor, args, onHide, context, contextKeyService } =
options;
const menu = this['electronMenuFactory'].createElectronContextMenu(
menuPath,
args,
context,
contextKeyService,
this.showDisabled(options)
);
const { x, y } = coordinateFromAnchor(anchor);
const zoom = webFrame.getZoomFactor();
// TODO: Remove the offset once Electron fixes https://github.com/electron/electron/issues/31641
const offset = process.platform === 'win32' ? 0 : 2;
// x and y values must be Ints or else there is a conversion error
menu.popup({
x: Math.round(x * zoom) + offset,
y: Math.round(y * zoom) + offset,
const menuHandle = window.electronTheiaCore.popup(menu, x, y, () => {
if (onHide) {
onHide();
}
});
// native context menu stops the event loop, so there is no keyboard events
this.context.resetAltPressed();
if (onHide) {
menu.once('menu-will-close', () => onHide());
}
return new ElectronContextMenuAccess(menu);
return new ElectronContextMenuAccess(menuHandle);
} else {
return super.doRender(options);
}

View File

@ -1,4 +1,4 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { ContextMatcher } from '@theia/core/lib/browser/context-key-service';
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import {
@ -11,15 +11,19 @@ import {
} from '@theia/core/lib/common/menu';
import { isOSX } from '@theia/core/lib/common/os';
import {
ElectronMainMenuFactory as TheiaElectronMainMenuFactory,
ElectronMenuItemRole,
ElectronMenuOptions,
ElectronMainMenuFactory as TheiaElectronMainMenuFactory,
} from '@theia/core/lib/electron-browser/menu/electron-main-menu-factory';
import type {
MenuDto,
MenuRole,
} from '@theia/core/lib/electron-common/electron-api';
import { inject, injectable } from '@theia/core/shared/inversify';
import {
ArduinoMenus,
PlaceholderMenuNode,
} from '../../../browser/menu/arduino-menus';
import debounce from 'lodash.debounce';
@injectable()
export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
@ -30,7 +34,27 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
private updateWhenReady = false;
override postConstruct(): void {
super.postConstruct();
// #region Theia `postConstruct` customizations with calling IDE2 `setMenu`
this.preferencesService.onPreferenceChanged(
debounce((e) => {
if (e.preferenceName === 'window.menuBarVisibility') {
this.setMenuBar();
}
if (this._menu) {
for (const cmd of this._toggledCommands) {
const menuItem = this.findMenuById(this._menu, cmd);
if (menuItem) {
menuItem.checked = this.commandRegistry.isToggled(cmd);
}
}
window.electronArduino.setMenu(this._menu); // calls the IDE2-specific implementation
}
}, 10)
);
this.keybindingRegistry.onKeybindingsChanged(() => {
this.setMenuBar();
});
// #endregion Theia `postConstruct`
this.appStateService.reachedState('ready').then(() => {
this.appReady = true;
if (this.updateWhenReady) {
@ -39,18 +63,18 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
});
}
override createElectronMenuBar(): Electron.Menu {
override createElectronMenuBar(): MenuDto[] {
this._toggledCommands.clear(); // https://github.com/eclipse-theia/theia/issues/8977
const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR);
const template = this.fillMenuTemplate([], menuModel, [], {
const menu = this.fillMenuTemplate([], menuModel, [], {
rootMenuPath: MAIN_MENU_BAR,
});
if (isOSX) {
template.unshift(this.createOSXMenu());
menu.unshift(this.createOSXMenu());
}
const menu = remote.Menu.buildFromTemplate(this.escapeAmpersand(template));
this._menu = menu;
return menu;
const escapedMenu = this.escapeAmpersand(menu);
this._menu = escapedMenu;
return escapedMenu;
}
override async setMenuBar(): Promise<void> {
@ -63,11 +87,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
}
await this.preferencesService.ready;
const createdMenuBar = this.createElectronMenuBar();
if (isOSX) {
remote.Menu.setApplicationMenu(createdMenuBar);
} else {
remote.getCurrentWindow().setMenu(createdMenuBar);
}
window.electronArduino.setMenu(createdMenuBar);
}
override createElectronContextMenu(
@ -75,35 +95,68 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[],
context?: HTMLElement,
contextKeyService?: ContextMatcher,
showDisabled?: boolean
): Electron.Menu {
): MenuDto[] {
const menuModel = this.menuProvider.getMenu(menuPath);
const template = this.fillMenuTemplate([], menuModel, args, {
return this.fillMenuTemplate([], menuModel, args, {
showDisabled,
context,
rootMenuPath: menuPath,
contextKeyService,
});
return remote.Menu.buildFromTemplate(this.escapeAmpersand(template));
}
protected override async execute(
commandId: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any[],
menuPath: MenuPath
): Promise<void> {
try {
// This is workaround for https://github.com/eclipse-theia/theia/issues/446.
// Electron menus do not update based on the `isEnabled`, `isVisible` property of the command.
// We need to check if we can execute it.
if (this.menuCommandExecutor.isEnabled(menuPath, commandId, ...args)) {
await this.menuCommandExecutor.executeCommand(
menuPath,
commandId,
...args
);
if (
this._menu &&
this.menuCommandExecutor.isVisible(menuPath, commandId, ...args)
) {
const item = this.findMenuById(this._menu, commandId);
if (item) {
item.checked = this.menuCommandExecutor.isToggled(
menuPath,
commandId,
...args
);
window.electronArduino.setMenu(this._menu); // overridden to call the IDE2-specific implementation.
}
}
}
} catch {
// no-op
}
}
// TODO: remove after https://github.com/eclipse-theia/theia/pull/9231
private escapeAmpersand(
template: Electron.MenuItemConstructorOptions[]
): Electron.MenuItemConstructorOptions[] {
private escapeAmpersand(template: MenuDto[]): MenuDto[] {
for (const option of template) {
if (option.label) {
option.label = option.label.replace(/\&+/g, '&$&');
}
if (option.submenu) {
this.escapeAmpersand(
option.submenu as Electron.MenuItemConstructorOptions[]
);
this.escapeAmpersand(option.submenu);
}
}
return template;
}
protected override createOSXMenu(): Electron.MenuItemConstructorOptions {
protected override createOSXMenu(): MenuDto {
const { submenu } = super.createOSXMenu();
const label = FrontendApplicationConfigProvider.get().applicationName;
if (!!submenu && Array.isArray(submenu)) {
@ -142,7 +195,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
protected override roleFor(id: string): ElectronMenuItemRole | undefined {
protected override roleFor(id: string): MenuRole | undefined {
// MenuItem `roles` are completely broken on macOS:
// - https://github.com/eclipse-theia/theia/issues/11217,
// - https://github.com/arduino/arduino-ide/issues/969
@ -151,11 +204,11 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
}
protected override fillMenuTemplate(
parentItems: Electron.MenuItemConstructorOptions[],
parentItems: MenuDto[],
menuModel: MenuNode,
args: unknown[] | undefined,
options: ElectronMenuOptions
): Electron.MenuItemConstructorOptions[] {
): MenuDto[] {
if (menuModel instanceof PlaceholderMenuNode) {
parentItems.push({
label: menuModel.label,
@ -172,24 +225,28 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
// Source: https://github.com/eclipse-theia/theia/blob/5e641750af83383f2ce0cb3432ec333df70778a8/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts#L132-L203
// See https://github.com/arduino/arduino-ide/issues/1533
private superFillMenuTemplate(
parentItems: Electron.MenuItemConstructorOptions[],
parentItems: MenuDto[],
menu: MenuNode,
args: unknown[] = [],
options: ElectronMenuOptions
): Electron.MenuItemConstructorOptions[] {
): MenuDto[] {
const showDisabled = options?.showDisabled !== false;
if (
CompoundMenuNode.is(menu) &&
this.visibleSubmenu(menu) && // customization for #569 and #655
this.undefinedOrMatch(menu.when, options.context)
this.undefinedOrMatch(
options.contextKeyService ?? this.contextKeyService,
menu.when,
options.context
)
) {
const role = CompoundMenuNode.getRole(menu);
if (role === CompoundMenuNodeRole.Group && menu.id === 'inline') {
return parentItems;
}
const children = CompoundMenuNode.getFlatChildren(menu.children);
const myItems: Electron.MenuItemConstructorOptions[] = [];
const myItems: MenuDto[] = [];
children.forEach((child) =>
this.fillMenuTemplate(myItems, child, args, options)
);
@ -236,7 +293,11 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
commandId,
...args
) ||
!this.undefinedOrMatch(node.when, options.context)
!this.undefinedOrMatch(
options.contextKeyService ?? this.contextKeyService,
node.when,
options.context
)
) {
return parentItems;
}
@ -258,7 +319,7 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
const accelerator = bindings[0] && this.acceleratorFor(bindings[0]);
const menuItem: Electron.MenuItemConstructorOptions = {
const menuItem: MenuDto = {
id: node.id,
label: node.label,
type: this.commandRegistry.getToggledHandler(commandId, ...args)
@ -268,14 +329,14 @@ export class ElectronMainMenuFactory extends TheiaElectronMainMenuFactory {
enabled: this.commandRegistry.isEnabled(commandId, ...args), // Unlike Theia https://github.com/eclipse-theia/theia/blob/v1.31.1/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts#L183
visible: true,
accelerator,
click: () => this.execute(commandId, args, options.rootMenuPath),
execute: () => this.execute(commandId, args, options.rootMenuPath),
};
if (isOSX) {
const role = this.roleFor(node.id);
if (role) {
menuItem.role = role;
delete menuItem.click;
delete menuItem.execute;
}
}
parentItems.push(menuItem);

View File

@ -1,57 +1,40 @@
import { inject, injectable } from '@theia/core/shared/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 type { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import type { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
import type { CommandRegistry } from '@theia/core/lib/common/command';
import type { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { isOSX } from '@theia/core/lib/common/os';
import {
ElectronMenuContribution as TheiaElectronMenuContribution,
ElectronCommands,
ElectronMenuContribution as TheiaElectronMenuContribution,
} from '@theia/core/lib/electron-browser/menu/electron-menu-contribution';
import { MainMenuManager } from '../../../common/main-menu-manager';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
import { ZoomLevel } from '@theia/core/lib/electron-browser/window/electron-window-preferences';
import { PreferenceScope } from '@theia/core/lib/browser/preferences/preference-scope';
import {
getCurrentWindow,
getCurrentWebContents,
} from '@theia/core/electron-shared/@electron/remote';
import type { MenuDto } from '@theia/core/lib/electron-common/electron-api';
import { inject, injectable } from '@theia/core/shared/inversify';
import type { MainMenuManager } from '../../../common/main-menu-manager';
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
@injectable()
export class ElectronMenuContribution
extends TheiaElectronMenuContribution
implements MainMenuManager
{
@inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService;
export class ElectronMenuUpdater implements MainMenuManager {
@inject(ElectronMainMenuFactory)
protected readonly factory: ElectronMainMenuFactory;
// private appReady = false;
// private updateWhenReady = false;
override onStart(app: FrontendApplication): void {
super.onStart(app);
this.appStateService.reachedState('ready').then(() => {
// this.appReady = true;
// if (this.updateWhenReady) {
// this.update();
// }
});
public update(): void {
this.setMenu();
}
private setMenu(): void {
window.electronArduino.setMenu(this.factory.createElectronMenuBar());
}
}
@injectable()
export class ElectronMenuContribution extends TheiaElectronMenuContribution {
protected override hideTopPanel(): void {
// NOOP
// We reuse the `div` for the Arduino toolbar.
}
update(): void {
// if (this.appReady) {
(this as any).setMenu();
// } else {
// this.updateWhenReady = true;
// }
}
override registerCommands(registry: CommandRegistry): void {
this.theiaRegisterCommands(registry);
super.registerCommands(registry);
registry.unregisterCommand(ElectronCommands.CLOSE_WINDOW);
}
@ -67,80 +50,17 @@ export class ElectronMenuContribution
registry.unregisterKeybinding(ElectronCommands.ZOOM_OUT.id);
}
// Copied from Theia: https://github.com/eclipse-theia/theia/blob/9ec8835cf35d5a46101a62ae93285aeb37a2f382/packages/core/src/electron-browser/menu/electron-menu-contribution.ts#L260-L314
// Unlike the Theia implementation, this does not require synchronously the browser window, but use a function only when the command handler executes.
private theiaRegisterCommands(registry: CommandRegistry): void {
const currentWindow = () => getCurrentWindow();
registry.registerCommand(ElectronCommands.TOGGLE_DEVELOPER_TOOLS, {
execute: () => {
const webContent = getCurrentWebContents();
if (!webContent.isDevToolsOpened()) {
webContent.openDevTools();
} else {
webContent.closeDevTools();
}
},
});
registry.registerCommand(ElectronCommands.RELOAD, {
execute: () => this.windowService.reload(),
});
registry.registerCommand(ElectronCommands.CLOSE_WINDOW, {
execute: () => currentWindow().close(),
});
registry.registerCommand(ElectronCommands.ZOOM_IN, {
execute: () => {
const webContents = currentWindow().webContents;
// When starting at a level that is not a multiple of 0.5, increment by at most 0.5 to reach the next highest multiple of 0.5.
let zoomLevel =
Math.floor(webContents.zoomLevel / ZoomLevel.VARIATION) *
ZoomLevel.VARIATION +
ZoomLevel.VARIATION;
if (zoomLevel > ZoomLevel.MAX) {
zoomLevel = ZoomLevel.MAX;
return;
}
this.preferenceService.set(
'window.zoomLevel',
zoomLevel,
PreferenceScope.User
);
},
});
registry.registerCommand(ElectronCommands.ZOOM_OUT, {
execute: () => {
const webContents = currentWindow().webContents;
// When starting at a level that is not a multiple of 0.5, decrement by at most 0.5 to reach the next lowest multiple of 0.5.
let zoomLevel =
Math.ceil(webContents.zoomLevel / ZoomLevel.VARIATION) *
ZoomLevel.VARIATION -
ZoomLevel.VARIATION;
if (zoomLevel < ZoomLevel.MIN) {
zoomLevel = ZoomLevel.MIN;
return;
}
this.preferenceService.set(
'window.zoomLevel',
zoomLevel,
PreferenceScope.User
);
},
});
registry.registerCommand(ElectronCommands.RESET_ZOOM, {
execute: () =>
this.preferenceService.set(
'window.zoomLevel',
ZoomLevel.DEFAULT,
PreferenceScope.User
),
});
registry.registerCommand(ElectronCommands.TOGGLE_FULL_SCREEN, {
isEnabled: () => currentWindow().isFullScreenable(),
isVisible: () => currentWindow().isFullScreenable(),
execute: () =>
currentWindow().setFullScreen(!currentWindow().isFullScreen()),
});
protected override setMenu(
app: FrontendApplication,
electronMenu: MenuDto[] | undefined = this.factory.createElectronMenuBar()
): void {
if (!isOSX) {
this.hideTopPanel(); // no app args. the overridden method is noop in IDE2.
if (this.titleBarStyle === 'custom' && !this.menuBar) {
this.createCustomTitleBar(app);
return;
}
}
window.electronArduino.setMenu(electronMenu); // overridden to call the IDE20-specific implementation.
}
}

View File

@ -5,11 +5,15 @@ import { ContainerModule } from '@theia/core/shared/inversify';
import { MainMenuManager } from '../../../common/main-menu-manager';
import { ElectronContextMenuRenderer } from './electron-context-menu-renderer';
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
import { ElectronMenuContribution } from './electron-menu-contribution';
import {
ElectronMenuContribution,
ElectronMenuUpdater,
} from './electron-menu-contribution';
export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ElectronMenuContribution).toSelf().inSingletonScope();
bind(MainMenuManager).toService(ElectronMenuContribution);
bind(ElectronMenuUpdater).toSelf().inSingletonScope();
bind(MainMenuManager).toService(ElectronMenuUpdater);
bind(ElectronContextMenuRenderer).toSelf().inSingletonScope();
rebind(ContextMenuRenderer).toService(ElectronContextMenuRenderer);
rebind(TheiaElectronMenuContribution).toService(ElectronMenuContribution);

View File

@ -1,23 +1,10 @@
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider';
import { ContainerModule } from '@theia/core/shared/inversify';
import { WindowServiceExt } from '../../../browser/theia/core/window-service-ext';
import {
ElectronMainWindowServiceExt,
electronMainWindowServiceExtPath,
} from '../../../electron-common/electron-main-window-service-ext';
import { ElectronWindowService } from './electron-window-service';
export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ElectronWindowService).toSelf().inSingletonScope();
rebind(WindowService).toService(ElectronWindowService);
bind(WindowServiceExt).toService(ElectronWindowService);
bind(ElectronMainWindowServiceExt)
.toDynamicValue(({ container }) =>
ElectronIpcConnectionProvider.createProxy(
container,
electronMainWindowServiceExtPath
)
)
.inSingletonScope();
});

View File

@ -1,94 +1,52 @@
import * as remote from '@theia/core/electron-shared/@electron/remote';
import { ipcRenderer } from '@theia/core/electron-shared/electron';
import {
ConnectionStatus,
ConnectionStatusService,
} from '@theia/core/lib/browser/connection-status-service';
import { nls } from '@theia/core/lib/common';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { NewWindowOptions } from '@theia/core/lib/common/window';
import type { NewWindowOptions } from '@theia/core/lib/common/window';
import { ElectronWindowService as TheiaElectronWindowService } from '@theia/core/lib/electron-browser/window/electron-window-service';
import { RELOAD_REQUESTED_SIGNAL } from '@theia/core/lib/electron-common/messaging/electron-messages';
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { injectable, postConstruct } from '@theia/core/shared/inversify';
import { WindowServiceExt } from '../../../browser/theia/core/window-service-ext';
import { ElectronMainWindowServiceExt } from '../../../electron-common/electron-main-window-service-ext';
import { StartupTask } from '../../../electron-common/startup-task';
import {
hasStartupTasks,
StartupTasks,
} from '../../../electron-common/startup-task';
@injectable()
export class ElectronWindowService
extends TheiaElectronWindowService
implements WindowServiceExt
{
@inject(ConnectionStatusService)
private readonly connectionStatusService: ConnectionStatusService;
@inject(ElectronMainWindowServiceExt)
private readonly mainWindowServiceExt: ElectronMainWindowServiceExt;
private _isFirstWindow: Deferred<boolean> | undefined;
@postConstruct()
protected override init(): void {
// NOOP
// Does not listen on Theia's `window.zoomLevel` changes.
// TODO: IDE2 must switch to the Theia preferences and drop the custom one.
// IDE2 listens on the zoom level changes in `ArduinoFrontendContribution#onStart`
}
protected shouldUnload(): boolean {
const offline =
this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE;
const detail = offline
? nls.localize(
'arduino/electron/couldNotSave',
'Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.'
)
: nls.localize(
'arduino/electron/unsavedChanges',
'Any unsaved changes will not be saved.'
);
const electronWindow = remote.getCurrentWindow();
const response = remote.dialog.showMessageBoxSync(electronWindow, {
type: 'question',
buttons: [
nls.localize('vscode/extensionsUtils/yes', 'Yes'),
nls.localize('vscode/extensionsUtils/no', 'No'),
],
title: nls.localize('vscode/Default/ConfirmTitle', 'Confirm'),
message: nls.localize(
'arduino/sketch/close',
'Are you sure you want to close the sketch?'
),
detail,
});
return response === 0; // 'Yes', close the window.
}
private _firstWindow: Deferred<boolean> | undefined;
async isFirstWindow(): Promise<boolean> {
if (this._firstWindow === undefined) {
this._firstWindow = new Deferred<boolean>();
const windowId = remote.getCurrentWindow().id; // This is expensive and synchronous so we check it once per FE.
this.mainWindowServiceExt
.isFirstWindow(windowId)
.then((firstWindow) => this._firstWindow?.resolve(firstWindow));
if (!this._isFirstWindow) {
this._isFirstWindow = new Deferred();
window.electronArduino
.isFirstWindow()
.then((isFirstWindow) => this._isFirstWindow?.resolve(isFirstWindow));
}
return this._firstWindow.promise;
return this._isFirstWindow.promise;
}
// Overridden because the default Theia implementation destroys the additional properties of the `options` arg, such as `tasks`.
// Overridden because the default Theia implementation destructures the additional properties of the `options` arg, such as `tasks`.
// https://github.com/eclipse-theia/theia/blob/2deedbad70bd4b503bf9c7e733ab9603f492600f/packages/core/src/electron-browser/window/electron-window-service.ts#L43
override openNewWindow(url: string, options?: NewWindowOptions): undefined {
return this.delegate.openNewWindow(url, options);
}
// Overridden to support optional task owner params and make `tsc` happy.
override reload(options?: StartupTask.Owner): void {
if (options?.tasks && options.tasks.length) {
const { tasks } = options;
ipcRenderer.send(RELOAD_REQUESTED_SIGNAL, { tasks });
override reload(options?: StartupTasks): void {
if (hasStartupTasks(options)) {
window.electronArduino.requestReload(options);
} else {
ipcRenderer.send(RELOAD_REQUESTED_SIGNAL);
window.electronTheiaCore.requestReload();
}
}
close(): void {
window.electronTheiaCore.close();
}
}

View File

@ -0,0 +1,92 @@
import type {
MessageBoxOptions as ElectronMessageBoxOptions,
MessageBoxReturnValue as ElectronMessageBoxReturnValue,
OpenDialogOptions as ElectronOpenDialogOptions,
OpenDialogReturnValue as ElectronOpenDialogReturnValue,
SaveDialogOptions as ElectronSaveDialogOptions,
SaveDialogReturnValue as ElectronSaveDialogReturnValue,
} from '@theia/core/electron-shared/electron';
import type { Disposable } from '@theia/core/lib/common/disposable';
import type {
InternalMenuDto as TheiaInternalMenuDto,
MenuDto,
} from '@theia/core/lib/electron-common/electron-api';
import type { Sketch } from '../common/protocol/sketches-service';
import type { StartupTasks } from './startup-task';
export interface InternalMenuDto
extends Omit<TheiaInternalMenuDto, 'handlerId'> {
// Theia handles the menus with a running-index handler ID. https://github.com/eclipse-theia/theia/issues/12493
// IDE2 keeps the menu `nodeId` instead of the running-index.
nodeId?: string;
}
export type MessageBoxOptions = Omit<
ElectronMessageBoxOptions,
'icon' | 'signal'
>;
export type MessageBoxReturnValue = ElectronMessageBoxReturnValue;
export type OpenDialogOptions = ElectronOpenDialogOptions;
export type OpenDialogReturnValue = ElectronOpenDialogReturnValue;
export type SaveDialogOptions = ElectronSaveDialogOptions;
export type SaveDialogReturnValue = ElectronSaveDialogReturnValue;
export interface ShowPlotterWindowParams {
readonly url: string;
readonly forceReload?: boolean;
}
export function isShowPlotterWindowParams(
arg: unknown
): arg is ShowPlotterWindowParams {
return (
typeof arg === 'object' &&
typeof (<ShowPlotterWindowParams>arg).url === 'string' &&
((<ShowPlotterWindowParams>arg).forceReload === undefined ||
typeof (<ShowPlotterWindowParams>arg).forceReload === 'boolean')
);
}
export interface ElectronArduino {
showMessageBox(options: MessageBoxOptions): Promise<MessageBoxReturnValue>;
showOpenDialog(options: OpenDialogOptions): Promise<OpenDialogReturnValue>;
showSaveDialog(options: SaveDialogOptions): Promise<SaveDialogReturnValue>;
appVersion(): Promise<string>;
quitApp(): void;
isFirstWindow(): Promise<boolean>;
requestReload(tasks: StartupTasks): void;
registerStartupTasksHandler(
handler: (tasks: StartupTasks) => void
): Disposable;
scheduleDeletion(sketch: Sketch): void;
setRepresentedFilename(fsPath: string): void;
showPlotterWindow(params: { url: string; forceReload?: boolean }): void;
registerPlotterWindowCloseHandler(handler: () => void): Disposable;
openPath(fsPath: string): void;
// Unlike the Theia implementation, IDE2 uses the command IDs, and not the running-index handler IDs.
// https://github.com/eclipse-theia/theia/issues/12493
setMenu(menu: MenuDto[] | undefined): void;
}
declare global {
interface Window {
electronArduino: ElectronArduino;
}
}
// renderer to main
export const CHANNEL_SHOW_MESSAGE_BOX = 'Arduino:ShowMessageBox';
export const CHANNEL_SHOW_OPEN_DIALOG = 'Arduino:ShowOpenDialog';
export const CHANNEL_SHOW_SAVE_DIALOG = 'Arduino:ShowSaveDialog';
export const CHANNEL_APP_VERSION = 'Arduino:AppVersion';
export const CHANNEL_QUIT_APP = 'Arduino:QuitApp';
export const CHANNEL_IS_FIRST_WINDOW = 'Arduino:IsFirstWindow';
export const CHANNEL_SCHEDULE_DELETION = 'Arduino:ScheduleDeletion';
export const CHANNEL_SET_REPRESENTED_FILENAME =
'Arduino:SetRepresentedFilename';
export const CHANNEL_SHOW_PLOTTER_WINDOW = 'Arduino:ShowPlotterWindow';
export const CHANNEL_OPEN_PATH = 'Arduino:OpenPath';
export const CHANNEL_SET_MENU_WITH_NODE_ID = 'Arduino:SetMenuWithNodeId';
// main to renderer
export const CHANNEL_SEND_STARTUP_TASKS = 'Arduino:SendStartupTasks';
export const CHANNEL_PLOTTER_WINDOW_DID_CLOSE = 'Arduino:PlotterWindowDidClose';
export const CHANNEL_MAIN_MENU_ITEM_DID_CLICK = 'Arduino:MainMenuItemDidClick';

View File

@ -1,7 +0,0 @@
export const electronMainWindowServiceExtPath = '/services/electron-window-ext';
export const ElectronMainWindowServiceExt = Symbol(
'ElectronMainWindowServiceExt'
);
export interface ElectronMainWindowServiceExt {
isFirstWindow(windowId: number): Promise<boolean>;
}

View File

@ -1 +0,0 @@
export const SCHEDULE_DELETION_SIGNAL = 'arduino/scheduleDeletion';

View File

@ -1,50 +1,46 @@
export const StartupTaskProvider = Symbol('StartupTaskProvider');
export interface StartupTaskProvider {
tasks(): StartupTask[];
}
export interface StartupTask {
command: string;
readonly command: string;
/**
* Must be JSON serializable.
* See the restrictions [here](https://www.electronjs.org/docs/latest/api/web-contents#contentssendchannel-args).
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args?: any[];
}
export namespace StartupTask {
export function is(arg: unknown): arg is StartupTask {
if (typeof arg === 'object') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const object = arg as any;
return (
'command' in object &&
typeof object['command'] === 'string' &&
(!('args' in object) || Array.isArray(object['args']))
);
}
return false;
}
export function has(arg: unknown): arg is unknown & Owner {
if (typeof arg === 'object') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const object = arg as any;
return (
'tasks' in object &&
Array.isArray(object['tasks']) &&
object['tasks'].every(is)
);
}
return false;
}
export namespace Messaging {
export const STARTUP_TASKS_SIGNAL = 'arduino/startupTasks';
export function APP_READY_SIGNAL(id: number): string {
return `arduino/appReady${id}`;
}
}
export interface Owner {
readonly tasks: StartupTask[];
}
readonly args?: any[];
}
export const StartupTaskProvider = Symbol('StartupTaskProvider');
export interface StartupTaskProvider {
tasks(): StartupTask[];
export interface StartupTasks {
readonly tasks: StartupTask[];
}
export function isStartupTask(arg: unknown): arg is StartupTask {
if (typeof arg === 'object') {
if (
(<StartupTask>arg).command !== undefined &&
typeof (<StartupTask>arg).command === 'string'
) {
return (
(<StartupTask>arg).args === undefined ||
((<StartupTask>arg).args !== undefined &&
Array.isArray((<StartupTask>arg).args))
);
}
}
return false;
}
export function hasStartupTasks(arg: unknown): arg is unknown & StartupTasks {
if (typeof arg === 'object') {
return (
(<StartupTasks>arg).tasks !== undefined &&
Array.isArray((<StartupTasks>arg).tasks) &&
Boolean((<StartupTasks>arg).tasks.length) &&
(<StartupTasks>arg).tasks.every(isStartupTask)
);
}
return false;
}

View File

@ -5,7 +5,6 @@ import {
ElectronMainApplication as TheiaElectronMainApplication,
ElectronMainApplicationContribution,
} from '@theia/core/lib/electron-main/electron-main-application';
import { ElectronMessagingContribution as TheiaElectronMessagingContribution } from '@theia/core/lib/electron-main/messaging/electron-messaging-contribution';
import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window';
import { ContainerModule } from '@theia/core/shared/inversify';
import {
@ -13,12 +12,11 @@ import {
IDEUpdaterClient,
IDEUpdaterPath,
} from '../common/protocol/ide-updater';
import { electronMainWindowServiceExtPath } from '../electron-common/electron-main-window-service-ext';
import { IsTempSketch } from '../node/is-temp-sketch';
import { ElectronArduino } from './electron-arduino';
import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl';
import { ElectronMainApplication } from './theia/electron-main-application';
import { ElectronMainWindowServiceImpl } from './theia/electron-main-window-service';
import { ElectronMessagingContribution } from './theia/electron-messaging-contribution';
import { TheiaElectronWindow } from './theia/theia-electron-window';
export default new ContainerModule((bind, unbind, isBound, rebind) => {
@ -50,20 +48,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(TheiaElectronWindow).toSelf();
rebind(DefaultTheiaElectronWindow).toService(TheiaElectronWindow);
bind(ElectronConnectionHandler)
.toDynamicValue(
(context) =>
new JsonRpcConnectionHandler(electronMainWindowServiceExtPath, () =>
context.container.get(ElectronMainWindowServiceImpl)
)
)
.inSingletonScope();
bind(IsTempSketch).toSelf().inSingletonScope();
// Fix for cannot reload window: https://github.com/eclipse-theia/theia/issues/11600
bind(ElectronMessagingContribution).toSelf().inSingletonScope();
rebind(TheiaElectronMessagingContribution).toService(
ElectronMessagingContribution
);
bind(ElectronArduino).toSelf().inSingletonScope();
bind(ElectronMainApplicationContribution).toService(ElectronArduino);
});

Some files were not shown because too many files have changed in this diff Show More