1
1
import assert from 'assert' ;
2
2
import { existsSync } from 'fs' ;
3
+ import { basename } from 'path' ;
3
4
import * as vscode from 'vscode' ;
4
5
import { SymbolKind } from 'vscode' ;
5
6
import { Disposable } from 'vscode-jsonrpc' ;
@@ -10,10 +11,13 @@ import { adaExtState, logger, mainOutputChannel } from './extension';
10
11
import { findAdaMain , getProjectFileRelPath , getSymbols } from './helpers' ;
11
12
import {
12
13
SimpleTaskDef ,
14
+ TASK_PROVE_SUPB_PLAIN_NAME ,
15
+ TASK_TYPE_SPARK ,
13
16
findBuildAndRunTask ,
14
17
getBuildAndRunTasks ,
15
18
getConventionalTaskLabel ,
16
19
isFromWorkspace ,
20
+ workspaceTasksFirst ,
17
21
} from './taskProviders' ;
18
22
19
23
/**
@@ -50,6 +54,7 @@ export const CMD_GET_PROJECT_FILE = 'ada.getProjectFile';
50
54
51
55
export const CMD_SPARK_LIMIT_SUBP_ARG = 'ada.spark.limitSubpArg' ;
52
56
export const CMD_SPARK_LIMIT_REGION_ARG = 'ada.spark.limitRegionArg' ;
57
+ export const CMD_SPARK_PROVE_SUBP = 'ada.spark.proveSubprogram' ;
53
58
54
59
export function registerCommands ( context : vscode . ExtensionContext , clients : ExtensionState ) {
55
60
context . subscriptions . push ( vscode . commands . registerCommand ( 'ada.otherFile' , otherFileHandler ) ) ;
@@ -125,6 +130,9 @@ export function registerCommands(context: vscode.ExtensionContext, clients: Exte
125
130
context . subscriptions . push (
126
131
vscode . commands . registerCommand ( CMD_SPARK_LIMIT_REGION_ARG , sparkLimitRegionArg )
127
132
) ;
133
+ context . subscriptions . push (
134
+ vscode . commands . registerCommand ( CMD_SPARK_PROVE_SUBP , sparkProveSubprogram )
135
+ ) ;
128
136
}
129
137
/**
130
138
* Add a subprogram box above the subprogram enclosing the cursor's position, if any.
@@ -640,8 +648,8 @@ export async function sparkLimitSubpArg(): Promise<string[]> {
640
648
return getEnclosingSymbol ( vscode . window . activeTextEditor , [ vscode . SymbolKind . Function ] ) . then (
641
649
( Symbol ) => {
642
650
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 ) ] ;
645
653
} else {
646
654
/**
647
655
* If we can't find a subprogram, we use the VS Code predefined
@@ -658,6 +666,19 @@ export async function sparkLimitSubpArg(): Promise<string[]> {
658
666
) ;
659
667
}
660
668
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
+
661
682
/**
662
683
* @returns the gnatprove `--limit-region=file:from:to` argument corresponding
663
684
* to the current editor's selection.
@@ -731,3 +752,71 @@ export async function getEnclosingSymbol(
731
752
732
753
return null ;
733
754
}
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
+ }
0 commit comments