Skip to content

Commit f6d7795

Browse files
author
automatic-merge
committed
Merge remote branch 'origin/master' into edge
2 parents af8b78e + cb05ec1 commit f6d7795

File tree

6 files changed

+316
-31
lines changed

6 files changed

+316
-31
lines changed

integration/vscode/ada/package.json

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,10 @@
476476
},
477477
"args": {
478478
"type": "array",
479-
"description": "Extra command line arguments"
479+
"items": {
480+
"type": "string"
481+
},
482+
"description": "Extra build command line arguments"
480483
}
481484
},
482485
"oneOf": [
@@ -535,6 +538,13 @@
535538
"executable": {
536539
"type": "string",
537540
"description": "Path to main executable file (if it cannot be computed automatically)"
541+
},
542+
"mainArgs": {
543+
"type": "array",
544+
"items": {
545+
"type": "string"
546+
},
547+
"description": "Arguments passed to the main executable invocation"
538548
}
539549
},
540550
"additionalProperties": false
@@ -574,6 +584,9 @@
574584
},
575585
"args": {
576586
"type": "array",
587+
"items": {
588+
"type": "string"
589+
},
577590
"description": "Extra command line arguments"
578591
}
579592
},
@@ -630,25 +643,38 @@
630643
},
631644
{
632645
"command": "als-reload-project",
633-
"title": "Ada: Reload project"
646+
"title": "Ada: Reload project",
647+
"icon": "$(refresh)"
634648
},
635649
{
636650
"command": "ada.subprogramBox",
637651
"title": "Ada: Add subprogram box"
638652
},
639653
{
640654
"command": "ada.showExtensionOutput",
641-
"title": "Ada: Show Output",
655+
"title": "Ada: Show extension output",
642656
"when": "ADA_PROJECT_CONTEXT"
643657
},
644658
{
645659
"command": "ada.showAdaLSOutput",
646-
"title": "Ada: Show Ada Language Server Output",
660+
"title": "Ada: Show Ada Language Server output",
647661
"when": "ADA_PROJECT_CONTEXT"
648662
},
649663
{
650664
"command": "ada.showGprLSOutput",
651-
"title": "Ada: Show GPR Language Server Output",
665+
"title": "Ada: Show GPR Language Server output",
666+
"when": "ADA_PROJECT_CONTEXT"
667+
},
668+
{
669+
"command": "ada.runMainAsk",
670+
"title": "Ada: Build and run project main...",
671+
"icon": "$(run-all)",
672+
"when": "ADA_PROJECT_CONTEXT"
673+
},
674+
{
675+
"command": "ada.runMainLast",
676+
"title": "Ada: Build and run last used main",
677+
"icon": "$(run)",
652678
"when": "ADA_PROJECT_CONTEXT"
653679
}
654680
],
@@ -674,6 +700,18 @@
674700
"command": "ada.subprogramBox",
675701
"when": "ADA_PROJECT_CONTEXT"
676702
}
703+
],
704+
"editor/title/run": [
705+
{
706+
"command": "ada.runMainLast",
707+
"when": "editorLangId == ada",
708+
"group": "navigation@0"
709+
},
710+
{
711+
"command": "ada.runMainAsk",
712+
"when": "editorLangId == ada",
713+
"group": "navigation@1"
714+
}
677715
]
678716
},
679717
"walkthroughs": [

integration/vscode/ada/src/clients.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { logger } from './extension';
1111
import GnatTaskProvider from './gnatTaskProvider';
1212
import GprTaskProvider from './gprTaskProvider';
13-
import { logErrorAndThrow } from './helpers';
13+
import { logErrorAndThrow, setCustomEnvironment } from './helpers';
1414
import { registerTaskProviders } from './taskProviders';
1515

1616
export class ContextClients {
@@ -145,15 +145,15 @@ function createClient(
145145

146146
logger.debug(`Using ALS at: ${serverExecPath}`);
147147

148-
// The debug options for the server
149-
// let debugOptions = { execArgv: [] };
150-
// If the extension is launched in debug mode then the debug server options are used
151-
// Otherwise the run options are used
148+
// Copy this process's environment
149+
const serverEnv: NodeJS.ProcessEnv = { ...process.env };
150+
// Set custom environment
151+
setCustomEnvironment(serverEnv);
152152

153153
// Options to control the server
154154
const serverOptions: ServerOptions = {
155-
run: { command: serverExecPath, args: extra },
156-
debug: { command: serverExecPath, args: extra },
155+
run: { command: serverExecPath, args: extra, options: { env: serverEnv } },
156+
debug: { command: serverExecPath, args: extra, options: { env: serverEnv } },
157157
};
158158

159159
// Options to control the language client

integration/vscode/ada/src/commands.ts

Lines changed: 232 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import assert from 'assert';
2+
import { existsSync } from 'fs';
13
import * as vscode from 'vscode';
24
import { SymbolKind } from 'vscode';
5+
import { Disposable } from 'vscode-jsonrpc';
36
import { ContextClients } from './clients';
47
import { getOrAskForProgram } from './debugConfigProvider';
58
import { mainOutputChannel } from './extension';
6-
import { getEnclosingSymbol } from './taskProviders';
9+
import { getProjectFileRelPath } from './helpers';
10+
import { CustomTaskDefinition, getEnclosingSymbol } from './taskProviders';
711

812
export function registerCommands(context: vscode.ExtensionContext, clients: ContextClients) {
913
context.subscriptions.push(
@@ -26,6 +30,14 @@ export function registerCommands(context: vscode.ExtensionContext, clients: Cont
2630
)
2731
);
2832

33+
context.subscriptions.push(
34+
vscode.commands.registerCommand('ada.runMainLast', () => runMainLast())
35+
);
36+
37+
context.subscriptions.push(
38+
vscode.commands.registerCommand('ada.runMainAsk', () => runMainAsk())
39+
);
40+
2941
// This is a hidden command that gets called in the default debug
3042
// configuration snippet that gets offered in the launch.json file.
3143
context.subscriptions.push(
@@ -89,3 +101,222 @@ async function addSupbrogramBoxCommand() {
89101
}
90102
});
91103
}
104+
105+
let lastUsedTaskInfo: { source: string; name: string } | undefined;
106+
107+
/**
108+
* If a task was previously run through the commands `ada.runMainAsk` or
109+
* `ada.runMainLast`, re-run the same task. If not, defer to {@link runMainAsk}
110+
* to ask the User to select a task to run.
111+
*
112+
* @returns the TaskExecution corresponding to the task.
113+
*/
114+
async function runMainLast() {
115+
const buildAndRunTasks = await getBuildAndRunTasks();
116+
if (lastUsedTaskInfo) {
117+
const matchingTasks = buildAndRunTasks.filter(matchesLastUsedTask);
118+
assert(matchingTasks.length <= 1);
119+
const lastTask = matchingTasks.length == 1 ? matchingTasks[0] : undefined;
120+
if (lastTask) {
121+
return await vscode.tasks.executeTask(lastTask);
122+
}
123+
}
124+
125+
// No task was run so far, or the last one run no longer exists
126+
return runMainAsk();
127+
}
128+
129+
/**
130+
*
131+
* @param t - a task
132+
* @returns `true` if the given task matches the last executed task
133+
*/
134+
function matchesLastUsedTask(t: vscode.Task): boolean {
135+
return t.source == lastUsedTaskInfo?.source && t.name == lastUsedTaskInfo?.name;
136+
}
137+
138+
/**
139+
*
140+
* @param task - a task
141+
* @returns the label to be displayed to the user in the quick picker for that task
142+
*/
143+
function getTaskLabel(task: vscode.Task): string {
144+
return isFromWorkspace(task) ? `(From Workspace) ${task.name}` : getConventionalTaskLabel(task);
145+
}
146+
147+
/**
148+
*
149+
* @param task - a task
150+
* @returns the label typically generated for that task by vscode. For tasks not
151+
* defined explicitely in the workspace, this is `ada: <task name>`. For tasks
152+
* defined in the workspace simply return the name which should already include
153+
* the convention.
154+
*/
155+
function getConventionalTaskLabel(task: vscode.Task): string {
156+
return isFromWorkspace(task) ? task.name : `${task.source}: ${task.name}`;
157+
}
158+
159+
/**
160+
*
161+
* @param task - a task
162+
* @returns `true` if the task is defined explicitely in the workspace's tasks.json
163+
*/
164+
function isFromWorkspace(task: vscode.Task) {
165+
return task.source == 'Workspace';
166+
}
167+
168+
interface TaskQuickPickItem extends vscode.QuickPickItem {
169+
task: vscode.Task;
170+
}
171+
172+
/**
173+
* Propose to the User a list of build and run tasks, one for each main defined
174+
* in the project.
175+
*
176+
* Tasks defined explicitely in the workspace are identified as such in the
177+
* offered list and proposed first.
178+
*
179+
* The User can choose either to run the task as is, or click the secondary
180+
* button to add the task to tasks.json (if not already there) and configure it
181+
* there.
182+
*/
183+
async function runMainAsk() {
184+
function createQuickPickItem(task: vscode.Task): TaskQuickPickItem {
185+
return {
186+
// Mark the last used task with a leading star
187+
label: (matchesLastUsedTask(task) ? '$(star) ' : '') + getTaskLabel(task),
188+
// Add a description to the last used task
189+
description: matchesLastUsedTask(task) ? 'last used' : undefined,
190+
task: task,
191+
// Add a button allowing to configure the task in tasks.json
192+
buttons: [
193+
{
194+
iconPath: new vscode.ThemeIcon('gear'),
195+
tooltip: 'Configure task in tasks.json, e.g. to add main arguments',
196+
},
197+
],
198+
};
199+
}
200+
const adaTasksMain = await getBuildAndRunTasks();
201+
202+
if (adaTasksMain.length > 0) {
203+
const tasksFromWorkspace = adaTasksMain.filter(isFromWorkspace);
204+
const tasksFromExtension = adaTasksMain.filter((v) => !isFromWorkspace(v));
205+
206+
// Propose workspace-configured tasks first
207+
const quickPickItems: TaskQuickPickItem[] = tasksFromWorkspace.map(createQuickPickItem);
208+
209+
if (tasksFromWorkspace.length > 0) {
210+
// Use a separator between workspace tasks and implicit tasks provided by the extension
211+
quickPickItems.push({
212+
kind: vscode.QuickPickItemKind.Separator,
213+
label: '',
214+
// Use any valid task to avoid allowing 'undefined' in the type declaration
215+
task: adaTasksMain[0],
216+
});
217+
}
218+
219+
quickPickItems.push(...tasksFromExtension.map(createQuickPickItem));
220+
221+
// Create the quick picker
222+
const qp = vscode.window.createQuickPick<TaskQuickPickItem>();
223+
qp.items = qp.items.concat(quickPickItems);
224+
225+
// Array for event handlers to be disposed after the quick picker is disposed
226+
const disposables: Disposable[] = [];
227+
try {
228+
const choice: TaskQuickPickItem | undefined = await new Promise((resolve) => {
229+
// Add event handlers to the quick picker
230+
disposables.push(
231+
qp.onDidChangeSelection((items) => {
232+
// When the User selects an option, resolve the Promise
233+
// and hide the quick picker
234+
const item = items[0];
235+
if (item) {
236+
resolve(item);
237+
qp.hide();
238+
}
239+
}),
240+
qp.onDidHide(() => {
241+
resolve(undefined);
242+
}),
243+
qp.onDidTriggerItemButton(async (e) => {
244+
// When the User selects the secondary button, find or
245+
// create the task in the tasks.json file
246+
247+
// There's only one button, so let's assert that
248+
assert(e.item.buttons && e.item.buttons[0]);
249+
assert(e.button == e.item.buttons[0]);
250+
251+
const tasks: vscode.TaskDefinition[] =
252+
vscode.workspace.getConfiguration('tasks').get('tasks') ?? [];
253+
254+
// Check if the task is already defined in tasks.json
255+
if (!tasks.find((t) => t?.label == getConventionalTaskLabel(e.item.task))) {
256+
// If the task doesn't exist, create it
257+
258+
// Copy the definition and add a label
259+
const def: CustomTaskDefinition = {
260+
...(e.item.task.definition as CustomTaskDefinition),
261+
label: getConventionalTaskLabel(e.item.task),
262+
};
263+
tasks.push(def);
264+
await vscode.workspace.getConfiguration().update('tasks.tasks', tasks);
265+
}
266+
267+
// Then open tasks.json in an editor
268+
if (vscode.workspace.workspaceFolders) {
269+
const tasksUri = vscode.workspace.workspaceFolders
270+
.map((ws) => vscode.Uri.joinPath(ws.uri, '.vscode', 'tasks.json'))
271+
.find((v) => existsSync(v.fsPath));
272+
if (tasksUri) {
273+
await vscode.window.showTextDocument(tasksUri);
274+
}
275+
}
276+
resolve(undefined);
277+
qp.hide();
278+
})
279+
);
280+
281+
// Show the quick picker
282+
qp.show();
283+
});
284+
285+
if (choice) {
286+
// If a task was selected, mark it as the last executed task and
287+
// run it
288+
lastUsedTaskInfo = {
289+
source: choice.task.source,
290+
name: choice.task.name,
291+
};
292+
return await vscode.tasks.executeTask(choice.task);
293+
} else {
294+
return undefined;
295+
}
296+
} finally {
297+
disposables.forEach((d) => d.dispose());
298+
}
299+
} else {
300+
void vscode.window.showWarningMessage(
301+
`There are no Mains defined in the workspace project ${await getProjectFileRelPath()}`
302+
);
303+
return undefined;
304+
}
305+
}
306+
307+
/**
308+
*
309+
* @returns Array of tasks of type `ada` and kind `buildAndRunMain`. This
310+
* includes tasks automatically provided by the extension as well as
311+
* user-defined tasks in tasks.json.
312+
*/
313+
async function getBuildAndRunTasks() {
314+
return await vscode.tasks
315+
.fetchTasks({ type: 'ada' })
316+
.then((tasks) =>
317+
tasks.filter(
318+
(t) =>
319+
(t.definition as CustomTaskDefinition).configuration.kind == 'buildAndRunMain'
320+
)
321+
);
322+
}

0 commit comments

Comments
 (0)