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.
This commit is contained in:
Andrew Eisenberg 2020-03-23 14:00:39 -07:00 коммит произвёл Andrew Eisenberg
Родитель 6f935ae6e4
Коммит 00026a7727
4 изменённых файлов: 95 добавлений и 9 удалений

Просмотреть файл

@ -226,6 +226,10 @@
"command": "codeQLQueryHistory.showQueryLog",
"title": "Show Query Log"
},
{
"command": "codeQLQueryHistory.showQueryText",
"title": "Show Query Text"
},
{
"command": "codeQLQueryResults.nextPathStep",
"title": "CodeQL: Show Next Step on Path"
@ -295,6 +299,11 @@
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.showQueryText",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLTests.showOutputDifferences",
"group": "qltest@1",
@ -355,6 +364,10 @@
"command": "codeQLQueryHistory.showQueryLog",
"when": "false"
},
{
"command": "codeQLQueryHistory.showQueryText",
"when": "false"
},
{
"command": "codeQLQueryHistory.setLabel",
"when": "false"

Просмотреть файл

@ -6,6 +6,7 @@ import { QueryHistoryConfig } from './config';
import { QueryWithResults } from './run-queries';
import * as helpers from './helpers';
import { logger } from './logging';
import { URLSearchParams } from 'url';
/**
* query-history.ts
@ -18,9 +19,32 @@ import { logger } from './logging';
export type QueryHistoryItemOptions = {
label?: string; // user-settable label
queryText?: string; // stored query for quick query
queryText?: string; // text of the selected file
isQuickQuery?: boolean;
}
const SHOW_QUERY_TEXT_MSG = `\
////////////////////////////////////////////////////////////////////////////////////
// This is the text of the entire query file when it was executed for this query //
// run. The text or dependent libraries may have changed since then. //
// //
// This buffer is readonly. To re-execute this query, you must open the original //
// query file. //
////////////////////////////////////////////////////////////////////////////////////
`;
const SHOW_QUERY_TEXT_QUICK_EVAL_MSG = `\
////////////////////////////////////////////////////////////////////////////////////
// This is the Quick Eval selection of the query file when it was executed for //
// this query run. The text or dependent libraries may have changed since then. //
// //
// This buffer is readonly. To re-execute this query, you must open the original //
// query file. //
////////////////////////////////////////////////////////////////////////////////////
`;
/**
* Path to icon to display next to a failed query history item.
*/
@ -137,7 +161,7 @@ export class QueryHistoryManager {
const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(queryHistoryItem.query.program.queryPath));
const editor = await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One);
const queryText = queryHistoryItem.options.queryText;
if (queryText !== undefined) {
if (queryText !== undefined && queryHistoryItem.options.isQuickQuery) {
await editor.edit(edit => edit.replace(textDocument.validateRange(
new vscode.Range(0, 0, textDocument.lineCount, 0)), queryText)
);
@ -218,6 +242,36 @@ export class QueryHistoryManager {
}
}
async handleShowQueryText(queryHistoryItem: CompletedQuery) {
try {
const queryName = queryHistoryItem.queryName.endsWith('.ql') ? queryHistoryItem.queryName : queryHistoryItem.queryName + '.ql';
const params = new URLSearchParams({
isQuickEval: String(!!queryHistoryItem.query.quickEvalPosition),
queryText: await this.getQueryText(queryHistoryItem)
});
const uri = vscode.Uri.parse(`codeql:${queryHistoryItem.query.queryID}-${queryName}?${params.toString()}`);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, { preview: false });
} catch (e) {
helpers.showAndLogErrorMessage(e.message);
}
}
async getQueryText(queryHistoryItem: CompletedQuery): Promise<string> {
if (queryHistoryItem.options.queryText) {
return queryHistoryItem.options.queryText;
} else if (queryHistoryItem.query.quickEvalPosition) {
// capture all selected lines
const startLine = queryHistoryItem.query.quickEvalPosition.line;
const endLine = queryHistoryItem.query.quickEvalPosition.endLine;
const textDocument =
await vscode.workspace.openTextDocument(queryHistoryItem.query.quickEvalPosition.fileName);
return textDocument.getText(new vscode.Range(startLine - 1, 0, endLine, 0));
} else {
return '';
}
}
constructor(
ctx: ExtensionContext,
private queryHistoryConfigListener: QueryHistoryConfig,
@ -240,12 +294,24 @@ export class QueryHistoryManager {
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.removeHistoryItem', this.handleRemoveHistoryItem.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.setLabel', this.handleSetLabel.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryLog', this.handleShowQueryLog.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryText', this.handleShowQueryText.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.itemClicked', async (item) => {
return this.handleItemClicked(item);
}));
queryHistoryConfigListener.onDidChangeQueryHistoryConfiguration(() => {
this.treeDataProvider.refresh();
});
// displays query text in a read-only document
vscode.workspace.registerTextDocumentContentProvider('codeql', {
provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
const params = new URLSearchParams(uri.query)
return (
JSON.parse(params.get('isQuickEval') || '') ? SHOW_QUERY_TEXT_QUICK_EVAL_MSG : SHOW_QUERY_TEXT_MSG
) + params.get('queryText');
}
});
}
addQuery(info: QueryWithResults): CompletedQuery {

Просмотреть файл

@ -14,7 +14,7 @@ export class CompletedQuery implements QueryWithResults {
readonly query: QueryInfo;
readonly result: messages.EvaluationResult;
readonly database: DatabaseInfo;
readonly logFileLocation?: string
readonly logFileLocation?: string;
options: QueryHistoryItemOptions;
dispose: () => void;

Просмотреть файл

@ -239,8 +239,10 @@ async function getSelectedPosition(editor: vscode.TextEditor): Promise<messages.
// Convert from 0-based to 1-based line and column numbers.
return {
fileName: await convertToQlPath(editor.document.fileName),
line: pos.line + 1, column: pos.character + 1,
endLine: posEnd.line + 1, endColumn: posEnd.character + 1
line: pos.line + 1,
column: pos.character + 1,
endLine: posEnd.line + 1,
endColumn: posEnd.character + 1
};
}
@ -326,6 +328,7 @@ async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<b
type SelectedQuery = {
queryPath: string;
quickEvalPosition?: messages.Position;
quickEvalText?: string;
};
/**
@ -382,6 +385,7 @@ export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | u
}
let quickEvalPosition: messages.Position | undefined = undefined;
let quickEvalText: string | undefined = undefined;
if (quickEval) {
if (editor == undefined) {
throw new Error('Can\'t run quick evaluation without an active editor.');
@ -392,9 +396,10 @@ export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | u
throw new Error('The selected resource for quick evaluation should match the active editor.');
}
quickEvalPosition = await getSelectedPosition(editor);
quickEvalText = editor.document.getText(editor.selection);
}
return { queryPath, quickEvalPosition };
return { queryPath, quickEvalPosition, quickEvalText };
}
export async function compileAndRunQueryAgainstDatabase(
@ -411,12 +416,14 @@ export async function compileAndRunQueryAgainstDatabase(
}
// Determine which query to run, based on the selection and the active editor.
const { queryPath, quickEvalPosition } = await determineSelectedQuery(selectedQueryUri, quickEval);
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval);
// If this is quick query, store the query text
const historyItemOptions: QueryHistoryItemOptions = {};
if (isQuickQueryPath(queryPath)) {
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
historyItemOptions.isQuickQuery === isQuickQueryPath(queryPath);
if (quickEval) {
historyItemOptions.queryText = quickEvalText;
}
// Get the workspace folder paths.