mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-04-19 12:57:17 +00:00
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:
parent
964ea3bc0c
commit
192aac5a81
@ -16,6 +16,7 @@ module.exports = {
|
||||
'docs/*',
|
||||
'scripts/*',
|
||||
'electron-app/*',
|
||||
'!electron-app/webpack.config.js',
|
||||
'plugins/*',
|
||||
'arduino-ide-extension/src/node/cli-protocol',
|
||||
],
|
||||
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -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
|
||||
|
4
.github/workflows/check-i18n-task.yml
vendored
4
.github/workflows/check-i18n-task.yml
vendored
@ -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
|
||||
|
4
.github/workflows/i18n-nightly-push.yml
vendored
4
.github/workflows/i18n-nightly-push.yml
vendored
@ -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
|
||||
|
4
.github/workflows/i18n-weekly-pull.yml
vendored
4
.github/workflows/i18n-weekly-pull.yml
vendored
@ -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
2
.gitignore
vendored
@ -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.
|
||||
|
@ -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"
|
||||
}
|
||||
|
13
arduino-ide-extension/src/browser/app-service.ts
Normal file
13
arduino-ide-extension/src/browser/app-service.ts
Normal 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
|
||||
}
|
@ -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};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
});
|
||||
|
@ -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'],
|
||||
|
@ -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';
|
||||
|
@ -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');
|
||||
|
@ -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: [
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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> {
|
||||
|
@ -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(
|
||||
|
@ -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';
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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), {
|
||||
|
@ -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(
|
||||
|
@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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';
|
||||
|
15
arduino-ide-extension/src/browser/dialog-service.ts
Normal file
15
arduino-ide-extension/src/browser/dialog-service.ts
Normal 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>;
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import debounce = require('p-debounce');
|
||||
import debounce from 'p-debounce';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
|
@ -43,7 +43,8 @@ export class DefaultDebugSessionFactory extends TheiaDefaultDebugSessionFactory
|
||||
this.messages,
|
||||
this.fileService,
|
||||
this.debugContributionProvider,
|
||||
this.workspaceService
|
||||
this.workspaceService,
|
||||
2_000
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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()];
|
||||
}
|
||||
}
|
@ -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}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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))
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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),
|
||||
|
@ -35,6 +35,7 @@ function sketchBaseDir(sketch: Create.Sketch): FileStat {
|
||||
isDirectory: true,
|
||||
isFile: false,
|
||||
isSymbolicLink: false,
|
||||
isReadonly: false,
|
||||
resource: createPath,
|
||||
mtime,
|
||||
ctime,
|
||||
|
@ -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 (
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
export const SHOW_PLOTTER_WINDOW = 'SHOW_PLOTTER_WINDOW';
|
||||
export const CLOSE_PLOTTER_WINDOW = 'CLOSE_PLOTTER_WINDOW';
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
@ -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);
|
||||
}
|
||||
}
|
131
arduino-ide-extension/src/electron-browser/preload.ts
Normal file
131
arduino-ide-extension/src/electron-browser/preload.ts
Normal 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');
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
@ -1,7 +0,0 @@
|
||||
export const electronMainWindowServiceExtPath = '/services/electron-window-ext';
|
||||
export const ElectronMainWindowServiceExt = Symbol(
|
||||
'ElectronMainWindowServiceExt'
|
||||
);
|
||||
export interface ElectronMainWindowServiceExt {
|
||||
isFirstWindow(windowId: number): Promise<boolean>;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export const SCHEDULE_DELETION_SIGNAL = 'arduino/scheduleDeletion';
|
@ -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;
|
||||
}
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user