mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-14 04:39:28 +00:00
Avoid deleting the workspace when it's still in use.
- From now on, NSFW service disposes after last reference is removed. No more 10sec delay. - Moved the temp workspace deletion to a startup task. - Can set initial task for the window from electron-main. - Removed the `browser-app`. Closes #39 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -105,7 +105,8 @@ import {
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater';
|
||||
import { BoardsDataStore } from './boards/boards-data-store';
|
||||
import { ILogger } from '@theia/core';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
|
||||
import {
|
||||
FileSystemExt,
|
||||
FileSystemExtPath,
|
||||
@@ -308,7 +309,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 './widgets/sketchbook/startup-task';
|
||||
import { StartupTasks } from './contributions/startup-task';
|
||||
import { IndexesUpdateProgress } from './contributions/indexes-update-progress';
|
||||
import { Daemon } from './contributions/daemon';
|
||||
import { FirstStartupInstaller } from './contributions/first-startup-installer';
|
||||
@@ -334,6 +335,8 @@ import {
|
||||
} from './widgets/component-list/filter-renderer';
|
||||
import { CheckForUpdates } from './contributions/check-for-updates';
|
||||
import { OutputEditorFactory } from './theia/output/output-editor-factory';
|
||||
import { StartupTaskProvider } from '../electron-common/startup-task';
|
||||
import { DeleteSketch } from './contributions/delete-sketch';
|
||||
|
||||
const registerArduinoThemes = () => {
|
||||
const themes: MonacoThemeJson[] = [
|
||||
@@ -433,6 +436,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
// Boards service client to receive and delegate notifications from the backend.
|
||||
bind(BoardsServiceProvider).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(BoardsServiceProvider);
|
||||
bind(CommandContribution).toService(BoardsServiceProvider);
|
||||
|
||||
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
|
||||
bind(FrontendApplicationContribution)
|
||||
@@ -757,6 +761,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
Contribution.configure(bind, OpenBoardsConfig);
|
||||
Contribution.configure(bind, SketchFilesTracker);
|
||||
Contribution.configure(bind, CheckForUpdates);
|
||||
Contribution.configure(bind, DeleteSketch);
|
||||
|
||||
bindContributionProvider(bind, StartupTaskProvider);
|
||||
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
|
||||
|
||||
// Disabled the quick-pick customization from Theia when multiple formatters are available.
|
||||
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
|
||||
|
||||
@@ -413,53 +413,5 @@ export namespace BoardsConfig {
|
||||
const { name } = selectedBoard;
|
||||
return `${name}${port ? ` at ${port.address}` : ''}`;
|
||||
}
|
||||
|
||||
export function setConfig(
|
||||
config: Config | undefined,
|
||||
urlToAttachTo: URL
|
||||
): URL {
|
||||
const copy = new URL(urlToAttachTo.toString());
|
||||
if (!config) {
|
||||
copy.searchParams.delete('boards-config');
|
||||
return copy;
|
||||
}
|
||||
|
||||
const selectedBoard = config.selectedBoard
|
||||
? {
|
||||
name: config.selectedBoard.name,
|
||||
fqbn: config.selectedBoard.fqbn,
|
||||
}
|
||||
: undefined;
|
||||
const selectedPort = config.selectedPort
|
||||
? {
|
||||
protocol: config.selectedPort.protocol,
|
||||
address: config.selectedPort.address,
|
||||
}
|
||||
: undefined;
|
||||
const jsonConfig = JSON.stringify({ selectedBoard, selectedPort });
|
||||
copy.searchParams.set('boards-config', encodeURIComponent(jsonConfig));
|
||||
return copy;
|
||||
}
|
||||
|
||||
export function getConfig(url: URL): Config | undefined {
|
||||
const encoded = url.searchParams.get('boards-config');
|
||||
if (!encoded) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const raw = decodeURIComponent(encoded);
|
||||
const candidate = JSON.parse(raw);
|
||||
if (typeof candidate === 'object') {
|
||||
return candidate;
|
||||
}
|
||||
console.warn(
|
||||
`Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}`
|
||||
);
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
console.log(`Could not get board config from URL: ${url}.`, e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { ILogger } from '@theia/core/lib/common/logger';
|
||||
import { CommandService } from '@theia/core/lib/common/command';
|
||||
import {
|
||||
Command,
|
||||
CommandContribution,
|
||||
CommandRegistry,
|
||||
CommandService,
|
||||
} from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { RecursiveRequired } from '../../common/types';
|
||||
@@ -23,9 +28,18 @@ import { nls } from '@theia/core/lib/common';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { Unknown } from '../../common/nls';
|
||||
import {
|
||||
StartupTask,
|
||||
StartupTaskProvider,
|
||||
} from '../../electron-common/startup-task';
|
||||
|
||||
@injectable()
|
||||
export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
export class BoardsServiceProvider
|
||||
implements
|
||||
FrontendApplicationContribution,
|
||||
StartupTaskProvider,
|
||||
CommandContribution
|
||||
{
|
||||
@inject(ILogger)
|
||||
protected logger: ILogger;
|
||||
|
||||
@@ -50,6 +64,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
AvailableBoard[]
|
||||
>();
|
||||
protected readonly onAvailablePortsChangedEmitter = new Emitter<Port[]>();
|
||||
private readonly inheritedConfig = new Deferred<BoardsConfig.Config>();
|
||||
|
||||
/**
|
||||
* Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it.
|
||||
@@ -115,6 +130,13 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(USE_INHERITED_CONFIG, {
|
||||
execute: (inheritedConfig: BoardsConfig.Config) =>
|
||||
this.inheritedConfig.resolve(inheritedConfig),
|
||||
});
|
||||
}
|
||||
|
||||
get reconciled(): Promise<void> {
|
||||
return this._reconciled.promise;
|
||||
}
|
||||
@@ -655,11 +677,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
let storedLatestBoardsConfig = await this.getData<
|
||||
BoardsConfig.Config | undefined
|
||||
>('latest-boards-config');
|
||||
// Try to get from the URL if it was not persisted.
|
||||
// Try to get from the startup task. Wait for it, then timeout. Maybe it never arrives.
|
||||
if (!storedLatestBoardsConfig) {
|
||||
storedLatestBoardsConfig = BoardsConfig.Config.getConfig(
|
||||
new URL(window.location.href)
|
||||
);
|
||||
storedLatestBoardsConfig = await Promise.race([
|
||||
this.inheritedConfig.promise,
|
||||
new Promise<undefined>((resolve) =>
|
||||
setTimeout(() => resolve(undefined), 2_000)
|
||||
),
|
||||
]);
|
||||
}
|
||||
if (storedLatestBoardsConfig) {
|
||||
this.latestBoardsConfig = storedLatestBoardsConfig;
|
||||
@@ -682,8 +707,31 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
|
||||
key
|
||||
);
|
||||
}
|
||||
|
||||
tasks(): StartupTask[] {
|
||||
return [
|
||||
{
|
||||
command: USE_INHERITED_CONFIG.id,
|
||||
args: [this.boardsConfig],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It should be neither visible nor called from outside.
|
||||
*
|
||||
* This service creates a startup task with the current board config and
|
||||
* passes the task to the electron-main process so that the new window
|
||||
* can inherit the boards config state of this service.
|
||||
*
|
||||
* Note that the state is always set, but new windows might ignore it.
|
||||
* For example, the new window already has a valid boards config persisted to the local storage.
|
||||
*/
|
||||
const USE_INHERITED_CONFIG: Command = {
|
||||
id: 'arduino-use-inherited-boards-config',
|
||||
};
|
||||
|
||||
/**
|
||||
* Representation of a ready-to-use board, either the user has configured it or was automatically recognized by the CLI.
|
||||
* An available board was not necessarily recognized by the CLI (e.g.: it is a 3rd party board) or correctly configured but ready for `verify`.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { SketchesError } from '../../common/protocol';
|
||||
import {
|
||||
Command,
|
||||
CommandRegistry,
|
||||
SketchContribution,
|
||||
Sketch,
|
||||
} from './contribution';
|
||||
|
||||
@injectable()
|
||||
export class DeleteSketch extends SketchContribution {
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(DeleteSketch.Commands.DELETE_SKETCH, {
|
||||
execute: (uri: string) => this.deleteSketch(uri),
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteSketch(uri: string): Promise<void> {
|
||||
const sketch = await this.loadSketch(uri);
|
||||
if (!sketch) {
|
||||
console.info(`Sketch not found at ${uri}. Skipping deletion.`);
|
||||
return;
|
||||
}
|
||||
return this.sketchService.deleteSketch(sketch);
|
||||
}
|
||||
|
||||
private async loadSketch(uri: string): Promise<Sketch | undefined> {
|
||||
try {
|
||||
const sketch = await this.sketchService.loadSketch(uri);
|
||||
return sketch;
|
||||
} catch (err) {
|
||||
if (SketchesError.NotFound.is(err)) {
|
||||
return undefined;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
export namespace DeleteSketch {
|
||||
export namespace Commands {
|
||||
export const DELETE_SKETCH: Command = {
|
||||
id: 'arduino-delete-sketch',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,21 +12,19 @@ import {
|
||||
} from './contribution';
|
||||
import { nls } from '@theia/core/lib/common';
|
||||
import { ApplicationShell, NavigatableWidget, Saveable } from '@theia/core/lib/browser';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||
import { WorkspaceInput } from '@theia/workspace/lib/browser';
|
||||
import { StartupTask } from '../../electron-common/startup-task';
|
||||
import { DeleteSketch } from './delete-sketch';
|
||||
|
||||
@injectable()
|
||||
export class SaveAsSketch extends SketchContribution {
|
||||
|
||||
@inject(ApplicationShell)
|
||||
protected readonly applicationShell: ApplicationShell;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected override readonly editorManager: EditorManager;
|
||||
private readonly applicationShell: ApplicationShell;
|
||||
|
||||
@inject(WindowService)
|
||||
protected readonly windowService: WindowService;
|
||||
private readonly windowService: WindowService;
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, {
|
||||
@@ -107,21 +105,19 @@ export class SaveAsSketch extends SketchContribution {
|
||||
this.sketchService.markAsRecentlyOpened(workspaceUri);
|
||||
}
|
||||
}
|
||||
const options: WorkspaceInput & StartupTask.Owner = {
|
||||
preserveWindow: true,
|
||||
tasks: [],
|
||||
};
|
||||
if (workspaceUri && openAfterMove) {
|
||||
this.windowService.setSafeToShutDown();
|
||||
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||
// This window will navigate away.
|
||||
// Explicitly stop the contribution to dispose the file watcher before deleting the temp sketch.
|
||||
// Otherwise, users might see irrelevant _Unable to watch for file changes in this large workspace._ notification.
|
||||
// https://github.com/arduino/arduino-ide/issues/39.
|
||||
this.sketchServiceClient.onStop();
|
||||
// TODO: consider implementing the temp sketch deletion the following way:
|
||||
// Open the other sketch with a `delete the temp sketch` startup-task.
|
||||
this.sketchService.notifyDeleteSketch(sketch); // This is a notification and will execute on the backend.
|
||||
options.tasks.push({
|
||||
command: DeleteSketch.Commands.DELETE_SKETCH.id,
|
||||
args: [sketch.uri],
|
||||
});
|
||||
}
|
||||
this.workspaceService.open(new URI(workspaceUri), {
|
||||
preserveWindow: true,
|
||||
});
|
||||
this.workspaceService.open(new URI(workspaceUri), options);
|
||||
}
|
||||
return !!workspaceUri;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
|
||||
import { FileChangeType } from '@theia/filesystem/lib/common/files';
|
||||
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
|
||||
import { Sketch, SketchContribution, URI } from './contribution';
|
||||
import { Sketch, SketchContribution } from './contribution';
|
||||
import { OpenSketchFiles } from './open-sketch-files';
|
||||
|
||||
@injectable()
|
||||
@@ -31,7 +31,6 @@ export class SketchFilesTracker extends SketchContribution {
|
||||
override onReady(): void {
|
||||
this.sketchServiceClient.currentSketch().then(async (sketch) => {
|
||||
if (CurrentSketch.isValid(sketch)) {
|
||||
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
|
||||
this.toDisposeOnStop.push(
|
||||
this.fileService.onDidFilesChange(async (event) => {
|
||||
for (const { type, resource } of event.changes) {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
MenuBarWidget,
|
||||
} from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class BrowserMainMenuFactory
|
||||
extends TheiaBrowserMainMenuFactory
|
||||
implements MainMenuManager
|
||||
{
|
||||
protected menuBar: MenuBarWidget | undefined;
|
||||
|
||||
override createMenuBar(): MenuBarWidget {
|
||||
this.menuBar = super.createMenuBar();
|
||||
return this.menuBar;
|
||||
}
|
||||
|
||||
update(): void {
|
||||
if (this.menuBar) {
|
||||
this.menuBar.clearMenus();
|
||||
this.fillMenuBar(this.menuBar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import '../../../../src/browser/style/browser-menu.css';
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
BrowserMenuBarContribution,
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
} from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ArduinoMenuContribution } from './browser-menu-plugin';
|
||||
import { BrowserMainMenuFactory } from './browser-main-menu-factory';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(BrowserMainMenuFactory).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(BrowserMainMenuFactory);
|
||||
rebind(TheiaBrowserMainMenuFactory).toService(BrowserMainMenuFactory);
|
||||
rebind(BrowserMenuBarContribution)
|
||||
.to(ArduinoMenuContribution)
|
||||
.inSingletonScope();
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { DefaultWindowService as TheiaDefaultWindowService } from '@theia/core/lib/browser/window/default-window-service';
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { DefaultWindowService } from './default-window-service';
|
||||
import { WindowServiceExt } from './window-service-ext';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(DefaultWindowService).toSelf().inSingletonScope();
|
||||
rebind(TheiaDefaultWindowService).toService(DefaultWindowService);
|
||||
bind(WindowServiceExt).toService(DefaultWindowService);
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import { DefaultWindowService as TheiaDefaultWindowService } from '@theia/core/lib/browser/window/default-window-service';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { WindowServiceExt } from './window-service-ext';
|
||||
|
||||
@injectable()
|
||||
export class DefaultWindowService
|
||||
extends TheiaDefaultWindowService
|
||||
implements WindowServiceExt
|
||||
{
|
||||
/**
|
||||
* The default implementation always resolves to `true`.
|
||||
* IDE2 does not use it. It's currently an electron-only app.
|
||||
*/
|
||||
async isFirstWindow(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { StartupTask } from '../../../electron-common/startup-task';
|
||||
|
||||
export const WindowServiceExt = Symbol('WindowServiceExt');
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,54 +1,41 @@
|
||||
import * as remote from '@theia/core/electron-shared/@electron/remote';
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
|
||||
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||
import { FocusTracker, Widget } from '@theia/core/lib/browser';
|
||||
import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import {
|
||||
DEFAULT_WINDOW_HASH,
|
||||
NewWindowOptions,
|
||||
} from '@theia/core/lib/common/window';
|
||||
import {
|
||||
WorkspaceInput,
|
||||
WorkspaceService as TheiaWorkspaceService,
|
||||
} from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
import {
|
||||
SketchesService,
|
||||
Sketch,
|
||||
} from '../../../common/protocol/sketches-service';
|
||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||
import { BoardsConfig } from '../../boards/boards-config';
|
||||
import { FileStat } from '@theia/filesystem/lib/common/files';
|
||||
import {
|
||||
StartupTask,
|
||||
StartupTasks,
|
||||
} from '../../widgets/sketchbook/startup-task';
|
||||
import { setURL } from '../../utils/window';
|
||||
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)
|
||||
protected readonly sketchService: SketchesService;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(LabelProvider)
|
||||
protected override readonly labelProvider: LabelProvider;
|
||||
|
||||
@inject(MessageService)
|
||||
protected override readonly messageService: MessageService;
|
||||
|
||||
private readonly sketchService: SketchesService;
|
||||
@inject(ApplicationServer)
|
||||
protected readonly applicationServer: ApplicationServer;
|
||||
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
private readonly applicationServer: ApplicationServer;
|
||||
@inject(WindowServiceExt)
|
||||
private readonly windowServiceExt: WindowServiceExt;
|
||||
@inject(ContributionProvider)
|
||||
@named(StartupTaskProvider)
|
||||
private readonly providers: ContributionProvider<StartupTaskProvider>;
|
||||
|
||||
private version?: string;
|
||||
|
||||
@@ -156,27 +143,33 @@ export class WorkspaceService extends TheiaWorkspaceService {
|
||||
}
|
||||
|
||||
protected override reloadWindow(options?: WorkspaceInput): void {
|
||||
if (StartupTasks.WorkspaceInput.is(options)) {
|
||||
setURL(StartupTask.append(options.tasks, new URL(window.location.href)));
|
||||
}
|
||||
super.reloadWindow();
|
||||
const tasks = this.tasks(options);
|
||||
this.setURLFragment(this._workspace?.resource.path.toString() || '');
|
||||
this.windowServiceExt.reload({ tasks });
|
||||
}
|
||||
|
||||
protected override openNewWindow(
|
||||
workspacePath: string,
|
||||
options?: WorkspaceInput
|
||||
): void {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
let url = BoardsConfig.Config.setConfig(
|
||||
boardsConfig,
|
||||
new URL(window.location.href)
|
||||
); // Set the current boards config for the new browser window.
|
||||
url.hash = workspacePath;
|
||||
if (StartupTasks.WorkspaceInput.is(options)) {
|
||||
url = StartupTask.append(options.tasks, url);
|
||||
}
|
||||
const tasks = this.tasks(options);
|
||||
const url = new URL(window.location.href);
|
||||
url.hash = encodeURI(workspacePath);
|
||||
this.windowService.openNewWindow(
|
||||
url.toString(),
|
||||
Object.assign({} as NewWindowOptions, { tasks })
|
||||
);
|
||||
}
|
||||
|
||||
this.windowService.openNewWindow(url.toString());
|
||||
private tasks(options?: WorkspaceInput): StartupTask[] {
|
||||
const tasks = this.providers
|
||||
.getContributions()
|
||||
.map((contribution) => contribution.tasks())
|
||||
.reduce((prev, curr) => prev.concat(curr), []);
|
||||
if (StartupTask.has(options)) {
|
||||
tasks.push(...options.tasks);
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
protected onCurrentWidgetChange({
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { WorkspaceInput as TheiaWorkspaceInput } from '@theia/workspace/lib/browser';
|
||||
import { Contribution } from '../../contributions/contribution';
|
||||
import { setURL } from '../../utils/window';
|
||||
|
||||
@injectable()
|
||||
export class StartupTasks extends Contribution {
|
||||
override onReady(): void {
|
||||
const tasks = StartupTask.get(new URL(window.location.href));
|
||||
console.log(`Executing startup tasks: ${JSON.stringify(tasks)}`);
|
||||
tasks.forEach(({ command, 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
|
||||
)
|
||||
)
|
||||
);
|
||||
if (tasks.length) {
|
||||
// Remove the startup tasks after the execution.
|
||||
// Otherwise, IDE2 executes them again on a window reload event.
|
||||
setURL(StartupTask.set([], new URL(window.location.href)));
|
||||
console.info(`Removed startup tasks from URL.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface StartupTask {
|
||||
command: string;
|
||||
/**
|
||||
* Must be JSON serializable.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
args?: any[];
|
||||
}
|
||||
export namespace StartupTask {
|
||||
const QUERY = 'startupTasks';
|
||||
export function is(arg: unknown): arg is StartupTasks {
|
||||
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';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function get(url: URL): StartupTask[] {
|
||||
const { searchParams } = url;
|
||||
const encodedTasks = searchParams.get(QUERY);
|
||||
if (encodedTasks) {
|
||||
const rawTasks = decodeURIComponent(encodedTasks);
|
||||
const tasks = JSON.parse(rawTasks);
|
||||
if (Array.isArray(tasks)) {
|
||||
return tasks.filter((task) => {
|
||||
if (StartupTask.is(task)) {
|
||||
return true;
|
||||
}
|
||||
console.warn(`Was not a task: ${JSON.stringify(task)}. Ignoring.`);
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
debugger;
|
||||
console.warn(`Startup tasks was not an array: ${rawTasks}. Ignoring.`);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
export function set(tasks: StartupTask[], url: URL): URL {
|
||||
const copy = new URL(url);
|
||||
copy.searchParams.set(QUERY, encodeURIComponent(JSON.stringify(tasks)));
|
||||
return copy;
|
||||
}
|
||||
export function append(tasks: StartupTask[], url: URL): URL {
|
||||
return set([...get(url), ...tasks], url);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace StartupTasks {
|
||||
export interface WorkspaceInput extends TheiaWorkspaceInput {
|
||||
tasks: StartupTask[];
|
||||
}
|
||||
export namespace WorkspaceInput {
|
||||
export function is(
|
||||
input: (TheiaWorkspaceInput & Partial<WorkspaceInput>) | undefined
|
||||
): input is WorkspaceInput {
|
||||
return !!input && !!input.tasks;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user