Skip to content

Commit c8ed8b2

Browse files
Add code lens for quick evaluation (#1035)
* Add code lens for quick eval command * Ensure commented out predicates do not have code lens * Improve conditional check for commented out predicate detection * Refactor regex * Move comment check to eliminate evaluating regex more than once Co-authored-by: marcnjaramillo <[email protected]>
1 parent 58f4a82 commit c8ed8b2

File tree

4 files changed

+88
-16
lines changed

4 files changed

+88
-16
lines changed

extensions/ql-vscode/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [UNRELEASED]
44

5+
- Add a CodeLens to make the Quick Evaluation command more accessible. Click the `Quick Evaluation` prompt above a predicate definition in the editor to evaluate that predicate on its own. [#1035](https://github.com/github/vscode-codeql/pull/1035)
56
- Fix a bug where the _Alerts_ option would show in the results view even if there is no alerts table available. [#1038](https://github.com/github/vscode-codeql/pull/1038)
67

78
## 1.5.8 - 2 December 2021

extensions/ql-vscode/src/extension.ts

+27-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
window as Window,
1212
env,
1313
window,
14-
QuickPickItem
14+
QuickPickItem,
15+
Range
1516
} from 'vscode';
1617
import { LanguageClient } from 'vscode-languageclient';
1718
import * as os from 'os';
@@ -21,6 +22,7 @@ import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
2122

2223
import { AstViewer } from './astViewer';
2324
import * as archiveFilesystemProvider from './archive-filesystem-provider';
25+
import QuickEvalCodeLensProvider from './quickEvalCodeLensProvider';
2426
import { CodeQLCliServer, CliVersionConstraint } from './cli';
2527
import {
2628
CliConfigListener,
@@ -156,6 +158,7 @@ export interface CodeQLExtensionInterface {
156158
* @returns CodeQLExtensionInterface
157159
*/
158160
export async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionInterface | Record<string, never>> {
161+
159162
void logger.log(`Starting ${extensionId} extension`);
160163
if (extension === undefined) {
161164
throw new Error(`Can't find extension ${extensionId}`);
@@ -166,6 +169,9 @@ export async function activate(ctx: ExtensionContext): Promise<CodeQLExtensionIn
166169
await initializeTelemetry(extension, ctx);
167170
languageSupport.install();
168171

172+
const codelensProvider = new QuickEvalCodeLensProvider();
173+
languages.registerCodeLensProvider({ scheme: 'file', language: 'ql' }, codelensProvider);
174+
169175
ctx.subscriptions.push(distributionConfigListener);
170176
const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;
171177
const distributionManager = new DistributionManager(distributionConfigListener, codeQlVersionRange, ctx);
@@ -471,6 +477,7 @@ async function activateWithInstalledDistribution(
471477
progress: ProgressCallback,
472478
token: CancellationToken,
473479
databaseItem: DatabaseItem | undefined,
480+
range?: Range
474481
): Promise<void> {
475482
if (qs !== undefined) {
476483
// If no databaseItem is specified, use the database currently selected in the Databases UI
@@ -485,7 +492,9 @@ async function activateWithInstalledDistribution(
485492
quickEval,
486493
selectedQuery,
487494
progress,
488-
token
495+
token,
496+
undefined,
497+
range
489498
);
490499
const item = qhm.buildCompletedQuery(info);
491500
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
@@ -733,6 +742,22 @@ async function activateWithInstalledDistribution(
733742
cancellable: true
734743
})
735744
);
745+
746+
ctx.subscriptions.push(
747+
commandRunnerWithProgress(
748+
'codeQL.codeLensQuickEval',
749+
async (
750+
progress: ProgressCallback,
751+
token: CancellationToken,
752+
uri: Uri,
753+
range: Range
754+
) => await compileAndRunQuery(true, uri, progress, token, undefined, range),
755+
{
756+
title: 'Running query',
757+
cancellable: true
758+
})
759+
);
760+
736761
ctx.subscriptions.push(
737762
commandRunnerWithProgress('codeQL.quickQuery', async (
738763
progress: ProgressCallback,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
CodeLensProvider,
3+
TextDocument,
4+
CodeLens,
5+
Command,
6+
Range
7+
} from 'vscode';
8+
9+
class QuickEvalCodeLensProvider implements CodeLensProvider {
10+
async provideCodeLenses(document: TextDocument): Promise<CodeLens[]> {
11+
12+
const codeLenses: CodeLens[] = [];
13+
14+
for (let index = 0; index < document.lineCount; index++) {
15+
const textLine = document.lineAt(index);
16+
17+
// Match a predicate signature, including predicate name, parameter list, and opening brace.
18+
// This currently does not match predicates that span multiple lines.
19+
const regex = new RegExp(/(\w+)\s*\([^()]*\)\s*\{/);
20+
21+
const matches = textLine.text.match(regex);
22+
23+
// Make sure that a code lens is not generated for any predicate that is commented out.
24+
if (matches && !(/^\s*\/\//).test(textLine.text)) {
25+
const range: Range = new Range(
26+
textLine.range.start.line, matches.index!,
27+
textLine.range.end.line, matches.index! + 1
28+
);
29+
30+
const command: Command = {
31+
command: 'codeQL.codeLensQuickEval',
32+
title: `Quick Evaluation: ${matches[1]}`,
33+
arguments: [document.uri, range]
34+
};
35+
const codeLens = new CodeLens(range, command);
36+
codeLenses.push(codeLens);
37+
}
38+
}
39+
return codeLenses;
40+
}
41+
}
42+
43+
export default QuickEvalCodeLensProvider;

extensions/ql-vscode/src/run-queries.ts

+17-14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as tmp from 'tmp-promise';
55
import {
66
CancellationToken,
77
ConfigurationTarget,
8+
Range,
89
TextDocument,
910
TextEditor,
1011
Uri,
@@ -332,17 +333,18 @@ async function convertToQlPath(filePath: string): Promise<string> {
332333

333334

334335
/** Gets the selected position within the given editor. */
335-
async function getSelectedPosition(editor: TextEditor): Promise<messages.Position> {
336-
const pos = editor.selection.start;
337-
const posEnd = editor.selection.end;
338-
// Convert from 0-based to 1-based line and column numbers.
339-
return {
340-
fileName: await convertToQlPath(editor.document.fileName),
341-
line: pos.line + 1,
342-
column: pos.character + 1,
343-
endLine: posEnd.line + 1,
344-
endColumn: posEnd.character + 1
345-
};
336+
async function getSelectedPosition(editor: TextEditor, range?: Range): Promise<messages.Position> {
337+
const selectedRange = range || editor.selection;
338+
const pos = selectedRange.start;
339+
const posEnd = selectedRange.end;
340+
// Convert from 0-based to 1-based line and column numbers.
341+
return {
342+
fileName: await convertToQlPath(editor.document.fileName),
343+
line: pos.line + 1,
344+
column: pos.character + 1,
345+
endLine: posEnd.line + 1,
346+
endColumn: posEnd.character + 1
347+
};
346348
}
347349

348350
/**
@@ -490,7 +492,7 @@ type SelectedQuery = {
490492
* @param selectedResourceUri The selected resource when the command was run.
491493
* @param quickEval Whether the command being run is `Quick Evaluation`.
492494
*/
493-
export async function determineSelectedQuery(selectedResourceUri: Uri | undefined, quickEval: boolean): Promise<SelectedQuery> {
495+
export async function determineSelectedQuery(selectedResourceUri: Uri | undefined, quickEval: boolean, range?: Range): Promise<SelectedQuery> {
494496
const editor = window.activeTextEditor;
495497

496498
// Choose which QL file to use.
@@ -544,7 +546,7 @@ export async function determineSelectedQuery(selectedResourceUri: Uri | undefine
544546
// Report an error if we end up in this (hopefully unlikely) situation.
545547
throw new Error('The selected resource for quick evaluation should match the active editor.');
546548
}
547-
quickEvalPosition = await getSelectedPosition(editor);
549+
quickEvalPosition = await getSelectedPosition(editor, range);
548550
quickEvalText = editor.document.getText(editor.selection);
549551
}
550552

@@ -560,13 +562,14 @@ export async function compileAndRunQueryAgainstDatabase(
560562
progress: ProgressCallback,
561563
token: CancellationToken,
562564
templates?: messages.TemplateDefinitions,
565+
range?: Range
563566
): Promise<QueryWithResults> {
564567
if (!db.contents || !db.contents.dbSchemeUri) {
565568
throw new Error(`Database ${db.databaseUri} does not have a CodeQL database scheme.`);
566569
}
567570

568571
// Determine which query to run, based on the selection and the active editor.
569-
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval);
572+
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval, range);
570573

571574
const historyItemOptions: QueryHistoryItemOptions = {};
572575
historyItemOptions.isQuickQuery === isQuickQueryPath(queryPath);

0 commit comments

Comments
 (0)