Add the compare-interface

This module will behave like the interface.ts module and handle
interactions between the compare webview and the extension.
This commit is contained in:
Andrew Eisenberg
2020-05-29 15:16:23 -07:00
parent 2ab4c1ac14
commit e9fbd6d430
6 changed files with 163 additions and 29 deletions

View File

@@ -314,6 +314,10 @@
"command": "codeQLQueryHistory.setLabel",
"title": "Set Label"
},
{
"command": "codeQLQueryHistory.compareWith",
"title": "Compare with..."
},
{
"command": "codeQL.restartQueryServer",
"title": "CodeQL: Restart Query Server"
@@ -401,6 +405,11 @@
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.compareWith",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"group": "9_qlCommands",
@@ -523,6 +532,10 @@
{
"command": "codeQLQueryHistory.setLabel",
"when": "false"
},
{
"command": "codeQLQueryHistory.compareWith",
"when": "false"
}
],
"editor/context": [

View File

@@ -0,0 +1,85 @@
import { DisposableObject } from "semmle-vscode-utils";
import { WebviewPanel, ExtensionContext, window as Window, ViewColumn, Uri } from "vscode";
import * as path from 'path';
import { tmpDir } from "../run-queries";
import { CompletedQuery } from "../query-results";
import { CompareViewMessage } from "../interface-types";
import { Logger } from "../logging";
import { CodeQLCliServer } from "../cli";
import { DatabaseManager } from "../databases";
import { getHtmlForWebview, WebviewReveal } from "../webview-utils";
import { showAndLogErrorMessage } from "../helpers";
interface ComparePair {
from: CompletedQuery;
to: CompletedQuery;
}
export class CompareInterfaceManager extends DisposableObject {
private comparePair: ComparePair | undefined;
private panel: WebviewPanel | undefined;
constructor(
public ctx: ExtensionContext,
public databaseManager: DatabaseManager,
public cliServer: CodeQLCliServer,
public logger: Logger
) {
super();
}
showResults(from: CompletedQuery, to: CompletedQuery, forceReveal = WebviewReveal.NotForced) {
this.comparePair = { from, to };
if (forceReveal === WebviewReveal.Forced) {
this.getPanel().reveal(undefined, true);
}
}
getPanel(): WebviewPanel {
if (this.panel == undefined) {
const { ctx } = this;
const panel = (this.panel = Window.createWebviewPanel(
"compareView", // internal name
"Compare CodeQL Query Results", // user-visible name
{ viewColumn: ViewColumn.Beside, preserveFocus: true },
{
enableScripts: true,
enableFindWidget: true,
retainContextWhenHidden: true,
localResourceRoots: [
Uri.file(tmpDir.name),
Uri.file(path.join(this.ctx.extensionPath, "out")),
],
}
));
this.panel.onDidDispose(
() => this.panel = undefined,
null,
ctx.subscriptions
);
const scriptPathOnDisk = Uri.file(
ctx.asAbsolutePath("out/compareView.js")
);
const stylesheetPathOnDisk = Uri.file(
ctx.asAbsolutePath("out/compareView.css")
);
panel.webview.html = getHtmlForWebview(panel.webview, scriptPathOnDisk, stylesheetPathOnDisk);
panel.webview.onDidReceiveMessage(
async (e) => this.handleMsgFromView(e),
undefined,
ctx.subscriptions
);
}
return this.panel;
}
private async handleMsgFromView(msg: CompareViewMessage): Promise<void> {
/** TODO */
showAndLogErrorMessage(JSON.stringify(msg));
showAndLogErrorMessage(JSON.stringify(this.comparePair));
}
}

View File

@@ -170,3 +170,9 @@ interface ChangeInterpretedResultsSortMsg {
*/
sortState?: InterpretedResultsSortState;
}
export interface CompareViewMessage {
t: 'change-compare';
newResultSetName: string;
// TODO do we need to include the ids of the queries
}

View File

@@ -135,10 +135,15 @@ class HistoryTreeDataProvider implements vscode.TreeDataProvider<CompletedQuery>
}
this.refresh();
}
}
get allHistory(): CompletedQuery[] {
return this.history;
}
refresh() {
this._onDidChangeTreeData.fire();
this._onDidChangeTreeData.fire(undefined);
}
}
@@ -152,7 +157,6 @@ export class QueryHistoryManager {
treeDataProvider: HistoryTreeDataProvider;
ctx: ExtensionContext;
treeView: vscode.TreeView<CompletedQuery>;
selectedCallback: ((item: CompletedQuery) => void) | undefined;
lastItemClick: { time: Date; item: CompletedQuery } | undefined;
async invokeCallbackOn(queryHistoryItem: CompletedQuery) {
@@ -200,6 +204,22 @@ export class QueryHistoryManager {
}
}
async handleCompareWith(query: CompletedQuery) {
const dbName = query.database.name;
const comparableQueryLabels = this.treeDataProvider.allHistory
.filter((otherQuery) => otherQuery !== query && otherQuery.didRunSuccessfully && otherQuery.database.name === dbName)
.map(otherQuery => ({
label: otherQuery.toString(),
description: otherQuery.databaseName,
detail: otherQuery.statusString,
query: otherQuery
}));
const choice = await vscode.window.showQuickPick(comparableQueryLabels);
if (choice) {
this.doCompareCallback(query, choice.query);
}
}
async handleItemClicked(queryHistoryItem: CompletedQuery) {
this.treeDataProvider.setCurrentItem(queryHistoryItem);
@@ -277,10 +297,10 @@ export class QueryHistoryManager {
constructor(
ctx: ExtensionContext,
private queryHistoryConfigListener: QueryHistoryConfig,
selectedCallback?: (item: CompletedQuery) => Promise<void>
private selectedCallback: (item: CompletedQuery) => Promise<void>,
private doCompareCallback: (from: CompletedQuery, to: CompletedQuery) => Promise<void>,
) {
this.ctx = ctx;
this.selectedCallback = selectedCallback;
const treeDataProvider = this.treeDataProvider = new HistoryTreeDataProvider(ctx);
this.treeView = Window.createTreeView('codeQLQueryHistory', { treeDataProvider });
// Lazily update the tree view selection due to limitations of TreeView API (see
@@ -296,6 +316,7 @@ export class QueryHistoryManager {
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.openQuery', this.handleOpenQuery));
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.compareWith', this.handleCompareWith.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.viewSarif', this.handleViewSarif.bind(this)));

View File

@@ -1,8 +1,8 @@
import { expect } from 'chai';
import * as path from 'path';
import * as tmp from 'tmp';
import { window, ViewColumn, Uri } from 'vscode';
import { fileUriToWebviewUri, webviewUriToFileUri } from '../../interface';
import { expect } from "chai";
import * as path from "path";
import * as tmp from "tmp";
import { window, ViewColumn, Uri } from "vscode";
import { fileUriToWebviewUri, webviewUriToFileUri } from '../../webview-utils';
describe('webview uri conversion', function() {
const fileSuffix = '.bqrs';

View File

@@ -1,5 +1,17 @@
import * as crypto from "crypto";
import { Uri, Location, Range, WebviewPanel, Webview, window as Window, workspace, ViewColumn, Selection, TextEditorRevealType, ThemeColor } from "vscode";
import {
Uri,
Location,
Range,
WebviewPanel,
Webview,
workspace,
window as Window,
ViewColumn,
Selection,
TextEditorRevealType,
ThemeColor,
} from "vscode";
import {
FivePartLocation,
LocationStyle,
@@ -136,25 +148,6 @@ export function getHtmlForWebview(
</html>`;
}
const findMatchBackground = new ThemeColor("editor.findMatchBackground");
const findRangeHighlightBackground = new ThemeColor(
"editor.findRangeHighlightBackground"
);
export const shownLocationDecoration = Window.createTextEditorDecorationType(
{
backgroundColor: findMatchBackground,
}
);
export const shownLocationLineDecoration = Window.createTextEditorDecorationType(
{
backgroundColor: findRangeHighlightBackground,
isWholeLine: true,
}
);
export async function showLocation(
loc: ResolvableLocationValue,
databaseItem: DatabaseItem
@@ -188,3 +181,19 @@ export async function showLocation(
editor.setDecorations(shownLocationLineDecoration, [range]);
}
}
const findMatchBackground = new ThemeColor("editor.findMatchBackground");
const findRangeHighlightBackground = new ThemeColor(
"editor.findRangeHighlightBackground"
);
export const shownLocationDecoration = Window.createTextEditorDecorationType({
backgroundColor: findMatchBackground,
});
export const shownLocationLineDecoration = Window.createTextEditorDecorationType(
{
backgroundColor: findRangeHighlightBackground,
isWholeLine: true,
}
);