Skip to content

Commit 9be355a

Browse files
committed
Add link to open query results from compare view
1 parent b803a80 commit 9be355a

File tree

5 files changed

+232
-86
lines changed

5 files changed

+232
-86
lines changed

extensions/ql-vscode/src/compare/compare-interface.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,13 @@ export class CompareInterfaceManager extends DisposableObject {
3535
private panelLoadedCallBacks: (() => void)[] = [];
3636

3737
constructor(
38-
public ctx: ExtensionContext,
39-
public databaseManager: DatabaseManager,
40-
public cliServer: CodeQLCliServer,
41-
public logger: Logger
38+
private ctx: ExtensionContext,
39+
private databaseManager: DatabaseManager,
40+
private cliServer: CodeQLCliServer,
41+
private logger: Logger,
42+
private showQueryResultsCallback: (
43+
item: CompletedQuery
44+
) => Promise<void>
4245
) {
4346
super();
4447
}
@@ -57,7 +60,11 @@ export class CompareInterfaceManager extends DisposableObject {
5760
currentResultSetName,
5861
fromResultSet,
5962
toResultSet,
60-
] = await this.findCommonResultSetNames(from, to, selectedResultSetName);
63+
] = await this.findCommonResultSetNames(
64+
from,
65+
to,
66+
selectedResultSetName
67+
);
6168
if (currentResultSetName) {
6269
await this.postMessage({
6370
t: "setComparisons",
@@ -147,7 +154,9 @@ export class CompareInterfaceManager extends DisposableObject {
147154
});
148155
}
149156

150-
private async handleMsgFromView(msg: FromCompareViewMessage): Promise<void> {
157+
private async handleMsgFromView(
158+
msg: FromCompareViewMessage
159+
): Promise<void> {
151160
switch (msg.t) {
152161
case "compareViewLoaded":
153162
this.panelLoaded = true;
@@ -162,6 +171,10 @@ export class CompareInterfaceManager extends DisposableObject {
162171
case "viewSourceFile":
163172
await jumpToLocation(msg, this.databaseManager, this.logger);
164173
break;
174+
175+
case "openQuery":
176+
await this.openQuery(msg.kind);
177+
break;
165178
}
166179
}
167180

@@ -183,7 +196,9 @@ export class CompareInterfaceManager extends DisposableObject {
183196
const fromSchemaNames = fromSchemas["result-sets"].map(
184197
(schema) => schema.name
185198
);
186-
const toSchemaNames = toSchemas["result-sets"].map((schema) => schema.name);
199+
const toSchemaNames = toSchemas["result-sets"].map(
200+
(schema) => schema.name
201+
);
187202
const commonResultSetNames = fromSchemaNames.filter((name) =>
188203
toSchemaNames.includes(name)
189204
);
@@ -229,7 +244,10 @@ export class CompareInterfaceManager extends DisposableObject {
229244
if (!schema) {
230245
throw new Error(`Schema ${resultSetName} not found.`);
231246
}
232-
const chunk = await this.cliServer.bqrsDecode(resultsPath, resultSetName);
247+
const chunk = await this.cliServer.bqrsDecode(
248+
resultsPath,
249+
resultSetName
250+
);
233251
const adaptedSchema = adaptSchema(schema);
234252
return adaptBqrs(adaptedSchema, chunk);
235253
}
@@ -241,4 +259,12 @@ export class CompareInterfaceManager extends DisposableObject {
241259
// Only compare columns that have the same name
242260
return resultsDiff(fromResults, toResults);
243261
}
262+
263+
private openQuery(kind: "from" | "to") {
264+
const toOpen =
265+
kind === "from" ? this.comparePair?.from : this.comparePair?.to;
266+
if (toOpen) {
267+
this.showQueryResultsCallback(toOpen);
268+
}
269+
}
244270
}

extensions/ql-vscode/src/compare/view/Compare.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,22 @@ export function Compare(props: {}): JSX.Element {
6060
<table className="vscode-codeql__compare-body">
6161
<thead>
6262
<tr>
63-
<td>{comparison.stats.fromQuery?.name}</td>
64-
<td>{comparison.stats.toQuery?.name}</td>
63+
<td>
64+
<a
65+
onClick={() => openQuery("from")}
66+
className="vscode-codeql__compare-open"
67+
>
68+
{comparison.stats.fromQuery?.name}
69+
</a>
70+
</td>
71+
<td>
72+
<a
73+
onClick={() => openQuery("to")}
74+
className="vscode-codeql__compare-open"
75+
>
76+
{comparison.stats.toQuery?.name}
77+
</a>
78+
</td>
6579
</tr>
6680
<tr>
6781
<td>{comparison.stats.fromQuery?.time}</td>
@@ -105,6 +119,13 @@ export function Compare(props: {}): JSX.Element {
105119
}
106120
}
107121

122+
async function openQuery(kind: "from" | "to") {
123+
vscode.postMessage({
124+
t: "openQuery",
125+
kind,
126+
});
127+
}
128+
108129
function createRows(rows: ResultRow[], databaseUri: string) {
109130
return (
110131
<tbody>

extensions/ql-vscode/src/extension.ts

Lines changed: 139 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -272,73 +272,118 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
272272
});
273273
}
274274

275-
async function activateWithInstalledDistribution(ctx: ExtensionContext, distributionManager: DistributionManager): Promise<void> {
275+
async function activateWithInstalledDistribution(
276+
ctx: ExtensionContext,
277+
distributionManager: DistributionManager
278+
): Promise<void> {
276279
beganMainExtensionActivation = true;
277280
// Remove any error stubs command handlers left over from first part
278281
// of activation.
279-
errorStubs.forEach(stub => stub.dispose());
282+
errorStubs.forEach((stub) => stub.dispose());
280283

281-
logger.log('Initializing configuration listener...');
282-
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(distributionManager);
284+
logger.log("Initializing configuration listener...");
285+
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(
286+
distributionManager
287+
);
283288
ctx.subscriptions.push(qlConfigurationListener);
284289

285-
logger.log('Initializing CodeQL cli server...');
290+
logger.log("Initializing CodeQL cli server...");
286291
const cliServer = new CodeQLCliServer(distributionManager, logger);
287292
ctx.subscriptions.push(cliServer);
288293

289-
logger.log('Initializing query server client.');
290-
const qs = new qsClient.QueryServerClient(qlConfigurationListener, cliServer, {
291-
logger: queryServerLogger,
292-
}, task => Window.withProgress({ title: 'CodeQL query server', location: ProgressLocation.Window }, task));
294+
logger.log("Initializing query server client.");
295+
const qs = new qsClient.QueryServerClient(
296+
qlConfigurationListener,
297+
cliServer,
298+
{
299+
logger: queryServerLogger,
300+
},
301+
(task) =>
302+
Window.withProgress(
303+
{ title: "CodeQL query server", location: ProgressLocation.Window },
304+
task
305+
)
306+
);
293307
ctx.subscriptions.push(qs);
294308
await qs.startQueryServer();
295309

296-
logger.log('Initializing database manager.');
310+
logger.log("Initializing database manager.");
297311
const dbm = new DatabaseManager(ctx, qlConfigurationListener, logger);
298312
ctx.subscriptions.push(dbm);
299-
logger.log('Initializing database panel.');
300-
const databaseUI = new DatabaseUI(ctx, cliServer, dbm, qs, getContextStoragePath(ctx));
313+
logger.log("Initializing database panel.");
314+
const databaseUI = new DatabaseUI(
315+
ctx,
316+
cliServer,
317+
dbm,
318+
qs,
319+
getContextStoragePath(ctx)
320+
);
301321
ctx.subscriptions.push(databaseUI);
302322

303-
logger.log('Initializing query history manager.');
323+
logger.log("Initializing query history manager.");
304324
const queryHistoryConfigurationListener = new QueryHistoryConfigListener();
325+
const showResults = async (item: CompletedQuery) =>
326+
showResultsForCompletedQuery(item, WebviewReveal.Forced);
327+
305328
const qhm = new QueryHistoryManager(
306329
ctx,
307330
queryHistoryConfigurationListener,
308-
async item => showResultsForCompletedQuery(item, WebviewReveal.Forced),
309-
async (from: CompletedQuery, to: CompletedQuery) => showResultsForComparison(from, to),
331+
showResults,
332+
async (from: CompletedQuery, to: CompletedQuery) =>
333+
showResultsForComparison(from, to),
310334
);
311-
logger.log('Initializing results panel interface.');
335+
logger.log("Initializing results panel interface.");
312336
const intm = new InterfaceManager(ctx, dbm, cliServer, queryServerLogger);
313337
ctx.subscriptions.push(intm);
314338

315-
logger.log('Initializing compare panel interface.');
316-
const cmpm = new CompareInterfaceManager(ctx, dbm, cliServer, queryServerLogger);
339+
logger.log("Initializing compare panel interface.");
340+
const cmpm = new CompareInterfaceManager(
341+
ctx,
342+
dbm,
343+
cliServer,
344+
queryServerLogger,
345+
showResults
346+
);
317347
ctx.subscriptions.push(cmpm);
318348

319-
logger.log('Initializing source archive filesystem provider.');
349+
logger.log("Initializing source archive filesystem provider.");
320350
archiveFilesystemProvider.activate(ctx);
321351

322-
async function showResultsForComparison(from: CompletedQuery, to: CompletedQuery): Promise<void> {
352+
async function showResultsForComparison(
353+
from: CompletedQuery,
354+
to: CompletedQuery
355+
): Promise<void> {
323356
try {
324357
await cmpm.showResults(from, to);
325358
} catch (e) {
326359
helpers.showAndLogErrorMessage(e.message);
327360
}
328361
}
329362

330-
async function showResultsForCompletedQuery(query: CompletedQuery, forceReveal: WebviewReveal): Promise<void> {
363+
async function showResultsForCompletedQuery(
364+
query: CompletedQuery,
365+
forceReveal: WebviewReveal
366+
): Promise<void> {
331367
await intm.showResults(query, forceReveal, false);
332368
}
333369

334-
async function compileAndRunQuery(quickEval: boolean, selectedQuery: Uri | undefined): Promise<void> {
370+
async function compileAndRunQuery(
371+
quickEval: boolean,
372+
selectedQuery: Uri | undefined
373+
): Promise<void> {
335374
if (qs !== undefined) {
336375
try {
337376
const dbItem = await databaseUI.getDatabaseItem();
338377
if (dbItem === undefined) {
339-
throw new Error('Can\'t run query without a selected database');
378+
throw new Error("Can't run query without a selected database");
340379
}
341-
const info = await compileAndRunQueryAgainstDatabase(cliServer, qs, dbItem, quickEval, selectedQuery);
380+
const info = await compileAndRunQueryAgainstDatabase(
381+
cliServer,
382+
qs,
383+
dbItem,
384+
quickEval,
385+
selectedQuery
386+
);
342387
const item = qhm.addQuery(info);
343388
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
344389
} catch (e) {
@@ -355,21 +400,28 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
355400

356401
ctx.subscriptions.push(tmpDirDisposal);
357402

358-
logger.log('Initializing CodeQL language server.');
359-
const client = new LanguageClient('CodeQL Language Server', () => spawnIdeServer(qlConfigurationListener), {
360-
documentSelector: [
361-
{ language: 'ql', scheme: 'file' },
362-
{ language: 'yaml', scheme: 'file', pattern: '**/qlpack.yml' }
363-
],
364-
synchronize: {
365-
configurationSection: 'codeQL'
403+
logger.log("Initializing CodeQL language server.");
404+
const client = new LanguageClient(
405+
"CodeQL Language Server",
406+
() => spawnIdeServer(qlConfigurationListener),
407+
{
408+
documentSelector: [
409+
{ language: "ql", scheme: "file" },
410+
{ language: "yaml", scheme: "file", pattern: "**/qlpack.yml" },
411+
],
412+
synchronize: {
413+
configurationSection: "codeQL",
414+
},
415+
// Ensure that language server exceptions are logged to the same channel as its output.
416+
outputChannel: ideServerLogger.outputChannel,
366417
},
367-
// Ensure that language server exceptions are logged to the same channel as its output.
368-
outputChannel: ideServerLogger.outputChannel
369-
}, true);
418+
true
419+
);
370420

371-
logger.log('Initializing QLTest interface.');
372-
const testExplorerExtension = extensions.getExtension<TestHub>(testExplorerExtensionId);
421+
logger.log("Initializing QLTest interface.");
422+
const testExplorerExtension = extensions.getExtension<TestHub>(
423+
testExplorerExtensionId
424+
);
373425
if (testExplorerExtension) {
374426
const testHub = testExplorerExtension.exports;
375427
const testAdapterFactory = new QLTestAdapterFactory(testHub, cliServer);
@@ -379,24 +431,58 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
379431
ctx.subscriptions.push(testUIService);
380432
}
381433

382-
logger.log('Registering top-level command palette commands.');
383-
ctx.subscriptions.push(commands.registerCommand('codeQL.runQuery', async (uri: Uri | undefined) => await compileAndRunQuery(false, uri)));
384-
ctx.subscriptions.push(commands.registerCommand('codeQL.quickEval', async (uri: Uri | undefined) => await compileAndRunQuery(true, uri)));
385-
ctx.subscriptions.push(commands.registerCommand('codeQL.quickQuery', async () => displayQuickQuery(ctx, cliServer, databaseUI)));
386-
ctx.subscriptions.push(commands.registerCommand('codeQL.restartQueryServer', async () => {
387-
await qs.restartQueryServer();
388-
helpers.showAndLogInformationMessage('CodeQL Query Server restarted.', { outputLogger: queryServerLogger });
389-
}));
390-
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseFolder', () => databaseUI.handleChooseDatabaseFolder()));
391-
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseArchive', () => databaseUI.handleChooseDatabaseArchive()));
392-
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseLgtm', () => databaseUI.handleChooseDatabaseLgtm()));
393-
ctx.subscriptions.push(commands.registerCommand('codeQL.chooseDatabaseInternet', () => databaseUI.handleChooseDatabaseInternet()));
394-
395-
logger.log('Starting language server.');
434+
logger.log("Registering top-level command palette commands.");
435+
ctx.subscriptions.push(
436+
commands.registerCommand(
437+
"codeQL.runQuery",
438+
async (uri: Uri | undefined) => await compileAndRunQuery(false, uri)
439+
)
440+
);
441+
ctx.subscriptions.push(
442+
commands.registerCommand(
443+
"codeQL.quickEval",
444+
async (uri: Uri | undefined) => await compileAndRunQuery(true, uri)
445+
)
446+
);
447+
ctx.subscriptions.push(
448+
commands.registerCommand("codeQL.quickQuery", async () =>
449+
displayQuickQuery(ctx, cliServer, databaseUI)
450+
)
451+
);
452+
ctx.subscriptions.push(
453+
commands.registerCommand("codeQL.restartQueryServer", async () => {
454+
await qs.restartQueryServer();
455+
helpers.showAndLogInformationMessage("CodeQL Query Server restarted.", {
456+
outputLogger: queryServerLogger,
457+
});
458+
})
459+
);
460+
ctx.subscriptions.push(
461+
commands.registerCommand("codeQL.chooseDatabaseFolder", () =>
462+
databaseUI.handleChooseDatabaseFolder()
463+
)
464+
);
465+
ctx.subscriptions.push(
466+
commands.registerCommand("codeQL.chooseDatabaseArchive", () =>
467+
databaseUI.handleChooseDatabaseArchive()
468+
)
469+
);
470+
ctx.subscriptions.push(
471+
commands.registerCommand("codeQL.chooseDatabaseLgtm", () =>
472+
databaseUI.handleChooseDatabaseLgtm()
473+
)
474+
);
475+
ctx.subscriptions.push(
476+
commands.registerCommand("codeQL.chooseDatabaseInternet", () =>
477+
databaseUI.handleChooseDatabaseInternet()
478+
)
479+
);
480+
481+
logger.log("Starting language server.");
396482
ctx.subscriptions.push(client.start());
397483

398484
// Jump-to-definition and find-references
399-
logger.log('Registering jump-to-definition handlers.');
485+
logger.log("Registering jump-to-definition handlers.");
400486
languages.registerDefinitionProvider(
401487
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
402488
new TemplateQueryDefinitionProvider(cliServer, qs, dbm)
@@ -406,7 +492,7 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
406492
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
407493
);
408494

409-
logger.log('Successfully finished extension initialization.');
495+
logger.log("Successfully finished extension initialization.");
410496
}
411497

412498
function getContextStoragePath(ctx: ExtensionContext) {

0 commit comments

Comments
 (0)