@@ -10,6 +10,7 @@ import path from 'node:path';
10
10
import setValue from 'set-value' ;
11
11
import unsetValue from 'unset-value' ;
12
12
import vscode from 'vscode' ;
13
+ import { isENOENT } from './errno' ;
13
14
import { ExecOptions , exec } from './exec' ;
14
15
import type { BoardIdentifier } from './typings' ;
15
16
import type { CortexDebugLaunchAttributes } from './typings/cortexDebug' ;
@@ -21,7 +22,7 @@ export function activateDebug(_: vscode.ExtensionContext): vscode.Disposable {
21
22
'arduino.debug.start' ,
22
23
async ( params : StartDebugParams ) => {
23
24
const launchConfig = await createLaunchConfig ( params ) ;
24
- return startDebug ( params . launchConfigPath , launchConfig ) ;
25
+ return startDebug ( params . launchConfigDirPath , launchConfig ) ;
25
26
}
26
27
) ,
27
28
vscode . commands . registerCommand (
@@ -46,10 +47,11 @@ export interface StartDebugParams {
46
47
*/
47
48
readonly sketchPath : string ;
48
49
/**
49
- * Location where the `launch.json` will be created on the fly before starting every debug session.
50
- * If not defined, it falls back to `sketchPath/.vscode/launch.json`.
50
+ * Absolute filesystem path of the directory where the `launch.json` will be updated before starting every debug session.
51
+ * If the launch config file is absent, it will be created.
52
+ * If not defined, it falls back to `sketchPath/.vscode/launch.json` and uses VS Code APIs to alter the config.
51
53
*/
52
- readonly launchConfigPath ?: string ;
54
+ readonly launchConfigDirPath ?: string ;
53
55
/**
54
56
* Absolute path to the `arduino-cli.yaml` file. If not specified, it falls back to `~/.arduinoIDE/arduino-cli.yaml`.
55
57
*/
@@ -115,10 +117,10 @@ function isCustomDebugConfig(arg: unknown): arg is CustomDebugConfig {
115
117
const cortexDebug = 'cortex-debug' ;
116
118
117
119
async function startDebug (
118
- launchConfigPath : string | undefined ,
120
+ launchConfigDirPath : string | undefined ,
119
121
launchConfig : ArduinoDebugLaunchConfig
120
122
) : Promise < StartDebugResult > {
121
- await updateLaunchConfigs ( launchConfigPath , launchConfig ) ;
123
+ await updateLaunchConfigs ( launchConfigDirPath , launchConfig ) ;
122
124
return vscode . debug . startDebugging ( undefined , launchConfig ) ;
123
125
}
124
126
@@ -188,7 +190,7 @@ async function loadDebugCustomJson(
188
190
) ;
189
191
return parseCustomDebugConfigs ( raw ) ;
190
192
} catch ( err ) {
191
- if ( err instanceof Error && 'code' in err && err . code === 'ENOENT' ) {
193
+ if ( isENOENT ( err ) ) {
192
194
return [ ] ;
193
195
}
194
196
throw err ;
@@ -218,7 +220,7 @@ function parseJson(raw: string): any | undefined {
218
220
}
219
221
220
222
function buildDebugInfoArgs (
221
- params : Omit < StartDebugParams , 'launchConfigPath ' >
223
+ params : Omit < StartDebugParams , 'launchConfigDirPath ' >
222
224
) : Readonly < {
223
225
file : string ;
224
226
args : readonly string [ ] ;
@@ -299,33 +301,78 @@ function replaceValue(
299
301
}
300
302
301
303
// Iteration plan:
302
- // 1. update json configs object with a single config entry and write the file with fs (IDE 2.2.1 behavior)
303
- // 2. update json configs object by merging in the current config entry, and write file with fs
304
+ // 1. (done) update json configs object with a single config entry and write the file with fs (IDE 2.2.1 behavior)
305
+ // 2. (done) update json configs object by merging in the current config entry, and write file with fs
304
306
// 3. same as (2) but use jsonc to nicely update the JSON file
305
307
// 4. use the getConfiguration('launch') API to update the config. It must be verified whether it works in Theia
306
308
async function updateLaunchConfigs (
307
- launchConfigPath : string | undefined ,
309
+ launchConfigDirPath : string | undefined ,
308
310
launchConfig : ArduinoDebugLaunchConfig
309
311
) : Promise < void > {
310
- const launchConfigs = {
311
- version : '0.2.0' ,
312
- configurations : [
313
- {
314
- ...launchConfig ,
315
- } ,
316
- ] ,
317
- } ;
318
- if ( launchConfigPath ) {
319
- await fs . mkdir ( launchConfigPath , { recursive : true } ) ;
312
+ const launchConfigs = await ( launchConfigDirPath
313
+ ? loadLaunchConfigsFile ( launchConfigDirPath )
314
+ : vscode . workspace . getConfiguration ( ) . get < LaunchConfigs > ( 'launch' ) ??
315
+ createEmptyLaunchConfigs ( ) ) ;
316
+
317
+ const index = launchConfigs . configurations . findIndex (
318
+ ( c ) => c [ 'configId' ] === launchConfig . configId
319
+ ) ;
320
+ if ( index < 0 ) {
321
+ launchConfigs . configurations . push ( launchConfig ) ;
322
+ } else {
323
+ launchConfigs . configurations . splice ( index , 1 , launchConfig ) ;
324
+ }
325
+
326
+ if ( launchConfigDirPath ) {
327
+ await fs . mkdir ( launchConfigDirPath , { recursive : true } ) ;
320
328
await fs . writeFile (
321
- path . join ( launchConfigPath , 'launch.json' ) ,
329
+ path . join ( launchConfigDirPath , 'launch.json' ) ,
322
330
JSON . stringify ( launchConfigs , null , 2 )
323
331
) ;
324
332
} else {
325
333
const configuration = vscode . workspace . getConfiguration ( ) ;
326
334
await configuration . update ( 'launch' , launchConfigs , false ) ;
327
335
}
328
336
}
337
+ type LaunchConfigs = {
338
+ version : '0.2.0' ;
339
+ configurations : vscode . DebugConfiguration [ ] ;
340
+ } ;
341
+ function createEmptyLaunchConfigs ( ) : LaunchConfigs {
342
+ return {
343
+ version : '0.2.0' ,
344
+ configurations : [ ] ,
345
+ } ;
346
+ }
347
+ function isLaunchConfigs ( arg : unknown ) : arg is LaunchConfigs {
348
+ return (
349
+ typeof arg === 'object' &&
350
+ arg !== null &&
351
+ ( < LaunchConfigs > arg ) . version === '0.2.0' &&
352
+ Array . isArray ( ( < LaunchConfigs > arg ) . configurations )
353
+ ) ;
354
+ }
355
+
356
+ async function loadLaunchConfigsFile (
357
+ launchConfigDirPath : string
358
+ ) : Promise < LaunchConfigs > {
359
+ try {
360
+ const raw = await fs . readFile (
361
+ path . join ( launchConfigDirPath , 'launch.json' ) ,
362
+ { encoding : 'utf8' }
363
+ ) ;
364
+ const maybeConfigs = parseJson ( raw ) ;
365
+ if ( isLaunchConfigs ( maybeConfigs ) ) {
366
+ return maybeConfigs ;
367
+ }
368
+ return createEmptyLaunchConfigs ( ) ;
369
+ } catch ( err ) {
370
+ if ( isENOENT ( err ) ) {
371
+ return createEmptyLaunchConfigs ( ) ;
372
+ }
373
+ throw err ;
374
+ }
375
+ }
329
376
330
377
async function cliExec < T = Record < string , unknown > > (
331
378
cliPath : string ,
0 commit comments