Skip to content

Commit 0075e89

Browse files
author
automatic-merge
committed
Merge remote branch 'origin/master' into edge
2 parents 6752d74 + e2e885a commit 0075e89

File tree

9 files changed

+483
-108
lines changed

9 files changed

+483
-108
lines changed

integration/vscode/ada/src/AdaCodeLensProvider.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ import {
1111
commands,
1212
Uri,
1313
} from 'vscode';
14-
import { CMD_BUILD_AND_DEBUG_MAIN, CMD_BUILD_AND_RUN_MAIN } from './commands';
15-
import { getMains, getSymbols } from './helpers';
14+
import { CMD_BUILD_AND_DEBUG_MAIN, CMD_BUILD_AND_RUN_MAIN, CMD_SPARK_PROVE_SUBP } from './commands';
15+
import { envHasExec, getMains, getSymbols } from './helpers';
1616

1717
export class AdaCodeLensProvider implements CodeLensProvider {
18-
static readonly ENABLE_SPARK_CODELENS = false;
19-
2018
onDidChangeCodeLenses?: Event<void> | undefined;
2119
provideCodeLenses(
2220
document: TextDocument,
@@ -73,7 +71,7 @@ export class AdaCodeLensProvider implements CodeLensProvider {
7371
});
7472

7573
let res2;
76-
if (AdaCodeLensProvider.ENABLE_SPARK_CODELENS) {
74+
if (envHasExec('gnatprove')) {
7775
/**
7876
* This is tentative deactivated code in preparation of SPARK support.
7977
*/
@@ -88,10 +86,11 @@ export class AdaCodeLensProvider implements CodeLensProvider {
8886
if (token?.isCancellationRequested) {
8987
throw new CancellationError();
9088
}
91-
// TODO make SPARK codelenses conditional to the availability of SPARK on PATH
92-
return new CodeLens(f.range, {
93-
title: '$(play-circle) Prove',
94-
command: 'TODO',
89+
90+
return new CodeLens(f.selectionRange, {
91+
title: '$(check) Prove',
92+
command: CMD_SPARK_PROVE_SUBP,
93+
arguments: [document.uri, f.selectionRange],
9594
});
9695
});
9796
});

integration/vscode/ada/src/ExtensionState.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ import { Disposable, ExecuteCommandRequest, LanguageClient } from 'vscode-langua
33
import { AdaCodeLensProvider } from './AdaCodeLensProvider';
44
import { createClient } from './clients';
55
import { AdaInitialDebugConfigProvider, initializeDebugging } from './debugConfigProvider';
6+
import { logger } from './extension';
67
import { GnatTaskProvider } from './gnatTaskProvider';
78
import { initializeTesting } from './gnattest';
89
import { GprTaskProvider } from './gprTaskProvider';
910
import { TERMINAL_ENV_SETTING_NAME } from './helpers';
10-
import { registerTaskProviders } from './taskProviders';
11-
import { logger } from './extension';
11+
import {
12+
SimpleTaskProvider,
13+
TASK_TYPE_ADA,
14+
TASK_TYPE_SPARK,
15+
createAdaTaskProvider,
16+
createSparkTaskProvider,
17+
} from './taskProviders';
1218

1319
/**
1420
* This class encapsulates all state that should be maintained throughout the
@@ -45,6 +51,9 @@ export class ExtensionState {
4551
cachedExecutables: string[] | undefined;
4652
cachedAlireTomls: vscode.Uri[] | undefined;
4753

54+
private adaTaskProvider?: SimpleTaskProvider;
55+
private sparkTaskProvider?: SimpleTaskProvider;
56+
4857
public clearALSCache() {
4958
this.cachedProjectFile = undefined;
5059
this.cachedObjectDir = undefined;
@@ -89,13 +98,18 @@ export class ExtensionState {
8998
};
9099

91100
public registerTaskProviders = (): void => {
101+
this.adaTaskProvider = createAdaTaskProvider();
102+
this.sparkTaskProvider = createSparkTaskProvider();
103+
92104
this.registeredTaskProviders = [
93105
vscode.tasks.registerTaskProvider(GnatTaskProvider.gnatType, new GnatTaskProvider()),
94106
vscode.tasks.registerTaskProvider(
95107
GprTaskProvider.gprTaskType,
96108
new GprTaskProvider(this.adaClient)
97109
),
98-
].concat(registerTaskProviders());
110+
vscode.tasks.registerTaskProvider(TASK_TYPE_ADA, this.adaTaskProvider),
111+
vscode.tasks.registerTaskProvider(TASK_TYPE_SPARK, this.sparkTaskProvider),
112+
];
99113
};
100114

101115
public unregisterTaskProviders = (): void => {
@@ -215,4 +229,13 @@ export class ExtensionState {
215229

216230
return this.cachedAlireTomls;
217231
}
232+
233+
/**
234+
*
235+
* @returns the SPARK task provider which can be useful for resolving tasks
236+
* created on the fly, e.g. when running SPARK CodeLenses.
237+
*/
238+
public getSparkTaskProvider() {
239+
return this.sparkTaskProvider;
240+
}
218241
}

integration/vscode/ada/src/commands.ts

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import assert from 'assert';
22
import { existsSync } from 'fs';
3+
import { basename } from 'path';
34
import * as vscode from 'vscode';
45
import { SymbolKind } from 'vscode';
56
import { Disposable } from 'vscode-jsonrpc';
@@ -10,10 +11,13 @@ import { adaExtState, logger, mainOutputChannel } from './extension';
1011
import { findAdaMain, getProjectFileRelPath, getSymbols } from './helpers';
1112
import {
1213
SimpleTaskDef,
14+
TASK_PROVE_SUPB_PLAIN_NAME,
15+
TASK_TYPE_SPARK,
1316
findBuildAndRunTask,
1417
getBuildAndRunTasks,
1518
getConventionalTaskLabel,
1619
isFromWorkspace,
20+
workspaceTasksFirst,
1721
} from './taskProviders';
1822

1923
/**
@@ -50,6 +54,7 @@ export const CMD_GET_PROJECT_FILE = 'ada.getProjectFile';
5054

5155
export const CMD_SPARK_LIMIT_SUBP_ARG = 'ada.spark.limitSubpArg';
5256
export const CMD_SPARK_LIMIT_REGION_ARG = 'ada.spark.limitRegionArg';
57+
export const CMD_SPARK_PROVE_SUBP = 'ada.spark.proveSubprogram';
5358

5459
export function registerCommands(context: vscode.ExtensionContext, clients: ExtensionState) {
5560
context.subscriptions.push(vscode.commands.registerCommand('ada.otherFile', otherFileHandler));
@@ -125,6 +130,9 @@ export function registerCommands(context: vscode.ExtensionContext, clients: Exte
125130
context.subscriptions.push(
126131
vscode.commands.registerCommand(CMD_SPARK_LIMIT_REGION_ARG, sparkLimitRegionArg)
127132
);
133+
context.subscriptions.push(
134+
vscode.commands.registerCommand(CMD_SPARK_PROVE_SUBP, sparkProveSubprogram)
135+
);
128136
}
129137
/**
130138
* Add a subprogram box above the subprogram enclosing the cursor's position, if any.
@@ -640,8 +648,8 @@ export async function sparkLimitSubpArg(): Promise<string[]> {
640648
return getEnclosingSymbol(vscode.window.activeTextEditor, [vscode.SymbolKind.Function]).then(
641649
(Symbol) => {
642650
if (Symbol) {
643-
const subprogram_line: string = (Symbol.range.start.line + 1).toString();
644-
return [`--limit-subp=\${fileBasename}:${subprogram_line}`];
651+
const range = Symbol.range;
652+
return [getLimitSubpArg('${fileBasename}', range)];
645653
} else {
646654
/**
647655
* If we can't find a subprogram, we use the VS Code predefined
@@ -658,6 +666,19 @@ export async function sparkLimitSubpArg(): Promise<string[]> {
658666
);
659667
}
660668

669+
/**
670+
*
671+
* @param filename - a filename
672+
* @param range - a {@link vscode.Range}
673+
* @returns the --limit-subp `gnatprove` CLI argument corresponding to the given
674+
* arguments. Note that lines and columns in {@link vscode.Range}es are
675+
* zero-based while the `gnatprove` convention is one-based. This function does
676+
* the conversion.
677+
*/
678+
function getLimitSubpArg(filename: string, range: vscode.Range) {
679+
return `--limit-subp=${filename}:${range.start.line + 1}`;
680+
}
681+
661682
/**
662683
* @returns the gnatprove `--limit-region=file:from:to` argument corresponding
663684
* to the current editor's selection.
@@ -731,3 +752,71 @@ export async function getEnclosingSymbol(
731752

732753
return null;
733754
}
755+
756+
/**
757+
* Command corresponding to the 'Prove' CodeLens provided on subprograms.
758+
*
759+
* It is implemented by fetching the 'Prove subbprogram' task and using it as a
760+
* template such that the User can customize the task to impact the CodeLens.
761+
*/
762+
async function sparkProveSubprogram(
763+
uri: vscode.Uri,
764+
range: vscode.Range
765+
): Promise<vscode.TaskExecution> {
766+
/**
767+
* Get the 'Prove subprogram' task. Prioritize workspace tasks so that User
768+
* customization of the task takes precedence.
769+
*/
770+
const task = (await vscode.tasks.fetchTasks({ type: TASK_TYPE_SPARK }))
771+
.sort(workspaceTasksFirst)
772+
.find(
773+
(t) =>
774+
getConventionalTaskLabel(t) == `${TASK_TYPE_SPARK}: ${TASK_PROVE_SUPB_PLAIN_NAME}`
775+
);
776+
assert(task);
777+
778+
/**
779+
* Create a copy of the task.
780+
*/
781+
const newTask = new vscode.Task(
782+
{ ...task.definition },
783+
task.scope ?? vscode.TaskScope.Workspace,
784+
task.name,
785+
task.source,
786+
undefined,
787+
task.problemMatchers
788+
);
789+
790+
/**
791+
* Replace the subp-region argument based on the parameter given to the
792+
* command.
793+
*/
794+
const taskDef = newTask.definition as SimpleTaskDef;
795+
assert(taskDef.args);
796+
const regionArg = '${command:ada.spark.limitSubpArg}';
797+
const regionArgIdx = taskDef.args.findIndex((arg) => arg == regionArg);
798+
if (regionArgIdx >= 0) {
799+
const fileBasename = basename(uri.fsPath);
800+
taskDef.args[regionArgIdx] = getLimitSubpArg(fileBasename, range);
801+
/**
802+
* Change the task name accordingly, otherwise all invocations appear
803+
* with the same name in the task history.
804+
*/
805+
newTask.name = `${task.name} - ${fileBasename}:${range.start.line + 1}`;
806+
} else {
807+
throw Error(
808+
`Task '${getConventionalTaskLabel(task)}' is missing a '${regionArg}' argument`
809+
);
810+
}
811+
812+
/**
813+
* Resolve the task.
814+
*/
815+
const resolvedTask = await adaExtState.getSparkTaskProvider()?.resolveTask(newTask);
816+
assert(resolvedTask);
817+
818+
/**
819+
* Execute the task.
820+
*/
821+
return await vscode.tasks.executeTask(resolvedTask);
822+
}

integration/vscode/ada/src/helpers.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import winston from 'winston';
2424
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2525
import { ExtensionState } from './ExtensionState';
2626
import { adaExtState, logger } from './extension';
27+
import { existsSync } from 'fs';
2728

2829
/**
2930
* Substitue any variable reference present in the given string. VS Code
@@ -436,3 +437,46 @@ export function getSymbols(
436437
export function escapeRegExp(text: string) {
437438
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
438439
}
440+
441+
/**
442+
*
443+
* @param execName - the name of an executable (without the extension) to find
444+
* using the PATH environment variable.
445+
* @returns `true` if the executable is found, `false` otherwise.
446+
*/
447+
export function envHasExec(execName: string): boolean {
448+
const exePath: string | undefined = which(execName);
449+
450+
return exePath != undefined;
451+
}
452+
453+
/**
454+
* Finds the path to an executable using the PATH environment variable.
455+
*
456+
* On Windows, the extension of the executable does not need to be provided. The
457+
* env variable PATHEXT is used to consider all applicable extensions (e.g.
458+
* .exe, .cmd).
459+
*
460+
* @param execName - name of executable to find using PATH environment variable.
461+
* @returns the full path to the executable if found, otherwise `undefined`
462+
*/
463+
export function which(execName: string) {
464+
const env = { ...process.env };
465+
setTerminalEnvironment(env);
466+
const pathVal = env.PATH;
467+
const paths = pathVal?.split(path.delimiter);
468+
const exeExtensions =
469+
process.platform == 'win32'
470+
? /**
471+
* On Windows use a default list of extensions in case PATHEXT is
472+
* not set.
473+
*/
474+
env.PATHEXT?.split(path.delimiter) ?? ['.exe', '.cmd', '.bat']
475+
: [''];
476+
477+
const exePath: string | undefined = paths
478+
?.flatMap((p) => exeExtensions.map((ext) => path.join(p, execName + ext)))
479+
.find(existsSync);
480+
481+
return exePath;
482+
}

0 commit comments

Comments
 (0)