diff --git a/.gitignore b/.gitignore index 4380cce54..b683e4e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ build/ Examples/ !electron/build/ src-gen/ -!webpack.config.js +webpack.config.js gen-webpack.config.js .DS_Store # switching from `electron` to `browser` in dev mode. @@ -15,8 +15,6 @@ gen-webpack.config.js yarn*.log # For the VS Code extensions used by Theia. plugins -# the config files for the CLI -arduino-ide-extension/data/cli/config # the tokens folder for the themes scripts/themes/tokens # environment variables diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 5efaf9cd1..0daf84ced 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -21,27 +21,31 @@ }, "dependencies": { "@grpc/grpc-js": "^1.6.7", - "@theia/application-package": "1.25.0", - "@theia/core": "1.25.0", - "@theia/editor": "1.25.0", - "@theia/electron": "1.25.0", - "@theia/filesystem": "1.25.0", - "@theia/keymaps": "1.25.0", - "@theia/markers": "1.25.0", - "@theia/monaco": "1.25.0", - "@theia/navigator": "1.25.0", - "@theia/outline-view": "1.25.0", - "@theia/output": "1.25.0", - "@theia/preferences": "1.25.0", - "@theia/search-in-workspace": "1.25.0", - "@theia/terminal": "1.25.0", - "@theia/workspace": "1.25.0", + "@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", "@tippyjs/react": "^4.2.5", - "@types/atob": "^2.1.2", "@types/auth0-js": "^9.14.0", "@types/btoa": "^1.2.3", "@types/dateformat": "^3.0.1", - "@types/deep-equal": "^1.0.1", "@types/deepmerge": "^2.2.0", "@types/glob": "^7.2.0", "@types/google-protobuf": "^3.7.2", @@ -50,49 +54,48 @@ "@types/lodash.debounce": "^4.0.6", "@types/ncp": "^2.0.4", "@types/node-fetch": "^2.5.7", + "@types/p-queue": "^2.3.1", "@types/ps-tree": "^1.1.0", - "@types/react-select": "^3.0.0", "@types/react-tabs": "^2.3.2", + "@types/react-virtualized": "^9.21.21", "@types/temp": "^0.8.34", "@types/which": "^1.3.1", - "ajv": "^6.5.3", "arduino-serial-plotter-webapp": "0.2.0", "async-mutex": "^0.3.0", - "atob": "^2.1.2", "auth0-js": "^9.14.0", "btoa": "^1.2.1", "classnames": "^2.3.1", "dateformat": "^3.0.3", - "deep-equal": "^2.0.5", "deepmerge": "2.0.1", "electron-updater": "^4.6.5", "fast-safe-stringify": "^2.1.1", "glob": "^7.1.6", "google-protobuf": "^3.20.1", "hash.js": "^1.1.7", - "is-valid-path": "^0.1.1", "js-yaml": "^3.13.1", "jwt-decode": "^3.1.2", "keytar": "7.2.0", "lodash.debounce": "^4.0.8", + "minimatch": "^3.1.2", "ncp": "^2.0.0", "node-fetch": "^2.6.1", "open": "^8.0.6", - "p-queue": "^5.0.0", + "p-debounce": "^2.1.0", + "p-queue": "^2.4.2", "ps-tree": "^1.2.0", "query-string": "^7.0.1", - "react-disable": "^0.1.0", + "react-disable": "^0.1.1", "react-markdown": "^8.0.0", - "react-select": "^3.0.4", + "react-perfect-scrollbar": "^1.5.8", + "react-select": "^5.6.0", "react-tabs": "^3.1.2", + "react-virtualized": "^9.22.3", "react-window": "^1.8.6", "semver": "^7.3.2", "string-natural-compare": "^2.0.3", "temp": "^0.9.1", "temp-dir": "^2.0.0", "tree-kill": "^1.2.1", - "upath": "^1.1.2", - "url": "^0.11.0", "which": "^1.3.1" }, "devDependencies": { @@ -101,11 +104,10 @@ "@types/chai-string": "^1.4.2", "@types/mocha": "^5.2.7", "@types/react-window": "^1.8.5", - "@types/sinon": "^10.0.6", - "@types/sinon-chai": "^3.2.6", "chai": "^4.2.0", "chai-string": "^1.5.0", "decompress": "^4.2.0", + "decompress-tarbz2": "^4.1.1", "decompress-targz": "^4.1.1", "decompress-unzip": "^4.0.1", "download": "^7.1.0", @@ -115,9 +117,6 @@ "moment": "^2.24.0", "protoc": "^1.0.4", "shelljs": "^0.8.3", - "sinon": "^12.0.1", - "sinon-chai": "^3.7.0", - "typemoq": "^2.1.0", "uuid": "^3.2.1", "yargs": "^11.1.0" }, diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index dfeffb0cb..24adca5c6 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -3,10 +3,7 @@ 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'; -import { - TabBarToolbarContribution, - TabBarToolbarFactory, -} from '@theia/core/lib/browser/shell/tab-bar-toolbar'; +import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider'; import { FrontendApplicationContribution, @@ -84,10 +81,7 @@ import { BoardsAutoInstaller } from './boards/boards-auto-installer'; import { ShellLayoutRestorer } from './theia/core/shell-layout-restorer'; import { ListItemRenderer } from './widgets/component-list/list-item-renderer'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; -import { - MonacoThemeJson, - MonacoThemingService, -} from '@theia/monaco/lib/browser/monaco-theming-service'; + import { ArduinoDaemonPath, ArduinoDaemon, @@ -137,7 +131,6 @@ import { Settings } from './contributions/settings'; import { WorkspaceCommandContribution } from './theia/workspace/workspace-commands'; import { WorkspaceDeleteHandler as TheiaWorkspaceDeleteHandler } from '@theia/workspace/lib/browser/workspace-delete-handler'; import { WorkspaceDeleteHandler } from './theia/workspace/workspace-delete-handler'; -import { TabBarToolbar } from './theia/core/tab-bar-toolbar'; import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory'; import { EditorWidgetFactory } from './theia/editor/editor-widget-factory'; import { BurnBootloader } from './contributions/burn-bootloader'; @@ -181,8 +174,6 @@ import { EditorCommandContribution } from './theia/editor/editor-command'; import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@theia/navigator/lib/browser/navigator-tab-bar-decorator'; import { NavigatorTabBarDecorator } from './theia/navigator/navigator-tab-bar-decorator'; import { Debug } from './contributions/debug'; -import { DebugSessionManager } from './theia/debug/debug-session-manager'; -import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; import { Sketchbook } from './contributions/sketchbook'; import { DebugFrontendApplicationContribution } from './theia/debug/debug-frontend-application-contribution'; import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; @@ -271,7 +262,6 @@ import { IDEUpdaterClientImpl } from './ide-updater/ide-updater-client-impl'; import { IDEUpdaterDialog, IDEUpdaterDialogProps, - IDEUpdaterDialogWidget, } from './dialogs/ide-updater/ide-updater-dialog'; import { ElectronIpcConnectionProvider } from '@theia/core/lib/electron-browser/messaging/electron-ipc-connection-provider'; import { MonitorModel } from './monitor-model'; @@ -313,10 +303,6 @@ import { SelectedBoard } from './contributions/selected-board'; import { CheckForIDEUpdates } from './contributions/check-for-ide-updates'; import { OpenBoardsConfig } from './contributions/open-boards-config'; import { SketchFilesTracker } from './contributions/sketch-files-tracker'; -import { MonacoThemeServiceIsReady } from './utils/window'; -import { Deferred } from '@theia/core/lib/common/promise-util'; -import { StatusBarImpl } from './theia/core/status-bar'; -import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser'; import { EditorMenuContribution } from './theia/editor/editor-file'; import { EditorMenuContribution as TheiaEditorMenuContribution } from '@theia/editor/lib/browser/editor-menu'; import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget'; @@ -337,32 +323,16 @@ import { InterfaceScale } from './contributions/interface-scale'; import { OpenHandler } from '@theia/core/lib/browser/opener-service'; import { NewCloudSketch } from './contributions/new-cloud-sketch'; import { SketchbookCompositeWidget } from './widgets/sketchbook/sketchbook-composite-widget'; - -const registerArduinoThemes = () => { - const themes: MonacoThemeJson[] = [ - { - id: 'arduino-theme', - label: 'Light (Arduino)', - uiTheme: 'vs', - json: require('../../src/browser/data/default.color-theme.json'), - }, - { - id: 'arduino-theme-dark', - label: 'Dark (Arduino)', - uiTheme: 'vs-dark', - json: require('../../src/browser/data/dark.color-theme.json'), - }, - ]; - themes.forEach((theme) => MonacoThemingService.register(theme)); -}; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const global = window as any; -const ready = global[MonacoThemeServiceIsReady] as Deferred; -if (ready) { - ready.promise.then(registerArduinoThemes); -} else { - registerArduinoThemes(); -} +import { WindowTitleUpdater } from './theia/core/window-title-updater'; +import { WindowTitleUpdater as TheiaWindowTitleUpdater } from '@theia/core/lib/browser/window/window-title-updater'; +import { ThemeService } from './theia/core/theming'; +import { ThemeService as TheiaThemeService } from '@theia/core/lib/browser/theming'; +import { MonacoThemingService } from './theia/monaco/monaco-theming-service'; +import { MonacoThemingService as TheiaMonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service'; +import { TypeHierarchyServiceProvider } from './theia/typehierarchy/type-hierarchy-service'; +import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service'; +import { TypeHierarchyContribution } from './theia/typehierarchy/type-hierarchy-contribution'; +import { TypeHierarchyContribution as TheiaTypeHierarchyContribution } from '@theia/typehierarchy/lib/browser/typehierarchy-contribution'; export default new ContainerModule((bind, unbind, isBound, rebind) => { // Commands and toolbar items @@ -587,14 +557,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .to(WorkspaceDeleteHandler) .inSingletonScope(); rebind(TheiaEditorWidgetFactory).to(EditorWidgetFactory).inSingletonScope(); - rebind(TabBarToolbarFactory).toFactory( - ({ container: parentContainer }) => - () => { - const container = parentContainer.createChild(); - container.bind(TabBarToolbar).toSelf().inSingletonScope(); - return container.get(TabBarToolbar); - } - ); bind(OutputChannelManager).toSelf().inSingletonScope(); rebind(TheiaOutputChannelManager).toService(OutputChannelManager); bind(OutputChannelRegistryMainImpl).toSelf().inTransientScope(); @@ -838,9 +800,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(AboutDialog).toSelf().inSingletonScope(); rebind(TheiaAboutDialog).toService(AboutDialog); - // To avoid running `Save All` when there are no dirty editors before starting the debug session. - bind(DebugSessionManager).toSelf().inSingletonScope(); - rebind(TheiaDebugSessionManager).toService(DebugSessionManager); // To remove the `Run` menu item from the application menu. bind(DebugFrontendApplicationContribution).toSelf().inSingletonScope(); rebind(TheiaDebugFrontendApplicationContribution).toService( @@ -854,10 +813,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(WidgetManager).toSelf().inSingletonScope(); rebind(TheiaWidgetManager).toService(WidgetManager); - // To avoid running a status bar update on every single `keypress` event from the editor. - bind(StatusBarImpl).toSelf().inSingletonScope(); - rebind(TheiaStatusBarImpl).toService(StatusBarImpl); - // Debounced update for the tab-bar toolbar when typing in the editor. bind(DockPanelRenderer).toSelf(); rebind(TheiaDockPanelRenderer).toService(DockPanelRenderer); @@ -942,7 +897,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(LocalCacheFsProvider).toSelf().inSingletonScope(); bind(FileServiceContribution).toService(LocalCacheFsProvider); bind(CloudSketchbookCompositeWidget).toSelf(); - bind(WidgetFactory).toDynamicValue((ctx) => ({ + bind(WidgetFactory).toDynamicValue((ctx) => ({ id: 'cloud-sketchbook-composite-widget', createWidget: () => ctx.container.get(CloudSketchbookCompositeWidget), })); @@ -958,7 +913,6 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { title: 'UploadCertificate', }); - bind(IDEUpdaterDialogWidget).toSelf().inSingletonScope(); bind(IDEUpdaterDialog).toSelf().inSingletonScope(); bind(IDEUpdaterDialogProps).toConstantValue({ title: 'IDEUpdater', @@ -991,4 +945,23 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TheiaHostedPluginSupport).toService(HostedPluginSupport); bind(HostedPluginEvents).toSelf().inSingletonScope(); bind(FrontendApplicationContribution).toService(HostedPluginEvents); + + // custom window titles + bind(WindowTitleUpdater).toSelf().inSingletonScope(); + rebind(TheiaWindowTitleUpdater).toService(WindowTitleUpdater); + + // register Arduino themes + bind(ThemeService).toSelf().inSingletonScope(); + rebind(TheiaThemeService).toService(ThemeService); + bind(MonacoThemingService).toSelf().inSingletonScope(); + rebind(TheiaMonacoThemingService).toService(MonacoThemingService); + + // disable type-hierarchy support + // https://github.com/eclipse-theia/theia/commit/16c88a584bac37f5cf3cc5eb92ffdaa541bda5be + bind(TypeHierarchyServiceProvider).toSelf().inSingletonScope(); + rebind(TheiaTypeHierarchyServiceProvider).toService( + TypeHierarchyServiceProvider + ); + bind(TypeHierarchyContribution).toSelf().inSingletonScope(); + rebind(TheiaTypeHierarchyContribution).toService(TypeHierarchyContribution); }); diff --git a/arduino-ide-extension/src/browser/contributions/sketch-control.ts b/arduino-ide-extension/src/browser/contributions/sketch-control.ts index 62f2d8ce8..eef34817e 100644 --- a/arduino-ide-extension/src/browser/contributions/sketch-control.ts +++ b/arduino-ide-extension/src/browser/contributions/sketch-control.ts @@ -235,7 +235,7 @@ export class SketchControl extends SketchContribution { }); registry.registerKeybinding({ command: CommonCommands.PREVIOUS_TAB.id, - keybinding: 'CtrlCmd+Alt+Left', // TODO: check why electron does not show the keybindings in the UI. + keybinding: 'CtrlCmd+Alt+Left', }); registry.registerKeybinding({ command: CommonCommands.NEXT_TAB.id, diff --git a/arduino-ide-extension/src/browser/create/create-uri.ts b/arduino-ide-extension/src/browser/create/create-uri.ts index 658a65ac1..be1a30e9c 100644 --- a/arduino-ide-extension/src/browser/create/create-uri.ts +++ b/arduino-ide-extension/src/browser/create/create-uri.ts @@ -1,4 +1,4 @@ -import { URI as Uri } from 'vscode-uri'; +import { URI as Uri } from '@theia/core/shared/vscode-uri'; import URI from '@theia/core/lib/common/uri'; import { toPosixPath, parentPosix, posix } from './create-paths'; import { Create } from './typings'; diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx index 93da416e5..ecb4be944 100644 --- a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-component.tsx @@ -1,7 +1,6 @@ import { nls } from '@theia/core/lib/common'; -import { shell } from 'electron'; +import { shell } from '@theia/core/electron-shared/@electron/remote'; import * as React from '@theia/core/shared/react'; -import * as ReactDOM from '@theia/core/shared/react-dom'; import ReactMarkdown from 'react-markdown'; import { ProgressInfo, UpdateInfo } from '../../../common/protocol/ide-updater'; import ProgressBar from '../../components/ProgressBar'; @@ -28,32 +27,19 @@ export const IDEUpdaterComponent = ({ }, }: IDEUpdaterComponentProps): React.ReactElement => { const { version, releaseNotes } = updateInfo; - const changelogDivRef = - React.useRef() as React.MutableRefObject; + const [changelog, setChangelog] = React.useState(''); React.useEffect(() => { - if (!!releaseNotes && changelogDivRef.current) { - let changelog: string; - if (typeof releaseNotes === 'string') changelog = releaseNotes; - else - changelog = releaseNotes.reduce((acc, item) => { - return item.note ? (acc += `${item.note}\n\n`) : acc; - }, ''); - ReactDOM.render( - ( - href && shell.openExternal(href)} {...props}> - {children} - - ), - }} - > - {changelog} - , - changelogDivRef.current + if (releaseNotes) { + setChangelog( + typeof releaseNotes === 'string' + ? releaseNotes + : releaseNotes.reduce( + (acc, item) => (item.note ? (acc += `${item.note}\n\n`) : acc), + '' + ) ); } - }, [updateInfo]); + }, [releaseNotes, changelog]); const DownloadCompleted: () => React.ReactElement = () => (
@@ -106,9 +92,24 @@ export const IDEUpdaterComponent = ({ version )}
- {releaseNotes && ( + {changelog && (
-
+
)}
diff --git a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx index 7dc2d7347..c4f2d940b 100644 --- a/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/ide-updater/ide-updater-dialog.tsx @@ -5,10 +5,8 @@ import { postConstruct, } from '@theia/core/shared/inversify'; import { DialogProps } from '@theia/core/lib/browser/dialogs'; -import { AbstractDialog } from '../../theia/dialogs/dialogs'; -import { Widget } from '@theia/core/shared/@phosphor/widgets'; import { Message } from '@theia/core/shared/@phosphor/messaging'; -import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; +import { ReactDialog } from '../../theia/dialogs/dialogs'; import { nls } from '@theia/core'; import { IDEUpdaterComponent, UpdateProgress } from './ide-updater-component'; import { @@ -22,47 +20,11 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service'; const DOWNLOAD_PAGE_URL = 'https://www.arduino.cc/en/software'; -@injectable() -export class IDEUpdaterDialogWidget extends ReactWidget { - private _updateInfo: UpdateInfo; - private _updateProgress: UpdateProgress = {}; - - setUpdateInfo(updateInfo: UpdateInfo): void { - this._updateInfo = updateInfo; - this.update(); - } - - mergeUpdateProgress(updateProgress: UpdateProgress): void { - this._updateProgress = { ...this._updateProgress, ...updateProgress }; - this.update(); - } - - get updateInfo(): UpdateInfo { - return this._updateInfo; - } - - get updateProgress(): UpdateProgress { - return this._updateProgress; - } - - protected render(): React.ReactNode { - return !!this._updateInfo ? ( - - ) : null; - } -} - @injectable() export class IDEUpdaterDialogProps extends DialogProps {} @injectable() -export class IDEUpdaterDialog extends AbstractDialog { - @inject(IDEUpdaterDialogWidget) - private readonly widget: IDEUpdaterDialogWidget; - +export class IDEUpdaterDialog extends ReactDialog { @inject(IDEUpdater) private readonly updater: IDEUpdater; @@ -75,6 +37,9 @@ export class IDEUpdaterDialog extends AbstractDialog { @inject(WindowService) private readonly windowService: WindowService; + private _updateInfo: UpdateInfo | undefined; + private _updateProgress: UpdateProgress = {}; + constructor( @inject(IDEUpdaterDialogProps) protected override readonly props: IDEUpdaterDialogProps @@ -94,26 +59,34 @@ export class IDEUpdaterDialog extends AbstractDialog { protected init(): void { this.updaterClient.onUpdaterDidFail((error) => { this.appendErrorButtons(); - this.widget.mergeUpdateProgress({ error }); + this.mergeUpdateProgress({ error }); }); this.updaterClient.onDownloadProgressDidChange((progressInfo) => { - this.widget.mergeUpdateProgress({ progressInfo }); + this.mergeUpdateProgress({ progressInfo }); }); this.updaterClient.onDownloadDidFinish(() => { this.appendInstallButtons(); - this.widget.mergeUpdateProgress({ downloadFinished: true }); + this.mergeUpdateProgress({ downloadFinished: true }); }); } - get value(): UpdateInfo { - return this.widget.updateInfo; + protected render(): React.ReactNode { + return ( + this.updateInfo && ( + + ) + ); + } + + get value(): UpdateInfo | undefined { + return this.updateInfo; } protected override onAfterAttach(msg: Message): void { - if (this.widget.isAttached) { - Widget.detach(this.widget); - } - Widget.attach(this.widget, this.contentNode); + this.update(); this.appendInitialButtons(); super.onAfterAttach(msg); } @@ -196,15 +169,19 @@ export class IDEUpdaterDialog extends AbstractDialog { } private skipVersion(): void { + if (!this.updateInfo) { + console.warn(`Nothing to skip. No update info is available`); + return; + } this.localStorageService.setData( SKIP_IDE_VERSION, - this.widget.updateInfo.version + this.updateInfo.version ); this.close(); } private startDownload(): void { - this.widget.mergeUpdateProgress({ + this.mergeUpdateProgress({ downloadStarted: true, }); this.clearButtons(); @@ -216,31 +193,48 @@ export class IDEUpdaterDialog extends AbstractDialog { this.close(); } + private set updateInfo(updateInfo: UpdateInfo | undefined) { + this._updateInfo = updateInfo; + this.update(); + } + + private get updateInfo(): UpdateInfo | undefined { + return this._updateInfo; + } + + private get updateProgress(): UpdateProgress { + return this._updateProgress; + } + + private mergeUpdateProgress(updateProgress: UpdateProgress): void { + this._updateProgress = { ...this._updateProgress, ...updateProgress }; + this.update(); + } + override async open( data: UpdateInfo | undefined = undefined ): Promise { if (data && data.version) { - this.widget.mergeUpdateProgress({ + this.mergeUpdateProgress({ progressInfo: undefined, downloadStarted: false, downloadFinished: false, error: undefined, }); - this.widget.setUpdateInfo(data); + this.updateInfo = data; return super.open(); } } protected override onActivateRequest(msg: Message): void { super.onActivateRequest(msg); - this.widget.activate(); + this.update(); } override close(): void { - this.widget.dispose(); if ( - this.widget.updateProgress?.downloadStarted && - !this.widget.updateProgress?.downloadFinished + this.updateProgress?.downloadStarted && + !this.updateProgress?.downloadFinished ) { this.updater.stopDownload(); } diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx index 3138c7242..414cee7d7 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-component.tsx @@ -218,16 +218,14 @@ export class SettingsComponent extends React.Component<
@@ -612,11 +610,11 @@ export class SettingsComponent extends React.Component< event: React.ChangeEvent ): void => { const { selectedIndex } = event.target.options; - const theme = ThemeService.get().getThemes()[selectedIndex]; + const theme = this.props.themeService.getThemes()[selectedIndex]; if (theme) { this.setState({ themeId: theme.id }); - if (ThemeService.get().getCurrentTheme().id !== theme.id) { - ThemeService.get().setCurrentTheme(theme.id); + if (this.props.themeService.getCurrentTheme().id !== theme.id) { + this.props.themeService.setCurrentTheme(theme.id); } } }; @@ -755,6 +753,7 @@ export namespace SettingsComponent { readonly fileDialogService: FileDialogService; readonly windowService: WindowService; readonly localizationProvider: AsyncLocalizationProvider; + readonly themeService: ThemeService; } export type State = Settings & { rawAdditionalUrlsValue: string; diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx index 7ebc7c5ba..0c9e51e43 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings-dialog.tsx @@ -35,6 +35,9 @@ export class SettingsWidget extends ReactWidget { @inject(AsyncLocalizationProvider) protected readonly localizationProvider: AsyncLocalizationProvider; + @inject(ThemeService) + private readonly themeService: ThemeService; + protected render(): React.ReactNode { return ( ); } @@ -59,6 +63,9 @@ export class SettingsDialog extends AbstractDialog> { @inject(SettingsWidget) protected readonly widget: SettingsWidget; + @inject(ThemeService) + private readonly themeService: ThemeService; + constructor( @inject(SettingsDialogProps) protected override readonly props: SettingsDialogProps @@ -121,11 +128,11 @@ export class SettingsDialog extends AbstractDialog> { } override async open(): Promise | undefined> { - const themeIdBeforeOpen = ThemeService.get().getCurrentTheme().id; + const themeIdBeforeOpen = this.themeService.getCurrentTheme().id; const result = await super.open(); if (!result) { - if (ThemeService.get().getCurrentTheme().id !== themeIdBeforeOpen) { - ThemeService.get().setCurrentTheme(themeIdBeforeOpen); + if (this.themeService.getCurrentTheme().id !== themeIdBeforeOpen) { + this.themeService.setCurrentTheme(themeIdBeforeOpen); } } return result; diff --git a/arduino-ide-extension/src/browser/dialogs/settings/settings.ts b/arduino-ide-extension/src/browser/dialogs/settings/settings.ts index 7033f8e8a..c8c0344ec 100644 --- a/arduino-ide-extension/src/browser/dialogs/settings/settings.ts +++ b/arduino-ide-extension/src/browser/dialogs/settings/settings.ts @@ -5,7 +5,7 @@ import { } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { Emitter } from '@theia/core/lib/common/event'; -import { Deferred, timeout } from '@theia/core/lib/common/promise-util'; +import { Deferred } from '@theia/core/lib/common/promise-util'; import { deepClone } from '@theia/core/lib/common/objects'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { ThemeService } from '@theia/core/lib/browser/theming'; @@ -25,6 +25,8 @@ import { LanguageInfo, } from '@theia/core/lib/common/i18n/localization'; import { ElectronCommands } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'; +import { DefaultTheme } from '@theia/application-package/lib/application-props'; +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; export const EDITOR_SETTING = 'editor'; export const FONT_SIZE_SETTING = `${EDITOR_SETTING}.fontSize`; @@ -101,6 +103,9 @@ export class SettingsService { @inject(CommandService) protected commandService: CommandService; + @inject(ThemeService) + private readonly themeService: ThemeService; + protected readonly onDidChangeEmitter = new Emitter>(); readonly onDidChange = this.onDidChangeEmitter.event; protected readonly onDidResetEmitter = new Emitter>(); @@ -141,10 +146,9 @@ export class SettingsService { this.preferenceService.get(FONT_SIZE_SETTING, 12), this.preferenceService.get( 'workbench.colorTheme', - window.matchMedia && - window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'arduino-theme-dark' - : 'arduino-theme' + DefaultTheme.defaultForOSTheme( + FrontendApplicationConfigProvider.get().defaultTheme + ) ), this.preferenceService.get( AUTO_SAVE_SETTING, @@ -231,11 +235,7 @@ export class SettingsService { 'Invalid editor font size. It must be a positive integer.' ); } - if ( - !ThemeService.get() - .getThemes() - .find(({ id }) => id === themeId) - ) { + if (!this.themeService.getThemes().find(({ id }) => id === themeId)) { return nls.localize( 'arduino/preferences/invalid.theme', 'Invalid theme.' @@ -252,7 +252,6 @@ export class SettingsService { private async savePreference(name: string, value: unknown): Promise { await this.preferenceService.set(name, value, PreferenceScope.User); - await timeout(5); } async save(): Promise { @@ -283,19 +282,21 @@ export class SettingsService { (config as any).network = network; (config as any).locale = currentLanguage; - await this.savePreference('editor.fontSize', editorFontSize); - await this.savePreference('workbench.colorTheme', themeId); - await this.savePreference(AUTO_SAVE_SETTING, autoSave); - await this.savePreference('editor.quickSuggestions', quickSuggestions); - await this.savePreference(AUTO_SCALE_SETTING, autoScaleInterface); - await this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale); - await this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale); - await this.savePreference(COMPILE_VERBOSE_SETTING, verboseOnCompile); - await this.savePreference(COMPILE_WARNINGS_SETTING, compilerWarnings); - await this.savePreference(UPLOAD_VERBOSE_SETTING, verboseOnUpload); - await this.savePreference(UPLOAD_VERIFY_SETTING, verifyAfterUpload); - await this.savePreference(SHOW_ALL_FILES_SETTING, sketchbookShowAllFiles); - await this.configService.setConfiguration(config); + await Promise.all([ + this.savePreference('editor.fontSize', editorFontSize), + this.savePreference('workbench.colorTheme', themeId), + this.savePreference(AUTO_SAVE_SETTING, autoSave), + this.savePreference('editor.quickSuggestions', quickSuggestions), + this.savePreference(AUTO_SCALE_SETTING, autoScaleInterface), + this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale), + this.savePreference(ZOOM_LEVEL_SETTING, interfaceScale), + this.savePreference(COMPILE_VERBOSE_SETTING, verboseOnCompile), + this.savePreference(COMPILE_WARNINGS_SETTING, compilerWarnings), + this.savePreference(UPLOAD_VERBOSE_SETTING, verboseOnUpload), + this.savePreference(UPLOAD_VERIFY_SETTING, verifyAfterUpload), + this.savePreference(SHOW_ALL_FILES_SETTING, sketchbookShowAllFiles), + this.configService.setConfiguration(config), + ]); this.onDidChangeEmitter.fire(this._settings); // after saving all the settings, if we need to change the language we need to perform a reload diff --git a/arduino-ide-extension/src/browser/local-cache/local-cache-fs-provider.ts b/arduino-ide-extension/src/browser/local-cache/local-cache-fs-provider.ts index 5a9a77c71..ee2aebf99 100644 --- a/arduino-ide-extension/src/browser/local-cache/local-cache-fs-provider.ts +++ b/arduino-ide-extension/src/browser/local-cache/local-cache-fs-provider.ts @@ -1,5 +1,5 @@ import { inject, injectable } from '@theia/core/shared/inversify'; -import { URI as Uri } from 'vscode-uri'; +import { URI as Uri } from '@theia/core/shared/vscode-uri'; import URI from '@theia/core/lib/common/uri'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index f9aba5ed4..a5a25230c 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -1,6 +1,5 @@ import * as React from '@theia/core/shared/react'; import { injectable, inject } from '@theia/core/shared/inversify'; -import { OptionsType } from 'react-select/src/types'; import { Emitter } from '@theia/core/lib/common/event'; import { Disposable } from '@theia/core/lib/common/disposable'; import { @@ -128,9 +127,7 @@ export class MonitorWidget extends ReactWidget { ); }; - protected get lineEndings(): OptionsType< - SerialMonitorOutput.SelectOption - > { + protected get lineEndings(): SerialMonitorOutput.SelectOption[] { return [ { label: nls.localize('arduino/serial/noLineEndings', 'No Line Ending'), diff --git a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts index 0785cad03..c03b18869 100644 --- a/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/theia/core/common-frontend-contribution.ts @@ -22,7 +22,7 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution CommonCommands.TOGGLE_MAXIMIZED, CommonCommands.PIN_TAB, CommonCommands.UNPIN_TAB, - CommonCommands.NEW_FILE, + CommonCommands.NEW_UNTITLED_FILE, ]) { commandRegistry.unregisterCommand(command); } diff --git a/arduino-ide-extension/src/browser/theia/core/status-bar.ts b/arduino-ide-extension/src/browser/theia/core/status-bar.ts deleted file mode 100644 index 3e7782c3b..000000000 --- a/arduino-ide-extension/src/browser/theia/core/status-bar.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser'; - -@injectable() -export class StatusBarImpl extends TheiaStatusBarImpl { - override async removeElement(id: string): Promise { - await this.ready; - if (this.entries.delete(id)) { - // Unlike Theia, IDE2 updates the status bar only if the element to remove was among the entries. Otherwise, it's a NOOP. - this.update(); - } - } -} diff --git a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx b/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx deleted file mode 100644 index 42e086d2b..000000000 --- a/arduino-ide-extension/src/browser/theia/core/tab-bar-toolbar.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as React from '@theia/core/shared/react'; -import { injectable } from '@theia/core/shared/inversify'; -import { LabelIcon } from '@theia/core/lib/browser/label-parser'; -import { - TabBarToolbar as TheiaTabBarToolbar, - TabBarToolbarItem, -} from '@theia/core/lib/browser/shell/tab-bar-toolbar'; - -@injectable() -export class TabBarToolbar extends TheiaTabBarToolbar { - /** - * Copied over from Theia. Added an ID to the parent of the toolbar item (`--container`). - * CSS3 does not support parent selectors but we want to style the parent of the toolbar item. - */ - protected override renderItem(item: TabBarToolbarItem): React.ReactNode { - let innerText = ''; - const classNames = []; - if (item.text) { - for (const labelPart of this.labelParser.parse(item.text)) { - if (typeof labelPart !== 'string' && LabelIcon.is(labelPart)) { - const className = `fa fa-${labelPart.name}${ - labelPart.animation ? ' fa-' + labelPart.animation : '' - }`; - classNames.push(...className.split(' ')); - } else { - innerText = labelPart; - } - } - } - const command = this.commands.getCommand(item.command); - const iconClass = - (typeof item.icon === 'function' && item.icon()) || - item.icon || - (command && command.iconClass); - if (iconClass) { - classNames.push(iconClass); - } - const tooltip = item.tooltip || (command && command.label); - return ( -
-
- {innerText} -
-
- ); - } -} diff --git a/arduino-ide-extension/src/browser/theia/core/theming.ts b/arduino-ide-extension/src/browser/theia/core/theming.ts new file mode 100644 index 000000000..4438d94e8 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/core/theming.ts @@ -0,0 +1,26 @@ +import { ThemeService as TheiaThemeService } from '@theia/core/lib/browser/theming'; +import type { Theme } from '@theia/core/lib/common/theme'; +import { injectable } from '@theia/core/shared/inversify'; + +export namespace ArduinoThemes { + export const Light: Theme = { + id: 'arduino-theme', + type: 'light', + label: 'Light (Arduino)', + editorTheme: 'arduino-theme', + }; + export const Dark: Theme = { + id: 'arduino-theme-dark', + type: 'dark', + label: 'Dark (Arduino)', + editorTheme: 'arduino-theme-dark', + }; +} + +@injectable() +export class ThemeService extends TheiaThemeService { + protected override init(): void { + this.register(ArduinoThemes.Light, ArduinoThemes.Dark); + super.init(); + } +} diff --git a/arduino-ide-extension/src/browser/theia/core/widget-manager.ts b/arduino-ide-extension/src/browser/theia/core/widget-manager.ts index 2e98c2bfc..038b046a1 100644 --- a/arduino-ide-extension/src/browser/theia/core/widget-manager.ts +++ b/arduino-ide-extension/src/browser/theia/core/widget-manager.ts @@ -1,4 +1,3 @@ -import type { MaybePromise } from '@theia/core'; import type { Widget } from '@theia/core/lib/browser'; import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager'; import { @@ -8,7 +7,6 @@ import { } from '@theia/core/shared/inversify'; import { EditorWidget } from '@theia/editor/lib/browser'; import { OutputWidget } from '@theia/output/lib/browser/output-widget'; -import deepEqual = require('deep-equal'); import { CurrentSketch, SketchesServiceClientImpl, @@ -72,44 +70,4 @@ export class WidgetManager extends TheiaWidgetManager { title.className += title.className + ` ${uncloseableClass}`; } } - - /** - * Customized to find any existing widget based on `options` deepEquals instead of string equals. - * See https://github.com/eclipse-theia/theia/issues/11309. - */ - protected override doGetWidget( - key: string - ): MaybePromise | undefined { - const pendingWidget = this.findExistingWidget(key); - if (pendingWidget) { - return pendingWidget as MaybePromise; - } - return undefined; - } - - private findExistingWidget( - key: string - ): MaybePromise | undefined { - const parsed = this.parseJson(key); - for (const [candidateKey, widget] of [ - ...this.widgetPromises.entries(), - ...this.pendingWidgetPromises.entries(), - ]) { - const candidate = this.parseJson(candidateKey); - if (deepEqual(candidate, parsed)) { - return widget as MaybePromise; - } - } - return undefined; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private parseJson(json: string): any { - try { - return JSON.parse(json); - } catch (err) { - console.log(`Failed to parse JSON: <${json}>.`, err); - throw err; - } - } } diff --git a/arduino-ide-extension/src/browser/theia/core/window-title-updater.ts b/arduino-ide-extension/src/browser/theia/core/window-title-updater.ts new file mode 100644 index 000000000..7ad488ea9 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/core/window-title-updater.ts @@ -0,0 +1,66 @@ +import { NavigatableWidget } from '@theia/core/lib/browser'; +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; +import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; +import { Widget } from '@theia/core/lib/browser/widgets/widget'; +import { WindowTitleUpdater as TheiaWindowTitleUpdater } from '@theia/core/lib/browser/window/window-title-updater'; +import { ApplicationServer } from '@theia/core/lib/common/application-protocol'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; +import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; + +@injectable() +export class WindowTitleUpdater extends TheiaWindowTitleUpdater { + @inject(ApplicationServer) + private readonly applicationServer: ApplicationServer; + @inject(ApplicationShell) + private readonly applicationShell: ApplicationShell; + @inject(WorkspaceService) + private readonly workspaceService: WorkspaceService; + + private readonly applicationName = + FrontendApplicationConfigProvider.get().applicationName; + private applicationVersion: string | undefined; + + @postConstruct() + protected init(): void { + setTimeout( + () => + this.applicationServer.getApplicationInfo().then((info) => { + this.applicationVersion = info?.version; + if (this.applicationVersion) { + this.handleWidgetChange(this.applicationShell.currentWidget); + } + }), + 0 + ); + } + + protected override handleWidgetChange(widget?: Widget | undefined): void { + // Unlike Theia, IDE2 does not want to show in the window title if the current widget is dirty or now. + // Hence, IDE2 does not track widgets but updates the window title on current widget change. + this.updateTitleWidget(widget); + } + + protected override updateTitleWidget(widget?: Widget | undefined): void { + let activeEditorShort = ''; + const rootName = this.workspaceService.workspace?.name ?? ''; + let appName = `${this.applicationName}${ + this.applicationVersion ? ` ${this.applicationVersion}` : '' + }`; + if (rootName) { + appName = ` | ${appName}`; + } + const uri = NavigatableWidget.getUri(widget); + if (uri) { + const base = uri.path.base; + // Do not show the basename of the main sketch file. Only other sketch file names are visible in the title. + if (`${rootName}.ino` !== base) { + activeEditorShort = ` - ${base} `; + } + } + this.windowTitleService.update({ rootName, appName, activeEditorShort }); + } +} diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts index 0059f433c..2523c99c8 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-manager.ts @@ -1,5 +1,9 @@ import debounce = require('p-debounce'); -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { Event, Emitter } from '@theia/core/lib/common/event'; import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; @@ -126,7 +130,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager { const uri = tempFolderUri.resolve('launch.json'); const { value } = await this.fileService.read(uri); const configurations = DebugConfigurationModel.parse(JSON.parse(value)); - return { uri, configurations }; + return { uri, configurations, compounds: [] }; } catch (err) { if ( err instanceof FileOperationError && diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts index 225a003c1..4eaadf172 100644 --- a/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts +++ b/arduino-ide-extension/src/browser/theia/debug/debug-configuration-model.ts @@ -29,6 +29,7 @@ export class DebugConfigurationModel extends TheiaDebugConfigurationModel { return { uri: this.configUri, configurations: this.config, + compounds: [], }; } } diff --git a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts b/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts deleted file mode 100644 index 6eb2ebdeb..000000000 --- a/arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { injectable } from '@theia/core/shared/inversify'; -import { DebugError } from '@theia/debug/lib/common/debug-service'; -import { DebugSession } from '@theia/debug/lib/browser/debug-session'; -import { DebugSessionOptions } from '@theia/debug/lib/browser/debug-session-options'; -import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; -import { nls } from '@theia/core/lib/common'; - -@injectable() -export class DebugSessionManager extends TheiaDebugSessionManager { - override async start(options: DebugSessionOptions): Promise { - return this.progressService.withProgress( - nls.localize('theia/debug/start', 'Start...'), - 'debug', - async () => { - try { - // Only save when dirty. To avoid saving temporary sketches. - // This is a quick fix for not saving the editor when there are no dirty editors. - // // https://github.com/bcmi-labs/arduino-editor/pull/172#issuecomment-741831888 - if (this.shell.canSaveAll()) { - await this.shell.saveAll(); - } - await this.fireWillStartDebugSession(); - const resolved = await this.resolveConfiguration(options); - - //#region "cherry-picked" from here: https://github.com/eclipse-theia/theia/commit/e6b57ba4edabf797f3b4e67bc2968cdb8cc25b1e#diff-08e04edb57cd2af199382337aaf1dbdb31171b37ae4ab38a38d36cd77bc656c7R196-R207 - if (!resolved) { - // As per vscode API: https://code.visualstudio.com/api/references/vscode-api#DebugConfigurationProvider - // "Returning the value 'undefined' prevents the debug session from starting. - // Returning the value 'null' prevents the debug session from starting and opens the - // underlying debug configuration instead." - - if (resolved === null) { - this.debugConfigurationManager.openConfiguration(); - } - return undefined; - } - //#endregion end of cherry-pick - - // preLaunchTask isn't run in case of auto restart as well as postDebugTask - if (!options.configuration.__restart) { - const taskRun = await this.runTask( - options.workspaceFolderUri, - resolved.configuration.preLaunchTask, - true - ); - if (!taskRun) { - return undefined; - } - } - - const sessionId = await this.debug.createDebugSession( - resolved.configuration - ); - return this.doStart(sessionId, resolved); - } catch (e) { - if (DebugError.NotFound.is(e)) { - this.messageService.error( - nls.localize( - 'theia/debug/typeNotSupported', - 'The debug session type "{0}" is not supported.', - e.data.type - ) - ); - return undefined; - } - - this.messageService.error( - nls.localize( - 'theia/debug/startError', - 'There was an error starting the debug session, check the logs for more details.' - ) - ); - console.error('Error starting the debug session', e); - throw e; - } - } - ); - } - override async terminateSession(session?: DebugSession): Promise { - if (!session) { - this.updateCurrentSession(this._currentSession); - session = this._currentSession; - } - // The cortex-debug extension does not respond to close requests - // So we simply terminate the debug session immediately - // Alternatively the `super.terminateSession` call will terminate it after 5 seconds without a response - await this.debug.terminateDebugSession(session!.id); - await super.terminateSession(session); - } -} diff --git a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts deleted file mode 100644 index b93131c7f..000000000 --- a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { injectable, inject } from '@theia/core/shared/inversify'; - -import { - AbstractDialog as TheiaAbstractDialog, - codiconArray, - DialogProps, -} from '@theia/core/lib/browser'; - -@injectable() -export abstract class AbstractDialog extends TheiaAbstractDialog { - constructor(@inject(DialogProps) protected override readonly props: DialogProps) { - super(props); - - this.closeCrossNode.classList.remove(...codiconArray('close')); - this.closeCrossNode.classList.add('fa', 'fa-close'); - } -} diff --git a/arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx new file mode 100644 index 000000000..57eef5639 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/dialogs/dialogs.tsx @@ -0,0 +1,63 @@ +import { + AbstractDialog as TheiaAbstractDialog, + DialogProps, +} from '@theia/core/lib/browser/dialogs'; +import { ReactDialog as TheiaReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog'; +import { codiconArray, Message } from '@theia/core/lib/browser/widgets/widget'; +import { + Disposable, + DisposableCollection, +} from '@theia/core/lib/common/disposable'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import * as React from '@theia/core/shared/react'; +import { createRoot } from '@theia/core/shared/react-dom/client'; + +@injectable() +export abstract class AbstractDialog extends TheiaAbstractDialog { + constructor( + @inject(DialogProps) protected override readonly props: DialogProps + ) { + super(props); + + this.closeCrossNode.classList.remove(...codiconArray('close')); + this.closeCrossNode.classList.add('fa', 'fa-close'); + } +} + +@injectable() +export abstract class ReactDialog extends TheiaReactDialog { + protected override onUpdateRequest(msg: Message): void { + // This is tricky to bypass the default Theia code. + // Otherwise, there is a warning when opening the dialog for the second time. + // You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it. + const disposables = new DisposableCollection(); + if (!this.isMounted) { + // toggle the `isMounted` logic for the time being of the super call so that the `createRoot` does not run + this.isMounted = true; + disposables.push(Disposable.create(() => (this.isMounted = false))); + } + + // Always unset the `contentNodeRoot` so there is no double update when calling super. + const restoreContentNodeRoot = this.contentNodeRoot; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this.contentNodeRoot as any) = undefined; + disposables.push( + Disposable.create(() => (this.contentNodeRoot = restoreContentNodeRoot)) + ); + + try { + super.onUpdateRequest(msg); + } finally { + disposables.dispose(); + } + + // Use the patched rendering. + if (!this.isMounted) { + this.contentNodeRoot = createRoot(this.contentNode); + // Resetting the prop is missing from the Theia code. + // https://github.com/eclipse-theia/theia/blob/v1.31.1/packages/core/src/browser/dialogs/react-dialog.tsx#L41-L47 + this.isMounted = true; + } + this.contentNodeRoot?.render(<>{this.render()}); + } +} diff --git a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts index fc368c90d..3df32188c 100644 --- a/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts +++ b/arduino-ide-extension/src/browser/theia/editor/editor-widget-factory.ts @@ -20,7 +20,7 @@ export class EditorWidgetFactory extends TheiaEditorWidgetFactory { protected override async createEditor( uri: URI, - options: NavigatableWidgetOptions + options?: NavigatableWidgetOptions ): Promise { const widget = await super.createEditor(uri, options); return this.maybeUpdateCaption(widget); diff --git a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts index b260bdab3..b218dde3e 100644 --- a/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts +++ b/arduino-ide-extension/src/browser/theia/markers/problem-manager.ts @@ -3,7 +3,7 @@ import { injectable, postConstruct, } from '@theia/core/shared/inversify'; -import { Diagnostic } from 'vscode-languageserver-types'; +import { Diagnostic } from '@theia/core/shared/vscode-languageserver-types'; import URI from '@theia/core/lib/common/uri'; import { ILogger } from '@theia/core'; import { Marker } from '@theia/markers/lib/common/marker'; diff --git a/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx b/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx index 56652d66a..89ef20724 100644 --- a/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx +++ b/arduino-ide-extension/src/browser/theia/messages/notifications-renderer.tsx @@ -1,5 +1,4 @@ import * as React from '@theia/core/shared/react'; -import * as ReactDOM from '@theia/core/shared/react-dom'; import { inject, injectable, @@ -25,15 +24,14 @@ export class NotificationsRenderer extends TheiaNotificationsRenderer { } protected override render(): void { - ReactDOM.render( + this.containerRoot.render(
-
, - this.container +
); } } diff --git a/arduino-ide-extension/src/browser/theia/monaco/monaco-theming-service.ts b/arduino-ide-extension/src/browser/theia/monaco/monaco-theming-service.ts new file mode 100644 index 000000000..4951ba771 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/monaco/monaco-theming-service.ts @@ -0,0 +1,23 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { MonacoThemingService as TheiaMonacoThemingService } from '@theia/monaco/lib/browser/monaco-theming-service'; +import { ArduinoThemes } from '../core/theming'; + +@injectable() +export class MonacoThemingService extends TheiaMonacoThemingService { + override initialize(): void { + super.initialize(); + const { Light, Dark } = ArduinoThemes; + this.registerParsedTheme({ + id: Light.id, + label: Light.label, + uiTheme: 'vs', + json: require('../../../../src/browser/data/default.color-theme.json'), + }); + this.registerParsedTheme({ + id: Dark.id, + label: Dark.label, + uiTheme: 'vs-dark', + json: require('../../../../src/browser/data/dark.color-theme.json'), + }); + } +} diff --git a/arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-contribution.ts b/arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-contribution.ts new file mode 100644 index 000000000..5afd1e8cf --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-contribution.ts @@ -0,0 +1,32 @@ +import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding'; +import { CommandRegistry } from '@theia/core/lib/common/command'; +import { MenuModelRegistry } from '@theia/core/lib/common/menu'; +import { injectable } from '@theia/core/shared/inversify'; +import { + TypeHierarchyCommands, + TypeHierarchyContribution as TheiaTypeHierarchyContribution, +} from '@theia/typehierarchy/lib/browser/typehierarchy-contribution'; + +@injectable() +export class TypeHierarchyContribution extends TheiaTypeHierarchyContribution { + protected override init(): void { + // NOOP + } + + override registerCommands(registry: CommandRegistry): void { + super.registerCommands(registry); + registry.unregisterCommand(TypeHierarchyCommands.OPEN_SUBTYPE.id); + registry.unregisterCommand(TypeHierarchyCommands.OPEN_SUPERTYPE.id); + } + + override registerMenus(registry: MenuModelRegistry): void { + super.registerMenus(registry); + registry.unregisterMenuAction(TypeHierarchyCommands.OPEN_SUBTYPE.id); + registry.unregisterMenuAction(TypeHierarchyCommands.OPEN_SUPERTYPE.id); + } + + override registerKeybindings(registry: KeybindingRegistry): void { + super.registerKeybindings(registry); + registry.unregisterKeybinding(TypeHierarchyCommands.OPEN_SUBTYPE.id); + } +} diff --git a/arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-service.ts b/arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-service.ts new file mode 100644 index 000000000..1062eea89 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/typehierarchy/type-hierarchy-service.ts @@ -0,0 +1,9 @@ +import { injectable } from '@theia/core/shared/inversify'; +import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service'; + +@injectable() +export class TypeHierarchyServiceProvider extends TheiaTypeHierarchyServiceProvider { + override init(): void { + // NOOP + } +} diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts index 22c74728d..1310610a1 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts @@ -1,58 +1,37 @@ -import * as remote from '@theia/core/electron-shared/@electron/remote'; -import { injectable, inject, named } from '@theia/core/shared/inversify'; +import { ContributionProvider } from '@theia/core/lib/common/contribution-provider'; import URI from '@theia/core/lib/common/uri'; -import { EditorWidget } from '@theia/editor/lib/browser'; -import { ApplicationServer } from '@theia/core/lib/common/application-protocol'; -import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; -import { FocusTracker, Widget } from '@theia/core/lib/browser'; import { DEFAULT_WINDOW_HASH, NewWindowOptions, } from '@theia/core/lib/common/window'; +import { inject, injectable, named } from '@theia/core/shared/inversify'; +import { FileStat } from '@theia/filesystem/lib/common/files'; import { WorkspaceInput, WorkspaceService as TheiaWorkspaceService, } from '@theia/workspace/lib/browser/workspace-service'; import { - SketchesService, - Sketch, SketchesError, + SketchesService, } from '../../../common/protocol/sketches-service'; -import { FileStat } from '@theia/filesystem/lib/common/files'; import { StartupTask, StartupTaskProvider, } from '../../../electron-common/startup-task'; import { WindowServiceExt } from '../core/window-service-ext'; -import { ContributionProvider } from '@theia/core/lib/common/contribution-provider'; @injectable() export class WorkspaceService extends TheiaWorkspaceService { @inject(SketchesService) private readonly sketchService: SketchesService; - @inject(ApplicationServer) - private readonly applicationServer: ApplicationServer; @inject(WindowServiceExt) private readonly windowServiceExt: WindowServiceExt; @inject(ContributionProvider) @named(StartupTaskProvider) private readonly providers: ContributionProvider; - private version?: string; private _workspaceError: Error | undefined; - async onStart(application: FrontendApplication): Promise { - const info = await this.applicationServer.getApplicationInfo(); - this.version = info?.version; - application.shell.onDidChangeCurrentWidget( - this.onCurrentWidgetChange.bind(this) - ); - const newValue = application.shell.currentWidget - ? application.shell.currentWidget - : null; - this.onCurrentWidgetChange({ newValue, oldValue: null }); - } - get workspaceError(): Error | undefined { return this._workspaceError; } @@ -121,58 +100,6 @@ export class WorkspaceService extends TheiaWorkspaceService { } } - /** - * Copied from Theia as-is to be able to pass the original `options` down. - */ - protected override async doOpen( - uri: URI, - options?: WorkspaceInput - ): Promise { - const stat = await this.toFileStat(uri); - if (stat) { - if (!stat.isDirectory && !this.isWorkspaceFile(stat)) { - const message = `Not a valid workspace: ${uri.path.toString()}`; - this.messageService.error(message); - throw new Error(message); - } - // The same window has to be preserved too (instead of opening a new one), if the workspace root is not yet available and we are setting it for the first time. - // Option passed as parameter has the highest priority (for api developers), then the preference, then the default. - await this.roots; - const { preserveWindow } = { - preserveWindow: - this.preferences['workspace.preserveWindow'] || !this.opened, - ...options, - }; - await this.server.setMostRecentlyUsedWorkspace(uri.toString()); - if (preserveWindow) { - this._workspace = stat; - } - this.openWindow(stat, Object.assign(options ?? {}, { preserveWindow })); // Unlike Theia, IDE2 passes the whole `input` downstream and not only { preserveWindow } - return; - } - throw new Error( - 'Invalid workspace root URI. Expected an existing directory or workspace file.' - ); - } - - /** - * Copied from Theia. Can pass the `options` further down the chain. - */ - protected override openWindow(uri: FileStat, options?: WorkspaceInput): void { - const workspacePath = uri.resource.path.toString(); - if (this.shouldPreserveWindow(options)) { - this.reloadWindow(options); // Unlike Theia, IDE2 passes the `input` downstream. - } else { - try { - this.openNewWindow(workspacePath, options); // Unlike Theia, IDE2 passes the `input` downstream. - } catch (error) { - // Fall back to reloading the current window in case the browser has blocked the new window - this._workspace = uri; - this.logger.error(error.toString()).then(() => this.reloadWindow()); - } - } - } - protected override reloadWindow(options?: WorkspaceInput): void { const tasks = this.tasks(options); this.setURLFragment(this._workspace?.resource.path.toString() || ''); @@ -192,6 +119,10 @@ export class WorkspaceService extends TheiaWorkspaceService { ); } + protected override updateTitle(): void { + // NOOP. IDE2 handles the `window.title` updates solely via the customized `WindowTitleUpdater`. + } + private tasks(options?: WorkspaceInput): StartupTask[] { const tasks = this.providers .getContributions() @@ -202,37 +133,4 @@ export class WorkspaceService extends TheiaWorkspaceService { } return tasks; } - - protected onCurrentWidgetChange({ - newValue, - }: FocusTracker.IChangedArgs): void { - if (newValue instanceof EditorWidget) { - const { uri } = newValue.editor; - const currentWindow = remote.getCurrentWindow(); - currentWindow.setRepresentedFilename(uri.path.toString()); - if (Sketch.isSketchFile(uri.toString())) { - this.updateTitle(); - } else { - const title = this.workspaceTitle; - const fileName = this.labelProvider.getName(uri); - document.title = this.formatTitle( - title ? `${title} - ${fileName}` : fileName - ); - } - } else { - this.updateTitle(); - } - } - - protected override formatTitle(title?: string): string { - const version = this.version ? ` ${this.version}` : ''; - const name = `${this.applicationName} ${version}`; - return title ? `${title} | ${name}` : name; - } - - protected get workspaceTitle(): string | undefined { - if (this.workspace) { - return this.labelProvider.getName(this.workspace.resource); - } - } } diff --git a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx index 4ee84e82a..da3ff5f53 100644 --- a/arduino-ide-extension/src/browser/widgets/arduino-select.tsx +++ b/arduino-ide-extension/src/browser/widgets/arduino-select.tsx @@ -1,17 +1,22 @@ import * as React from '@theia/core/shared/react'; import Select from 'react-select'; -import { Styles } from 'react-select/src/styles'; -import { Props } from 'react-select/src/components'; -import { ThemeConfig } from 'react-select/src/theme'; +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 extends Select { - constructor(props: Readonly>) { +export class ArduinoSelect< + Option, + IsMulti extends boolean = false, + Group extends GroupBase