Skip to content

Commit 728c6a5

Browse files
committed
Refactor debug config provider for flexibility and testability
1 parent c4f41f1 commit 728c6a5

File tree

6 files changed

+226
-111
lines changed

6 files changed

+226
-111
lines changed

integration/vscode/ada/src/ExtensionState.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import * as vscode from 'vscode';
22
import { Disposable, LanguageClient } from 'vscode-languageclient/node';
3+
import { AdaCodeLensProvider } from './AdaCodeLensProvider';
34
import { createClient } from './clients';
5+
import { AdaInitialDebugConfigProvider, initializeDebugging } from './debugConfigProvider';
46
import { GnatTaskProvider } from './gnatTaskProvider';
57
import { GprTaskProvider } from './gprTaskProvider';
6-
import { registerTaskProviders } from './taskProviders';
78
import { TERMINAL_ENV_SETTING_NAME } from './helpers';
9+
import { registerTaskProviders } from './taskProviders';
810

911
/**
1012
* This class encapsulates all state that should be maintained throughout the
@@ -19,6 +21,12 @@ export class ExtensionState {
1921
public readonly adaClient: LanguageClient;
2022
public readonly gprClient: LanguageClient;
2123
public readonly context: vscode.ExtensionContext;
24+
public readonly dynamicDebugConfigProvider: {
25+
provideDebugConfigurations(
26+
_folder?: vscode.WorkspaceFolder | undefined
27+
): Promise<vscode.DebugConfiguration[]>;
28+
};
29+
public readonly initialDebugConfigProvider: AdaInitialDebugConfigProvider;
2230

2331
private registeredTaskProviders: Disposable[];
2432

@@ -39,6 +47,9 @@ export class ExtensionState {
3947
'**/.{adb,ads,adc,ada}'
4048
);
4149
this.registeredTaskProviders = [];
50+
const result = initializeDebugging(this.context);
51+
this.initialDebugConfigProvider = result.providerInitial;
52+
this.dynamicDebugConfigProvider = result.providerDynamic;
4253
}
4354

