Skip to content

Commit 2dacdd1

Browse files
committed
Rework the integration of debug configurations
* Do not create a launch.json until the User asks for a debug session * Do not expose hidden ada.initDebugFile command to Users * Rename ada.initDebugFile command to more meaningful ada.askForProgram * Do not use the debugger type "cppdbg" in package.json because it clashes with the existing CPP debugger. Instead we use the debugger type 'ada' in package.json but we never use it in actual resolved configurations. * Do not provide a JSON schema for debug configurations since the CPP extension already defines it for the 'cppdbg' type and we don't intend to actually use the 'ada' type. * Set the program field of cppdbg configurations to a full path as expected by the C++ debug extension.
1 parent b6c6562 commit 2dacdd1

File tree

5 files changed

+117
-146
lines changed

5 files changed

+117
-146
lines changed

integration/vscode/ada/package.json

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -508,11 +508,6 @@
508508
"command": "ada.showGprLSOutput",
509509
"title": "Ada: Show GPR Language Server Output",
510510
"when": "ADA_PROJECT_CONTEXT"
511-
},
512-
{
513-
"command": "ada.initDebugFile",
514-
"title": "Ada: Debug File",
515-
"when": "ADA_PROJECT_CONTEXT"
516511
}
517512
],
518513
"keybindings": [
@@ -589,7 +584,7 @@
589584
],
590585
"debuggers": [
591586
{
592-
"type": "cppdbg",
587+
"type": "ada",
593588
"label": "Ada",
594589
"languages": [
595590
"ada",
@@ -599,64 +594,22 @@
599594
"when": "ADA_PROJECT_CONTEXT",
600595
"configurationSnippets": [
601596
{
602-
"label": "Ada Debugger Launch",
597+
"label": "Ada: Debugger Launch",
603598
"description": "Launch configuration for Ada debugging.",
604599
"body": {
605600
"type": "cppdbg",
606601
"request": "launch",
607-
"name": "Ada Debugger Launch",
608-
"program": "your-executable-path",
602+
"name": "Ada: Debugger Launch",
603+
"program": "^\"\\${workspaceFolder}/\\${command:ada.askForProgram}\"",
609604
"args": [],
610-
"cwd": "${workspaceFolder}",
605+
"cwd": "^\"\\${workspaceFolder}\"",
611606
"stopAtEntry": false,
612-
"preLaunchTask": "your-prelaunch-task"
607+
"preLaunchTask": "ada: Build current project"
613608
}
614609
}
615610
],
616-
"configurationAttributes": {
617-
"launch": {
618-
"required": [
619-
"program"
620-
],
621-
"properties": [
622-
{
623-
"program": {
624-
"type": "string",
625-
"description": "Path to program executable.",
626-
"default": "${command:AskForProgram}"
627-
},
628-
"cwd": {
629-
"type": "string",
630-
"description": "The working directory of the target.",
631-
"default": "${workspaceRoot}"
632-
},
633-
"stopAtEntry": {
634-
"type": "boolean",
635-
"description": "If true, the debugger should stop at the entrypoint of the target.",
636-
"default": false
637-
},
638-
"args": {
639-
"description": "Arguments passed to the program entrypoint",
640-
"items": {
641-
"type": "string"
642-
},
643-
"type": [
644-
"array",
645-
"string"
646-
],
647-
"default": []
648-
},
649-
"preLaunchTask": {
650-
"type": "string",
651-
"description": "The build task to launch before debugging",
652-
"default": "ada: Build current project"
653-
}
654-
}
655-
]
656-
}
657-
},
658611
"variables": {
659-
"AskForProgram": "ada.initDebugFile"
612+
"AskForProgram": "ada.askForProgram"
660613
}
661614
}
662615
]

integration/vscode/ada/src/commands.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as vscode from 'vscode';
22
import { SymbolKind } from 'vscode';
3-
import { getEnclosingSymbol } from './gnatTaskProvider';
4-
import { mainLogChannel } from './extension';
53
import { ContextClients } from './clients';
64
import { AdaDebugConfigProvider } from './debugConfigProvider';
5+
import { mainLogChannel } from './extension';
6+
import { getEnclosingSymbol } from './gnatTaskProvider';
77

88
export function registerCommands(
99
context: vscode.ExtensionContext,
@@ -29,9 +29,12 @@ export function registerCommands(
2929
clients.gprClient.outputChannel.show()
3030
)
3131
);
32+
33+
// This is a hidden command that gets called in the default debug
34+
// configuration snippet that gets offered in the launch.json file.
3235
context.subscriptions.push(
33-
vscode.commands.registerCommand('ada.initDebugFile', async () => {
34-
const p = await debug.initDebugCmd();
36+
vscode.commands.registerCommand('ada.askForProgram', async () => {
37+
const p = await debug.askForProgram();
3538
return p;
3639
})
3740
);

integration/vscode/ada/src/debugConfigProvider.ts

Lines changed: 98 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import * as vscode from 'vscode';
21
import * as path from 'path';
2+
import * as vscode from 'vscode';
33
import { ContextClients } from './clients';
44
import { getExecutables, getMains } from './helpers';
55

@@ -26,72 +26,62 @@ interface AdaConfig extends vscode.DebugConfiguration {
2626
* @param clients - the language clients
2727
* @returns the debug configuration provider
2828
*/
29-
export async function initializeDebugging(ctx: vscode.ExtensionContext, clients: ContextClients) {
29+
export function initializeDebugging(ctx: vscode.ExtensionContext, clients: ContextClients) {
30+
// Instantiate a DebugConfigProvider for Ada and register it.
3031
const provider = new AdaDebugConfigProvider(clients);
31-
ctx.subscriptions.push(
32-
vscode.debug.registerDebugConfigurationProvider(
33-
AdaDebugConfigProvider.adaConfigType,
34-
provider
35-
)
36-
);
37-
38-
const workspaceConfig = vscode.workspace.getConfiguration();
39-
const configurations: vscode.DebugConfiguration[] =
40-
(workspaceConfig.get('launch.configurations') as vscode.DebugConfiguration[]) || [];
41-
42-
let status = false;
43-
for (const config of configurations) {
44-
if (config.name == 'Debug Ada') {
45-
status = true;
46-
break;
47-
}
48-
}
4932

50-
if (!status) {
51-
const initialDebugConfiguration = initializeConfig(undefined, '${command:AskForProgram}');
33+
// This provider is registered for the 'ada' debugger type. It means that
34+
// it is triggered either when a configuration with type 'ada' is launched,
35+
// or when the applicable context of the 'ada' debugger type is enabled
36+
// (see package.json/debuggers).
37+
//
38+
// However concretely we never define debug configurations of the type
39+
// 'ada'. All the provided configurations use the type 'cppdbg'. This means
40+
// that once a 'cppdbg' is declared in the launch.json file, this provider
41+
// is no longer called since it is not registered for the type 'cppdbg'.
42+
// Moreover, it is somewhat discouraged to register it for the type
43+
// 'cppdbg' since that type is provided by another extension.
44+
ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('ada', provider));
5245

53-
configurations.push(initialDebugConfiguration);
46+
// TODO it is also possible to register another provider with trigger kind
47+
// 'Dynamic', however the role of such a provider is unclear. In practical
48+
// experiments it ends up never being called. The above provider is enough
49+
// to make it possible to launch debug sessions without a launch.json.
5450

55-
await workspaceConfig.update(
56-
'launch.configurations',
57-
configurations,
58-
vscode.ConfigurationTarget.Workspace
59-
);
60-
}
6151
return provider;
6252
}
6353
/**
64-
* Initialize the GDB debug configuration for an executable,
65-
* either program or command must be specified
54+
* Initialize a debug configuration based on 'cppdbg' for the given executable
55+
* if specified. Otherwise the program field includes
56+
* ${command:ada.askForProgram} to prompt the User for an executable to debug.
57+
*
6658
* @param program - the executable to debug (optional)
67-
* @param command - the command to collect the program to debug (optional)
6859
* @returns an AdaConfig
6960
*/
70-
function initializeConfig(program?: string, command?: string): AdaConfig {
71-
// Get the executable name from the relative path
61+
function initializeConfig(program?: string): AdaConfig {
62+
// TODO it would be nice if this and the package.json configuration snippet
63+
// were the same.
7264
const config: AdaConfig = {
7365
type: 'cppdbg',
74-
name: 'Ada Config',
66+
name: 'Ada: Debugger Launch',
7567
request: 'launch',
7668
targetArchitecture: process.arch,
7769
cwd: '${workspaceFolder}',
78-
program: 'Ada executable to debug',
70+
program: '${workspaceFolder}/${command:ada.askForProgram}',
7971
stopAtEntry: false,
8072
externalConsole: false,
8173
args: [],
8274
MIMode: 'gdb',
83-
// preLaunchTask: 'gpr: Build Executable for File ' + main_name,
8475
preLaunchTask: 'ada: Build current project',
8576
setupCommands: setupCmd,
8677
};
87-
if (command) {
88-
config.name = 'Debug Ada';
89-
config.program = command;
90-
} else if (program) {
78+
79+
if (program) {
9180
const name = path.basename(program);
92-
config.name = 'Debug executable ' + name;
81+
config.name = 'Ada: Debug executable - ' + name;
9382
config.program = program;
9483
}
84+
9585
return config;
9686
}
9787

@@ -102,19 +92,23 @@ export class AdaDebugConfigProvider implements vscode.DebugConfigurationProvider
10292
constructor(clients: ContextClients) {
10393
this.clients = clients;
10494
}
105-
// Provider
95+
10696
async provideDebugConfigurations(
10797
folder: vscode.WorkspaceFolder | undefined,
10898
_token?: vscode.CancellationToken | undefined
10999
): Promise<vscode.DebugConfiguration[]> {
110-
// provide a non-existent debug/launch configuration
111-
const config: vscode.DebugConfiguration[] = [];
100+
// This method is called when no launch.json exists. The provider
101+
// should return a set of configurations to initialize the launch.json
102+
// file with.
103+
const configs: vscode.DebugConfiguration[] = [];
104+
112105
if (_token?.isCancellationRequested) {
113-
return [];
106+
return Promise.reject('Cancelled');
114107
}
108+
115109
if (folder != undefined) {
110+
// Offer a list of known Mains from the project
116111
const execs = await getExecutables(this.clients.adaClient);
117-
// Show the option for the user to choose the executable
118112
const quickpick = execs.map((e) => ({
119113
label: vscode.workspace.asRelativePath(e),
120114
description: 'Generate the associated configuration',
@@ -123,27 +117,48 @@ export class AdaDebugConfigProvider implements vscode.DebugConfigurationProvider
123117
placeHolder: 'Select a program to debug',
124118
});
125119
if (selectedProgram) {
126-
const configuration = initializeConfig(selectedProgram.label);
127-
config.push(configuration);
120+
// The cppdbg debug configuration exepects the executable to be
121+
// a full path rather than a path relative to the specified
122+
// cwd. That is why we include ${workspaceFolder}.
123+
const configuration = initializeConfig(
124+
`\${workspaceFolder}/${selectedProgram.label}`
125+
);
126+
configs.push(configuration);
127+
} else {
128+
return Promise.reject('Cancelled');
128129
}
129-
return config;
130-
} else {
131-
return config;
132130
}
131+
132+
return configs;
133133
}
134134

135135
async resolveDebugConfiguration(
136136
_folder: vscode.WorkspaceFolder | undefined,
137137
debugConfiguration: vscode.DebugConfiguration,
138138
_token?: vscode.CancellationToken | undefined
139139
): Promise<vscode.DebugConfiguration | undefined> {
140-
// resolve a incompleted debug/launch configuration
140+
// This method is called when a debug session is being started. The
141+
// debug configuration either comes from the launch.json file, or is
142+
// empty when no launch.json exists.
143+
141144
if (_token?.isCancellationRequested) {
142145
return undefined;
143146
}
147+
144148
if (debugConfiguration.request == 'launch') {
149+
// When the given debug configuration has its fields set, it means
150+
// that the debug configuration is coming from a launch.json file
151+
// and we don't want to alter it. Concretely this never occurs
152+
// because we register this provider for the debugger type 'ada'
153+
// which we never create in launch.json files. Instead we always
154+
// create 'cppdbg' configurations which never go through this
155+
// provider.
145156
return debugConfiguration;
146157
}
158+
159+
// We are operating without a launch.json. So we try to determine the
160+
// program to debug dynamically. If the current editor matches one of
161+
// the Mains of the project, then debug the corresponding executable.
147162
const file = vscode.window.activeTextEditor?.document.uri.path;
148163
if (file != undefined) {
149164
const mains = await getMains(this.clients.adaClient);
@@ -154,33 +169,44 @@ export class AdaDebugConfigProvider implements vscode.DebugConfigurationProvider
154169
return config;
155170
}
156171
}
157-
const quickpick = mains.map((e) => ({
158-
label: vscode.workspace.asRelativePath(e),
159-
description: 'Run & Debug',
160-
main: e,
161-
}));
162-
const selectedProgram = await vscode.window.showQuickPick(quickpick, {
163-
placeHolder: 'Select a main file',
164-
});
165-
if (selectedProgram) {
166-
const index = mains.indexOf(selectedProgram.main);
167-
const configuration = initializeConfig(execs[index]);
168-
return configuration;
169-
}
170172
}
173+
174+
// There is no current file or it matches no known Main of the project,
175+
// so we offer all Main in a QuickPicker for the user to choose from.
176+
const quickpick = execs.map((e) => ({
177+
label: vscode.workspace.asRelativePath(e),
178+
description: 'Run & Debug',
179+
fullPath: e,
180+
}));
181+
const selectedProgram = await vscode.window.showQuickPick(quickpick, {
182+
placeHolder: 'Select an executable to debug',
183+
});
184+
if (selectedProgram) {
185+
// This is an in-memory configuration that will not be stored. It's
186+
// okay to use the full path directly instead of using
187+
// ${workspaceFolder}.
188+
const configuration = initializeConfig(selectedProgram.fullPath);
189+
return configuration;
190+
}
191+
171192
return undefined;
172193
}
173194

174195
/**
175-
* Resolves the program path fron the 'Debug Ada' default configuration
176-
* @returns the executable path to debug
196+
* Consults the project for a list of Mains. If only one is defined, it is
197+
* returned immediately. If multiple ones are defines, a QuickPicker is
198+
* given to the User to choose and executable to debug or to specify in a
199+
* debug configuration.
200+
*
201+
* @returns the path of the executable to debug relative to the workspace
177202
*/
178-
async initDebugCmd(): Promise<string | undefined> {
203+
async askForProgram(): Promise<string | undefined> {
179204
const file = vscode.window.activeTextEditor?.document.uri.path;
180205
const mains = await getMains(this.clients.adaClient);
181206
const execs = await getExecutables(this.clients.adaClient);
182207

183-
if (mains.length == 1) return execs[0];
208+
if (execs.length == 1) return vscode.workspace.asRelativePath(execs[0]);
209+
184210
if (file != undefined) {
185211
for (let i = 0; i < mains.length; i++) {
186212
if (file == mains[i]) {
@@ -198,8 +224,9 @@ export class AdaDebugConfigProvider implements vscode.DebugConfigurationProvider
198224
});
199225
if (selectedProgram) {
200226
const index = mains.indexOf(selectedProgram.main);
201-
return execs[index];
227+
return vscode.workspace.asRelativePath(execs[index]);
202228
}
229+
203230
return undefined;
204231
}
205232
}

0 commit comments

Comments
 (0)