8
8
} from '@theia/core/electron-shared/electron' ;
9
9
import { fork } from 'child_process' ;
10
10
import { AddressInfo } from 'net' ;
11
- import { join , dirname } from 'path' ;
12
- import * as fs from 'fs-extra ' ;
11
+ import { join , dirname , isAbsolute , resolve } from 'path' ;
12
+ import { promises as fs , Stats } from 'fs' ;
13
13
import { MaybePromise } from '@theia/core/lib/common/types' ;
14
14
import { ElectronSecurityToken } from '@theia/core/lib/electron-common/electron-token' ;
15
15
import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props' ;
@@ -69,8 +69,10 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
69
69
// Explicitly set the app name to have better menu items on macOS. ("About", "Hide", and "Quit")
70
70
// See: https://github.com/electron-userland/electron-builder/issues/2468
71
71
// Regression in Theia: https://github.com/eclipse-theia/theia/issues/8701
72
+ console . log ( `${ config . applicationName } ${ app . getVersion ( ) } ` ) ;
72
73
app . on ( 'ready' , ( ) => app . setName ( config . applicationName ) ) ;
73
- this . attachFileAssociations ( ) ;
74
+ const cwd = process . cwd ( ) ;
75
+ this . attachFileAssociations ( cwd ) ;
74
76
this . useNativeWindowFrame = this . getTitleBarStyle ( config ) === 'native' ;
75
77
this . _config = config ;
76
78
this . hookApplicationEvents ( ) ;
@@ -84,7 +86,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
84
86
return this . launch ( {
85
87
secondInstance : false ,
86
88
argv : this . processArgv . getProcessArgvWithoutBin ( process . argv ) ,
87
- cwd : process . cwd ( ) ,
89
+ cwd
88
90
} ) ;
89
91
}
90
92
@@ -119,7 +121,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
119
121
let traceFile : string | undefined ;
120
122
if ( appPath ) {
121
123
const tracesPath = join ( appPath , 'traces' ) ;
122
- await fs . promises . mkdir ( tracesPath , { recursive : true } ) ;
124
+ await fs . mkdir ( tracesPath , { recursive : true } ) ;
123
125
traceFile = join ( tracesPath , `trace-${ new Date ( ) . toISOString ( ) } .trace` ) ;
124
126
}
125
127
console . log ( '>>> Content tracing has started...' ) ;
@@ -135,14 +137,16 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
135
137
} ) ( ) ;
136
138
}
137
139
138
- private attachFileAssociations ( ) : void {
140
+ private attachFileAssociations ( cwd : string ) : void {
139
141
// OSX: register open-file event
140
142
if ( os . isOSX ) {
141
- app . on ( 'open-file' , async ( event , uri ) => {
143
+ app . on ( 'open-file' , async ( event , path ) => {
142
144
event . preventDefault ( ) ;
143
- if ( uri . endsWith ( '.ino' ) && ( await fs . pathExists ( uri ) ) ) {
145
+ const resolvedPath = await this . resolvePath ( path , cwd ) ;
146
+ const sketchFolderPath = await this . isValidSketchPath ( resolvedPath ) ;
147
+ if ( sketchFolderPath ) {
144
148
this . openFilePromise . reject ( new InterruptWorkspaceRestoreError ( ) ) ;
145
- await this . openSketch ( dirname ( uri ) ) ;
149
+ await this . openSketch ( sketchFolderPath ) ;
146
150
}
147
151
} ) ;
148
152
setTimeout ( ( ) => this . openFilePromise . resolve ( ) , 500 ) ;
@@ -151,8 +155,49 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
151
155
}
152
156
}
153
157
154
- private async isValidSketchPath ( uri : string ) : Promise < boolean | undefined > {
155
- return typeof uri === 'string' && ( await fs . pathExists ( uri ) ) ;
158
+ /**
159
+ * The `path` argument is valid, if accessible and either pointing to a `.ino` file,
160
+ * or it's a directory, and one of the files in the directory is an `.ino` file.
161
+ * The resolved filesystem path is pointing to the sketch folder to open in IDE2.
162
+ * If `undefined`, `path` was pointing to neither an accessible sketch file nor a sketch folder.
163
+ *
164
+ * The sketch folder name and sketch file name can be different. This method is not that strict.
165
+ * The `path` must be an absolute path.
166
+ */
167
+ private async isValidSketchPath ( path : string ) : Promise < string | undefined > {
168
+ let stats : Stats | undefined = undefined ;
169
+ try {
170
+ stats = await fs . stat ( path ) ;
171
+ } catch ( err ) {
172
+ if ( 'code' in err && err . code === 'ENOENT' ) {
173
+ return undefined ;
174
+ }
175
+ throw err ;
176
+ }
177
+ if ( ! stats ) {
178
+ return undefined ;
179
+ }
180
+ if ( stats . isFile ( ) && path . endsWith ( '.ino' ) ) {
181
+ return dirname ( path ) ;
182
+ }
183
+ try {
184
+ const entries = await fs . readdir ( path , { withFileTypes : true } ) ;
185
+ if (
186
+ entries . some ( ( entry ) => entry . isFile ( ) && entry . name . endsWith ( '.ino' ) )
187
+ ) {
188
+ return path ;
189
+ }
190
+ } catch ( err ) {
191
+ throw err ;
192
+ }
193
+ return undefined ;
194
+ }
195
+
196
+ private async resolvePath ( path : string , cwd : string ) : Promise < string > {
197
+ if ( isAbsolute ( path ) ) {
198
+ return path ;
199
+ }
200
+ return fs . realpath ( resolve ( cwd , path ) ) ;
156
201
}
157
202
158
203
protected override async launch (
@@ -171,7 +216,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
171
216
throw err ;
172
217
}
173
218
174
- if ( ! os . isOSX && ( await this . launchFromArgs ( params ) ) ) {
219
+ if ( await this . launchFromArgs ( params ) ) {
175
220
// Application has received a file in its arguments and will skip the default application launch
176
221
return ;
177
222
}
@@ -185,7 +230,10 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
185
230
`Restoring workspace roots: ${ workspaces . map ( ( { file } ) => file ) } `
186
231
) ;
187
232
for ( const workspace of workspaces ) {
188
- if ( await this . isValidSketchPath ( workspace . file ) ) {
233
+ const resolvedPath = await this . resolvePath ( workspace . file , params . cwd ) ;
234
+ const sketchFolderPath = await this . isValidSketchPath ( resolvedPath ) ;
235
+ if ( sketchFolderPath ) {
236
+ workspace . file = sketchFolderPath ;
189
237
if ( this . isTempSketch . is ( workspace . file ) ) {
190
238
console . info (
191
239
`Skipped opening sketch. The sketch was detected as temporary. Workspace path: ${ workspace . file } .`
@@ -208,38 +256,37 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
208
256
) : Promise < boolean > {
209
257
// Copy to prevent manipulation of original array
210
258
const argCopy = [ ...params . argv ] ;
211
- let uri : string | undefined ;
212
- for ( const possibleUri of argCopy ) {
213
- if (
214
- possibleUri . endsWith ( '.ino' ) &&
215
- ( await this . isValidSketchPath ( possibleUri ) )
216
- ) {
217
- uri = possibleUri ;
259
+ let path : string | undefined ;
260
+ for ( const maybePath of argCopy ) {
261
+ const resolvedPath = await this . resolvePath ( maybePath , params . cwd ) ;
262
+ const sketchFolderPath = await this . isValidSketchPath ( resolvedPath ) ;
263
+ if ( sketchFolderPath ) {
264
+ path = sketchFolderPath ;
218
265
break ;
219
266
}
220
267
}
221
- if ( uri ) {
222
- await this . openSketch ( dirname ( uri ) ) ;
268
+ if ( path ) {
269
+ await this . openSketch ( path ) ;
223
270
return true ;
224
271
}
225
272
return false ;
226
273
}
227
274
228
275
private async openSketch (
229
- workspace : WorkspaceOptions | string
276
+ workspaceOrPath : WorkspaceOptions | string
230
277
) : Promise < BrowserWindow > {
231
278
const options = await this . getLastWindowOptions ( ) ;
232
279
let file : string ;
233
- if ( typeof workspace === 'object' ) {
234
- options . x = workspace . x ;
235
- options . y = workspace . y ;
236
- options . width = workspace . width ;
237
- options . height = workspace . height ;
238
- options . isMaximized = workspace . isMaximized ;
239
- options . isFullScreen = workspace . isFullScreen ;
240
- file = workspace . file ;
280
+ if ( typeof workspaceOrPath === 'object' ) {
281
+ options . x = workspaceOrPath . x ;
282
+ options . y = workspaceOrPath . y ;
283
+ options . width = workspaceOrPath . width ;
284
+ options . height = workspaceOrPath . height ;
285
+ options . isMaximized = workspaceOrPath . isMaximized ;
286
+ options . isFullScreen = workspaceOrPath . isFullScreen ;
287
+ file = workspaceOrPath . file ;
241
288
} else {
242
- file = workspace ;
289
+ file = workspaceOrPath ;
243
290
}
244
291
const [ uri , electronWindow ] = await Promise . all ( [
245
292
this . createWindowUri ( ) ,
0 commit comments