4455
public start = async () => {

integration/vscode/ada/src/commands.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { adaExtState, mainOutputChannel } from './extension';
1010
import { getProjectFileRelPath } from './helpers';
1111
import {
1212
CustomTaskDefinition,
13+
getBuildAndRunTasks,
1314
getConventionalTaskLabel,
1415
getEnclosingSymbol,
1516
isFromWorkspace,
@@ -302,23 +303,6 @@ async function buildAndRunMainAsk() {
302303
}
303304
}
304305

305-
/**
306-
*
307-
* @returns Array of tasks of type `ada` and kind `buildAndRunMain`. This
308-
* includes tasks automatically provided by the extension as well as
309-
* user-defined tasks in tasks.json.
310-
*/
311-
async function getBuildAndRunTasks() {
312-
return await vscode.tasks
313-
.fetchTasks({ type: 'ada' })
314-
.then((tasks) =>
315-
tasks.filter(
316-
(t) =>
317-
(t.definition as CustomTaskDefinition).configuration.kind == 'buildAndRunMain'
318-
)
319-
);
320-
}
321-
322306
// Take active editor URI and call execute 'als-other-file' command in LSP
323307
const otherFileHandler = () => {
324308
const activeEditor = vscode.window.activeTextEditor;

integration/vscode/ada/src/debugConfigProvider.ts

Lines changed: 119 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import assert from 'assert';
2+
import { existsSync } from 'fs';
3+
import path from 'path';
24
import * as vscode from 'vscode';
35
import { adaExtState } from './extension';
4-
import { AdaMain, exe, getAdaMains, getEvaluatedTerminalEnv, getProjectFile } from './helpers';
6+
import {
7+
AdaMain,
8+
exe,
9+
getAdaMains,
10+
getEvaluatedTerminalEnv,
11+
getProjectFile,
12+
getProjectFileRelPath,
13+
} from './helpers';
514
import { BUILD_PROJECT_TASK_NAME, getBuildTaskName } from './taskProviders';
6-
import path from 'path';
7-
import { existsSync } from 'fs';
15+
16+
export const ADA_DEBUG_BACKEND_TYPE = 'cppdbg';
817

918
/**
1019
* Ada Configuration for a debug session
@@ -29,14 +38,26 @@ export const adaDynamicDebugConfigProvider = {
2938
async provideDebugConfigurations(
3039
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3140
_folder?: vscode.WorkspaceFolder
32-
): Promise<vscode.DebugConfiguration[]> {
33-
const quickpick = await createQuickPickItems('Build & Debug');
34-
35-
const configs: AdaConfig[] = quickpick.flatMap((i) => {
36-
assert(i.adaMain);
37-
return [initializeConfig(i.adaMain), createAttachConfig(i.adaMain)];
41+
): Promise<AdaConfig[]> {
42+
const mains = await getAdaMains();
43+
const configs: AdaConfig[] = mains.flatMap((m) => {
44+
return [initializeConfig(m), createAttachConfig(m)];
3845
});
39-
46+
if (configs.length == 0) {
47+
/**
48+
* No configs were computed which means that there are no mains.
49+
*/
50+
const msg =
51+
`The project ${await getProjectFileRelPath()} does ` +
52+
`not have a Main attribute. Using debug configurations with no ` +
53+
`main program is not supported. Please provide a Main declaration in the ` +
54+
`project and reload the Visual Studio Code window to ` +
55+
`use debug configurations.`;
56+
void vscode.window.showWarningMessage(msg, {
57+
modal: true,
58+
});
59+
return Promise.reject(msg);
60+
}
4061
return configs;
4162
},
4263
};
@@ -47,9 +68,16 @@ export const adaDynamicDebugConfigProvider = {
4768
* @param ctx - the Ada extension context
4869
* @returns the debug configuration provider
4970
*/
50-
export function initializeDebugging(ctx: vscode.ExtensionContext) {
71+
export function initializeDebugging(ctx: vscode.ExtensionContext): {
72+
providerInitial: AdaInitialDebugConfigProvider;
73+
providerDynamic: {
74+
provideDebugConfigurations(
75+
_folder?: vscode.WorkspaceFolder | undefined
76+
): Promise<vscode.DebugConfiguration[]>;
77+
};
78+
} {
5179
// Instantiate a DebugConfigProvider for Ada and register it.
52-
const provider = new AdaDebugConfigProvider();
80+
const providerInitial = new AdaInitialDebugConfigProvider();
5381

5482
// This provider is registered for the 'ada' debugger type. It means that
5583
// it is triggered either when a configuration with type 'ada' is launched,
@@ -62,8 +90,14 @@ export function initializeDebugging(ctx: vscode.ExtensionContext) {
6290
// is no longer called since it is not registered for the type 'cppdbg'.
6391
// Moreover, it is somewhat discouraged to register it for the type
6492
// 'cppdbg' since that type is provided by another extension.
65-
ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('ada', provider));
66-
93+
ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('ada', providerInitial));
94+
95+
/**
96+
* This provider is registered for the 'Dynamic' trigger kind. It is called
97+
* to provide a choice of launch configurations that are unrelated to the
98+
* launch.json file. Such configurations are stored in memory and can be
99+
* relaunched by the User without being saved in the launch.json file.
100+
*/
67101
ctx.subscriptions.push(
68102
vscode.debug.registerDebugConfigurationProvider(
69103
'ada',
@@ -75,7 +109,7 @@ export function initializeDebugging(ctx: vscode.ExtensionContext) {
75109
)
76110
);
77111

78-
return provider;
112+
return { providerInitial: providerInitial, providerDynamic: adaDynamicDebugConfigProvider };
79113
}
80114

81115
let cachedGdb: string | undefined | null = undefined;
@@ -92,7 +126,7 @@ let cachedGdb: string | undefined | null = undefined;
92126
* the value only on the first call, and cache it for subsequent calls to return
93127
* it efficiently.
94128
*/
95-
function getOrFindGdb(): string | undefined {
129+
export function getOrFindGdb(): string | undefined {
96130
if (cachedGdb == undefined) {
97131
/**
98132
* If undefined yet, try to compute it.
@@ -141,11 +175,11 @@ function getOrFindGdb(): string | undefined {
141175
* 'program' parameter.
142176
* @returns an AdaConfig
143177
*/
144-
function initializeConfig(main: AdaMain, name?: string): AdaConfig {
178+
export function initializeConfig(main: AdaMain, name?: string): AdaConfig {
145179
// TODO it would be nice if this and the package.json configuration snippet
146180
// were the same.
147181
const config: AdaConfig = {
148-
type: 'cppdbg',
182+
type: ADA_DEBUG_BACKEND_TYPE,
149183
name: name ?? (main ? `Ada: Debug main - ${main.mainRelPath()}` : 'Ada: Debugger Launch'),
150184
request: 'launch',
151185
targetArchitecture: process.arch,
@@ -165,9 +199,12 @@ function initializeConfig(main: AdaMain, name?: string): AdaConfig {
165199
return config;
166200
}
167201

168-
export class AdaDebugConfigProvider implements vscode.DebugConfigurationProvider {
169-
public static adaConfigType = 'cppdbg';
170-
202+
/**
203+
* A provider of debug configurations for Ada that is called when no launch.json
204+
* exists. It offers a number of debug configurations based on the mains defined
205+
* in the project, and an option to create all available configurations.
206+
*/
207+
export class AdaInitialDebugConfigProvider implements vscode.DebugConfigurationProvider {
171208
async provideDebugConfigurations(
172209
folder: vscode.WorkspaceFolder | undefined,
173210
_token?: vscode.CancellationToken | undefined
@@ -182,39 +219,32 @@ export class AdaDebugConfigProvider implements vscode.DebugConfigurationProvider
182219
}
183220

184221
if (folder != undefined) {
185-
// Offer a list of known Mains from the project
186-
const itemDescription = 'Generate the associated launch configuration';
187-
const quickpick = await createQuickPickItems(itemDescription);
188-
189-
const generateAll: QuickPickAdaMain = {
190-
label: 'All of the above',
191-
description: 'Generate launch configurations for each Main file of the project',
192-
adaMain: undefined,
193-
};
194-
if (quickpick.length > 1) {
195-
quickpick.push(generateAll);
196-
}
197-
198-
const selectedProgram = await vscode.window.showQuickPick(quickpick, {
199-
placeHolder: 'Select a main file to create a launch configuration',
200-
});
222+
// Offer a list of choices to the User based on the same choices as
223+
// the dynamic debug config provider + a choice to create all
224+
// configs.
225+
const { quickpick, generateAll } = await createQuickPicksInitialLaunch();
226+
227+
const selectedProgram = await vscode.window.showQuickPick(
228+
quickpick,
229+
{
230+
placeHolder: 'Select a launch configuration to create in the launch.json file',
231+
},
232+
_token
233+
);
201234

202235
if (selectedProgram == generateAll) {
203-
for (let i = 0; i < quickpick.length; i++) {
204-
const item = quickpick[i];
205-
if (item != generateAll) {
206-
assert(item.adaMain);
207-
configs.push(initializeConfig(item.adaMain));
208-
}
209-
}
236+
configs.push(
237+
...quickpick
238+
.filter((item) => 'conf' in item)
239+
.map((item) => {
240+
assert('conf' in item);
241+
return item.conf;
242+
})
243+
);
210244
} else if (selectedProgram) {
211-
assert(selectedProgram.adaMain);
212-
213-
// The cppdbg debug configuration exepects the executable to be
214-
// a full path rather than a path relative to the specified
215-
// cwd. That is why we include ${workspaceFolder}.
216-
const configuration = initializeConfig(selectedProgram.adaMain);
217-
configs.push(configuration);
245+
assert('conf' in selectedProgram);
246+
assert(selectedProgram.conf);
247+
configs.push(selectedProgram.conf);
218248
} else {
219249
return Promise.reject('Cancelled');
220250
}
@@ -280,32 +310,43 @@ const setupCmd = [
280310
},
281311
];
282312

283-
type QuickPickAdaMain = {
284-
label: string;
285-
description: string;
286-
adaMain?: AdaMain;
287-
};
313+
interface QuickPickAdaMain extends vscode.QuickPickItem {
314+
conf: AdaConfig;
315+
}
288316

289317
/**
318+
* This function is used to create a quick picker in the scenario where no
319+
* launch.json exists and the User triggers the action to create a launch.json
320+
* from scratch.
290321
*
291-
* @param itemDescription - description to use for each item
292-
* @param mains - optional list of AdaMains if known on the caller site,
293-
* otherwise it will be computed by the call
294-
* @returns a list of objects to use with a QuickPicker, one per Main declared in the project.
322+
* @returns an array of quick pick items representing the Ada debug
323+
* configurations, including one item representing the action to create all
324+
* debug configurations
295325
*/
296-
async function createQuickPickItems(
297-
itemDescription: string,
298-
mains?: AdaMain[]
299-
): Promise<QuickPickAdaMain[]> {
300-
mains = mains ?? (await getAdaMains());
301-
302-
await assertProjectHasMains();
303-
304-
return mains.map((main) => ({
305-
label: vscode.workspace.asRelativePath(main.mainFullPath),
306-
description: itemDescription,
307-
adaMain: main,
326+
export async function createQuickPicksInitialLaunch(): Promise<{
327+
quickpick: (QuickPickAdaMain | vscode.QuickPickItem)[];
328+
generateAll: vscode.QuickPickItem;
329+
}> {
330+
// Offer the same list of debug configurations as the dynamic provider + an
331+
// option to generate all configurations
332+
const configs = await adaDynamicDebugConfigProvider.provideDebugConfigurations();
333+
const quickpick: (QuickPickAdaMain | vscode.QuickPickItem)[] = configs.map((conf) => ({
334+
label: conf.name,
335+
conf: conf,
308336
}));
337+
const generateAll: vscode.QuickPickItem = {
338+
label: 'All of the above',
339+
description: 'Create all of the above configurations in the launch.json file',
340+
};
341+
if (quickpick.length > 1) {
342+
quickpick.push({
343+
label: '',
344+
kind: vscode.QuickPickItemKind.Separator,
345+
});
346+
// Add the generateAll option only if there are multiple choices
347+
quickpick.push(generateAll);
348+
}
349+
return { quickpick, generateAll };
309350
}
310351

311352
/**
@@ -372,7 +413,11 @@ export async function getOrAskForProgram(mains?: AdaMain[]): Promise<AdaMain | u
372413

373414
// There is no current file or it matches no known Main of the project, so
374415
// we offer all Mains in a QuickPicker for the user to choose from.
375-
const quickpick = await createQuickPickItems('Select for debugging', mains);
416+
const quickpick = mains.map((m) => ({
417+
label: m.mainRelPath(),
418+
description: m.execRelPath(),
419+
adaMain: m,
420+
}));
376421
const selectedProgram = await vscode.window.showQuickPick(quickpick, {
377422
placeHolder: 'Select a main file to debug',
378423
});
@@ -404,7 +449,7 @@ async function getAdaMainForSourceFile(
404449
function createAttachConfig(adaMain: AdaMain): AdaConfig {
405450
return {
406451
name: `Ada: Attach debugger to running process - ${adaMain.mainRelPath()}`,
407-
type: 'cppdbg',
452+
type: ADA_DEBUG_BACKEND_TYPE,
408453
request: 'attach',
409454
program: `\${workspaceFolder}/${adaMain.execRelPath()}`,
410455
processId: '${command:pickProcess}',

integration/vscode/ada/src/extension.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ import { ExtensionState } from './ExtensionState';
2626
import { ALSClientFeatures } from './alsClientFeatures';
2727
import { alsCommandExecutor } from './alsExecuteCommand';
2828
import { registerCommands } from './commands';
29-
import { initializeDebugging } from './debugConfigProvider';
3029
import { initializeTestView } from './gnattest';
3130
import {
32-
assertSupportedEnvironments,
3331
TERMINAL_ENV_SETTING_NAME,
32+
assertSupportedEnvironments,
3433
getEvaluatedTerminalEnv,
3534
startedInDebugMode,
3635
} from './helpers';
@@ -164,8 +163,6 @@ async function activateExtension(context: vscode.ExtensionContext) {
164163

165164
await initializeTestView(context, adaExtState);
166165

167-
initializeDebugging(context);
168-
169166
/**
170167
* This can display a dialog to the User so don't wait on the result.
171168
*/

0 commit comments

Comments
 (0)