Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 25989de

Browse files
author
Akos Kitta
committedSep 8, 2022
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 <[email protected]>
1 parent dcc0c0a commit 25989de

36 files changed

+554
-575
lines changed
 

‎.vscode/launch.json

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -80,37 +80,6 @@
8080
"port": 9222,
8181
"webRoot": "${workspaceFolder}/electron-app"
8282
},
83-
{
84-
"type": "node",
85-
"request": "launch",
86-
"name": "App (Browser)",
87-
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
88-
"args": [
89-
"--hostname=0.0.0.0",
90-
"--port=3000",
91-
"--no-cluster",
92-
"--no-app-auto-install",
93-
"--plugins=local-dir:plugins"
94-
],
95-
"windows": {
96-
"env": {
97-
"NODE_ENV": "development",
98-
"NODE_PRESERVE_SYMLINKS": "1"
99-
}
100-
},
101-
"env": {
102-
"NODE_ENV": "development"
103-
},
104-
"sourceMaps": true,
105-
"outFiles": [
106-
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
107-
"${workspaceRoot}/browser-app/lib/**/*.js",
108-
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
109-
],
110-
"smartStep": true,
111-
"internalConsoleOptions": "openOnSessionStart",
112-
"outputCapture": "std"
113-
},
11483
{
11584
"type": "node",
11685
"request": "launch",

‎.vscode/tasks.json

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,6 @@
1212
"clear": false
1313
}
1414
},
15-
{
16-
"label": "Arduino IDE - Start Browser App",
17-
"type": "shell",
18-
"command": "yarn --cwd ./browser-app start",
19-
"group": "build",
20-
"presentation": {
21-
"reveal": "always",
22-
"panel": "new",
23-
"clear": true
24-
}
25-
},
2615
{
2716
"label": "Arduino IDE - Watch IDE Extension",
2817
"type": "shell",
@@ -34,17 +23,6 @@
3423
"clear": false
3524
}
3625
},
37-
{
38-
"label": "Arduino IDE - Watch Browser App",
39-
"type": "shell",
40-
"command": "yarn --cwd ./browser-app watch",
41-
"group": "build",
42-
"presentation": {
43-
"reveal": "always",
44-
"panel": "new",
45-
"clear": false
46-
}
47-
},
4826
{
4927
"label": "Arduino IDE - Watch Electron App",
5028
"type": "shell",
@@ -56,14 +34,6 @@
5634
"clear": false
5735
}
5836
},
59-
{
60-
"label": "Arduino IDE - Watch All [Browser]",
61-
"type": "shell",
62-
"dependsOn": [
63-
"Arduino IDE - Watch IDE Extension",
64-
"Arduino IDE - Watch Browser App"
65-
]
66-
},
6737
{
6838
"label": "Arduino IDE - Watch All [Electron]",
6939
"type": "shell",

‎arduino-ide-extension/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,9 @@
147147
"frontend": "lib/browser/arduino-ide-frontend-module"
148148
},
149149
{
150-
"frontend": "lib/browser/theia/core/browser-menu-module",
151150
"frontendElectron": "lib/electron-browser/theia/core/electron-menu-module"
152151
},
153152
{
154-
"frontend": "lib/browser/theia/core/browser-window-module",
155153
"frontendElectron": "lib/electron-browser/theia/core/electron-window-module"
156154
},
157155
{

‎arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ import {
105105
} from '@theia/core/lib/browser/connection-status-service';
106106
import { BoardsDataMenuUpdater } from './boards/boards-data-menu-updater';
107107
import { BoardsDataStore } from './boards/boards-data-store';
108-
import { ILogger } from '@theia/core';
108+
import { ILogger } from '@theia/core/lib/common/logger';
109+
import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider';
109110
import {
110111
FileSystemExt,
111112
FileSystemExtPath,
@@ -308,7 +309,7 @@ import { CoreErrorHandler } from './contributions/core-error-handler';
308309
import { CompilerErrors } from './contributions/compiler-errors';
309310
import { WidgetManager } from './theia/core/widget-manager';
310311
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
311-
import { StartupTasks } from './widgets/sketchbook/startup-task';
312+
import { StartupTasks } from './contributions/startup-task';
312313
import { IndexesUpdateProgress } from './contributions/indexes-update-progress';
313314
import { Daemon } from './contributions/daemon';
314315
import { FirstStartupInstaller } from './contributions/first-startup-installer';
@@ -334,6 +335,8 @@ import {
334335
} from './widgets/component-list/filter-renderer';
335336
import { CheckForUpdates } from './contributions/check-for-updates';
336337
import { OutputEditorFactory } from './theia/output/output-editor-factory';
338+
import { StartupTaskProvider } from '../electron-common/startup-task';
339+
import { DeleteSketch } from './contributions/delete-sketch';
337340

338341
const registerArduinoThemes = () => {
339342
const themes: MonacoThemeJson[] = [
@@ -433,6 +436,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
433436
// Boards service client to receive and delegate notifications from the backend.
434437
bind(BoardsServiceProvider).toSelf().inSingletonScope();
435438
bind(FrontendApplicationContribution).toService(BoardsServiceProvider);
439+
bind(CommandContribution).toService(BoardsServiceProvider);
436440

437441
// To be able to track, and update the menu based on the core settings (aka. board details) of the currently selected board.
438442
bind(FrontendApplicationContribution)
@@ -757,6 +761,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
757761
Contribution.configure(bind, OpenBoardsConfig);
758762
Contribution.configure(bind, SketchFilesTracker);
759763
Contribution.configure(bind, CheckForUpdates);
764+
Contribution.configure(bind, DeleteSketch);
765+
766+
bindContributionProvider(bind, StartupTaskProvider);
767+
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
760768

761769
// Disabled the quick-pick customization from Theia when multiple formatters are available.
762770
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.

‎arduino-ide-extension/src/browser/boards/boards-config.tsx

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -413,53 +413,5 @@ export namespace BoardsConfig {
413413
const { name } = selectedBoard;
414414
return `${name}${port ? ` at ${port.address}` : ''}`;
415415
}
416-
417-
export function setConfig(
418-
config: Config | undefined,
419-
urlToAttachTo: URL
420-
): URL {
421-
const copy = new URL(urlToAttachTo.toString());
422-
if (!config) {
423-
copy.searchParams.delete('boards-config');
424-
return copy;
425-
}
426-
427-
const selectedBoard = config.selectedBoard
428-
? {
429-
name: config.selectedBoard.name,
430-
fqbn: config.selectedBoard.fqbn,
431-
}
432-
: undefined;
433-
const selectedPort = config.selectedPort
434-
? {
435-
protocol: config.selectedPort.protocol,
436-
address: config.selectedPort.address,
437-
}
438-
: undefined;
439-
const jsonConfig = JSON.stringify({ selectedBoard, selectedPort });
440-
copy.searchParams.set('boards-config', encodeURIComponent(jsonConfig));
441-
return copy;
442-
}
443-
444-
export function getConfig(url: URL): Config | undefined {
445-
const encoded = url.searchParams.get('boards-config');
446-
if (!encoded) {
447-
return undefined;
448-
}
449-
try {
450-
const raw = decodeURIComponent(encoded);
451-
const candidate = JSON.parse(raw);
452-
if (typeof candidate === 'object') {
453-
return candidate;
454-
}
455-
console.warn(
456-
`Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}`
457-
);
458-
return undefined;
459-
} catch (e) {
460-
console.log(`Could not get board config from URL: ${url}.`, e);
461-
return undefined;
462-
}
463-
}
464416
}
465417
}

‎arduino-ide-extension/src/browser/boards/boards-service-provider.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { injectable, inject } from '@theia/core/shared/inversify';
22
import { Emitter } from '@theia/core/lib/common/event';
33
import { ILogger } from '@theia/core/lib/common/logger';
4-
import { CommandService } from '@theia/core/lib/common/command';
4+
import {
5+
Command,
6+
CommandContribution,
7+
CommandRegistry,
8+
CommandService,
9+
} from '@theia/core/lib/common/command';
510
import { MessageService } from '@theia/core/lib/common/message-service';
611
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
712
import { RecursiveRequired } from '../../common/types';
@@ -23,9 +28,18 @@ import { nls } from '@theia/core/lib/common';
2328
import { Deferred } from '@theia/core/lib/common/promise-util';
2429
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
2530
import { Unknown } from '../../common/nls';
31+
import {
32+
StartupTask,
33+
StartupTaskProvider,
34+
} from '../../electron-common/startup-task';
2635

2736
@injectable()
28-
export class BoardsServiceProvider implements FrontendApplicationContribution {
37+
export class BoardsServiceProvider
38+
implements
39+
FrontendApplicationContribution,
40+
StartupTaskProvider,
41+
CommandContribution
42+
{
2943
@inject(ILogger)
3044
protected logger: ILogger;
3145

@@ -50,6 +64,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
5064
AvailableBoard[]
5165
>();
5266
protected readonly onAvailablePortsChangedEmitter = new Emitter<Port[]>();
67+
private readonly inheritedConfig = new Deferred<BoardsConfig.Config>();
5368

5469
/**
5570
* 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 {
115130
});
116131
}
117132

133+
registerCommands(registry: CommandRegistry): void {
134+
registry.registerCommand(USE_INHERITED_CONFIG, {
135+
execute: (inheritedConfig: BoardsConfig.Config) =>
136+
this.inheritedConfig.resolve(inheritedConfig),
137+
});
138+
}
139+
118140
get reconciled(): Promise<void> {
119141
return this._reconciled.promise;
120142
}
@@ -655,11 +677,14 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
655677
let storedLatestBoardsConfig = await this.getData<
656678
BoardsConfig.Config | undefined
657679
>('latest-boards-config');
658-
// Try to get from the URL if it was not persisted.
680+
// Try to get from the startup task. Wait for it, then timeout. Maybe it never arrives.
659681
if (!storedLatestBoardsConfig) {
660-
storedLatestBoardsConfig = BoardsConfig.Config.getConfig(
661-
new URL(window.location.href)
662-
);
682+
storedLatestBoardsConfig = await Promise.race([
683+
this.inheritedConfig.promise,
684+
new Promise<undefined>((resolve) =>
685+
setTimeout(() => resolve(undefined), 2_000)
686+
),
687+
]);
663688
}
664689
if (storedLatestBoardsConfig) {
665690
this.latestBoardsConfig = storedLatestBoardsConfig;
@@ -682,8 +707,31 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
682707
key
683708
);
684709
}
710+
711+
tasks(): StartupTask[] {
712+
return [
713+
{
714+
command: USE_INHERITED_CONFIG.id,
715+
args: [this.boardsConfig],
716+
},
717+
];
718+
}
685719
}
686720

721+
/**
722+
* It should be neither visible nor called from outside.
723+
*
724+
* This service creates a startup task with the current board config and
725+
* passes the task to the electron-main process so that the new window
726+
* can inherit the boards config state of this service.
727+
*
728+
* Note that the state is always set, but new windows might ignore it.
729+
* For example, the new window already has a valid boards config persisted to the local storage.
730+
*/
731+
const USE_INHERITED_CONFIG: Command = {
732+
id: 'arduino-use-inherited-boards-config',
733+
};
734+
687735
/**
688736
* Representation of a ready-to-use board, either the user has configured it or was automatically recognized by the CLI.
689737
* 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`.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { injectable } from '@theia/core/shared/inversify';
2+
import { SketchesError } from '../../common/protocol';
3+
import {
4+
Command,
5+
CommandRegistry,
6+
SketchContribution,
7+
Sketch,
8+
} from './contribution';
9+
10+
@injectable()
11+
export class DeleteSketch extends SketchContribution {
12+
override registerCommands(registry: CommandRegistry): void {
13+
registry.registerCommand(DeleteSketch.Commands.DELETE_SKETCH, {
14+
execute: (uri: string) => this.deleteSketch(uri),
15+
});
16+
}
17+
18+
private async deleteSketch(uri: string): Promise<void> {
19+
const sketch = await this.loadSketch(uri);
20+
if (!sketch) {
21+
console.info(`Sketch not found at ${uri}. Skipping deletion.`);
22+
return;
23+
}
24+
return this.sketchService.deleteSketch(sketch);
25+
}
26+
27+
private async loadSketch(uri: string): Promise<Sketch | undefined> {
28+
try {
29+
const sketch = await this.sketchService.loadSketch(uri);
30+
return sketch;
31+
} catch (err) {
32+
if (SketchesError.NotFound.is(err)) {
33+
return undefined;
34+
}
35+
throw err;
36+
}
37+
}
38+
}
39+
export namespace DeleteSketch {
40+
export namespace Commands {
41+
export const DELETE_SKETCH: Command = {
42+
id: 'arduino-delete-sketch',
43+
};
44+
}
45+
}

‎arduino-ide-extension/src/browser/contributions/save-as-sketch.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@ import {
1212
} from './contribution';
1313
import { nls } from '@theia/core/lib/common';
1414
import { ApplicationShell, NavigatableWidget, Saveable } from '@theia/core/lib/browser';
15-
import { EditorManager } from '@theia/editor/lib/browser';
1615
import { WindowService } from '@theia/core/lib/browser/window/window-service';
1716
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
17+
import { WorkspaceInput } from '@theia/workspace/lib/browser';
18+
import { StartupTask } from '../../electron-common/startup-task';
19+
import { DeleteSketch } from './delete-sketch';
1820

1921
@injectable()
2022
export class SaveAsSketch extends SketchContribution {
21-
2223
@inject(ApplicationShell)
23-
protected readonly applicationShell: ApplicationShell;
24-
25-
@inject(EditorManager)
26-
protected override readonly editorManager: EditorManager;
24+
private readonly applicationShell: ApplicationShell;
2725

2826
@inject(WindowService)
29-
protected readonly windowService: WindowService;
27+
private readonly windowService: WindowService;
3028

3129
override registerCommands(registry: CommandRegistry): void {
3230
registry.registerCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH, {
@@ -107,21 +105,19 @@ export class SaveAsSketch extends SketchContribution {
107105
this.sketchService.markAsRecentlyOpened(workspaceUri);
108106
}
109107
}
108+
const options: WorkspaceInput & { tasks: StartupTask[] } = {
109+
preserveWindow: true,
110+
tasks: [],
111+
};
110112
if (workspaceUri && openAfterMove) {
111113
this.windowService.setSafeToShutDown();
112114
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
113-
// This window will navigate away.
114-
// Explicitly stop the contribution to dispose the file watcher before deleting the temp sketch.
115-
// Otherwise, users might see irrelevant _Unable to watch for file changes in this large workspace._ notification.
116-
// https://github.com/arduino/arduino-ide/issues/39.
117-
this.sketchServiceClient.onStop();
118-
// TODO: consider implementing the temp sketch deletion the following way:
119-
// Open the other sketch with a `delete the temp sketch` startup-task.
120-
this.sketchService.notifyDeleteSketch(sketch); // This is a notification and will execute on the backend.
115+
options.tasks.push({
116+
command: DeleteSketch.Commands.DELETE_SKETCH.id,
117+
args: [sketch.uri],
118+
});
121119
}
122-
this.workspaceService.open(new URI(workspaceUri), {
123-
preserveWindow: true,
124-
});
120+
this.workspaceService.open(new URI(workspaceUri), options);
125121
}
126122
return !!workspaceUri;
127123
}

‎arduino-ide-extension/src/browser/contributions/sketch-files-tracker.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { inject, injectable } from '@theia/core/shared/inversify';
44
import { FileSystemFrontendContribution } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
55
import { FileChangeType } from '@theia/filesystem/lib/common/files';
66
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
7-
import { Sketch, SketchContribution, URI } from './contribution';
7+
import { Sketch, SketchContribution } from './contribution';
88
import { OpenSketchFiles } from './open-sketch-files';
99

1010
@injectable()
@@ -31,7 +31,6 @@ export class SketchFilesTracker extends SketchContribution {
3131
override onReady(): void {
3232
this.sketchServiceClient.currentSketch().then(async (sketch) => {
3333
if (CurrentSketch.isValid(sketch)) {
34-
this.toDisposeOnStop.push(this.fileService.watch(new URI(sketch.uri)));
3534
this.toDisposeOnStop.push(
3635
this.fileService.onDidFilesChange(async (event) => {
3736
for (const { type, resource } of event.changes) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as remote from '@theia/core/electron-shared/@electron/remote';
2+
import type { IpcRendererEvent } from '@theia/core/electron-shared/electron';
3+
import { ipcRenderer } from '@theia/core/electron-shared/electron';
4+
import { injectable } from '@theia/core/shared/inversify';
5+
import { StartupTask } from '../../electron-common/startup-task';
6+
import { Contribution } from './contribution';
7+
8+
@injectable()
9+
export class StartupTasks extends Contribution {
10+
override onReady(): void {
11+
ipcRenderer.once(
12+
StartupTask.Messaging.STARTUP_TASKS_SIGNAL,
13+
(_: IpcRendererEvent, args: unknown) => {
14+
console.debug(
15+
`Received the startup tasks from the electron main process. Args: ${JSON.stringify(
16+
args
17+
)}`
18+
);
19+
if (!StartupTask.has(args)) {
20+
console.warn(`Could not detect 'tasks' from the signal. Skipping.`);
21+
return;
22+
}
23+
const tasks = args.tasks;
24+
if (tasks.length) {
25+
console.log(`Executing startup tasks:`);
26+
tasks.forEach(({ command, args = [] }) => {
27+
console.log(
28+
` - '${command}' ${
29+
args.length ? `, args: ${JSON.stringify(args)}` : ''
30+
}`
31+
);
32+
this.commandService
33+
.executeCommand(command, ...args)
34+
.catch((err) =>
35+
console.error(
36+
`Error occurred when executing the startup task '${command}'${
37+
args?.length ? ` with args: '${JSON.stringify(args)}` : ''
38+
}.`,
39+
err
40+
)
41+
);
42+
});
43+
}
44+
}
45+
);
46+
const { id } = remote.getCurrentWindow();
47+
console.debug(
48+
`Signalling app ready event to the electron main process. Sender ID: ${id}.`
49+
);
50+
ipcRenderer.send(StartupTask.Messaging.APP_READY_SIGNAL(id));
51+
}
52+
}

‎arduino-ide-extension/src/browser/theia/core/browser-main-menu-factory.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

‎arduino-ide-extension/src/browser/theia/core/browser-menu-module.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

‎arduino-ide-extension/src/browser/theia/core/browser-window-module.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

‎arduino-ide-extension/src/browser/theia/core/default-window-service.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import type { StartupTask } from '../../../electron-common/startup-task';
2+
13
export const WindowServiceExt = Symbol('WindowServiceExt');
24
export interface WindowServiceExt {
35
/**
46
* Returns with a promise that resolves to `true` if the current window is the first window.
57
*/
68
isFirstWindow(): Promise<boolean>;
9+
reload(options?: { tasks: StartupTask[] }): void;
710
}

‎arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,41 @@
11
import * as remote from '@theia/core/electron-shared/@electron/remote';
2-
import { injectable, inject } from '@theia/core/shared/inversify';
2+
import { injectable, inject, named } from '@theia/core/shared/inversify';
33
import URI from '@theia/core/lib/common/uri';
44
import { EditorWidget } from '@theia/editor/lib/browser';
5-
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
6-
import { MessageService } from '@theia/core/lib/common/message-service';
75
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
86
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
97
import { FocusTracker, Widget } from '@theia/core/lib/browser';
10-
import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window';
11-
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
8+
import {
9+
DEFAULT_WINDOW_HASH,
10+
NewWindowOptions,
11+
} from '@theia/core/lib/common/window';
1212
import {
1313
WorkspaceInput,
1414
WorkspaceService as TheiaWorkspaceService,
1515
} from '@theia/workspace/lib/browser/workspace-service';
16-
import { ConfigService } from '../../../common/protocol/config-service';
1716
import {
1817
SketchesService,
1918
Sketch,
2019
} from '../../../common/protocol/sketches-service';
21-
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
22-
import { BoardsConfig } from '../../boards/boards-config';
2320
import { FileStat } from '@theia/filesystem/lib/common/files';
2421
import {
2522
StartupTask,
26-
StartupTasks,
27-
} from '../../widgets/sketchbook/startup-task';
28-
import { setURL } from '../../utils/window';
23+
StartupTaskProvider,
24+
} from '../../../electron-common/startup-task';
25+
import { WindowServiceExt } from '../core/window-service-ext';
26+
import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
2927

3028
@injectable()
3129
export class WorkspaceService extends TheiaWorkspaceService {
3230
@inject(SketchesService)
33-
protected readonly sketchService: SketchesService;
34-
35-
@inject(ConfigService)
36-
protected readonly configService: ConfigService;
37-
38-
@inject(LabelProvider)
39-
protected override readonly labelProvider: LabelProvider;
40-
41-
@inject(MessageService)
42-
protected override readonly messageService: MessageService;
43-
31+
private readonly sketchService: SketchesService;
4432
@inject(ApplicationServer)
45-
protected readonly applicationServer: ApplicationServer;
46-
47-
@inject(FrontendApplicationStateService)
48-
protected readonly appStateService: FrontendApplicationStateService;
49-
50-
@inject(BoardsServiceProvider)
51-
protected readonly boardsServiceProvider: BoardsServiceProvider;
33+
private readonly applicationServer: ApplicationServer;
34+
@inject(WindowServiceExt)
35+
private readonly windowServiceExt: WindowServiceExt;
36+
@inject(ContributionProvider)
37+
@named(StartupTaskProvider)
38+
private readonly providers: ContributionProvider<StartupTaskProvider>;
5239

5340
private version?: string;
5441

@@ -156,27 +143,33 @@ export class WorkspaceService extends TheiaWorkspaceService {
156143
}
157144

158145
protected override reloadWindow(options?: WorkspaceInput): void {
159-
if (StartupTasks.WorkspaceInput.is(options)) {
160-
setURL(StartupTask.append(options.tasks, new URL(window.location.href)));
161-
}
162-
super.reloadWindow();
146+
const tasks = this.tasks(options);
147+
this.setURLFragment(this._workspace?.resource.path.toString() || '');
148+
this.windowServiceExt.reload({ tasks });
163149
}
164150

165151
protected override openNewWindow(
166152
workspacePath: string,
167153
options?: WorkspaceInput
168154
): void {
169-
const { boardsConfig } = this.boardsServiceProvider;
170-
let url = BoardsConfig.Config.setConfig(
171-
boardsConfig,
172-
new URL(window.location.href)
173-
); // Set the current boards config for the new browser window.
174-
url.hash = workspacePath;
175-
if (StartupTasks.WorkspaceInput.is(options)) {
176-
url = StartupTask.append(options.tasks, url);
177-
}
155+
const tasks = this.tasks(options);
156+
const url = new URL(window.location.href);
157+
url.hash = encodeURI(workspacePath);
158+
this.windowService.openNewWindow(
159+
url.toString(),
160+
Object.assign({} as NewWindowOptions, { tasks })
161+
);
162+
}
178163

179-
this.windowService.openNewWindow(url.toString());
164+
private tasks(options?: WorkspaceInput): StartupTask[] {
165+
const tasks = this.providers
166+
.getContributions()
167+
.map((contribution) => contribution.tasks())
168+
.reduce((prev, curr) => prev.concat(curr), []);
169+
if (StartupTask.has(options)) {
170+
tasks.push(...options.tasks);
171+
}
172+
return tasks;
180173
}
181174

182175
protected onCurrentWidgetChange({

‎arduino-ide-extension/src/browser/widgets/sketchbook/startup-task.ts

Lines changed: 0 additions & 92 deletions
This file was deleted.

‎arduino-ide-extension/src/common/protocol/sketches-service-client-impl.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,6 @@ export class SketchesServiceClientImpl
165165
.reachedState('started_contributions')
166166
.then(async () => {
167167
const currentSketch = await this.loadCurrentSketch();
168-
if (CurrentSketch.isValid(currentSketch)) {
169-
this.toDispose.pushAll([
170-
// Watch the file changes of the current sketch
171-
this.fileService.watch(new URI(currentSketch.uri), {
172-
recursive: true,
173-
excludes: [],
174-
}),
175-
]);
176-
}
177168
this.useCurrentSketch(currentSketch);
178169
});
179170
}

‎arduino-ide-extension/src/common/protocol/sketches-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ export interface SketchesService {
9797
getIdeTempFolderUri(sketch: Sketch): Promise<string>;
9898

9999
/**
100-
* Notifies the backend to recursively delete the sketch folder with all its content.
100+
* Recursively deletes the sketch folder with all its content.
101101
*/
102-
notifyDeleteSketch(sketch: Sketch): void;
102+
deleteSketch(sketch: Sketch): Promise<void>;
103103
}
104104

105105
export interface SketchRef {

‎arduino-ide-extension/src/electron-browser/theia/core/electron-window-service.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import * as remote from '@theia/core/electron-shared/@electron/remote';
2+
import { ipcRenderer } from '@theia/core/electron-shared/electron';
23
import {
34
ConnectionStatus,
45
ConnectionStatusService,
56
} from '@theia/core/lib/browser/connection-status-service';
67
import { nls } from '@theia/core/lib/common';
8+
import { Deferred } from '@theia/core/lib/common/promise-util';
9+
import { NewWindowOptions } from '@theia/core/lib/common/window';
710
import { ElectronWindowService as TheiaElectronWindowService } from '@theia/core/lib/electron-browser/window/electron-window-service';
11+
import { RELOAD_REQUESTED_SIGNAL } from '@theia/core/lib/electron-common/messaging/electron-messages';
812
import {
913
inject,
1014
injectable,
1115
postConstruct,
1216
} from '@theia/core/shared/inversify';
1317
import { WindowServiceExt } from '../../../browser/theia/core/window-service-ext';
1418
import { ElectronMainWindowServiceExt } from '../../../electron-common/electron-main-window-service-ext';
19+
import { StartupTask } from '../../../electron-common/startup-task';
1520

1621
@injectable()
1722
export class ElectronWindowService
@@ -60,14 +65,30 @@ export class ElectronWindowService
6065
return response === 0; // 'Yes', close the window.
6166
}
6267

63-
private _firstWindow: boolean | undefined;
68+
private _firstWindow: Deferred<boolean> | undefined;
6469
async isFirstWindow(): Promise<boolean> {
6570
if (this._firstWindow === undefined) {
71+
this._firstWindow = new Deferred<boolean>();
6672
const windowId = remote.getCurrentWindow().id; // This is expensive and synchronous so we check it once per FE.
67-
this._firstWindow = await this.mainWindowServiceExt.isFirstWindow(
68-
windowId
69-
);
73+
this.mainWindowServiceExt
74+
.isFirstWindow(windowId)
75+
.then((firstWindow) => this._firstWindow?.resolve(firstWindow));
76+
}
77+
return this._firstWindow.promise;
78+
}
79+
80+
// Overridden because the default Theia implementation destroys the additional properties of the `options` arg, such as `tasks`.
81+
override openNewWindow(url: string, options?: NewWindowOptions): undefined {
82+
return this.delegate.openNewWindow(url, options);
83+
}
84+
85+
// Overridden to support optional task owner params and make `tsc` happy.
86+
override reload(options?: StartupTask.Owner): void {
87+
if (options?.tasks && options.tasks.length) {
88+
const { tasks } = options;
89+
ipcRenderer.send(RELOAD_REQUESTED_SIGNAL, { tasks });
90+
} else {
91+
ipcRenderer.send(RELOAD_REQUESTED_SIGNAL);
7092
}
71-
return this._firstWindow;
7293
}
7394
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export interface StartupTask {
2+
command: string;
3+
/**
4+
* Must be JSON serializable.
5+
* See the restrictions [here](https://www.electronjs.org/docs/latest/api/web-contents#contentssendchannel-args).
6+
*/
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
args?: any[];
9+
}
10+
export namespace StartupTask {
11+
export function is(arg: unknown): arg is StartupTask {
12+
if (typeof arg === 'object') {
13+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
14+
const object = arg as any;
15+
return (
16+
'command' in object &&
17+
typeof object['command'] === 'string' &&
18+
(!('args' in object) || Array.isArray(object['args']))
19+
);
20+
}
21+
return false;
22+
}
23+
export function has(arg: unknown): arg is unknown & Owner {
24+
if (typeof arg === 'object') {
25+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26+
const object = arg as any;
27+
return (
28+
'tasks' in object &&
29+
Array.isArray(object['tasks']) &&
30+
object['tasks'].every(is)
31+
);
32+
}
33+
return false;
34+
}
35+
export namespace Messaging {
36+
export const STARTUP_TASKS_SIGNAL = 'arduino/startupTasks';
37+
export function APP_READY_SIGNAL(id: number): string {
38+
return `arduino/appReady${id}`;
39+
}
40+
}
41+
42+
export interface Owner {
43+
readonly tasks: StartupTask[];
44+
}
45+
}
46+
47+
export const StartupTaskProvider = Symbol('StartupTaskProvider');
48+
export interface StartupTaskProvider {
49+
tasks(): StartupTask[];
50+
}

‎arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@ import {
1212
IDEUpdaterClient,
1313
IDEUpdaterPath,
1414
} from '../common/protocol/ide-updater';
15-
import {
16-
ElectronMainWindowServiceExt,
17-
electronMainWindowServiceExtPath,
18-
} from '../electron-common/electron-main-window-service-ext';
15+
import { electronMainWindowServiceExtPath } from '../electron-common/electron-main-window-service-ext';
1916
import { IsTempSketch } from '../node/is-temp-sketch';
20-
import { ElectronMainWindowServiceExtImpl } from './electron-main-window-service-ext-impl';
2117
import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl';
2218
import { ElectronMainApplication } from './theia/electron-main-application';
2319
import { ElectronMainWindowServiceImpl } from './theia/electron-main-window-service';
@@ -52,14 +48,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
5248
bind(TheiaElectronWindow).toSelf();
5349
rebind(DefaultTheiaElectronWindow).toService(TheiaElectronWindow);
5450

55-
bind(ElectronMainWindowServiceExt)
56-
.to(ElectronMainWindowServiceExtImpl)
57-
.inSingletonScope();
5851
bind(ElectronConnectionHandler)
5952
.toDynamicValue(
6053
(context) =>
6154
new JsonRpcConnectionHandler(electronMainWindowServiceExtPath, () =>
62-
context.container.get(ElectronMainWindowServiceExt)
55+
context.container.get(ElectronMainWindowServiceImpl)
6356
)
6457
)
6558
.inSingletonScope();

‎arduino-ide-extension/src/electron-main/electron-main-window-service-ext-impl.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,63 @@
1-
import { inject, injectable } from '@theia/core/shared/inversify';
1+
import type { NewWindowOptions } from '@theia/core/lib/common/window';
2+
import type { BrowserWindow } from '@theia/core/electron-shared/electron';
23
import { ElectronMainWindowServiceImpl as TheiaElectronMainWindowService } from '@theia/core/lib/electron-main/electron-main-window-service-impl';
4+
import { inject, injectable } from '@theia/core/shared/inversify';
5+
import { ElectronMainWindowServiceExt } from '../../electron-common/electron-main-window-service-ext';
6+
import { StartupTask } from '../../electron-common/startup-task';
37
import { ElectronMainApplication } from './electron-main-application';
4-
import { NewWindowOptions } from '@theia/core/lib/common/window';
8+
import { load } from './window';
59

610
@injectable()
7-
export class ElectronMainWindowServiceImpl extends TheiaElectronMainWindowService {
11+
export class ElectronMainWindowServiceImpl
12+
extends TheiaElectronMainWindowService
13+
implements ElectronMainWindowServiceExt
14+
{
815
@inject(ElectronMainApplication)
916
protected override readonly app: ElectronMainApplication;
1017

11-
override openNewWindow(url: string, { external }: NewWindowOptions): undefined {
12-
if (!external) {
13-
const sanitizedUrl = this.sanitize(url);
14-
const existing = this.app.browserWindows.find(
15-
(window) => this.sanitize(window.webContents.getURL()) === sanitizedUrl
16-
);
17-
if (existing) {
18-
existing.focus();
19-
return;
20-
}
21-
}
22-
return super.openNewWindow(url, { external });
18+
async isFirstWindow(windowId: number): Promise<boolean> {
19+
return this.app.firstWindowId === windowId;
2320
}
2421

25-
private sanitize(url: string): string {
26-
const copy = new URL(url);
27-
const searchParams: string[] = [];
28-
copy.searchParams.forEach((_, key) => searchParams.push(key));
29-
for (const param of searchParams) {
30-
copy.searchParams.delete(param);
22+
override openNewWindow(url: string, options: NewWindowOptions): undefined {
23+
// External window has highest precedence.
24+
if (options?.external) {
25+
return super.openNewWindow(url, options);
26+
}
27+
28+
// Look for existing window with the same URL and focus it.
29+
const existing = this.app.browserWindows.find(
30+
({ webContents }) => webContents.getURL() === url
31+
);
32+
if (existing) {
33+
existing.focus();
34+
return undefined;
3135
}
32-
return copy.toString();
36+
37+
// Create new window and share the startup tasks.
38+
if (StartupTask.has(options)) {
39+
const { tasks } = options;
40+
this.app.createWindow().then((electronWindow) => {
41+
this.loadURL(electronWindow, url).then(() => {
42+
electronWindow.webContents.send(
43+
StartupTask.Messaging.STARTUP_TASKS_SIGNAL,
44+
{ tasks }
45+
);
46+
});
47+
});
48+
return undefined;
49+
}
50+
51+
// Default.
52+
return super.openNewWindow(url, options);
53+
}
54+
55+
private loadURL(
56+
electronWindow: BrowserWindow,
57+
url: string
58+
): Promise<BrowserWindow> {
59+
return load(electronWindow, (electronWindow) =>
60+
electronWindow.loadURL(url)
61+
);
3362
}
3463
}

‎arduino-ide-extension/src/electron-main/theia/theia-electron-window.ts

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { injectable } from '@theia/core/shared/inversify';
22
import { ipcMain, IpcMainEvent } from '@theia/electron/shared/electron';
3-
import { StopReason } from '@theia/core/lib/electron-common/messaging/electron-messages';
3+
import {
4+
RELOAD_REQUESTED_SIGNAL,
5+
StopReason,
6+
} from '@theia/core/lib/electron-common/messaging/electron-messages';
47
import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window';
58
import { FileUri } from '@theia/core/lib/node';
69
import URI from '@theia/core/lib/common/uri';
7-
import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state';
810
import { createDisposableListener } from '@theia/core/lib/electron-main/event-utils';
9-
import { APPLICATION_STATE_CHANGE_SIGNAL } from '@theia/core/lib/electron-common/messaging/electron-messages';
11+
import { StartupTask } from '../../electron-common/startup-task';
12+
import { load } from './window';
1013

1114
@injectable()
1215
export class TheiaElectronWindow extends DefaultTheiaElectronWindow {
@@ -38,30 +41,42 @@ export class TheiaElectronWindow extends DefaultTheiaElectronWindow {
3841
return false;
3942
}
4043

41-
// Note: does the same as the Theia impl, but logs state changes.
42-
protected override trackApplicationState(): void {
44+
protected override reload(tasks?: StartupTask[]): void {
45+
this.handleStopRequest(() => {
46+
this.applicationState = 'init';
47+
if (tasks && tasks.length) {
48+
load(this._window, (electronWindow) => electronWindow.reload()).then(
49+
(electronWindow) =>
50+
electronWindow.webContents.send(
51+
StartupTask.Messaging.STARTUP_TASKS_SIGNAL,
52+
{ tasks }
53+
)
54+
);
55+
} else {
56+
this._window.reload();
57+
}
58+
}, StopReason.Reload);
59+
}
60+
61+
protected override attachReloadListener(): void {
4362
createDisposableListener(
4463
ipcMain,
45-
APPLICATION_STATE_CHANGE_SIGNAL,
46-
(e: IpcMainEvent, state: FrontendApplicationState) => {
47-
console.log(
48-
'app-state-change',
49-
`>>> new app state <${state} was received from sender <${e.sender.id}>. current window ID: ${this._window.id}`
50-
);
64+
RELOAD_REQUESTED_SIGNAL,
65+
(e: IpcMainEvent, arg: unknown) => {
5166
if (this.isSender(e)) {
52-
this.applicationState = state;
53-
console.log(
54-
'app-state-change',
55-
`<<< new app state is <${this.applicationState}> for window <${this._window.id}>`
56-
);
57-
} else {
58-
console.log(
59-
'app-state-change',
60-
`<<< new app state <${state}> is ignored from <${e.sender.id}>. current window ID is <${this._window.id}>`
61-
);
67+
if (StartupTask.has(arg)) {
68+
this.reload(arg.tasks);
69+
} else {
70+
this.reload();
71+
}
6272
}
6373
},
6474
this.toDispose
6575
);
6676
}
77+
78+
// https://github.com/eclipse-theia/theia/issues/11600#issuecomment-1240657481
79+
protected override isSender(e: IpcMainEvent): boolean {
80+
return e.sender.id === this._window.webContents.id;
81+
}
6782
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { MaybePromise } from '@theia/core';
2+
import type { IpcMainEvent } from '@theia/core/electron-shared/electron';
3+
import { BrowserWindow, ipcMain } from '@theia/core/electron-shared/electron';
4+
import { DisposableCollection } from '@theia/core/lib/common/disposable';
5+
import { createDisposableListener } from '@theia/core/lib/electron-main/event-utils';
6+
import { StartupTask } from '../../electron-common/startup-task';
7+
8+
/**
9+
* Should be used to load (URL) or reload a window. The returning promise will resolve
10+
* when the app is ready to receive startup tasks.
11+
*/
12+
export async function load(
13+
electronWindow: BrowserWindow,
14+
doLoad: (electronWindow: BrowserWindow) => MaybePromise<void>
15+
): Promise<BrowserWindow> {
16+
const { id } = electronWindow;
17+
const toDispose = new DisposableCollection();
18+
const channel = StartupTask.Messaging.APP_READY_SIGNAL(id);
19+
return new Promise<BrowserWindow>((resolve, reject) => {
20+
toDispose.push(
21+
createDisposableListener(
22+
ipcMain,
23+
channel,
24+
({ sender: webContents }: IpcMainEvent) => {
25+
if (webContents.id === electronWindow.webContents.id) {
26+
resolve(electronWindow);
27+
}
28+
}
29+
)
30+
);
31+
Promise.resolve(doLoad(electronWindow)).catch(reject);
32+
}).finally(() => toDispose.dispose());
33+
}

‎arduino-ide-extension/src/node/arduino-ide-backend-module.ts

Lines changed: 20 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ContainerModule } from '@theia/core/shared/inversify';
1+
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
22
import { ArduinoDaemonImpl } from './arduino-daemon-impl';
33
import {
44
ArduinoFirmwareUploader,
@@ -110,6 +110,7 @@ import {
110110
SurveyNotificationServicePath,
111111
} from '../common/protocol/survey-service';
112112
import { IsTempSketch } from './is-temp-sketch';
113+
import { rebindNsfwFileSystemWatcher } from './theia/filesystem/nsfw-watcher/nsfw-bindings';
113114

114115
export default new ContainerModule((bind, unbind, isBound, rebind) => {
115116
bind(BackendApplication).toSelf().inSingletonScope();
@@ -288,6 +289,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
288289
)
289290
)
290291
.inSingletonScope();
292+
rebindNsfwFileSystemWatcher(rebind);
291293

292294
// Output service per connection.
293295
bind(ConnectionContainerModule).toConstantValue(
@@ -325,58 +327,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
325327
})
326328
);
327329

328-
// Logger for the Arduino daemon
329-
bind(ILogger)
330-
.toDynamicValue((ctx) => {
331-
const parentLogger = ctx.container.get<ILogger>(ILogger);
332-
return parentLogger.child('daemon');
333-
})
334-
.inSingletonScope()
335-
.whenTargetNamed('daemon');
336-
337-
// Logger for the Arduino daemon
338-
bind(ILogger)
339-
.toDynamicValue((ctx) => {
340-
const parentLogger = ctx.container.get<ILogger>(ILogger);
341-
return parentLogger.child('fwuploader');
342-
})
343-
.inSingletonScope()
344-
.whenTargetNamed('fwuploader');
345-
346-
// Logger for the "serial discovery".
347-
bind(ILogger)
348-
.toDynamicValue((ctx) => {
349-
const parentLogger = ctx.container.get<ILogger>(ILogger);
350-
return parentLogger.child('discovery-log'); // TODO: revert
351-
})
352-
.inSingletonScope()
353-
.whenTargetNamed('discovery-log'); // TODO: revert
354-
355-
// Logger for the CLI config service. From the CLI config (FS path aware), we make a URI-aware app config.
356-
bind(ILogger)
357-
.toDynamicValue((ctx) => {
358-
const parentLogger = ctx.container.get<ILogger>(ILogger);
359-
return parentLogger.child('config');
360-
})
361-
.inSingletonScope()
362-
.whenTargetNamed('config');
363-
364-
// Logger for the monitor manager and its services
365-
bind(ILogger)
366-
.toDynamicValue((ctx) => {
367-
const parentLogger = ctx.container.get<ILogger>(ILogger);
368-
return parentLogger.child(MonitorManagerName);
369-
})
370-
.inSingletonScope()
371-
.whenTargetNamed(MonitorManagerName);
372-
373-
bind(ILogger)
374-
.toDynamicValue((ctx) => {
375-
const parentLogger = ctx.container.get<ILogger>(ILogger);
376-
return parentLogger.child(MonitorServiceName);
377-
})
378-
.inSingletonScope()
379-
.whenTargetNamed(MonitorServiceName);
330+
[
331+
'daemon', // Logger for the Arduino daemon
332+
'fwuploader', // Arduino Firmware uploader
333+
'discovery-log', // Boards discovery
334+
'config', // Logger for the CLI config reading and manipulation
335+
MonitorManagerName, // Logger for the monitor manager and its services
336+
MonitorServiceName,
337+
].forEach((name) => bindChildLogger(bind, name));
380338

381339
// Remote sketchbook bindings
382340
bind(AuthenticationServiceImpl).toSelf().inSingletonScope();
@@ -423,3 +381,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
423381

424382
bind(IsTempSketch).toSelf().inSingletonScope();
425383
});
384+
385+
function bindChildLogger(bind: interfaces.Bind, name: string): void {
386+
bind(ILogger)
387+
.toDynamicValue(({ container }) =>
388+
container.get<ILogger>(ILogger).child(name)
389+
)
390+
.inSingletonScope()
391+
.whenTargetNamed(name);
392+
}

‎arduino-ide-extension/src/node/sketches-service-impl.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -561,14 +561,18 @@ void loop() {
561561
return path.join(os.tmpdir(), `arduino-ide2-${suffix}`);
562562
}
563563

564-
notifyDeleteSketch(sketch: Sketch): void {
565-
const sketchPath = FileUri.fsPath(sketch.uri);
566-
fs.rm(sketchPath, { recursive: true, maxRetries: 5 }, (error) => {
567-
if (error) {
568-
console.error(`Failed to delete sketch at ${sketchPath}.`, error);
569-
} else {
570-
console.error(`Successfully delete sketch at ${sketchPath}.`);
571-
}
564+
async deleteSketch(sketch: Sketch): Promise<void> {
565+
return new Promise<void>((resolve, reject) => {
566+
const sketchPath = FileUri.fsPath(sketch.uri);
567+
fs.rm(sketchPath, { recursive: true, maxRetries: 5 }, (error) => {
568+
if (error) {
569+
console.error(`Failed to delete sketch at ${sketchPath}.`, error);
570+
reject(error);
571+
} else {
572+
console.log(`Successfully deleted sketch at ${sketchPath}.`);
573+
resolve();
574+
}
575+
});
572576
});
573577
}
574578
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as yargs from '@theia/core/shared/yargs';
2+
import { JsonRpcProxyFactory } from '@theia/core';
3+
import { NoDelayDisposalTimeoutNsfwFileSystemWatcherService } from './nsfw-filesystem-service';
4+
import type { IPCEntryPoint } from '@theia/core/lib/node/messaging/ipc-protocol';
5+
import type { FileSystemWatcherServiceClient } from '@theia/filesystem/lib/common/filesystem-watcher-protocol';
6+
7+
const options: {
8+
verbose: boolean;
9+
} = yargs
10+
.option('verbose', {
11+
default: false,
12+
alias: 'v',
13+
type: 'boolean',
14+
})
15+
.option('nsfwOptions', {
16+
alias: 'o',
17+
type: 'string',
18+
coerce: JSON.parse,
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20+
}).argv as any;
21+
22+
export default <IPCEntryPoint>((connection) => {
23+
const server = new NoDelayDisposalTimeoutNsfwFileSystemWatcherService(
24+
options
25+
);
26+
const factory = new JsonRpcProxyFactory<FileSystemWatcherServiceClient>(
27+
server
28+
);
29+
server.setClient(factory.createProxy());
30+
factory.listen(connection);
31+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { join } from 'path';
2+
import { interfaces } from '@theia/core/shared/inversify';
3+
import {
4+
NsfwFileSystemWatcherServiceProcessOptions,
5+
NSFW_SINGLE_THREADED,
6+
spawnNsfwFileSystemWatcherServiceProcess,
7+
} from '@theia/filesystem/lib/node/filesystem-backend-module';
8+
import { FileSystemWatcherService } from '@theia/filesystem/lib/common/filesystem-watcher-protocol';
9+
import { NsfwFileSystemWatcherServerOptions } from '@theia/filesystem/lib/node/nsfw-watcher/nsfw-filesystem-service';
10+
import { FileSystemWatcherServiceDispatcher } from '@theia/filesystem/lib/node/filesystem-watcher-dispatcher';
11+
import { NoDelayDisposalTimeoutNsfwFileSystemWatcherService } from './nsfw-filesystem-service';
12+
13+
export function rebindNsfwFileSystemWatcher(rebind: interfaces.Rebind): void {
14+
rebind<NsfwFileSystemWatcherServiceProcessOptions>(
15+
NsfwFileSystemWatcherServiceProcessOptions
16+
).toConstantValue({
17+
entryPoint: join(__dirname, 'index.js'),
18+
});
19+
rebind<FileSystemWatcherService>(FileSystemWatcherService)
20+
.toDynamicValue((context) =>
21+
NSFW_SINGLE_THREADED
22+
? createNsfwFileSystemWatcherService(context)
23+
: spawnNsfwFileSystemWatcherServiceProcess(context)
24+
)
25+
.inSingletonScope();
26+
}
27+
28+
function createNsfwFileSystemWatcherService({
29+
container,
30+
}: interfaces.Context): FileSystemWatcherService {
31+
const options = container.get<NsfwFileSystemWatcherServerOptions>(
32+
NsfwFileSystemWatcherServerOptions
33+
);
34+
const dispatcher = container.get<FileSystemWatcherServiceDispatcher>(
35+
FileSystemWatcherServiceDispatcher
36+
);
37+
const server = new NoDelayDisposalTimeoutNsfwFileSystemWatcherService(
38+
options
39+
);
40+
server.setClient(dispatcher);
41+
return server;
42+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Minimatch } from 'minimatch';
2+
import type { WatchOptions } from '@theia/filesystem/lib/common/filesystem-watcher-protocol';
3+
import {
4+
NsfwFileSystemWatcherService,
5+
NsfwWatcher,
6+
} from '@theia/filesystem/lib/node/nsfw-watcher/nsfw-filesystem-service';
7+
8+
// Dispose the watcher immediately when the last reference is removed. By default, Theia waits 10 sec.
9+
// https://github.com/eclipse-theia/theia/issues/11639#issuecomment-1238980708
10+
const NoDelay = 0;
11+
12+
export class NoDelayDisposalTimeoutNsfwFileSystemWatcherService extends NsfwFileSystemWatcherService {
13+
protected override createWatcher(
14+
clientId: number,
15+
fsPath: string,
16+
options: WatchOptions
17+
): NsfwWatcher {
18+
const watcherOptions = {
19+
ignored: options.ignored.map(
20+
(pattern) => new Minimatch(pattern, { dot: true })
21+
),
22+
};
23+
return new NsfwWatcher(
24+
clientId,
25+
fsPath,
26+
watcherOptions,
27+
this.options,
28+
this.maybeClient,
29+
NoDelay
30+
);
31+
}
32+
}

‎browser-app/package.json

Lines changed: 0 additions & 62 deletions
This file was deleted.

‎browser-app/webpack.config.js

Lines changed: 0 additions & 20 deletions
This file was deleted.

‎electron/packager/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
let pkg = require('../working-copy/package.json');
127127
const workspaces = pkg.workspaces;
128128
// We cannot remove the `electron-app`. Otherwise, there is not way to collect the unused dependencies.
129-
const dependenciesToRemove = ['browser-app'];
129+
const dependenciesToRemove = [];
130130
for (const dependencyToRemove of dependenciesToRemove) {
131131
const index = workspaces.indexOf(dependencyToRemove);
132132
if (index !== -1) {

‎package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@
6969
},
7070
"workspaces": [
7171
"arduino-ide-extension",
72-
"electron-app",
73-
"browser-app"
72+
"electron-app"
7473
],
7574
"theiaPluginsDir": "plugins",
7675
"theiaPlugins": {

‎scripts/update-version.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ console.log(`🛠️ Updating current version from '${currentVersion}' to '${tar
2727
for (const toUpdate of [
2828
path.join(repoRootPath, 'package.json'),
2929
path.join(repoRootPath, 'electron-app', 'package.json'),
30-
path.join(repoRootPath, 'browser-app', 'package.json'),
3130
path.join(repoRootPath, 'arduino-ide-extension', 'package.json')
3231
]) {
3332
process.stdout.write(` Updating ${toUpdate}'...`);

0 commit comments

Comments
 (0)
Please sign in to comment.