Skip to content

Commit 00026a7

Browse files
committed
feat: User can see query text at time of execution
Add new command to view the query text in a synthetic, read-only document. Quick eval queries will show the text selected when initially running the query. Quick eval queries where the user has a single caret selection will show the entire line of text.
1 parent 6f935ae commit 00026a7

File tree

4 files changed

+95
-9
lines changed

4 files changed

+95
-9
lines changed

extensions/ql-vscode/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@
226226
"command": "codeQLQueryHistory.showQueryLog",
227227
"title": "Show Query Log"
228228
},
229+
{
230+
"command": "codeQLQueryHistory.showQueryText",
231+
"title": "Show Query Text"
232+
},
229233
{
230234
"command": "codeQLQueryResults.nextPathStep",
231235
"title": "CodeQL: Show Next Step on Path"
@@ -295,6 +299,11 @@
295299
"group": "9_qlCommands",
296300
"when": "view == codeQLQueryHistory"
297301
},
302+
{
303+
"command": "codeQLQueryHistory.showQueryText",
304+
"group": "9_qlCommands",
305+
"when": "view == codeQLQueryHistory"
306+
},
298307
{
299308
"command": "codeQLTests.showOutputDifferences",
300309
"group": "qltest@1",
@@ -355,6 +364,10 @@
355364
"command": "codeQLQueryHistory.showQueryLog",
356365
"when": "false"
357366
},
367+
{
368+
"command": "codeQLQueryHistory.showQueryText",
369+
"when": "false"
370+
},
358371
{
359372
"command": "codeQLQueryHistory.setLabel",
360373
"when": "false"

extensions/ql-vscode/src/query-history.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { QueryHistoryConfig } from './config';
66
import { QueryWithResults } from './run-queries';
77
import * as helpers from './helpers';
88
import { logger } from './logging';
9+
import { URLSearchParams } from 'url';
910

1011
/**
1112
* query-history.ts
@@ -18,9 +19,32 @@ import { logger } from './logging';
1819

1920
export type QueryHistoryItemOptions = {
2021
label?: string; // user-settable label
21-
queryText?: string; // stored query for quick query
22+
queryText?: string; // text of the selected file
23+
isQuickQuery?: boolean;
2224
}
2325

26+
const SHOW_QUERY_TEXT_MSG = `\
27+
////////////////////////////////////////////////////////////////////////////////////
28+
// This is the text of the entire query file when it was executed for this query //
29+
// run. The text or dependent libraries may have changed since then. //
30+
// //
31+
// This buffer is readonly. To re-execute this query, you must open the original //
32+
// query file. //
33+
////////////////////////////////////////////////////////////////////////////////////
34+
35+
`;
36+
37+
const SHOW_QUERY_TEXT_QUICK_EVAL_MSG = `\
38+
////////////////////////////////////////////////////////////////////////////////////
39+
// This is the Quick Eval selection of the query file when it was executed for //
40+
// this query run. The text or dependent libraries may have changed since then. //
41+
// //
42+
// This buffer is readonly. To re-execute this query, you must open the original //
43+
// query file. //
44+
////////////////////////////////////////////////////////////////////////////////////
45+
46+
`;
47+
2448
/**
2549
* Path to icon to display next to a failed query history item.
2650
*/
@@ -137,7 +161,7 @@ export class QueryHistoryManager {
137161
const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(queryHistoryItem.query.program.queryPath));
138162
const editor = await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One);
139163
const queryText = queryHistoryItem.options.queryText;
140-
if (queryText !== undefined) {
164+
if (queryText !== undefined && queryHistoryItem.options.isQuickQuery) {
141165
await editor.edit(edit => edit.replace(textDocument.validateRange(
142166
new vscode.Range(0, 0, textDocument.lineCount, 0)), queryText)
143167
);
@@ -218,6 +242,36 @@ export class QueryHistoryManager {
218242
}
219243
}
220244

245+
async handleShowQueryText(queryHistoryItem: CompletedQuery) {
246+
try {
247+
const queryName = queryHistoryItem.queryName.endsWith('.ql') ? queryHistoryItem.queryName : queryHistoryItem.queryName + '.ql';
248+
const params = new URLSearchParams({
249+
isQuickEval: String(!!queryHistoryItem.query.quickEvalPosition),
250+
queryText: await this.getQueryText(queryHistoryItem)
251+
});
252+
const uri = vscode.Uri.parse(`codeql:${queryHistoryItem.query.queryID}-${queryName}?${params.toString()}`);
253+
const doc = await vscode.workspace.openTextDocument(uri);
254+
await vscode.window.showTextDocument(doc, { preview: false });
255+
} catch (e) {
256+
helpers.showAndLogErrorMessage(e.message);
257+
}
258+
}
259+
260+
async getQueryText(queryHistoryItem: CompletedQuery): Promise<string> {
261+
if (queryHistoryItem.options.queryText) {
262+
return queryHistoryItem.options.queryText;
263+
} else if (queryHistoryItem.query.quickEvalPosition) {
264+
// capture all selected lines
265+
const startLine = queryHistoryItem.query.quickEvalPosition.line;
266+
const endLine = queryHistoryItem.query.quickEvalPosition.endLine;
267+
const textDocument =
268+
await vscode.workspace.openTextDocument(queryHistoryItem.query.quickEvalPosition.fileName);
269+
return textDocument.getText(new vscode.Range(startLine - 1, 0, endLine, 0));
270+
} else {
271+
return '';
272+
}
273+
}
274+
221275
constructor(
222276
ctx: ExtensionContext,
223277
private queryHistoryConfigListener: QueryHistoryConfig,
@@ -240,12 +294,24 @@ export class QueryHistoryManager {
240294
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.removeHistoryItem', this.handleRemoveHistoryItem.bind(this)));
241295
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.setLabel', this.handleSetLabel.bind(this)));
242296
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryLog', this.handleShowQueryLog.bind(this)));
297+
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryText', this.handleShowQueryText.bind(this)));
243298
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.itemClicked', async (item) => {
244299
return this.handleItemClicked(item);
245300
}));
246301
queryHistoryConfigListener.onDidChangeQueryHistoryConfiguration(() => {
247302
this.treeDataProvider.refresh();
248303
});
304+
305+
// displays query text in a read-only document
306+
vscode.workspace.registerTextDocumentContentProvider('codeql', {
307+
provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
308+
const params = new URLSearchParams(uri.query)
309+
310+
return (
311+
JSON.parse(params.get('isQuickEval') || '') ? SHOW_QUERY_TEXT_QUICK_EVAL_MSG : SHOW_QUERY_TEXT_MSG
312+
) + params.get('queryText');
313+
}
314+
});
249315
}
250316

