Skip to content

Commit 0207778

Browse files
authored
Enable opening the IDE from finder/explorer (#835)
* Enable opening the IDE from finder/explorer * Make opening windows from args a bit more lenient
1 parent d79f32e commit 0207778

File tree

2 files changed

+87
-12
lines changed

2 files changed

+87
-12
lines changed

arduino-ide-extension/src/electron-main/theia/electron-main-application.ts

+81-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { inject, injectable } from 'inversify';
2-
import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, screen } from '@theia/core/electron-shared/electron';
2+
import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, screen, Event as ElectronEvent } from '@theia/core/electron-shared/electron';
33
import { fork } from 'child_process';
44
import { AddressInfo } from 'net';
5-
import { join } from 'path';
5+
import { join, dirname } from 'path';
66
import * as fs from 'fs-extra';
77
import { initSplashScreen } from '../splash/splash-screen';
88
import { MaybePromise } from '@theia/core/lib/common/types';
@@ -16,6 +16,8 @@ import {
1616
import { SplashServiceImpl } from '../splash/splash-service-impl';
1717
import { URI } from '@theia/core/shared/vscode-uri';
1818
import * as electronRemoteMain from '@theia/core/electron-shared/@electron/remote/main';
19+
import { Deferred } from '@theia/core/lib/common/promise-util';
20+
import * as os from '@theia/core/lib/common/os';
1921

2022
app.commandLine.appendSwitch('disable-http-cache');
2123

@@ -36,6 +38,7 @@ const WORKSPACES = 'workspaces';
3638
export class ElectronMainApplication extends TheiaElectronMainApplication {
3739
protected _windows: BrowserWindow[] = [];
3840
protected startup = false;
41+
protected openFilePromise = new Deferred();
3942

4043
@inject(SplashServiceImpl)
4144
protected readonly splashService: SplashServiceImpl;
@@ -45,17 +48,52 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
4548
// See: https://github.com/electron-userland/electron-builder/issues/2468
4649
// Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701
4750
app.on('ready', () => app.setName(config.applicationName));
51+
this.attachFileAssociations();
4852
return super.start(config);
4953
}
5054

55+
attachFileAssociations() {
56+
// OSX: register open-file event
57+
if (os.isOSX) {
58+
app.on('open-file', async (event, uri) => {
59+
event.preventDefault();
60+
if (uri.endsWith('.ino') && await fs.pathExists(uri)) {
61+
this.openFilePromise.reject();
62+
await this.openSketch(dirname(uri));
63+
}
64+
});
65+
setTimeout(() => this.openFilePromise.resolve(), 500);
66+
} else {
67+
this.openFilePromise.resolve();
68+
}
69+
}
70+
71+
protected async isValidSketchPath(uri: string): Promise<boolean | undefined> {
72+
return typeof uri === 'string' && await fs.pathExists(uri);
73+
}
74+
5175
protected async launch(params: ElectronMainExecutionParams): Promise<void> {
76+
try {
77+
// When running on MacOS, we either have to wait until
78+
// 1. The `open-file` command has been received by the app, rejecting the promise
79+
// 2. A short timeout resolves the promise automatically, falling back to the usual app launch
80+
await this.openFilePromise.promise;
81+
} catch {
82+
// Application has received the `open-file` event and will skip the default application launch
83+
return;
84+
}
85+
86+
if (!os.isOSX && await this.launchFromArgs(params)) {
87+
// Application has received a file in its arguments and will skip the default application launch
88+
return;
89+
}
90+
5291
this.startup = true;
5392
const workspaces: WorkspaceOptions[] | undefined = this.electronStore.get(WORKSPACES);
5493
let useDefault = true;
5594
if (workspaces && workspaces.length > 0) {
5695
for (const workspace of workspaces) {
57-
const file = workspace.file;
58-
if (typeof file === 'string' && await fs.pathExists(file)) {
96+
if (await this.isValidSketchPath(workspace.file)) {
5997
useDefault = false;
6098
await this.openSketch(workspace);
6199
}
@@ -67,16 +105,39 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
67105
}
68106
}
69107

70-
protected async openSketch(workspace: WorkspaceOptions): Promise<BrowserWindow> {
108+
protected async launchFromArgs(params: ElectronMainExecutionParams): Promise<boolean> {
109+
// Copy to prevent manipulation of original array
110+
const argCopy = [...params.argv];
111+
let uri: string | undefined;
112+
for (const possibleUri of argCopy) {
113+
if (possibleUri.endsWith('.ino') && await this.isValidSketchPath(possibleUri)) {
114+
uri = possibleUri;
115+
break;
116+
}
117+
}
118+
if (uri) {
119+
await this.openSketch(dirname(uri));
120+
return true;
121+
}
122+
return false;
123+
}
124+
125+
protected async openSketch(workspace: WorkspaceOptions | string): Promise<BrowserWindow> {
71126
const options = await this.getLastWindowOptions();
72-
options.x = workspace.x;
73-
options.y = workspace.y;
74-
options.width = workspace.width;
75-
options.height = workspace.height;
76-
options.isMaximized = workspace.isMaximized;
77-
options.isFullScreen = workspace.isFullScreen;
127+
let file: string;
128+
if (typeof workspace === 'object') {
129+
options.x = workspace.x;
130+
options.y = workspace.y;
131+
options.width = workspace.width;
132+
options.height = workspace.height;
133+
options.isMaximized = workspace.isMaximized;
134+
options.isFullScreen = workspace.isFullScreen;
135+
file = workspace.file;
136+
} else {
137+
file = workspace;
138+
}
78139
const [uri, electronWindow] = await Promise.all([this.createWindowUri(), this.createWindow(options)]);
79-
electronWindow.loadURL(uri.withFragment(encodeURI(workspace.file)).toString(true));
140+
electronWindow.loadURL(uri.withFragment(encodeURI(file)).toString(true));
80141
return electronWindow;
81142
}
82143

@@ -101,6 +162,14 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
101162
});
102163
}
103164

165+
protected async onSecondInstance(event: ElectronEvent, argv: string[], cwd: string): Promise<void> {
166+
if (!os.isOSX && await this.launchFromArgs({ cwd, argv, secondInstance: true })) {
167+
// Application has received a file in its arguments
168+
return;
169+
}
170+
super.onSecondInstance(event, argv, cwd);
171+
}
172+
104173
/**
105174
* Use this rather than creating `BrowserWindow` instances from scratch, since some security parameters need to be set, this method will do it.
106175
*

electron/build/template-package.json

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@
6060
"directories": {
6161
"buildResources": "resources"
6262
},
63+
"fileAssociations": [
64+
{
65+
"ext": "ino",
66+
"role": "Editor"
67+
}
68+
],
6369
"files": [
6470
"src-gen",
6571
"lib",

0 commit comments

Comments
 (0)