251317
addQuery(info: QueryWithResults): CompletedQuery {

extensions/ql-vscode/src/query-results.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class CompletedQuery implements QueryWithResults {
1414
readonly query: QueryInfo;
1515
readonly result: messages.EvaluationResult;
1616
readonly database: DatabaseInfo;
17-
readonly logFileLocation?: string
17+
readonly logFileLocation?: string;
1818
options: QueryHistoryItemOptions;
1919
dispose: () => void;
2020

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,10 @@ async function getSelectedPosition(editor: vscode.TextEditor): Promise<messages.
239239
// Convert from 0-based to 1-based line and column numbers.
240240
return {
241241
fileName: await convertToQlPath(editor.document.fileName),
242-
line: pos.line + 1, column: pos.character + 1,
243-
endLine: posEnd.line + 1, endColumn: posEnd.character + 1
242+
line: pos.line + 1,
243+
column: pos.character + 1,
244+
endLine: posEnd.line + 1,
245+
endColumn: posEnd.character + 1
244246
};
245247
}
246248

@@ -326,6 +328,7 @@ async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<b
326328
type SelectedQuery = {
327329
queryPath: string;
328330
quickEvalPosition?: messages.Position;
331+
quickEvalText?: string;
329332
};
330333

331334
/**
@@ -382,6 +385,7 @@ export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | u
382385
}
383386

384387
let quickEvalPosition: messages.Position | undefined = undefined;
388+
let quickEvalText: string | undefined = undefined;
385389
if (quickEval) {
386390
if (editor == undefined) {
387391
throw new Error('Can\'t run quick evaluation without an active editor.');
@@ -392,9 +396,10 @@ export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | u
392396
throw new Error('The selected resource for quick evaluation should match the active editor.');
393397
}
394398
quickEvalPosition = await getSelectedPosition(editor);
399+
quickEvalText = editor.document.getText(editor.selection);
395400
}
396401

397-
return { queryPath, quickEvalPosition };
402+
return { queryPath, quickEvalPosition, quickEvalText };
398403
}
399404

400405
export async function compileAndRunQueryAgainstDatabase(
@@ -411,12 +416,14 @@ export async function compileAndRunQueryAgainstDatabase(
411416
}
412417

413418
// Determine which query to run, based on the selection and the active editor.
414-
const { queryPath, quickEvalPosition } = await determineSelectedQuery(selectedQueryUri, quickEval);
419+
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval);
415420

416421
// If this is quick query, store the query text
417422
const historyItemOptions: QueryHistoryItemOptions = {};
418-
if (isQuickQueryPath(queryPath)) {
419-
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
423+
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
424+
historyItemOptions.isQuickQuery === isQuickQueryPath(queryPath);
425+
if (quickEval) {
426+
historyItemOptions.queryText = quickEvalText;
420427
}
421428

422429
// Get the workspace folder paths.

0 commit comments

Comments
 (0)