diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index c037ef1d549..b3e71ee0a2c 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -496,6 +496,10 @@ "command": "codeQLQueryHistory.showQueryLog", "title": "Show Query Log" }, + { + "command": "codeQLQueryHistory.cancel", + "title": "Cancel" + }, { "command": "codeQLQueryHistory.showQueryText", "title": "Show Query Text" @@ -664,7 +668,7 @@ { "command": "codeQLQueryHistory.removeHistoryItem", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory" + "when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == cancelledResultsItem" }, { "command": "codeQLQueryHistory.setLabel", @@ -674,12 +678,12 @@ { "command": "codeQLQueryHistory.compareWith", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory" + "when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem" }, { "command": "codeQLQueryHistory.showQueryLog", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory" + "when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem" }, { "command": "codeQLQueryHistory.showQueryText", @@ -689,37 +693,37 @@ { "command": "codeQLQueryHistory.viewCsvResults", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory && viewItem != interpretedResultsItem" + "when": "viewItem == rawResultsItem" }, { "command": "codeQLQueryHistory.viewCsvAlerts", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem" + "when": "viewItem == interpretedResultsItem" }, { "command": "codeQLQueryHistory.viewSarifAlerts", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem" + "when": "viewItem == interpretedResultsItem" }, { "command": "codeQLQueryHistory.viewDil", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory" + "when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem" }, { - "command": "codeQL.previewQueryHelp", + "command": "codeQLQueryHistory.cancel", "group": "9_qlCommands", - "when": "view == codeQLQueryHistory && resourceScheme == .qhelp && isWorkspaceTrusted" + "when": "viewItem == inProgressResultsItem" }, { "command": "codeQLTests.showOutputDifferences", "group": "qltest@1", - "when": "view == test-explorer && viewItem == testWithSource" + "when": "viewItem == testWithSource" }, { "command": "codeQLTests.acceptOutput", "group": "qltest@2", - "when": "view == test-explorer && viewItem == testWithSource" + "when": "viewItem == testWithSource" } ], "explorer/context": [ @@ -862,6 +866,10 @@ "command": "codeQLQueryHistory.showQueryLog", "when": "false" }, + { + "command": "codeQLQueryHistory.cancel", + "when": "false" + }, { "command": "codeQLQueryHistory.showQueryText", "when": "false" diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index a70d0a76c58..911d939a62d 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -1,5 +1,6 @@ import { CancellationToken, + CancellationTokenSource, commands, Disposable, ExtensionContext, @@ -498,8 +499,12 @@ async function activateWithInstalledDistribution( databaseUri: databaseItem.databaseUri.toString(), }; + // handle cancellation from the history view. + const source = new CancellationTokenSource(); + token.onCancellationRequested(() => source.cancel()); + const initialInfo = await createInitialQueryInfo(selectedQuery, databaseInfo, quickEval, range); - const item = new FullQueryInfo(initialInfo, queryHistoryConfigurationListener); + const item = new FullQueryInfo(initialInfo, queryHistoryConfigurationListener, source); qhm.addQuery(item); try { const completedQueryInfo = await compileAndRunQueryAgainstDatabase( @@ -508,7 +513,7 @@ async function activateWithInstalledDistribution( databaseItem, initialInfo, progress, - token, + source.token, ); item.completeThisQuery(completedQueryInfo); await showResultsForCompletedQuery(item as FullCompletedQueryInfo, WebviewReveal.NotForced); @@ -519,6 +524,7 @@ async function activateWithInstalledDistribution( throw e; } finally { qhm.refreshTreeView(); + source.dispose(); } } } diff --git a/extensions/ql-vscode/src/query-history.ts b/extensions/ql-vscode/src/query-history.ts index 36cab4ce829..2e5928414ee 100644 --- a/extensions/ql-vscode/src/query-history.ts +++ b/extensions/ql-vscode/src/query-history.ts @@ -119,24 +119,24 @@ export class HistoryTreeDataProvider extends DisposableObject { arguments: [element], }; - // Mark this query history item according to whether it has a - // SARIF file so that we can make context menu items conditionally - // available. - const hasResults = await element.completedQuery?.query.hasInterpretedResults(); - treeItem.contextValue = hasResults - ? 'interpretedResultsItem' - : 'rawResultsItem'; - + // Populate the icon and the context value. We use the context value to + // control which commands are visible in the context menu. + let hasResults; switch (element.status) { case QueryStatus.InProgress: - // TODO this is not a good icon. treeItem.iconPath = new ThemeIcon('sync~spin'); + treeItem.contextValue = 'inProgressResultsItem'; break; case QueryStatus.Completed: + hasResults = await element.completedQuery?.query.hasInterpretedResults(); treeItem.iconPath = this.localSuccessIconPath; + treeItem.contextValue = hasResults + ? 'interpretedResultsItem' + : 'rawResultsItem'; break; case QueryStatus.Failed: treeItem.iconPath = this.failedIconPath; + treeItem.contextValue = 'cancelledResultsItem'; break; default: assertNever(element.status); @@ -332,6 +332,12 @@ export class QueryHistoryManager extends DisposableObject { this.handleShowQueryLog.bind(this) ) ); + this.push( + commandRunner( + 'codeQLQueryHistory.cancel', + this.handleCancel.bind(this) + ) + ); this.push( commandRunner( 'codeQLQueryHistory.showQueryText', @@ -439,7 +445,7 @@ export class QueryHistoryManager extends DisposableObject { const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect); (finalMultiSelect || [finalSingleItem]).forEach((item) => { - // TODO: Removing in progress queries is not supported yet + // Removing in progress queries is not supported yet if (item.status !== QueryStatus.InProgress) { this.treeDataProvider.remove(item); item.completedQuery?.dispose(); @@ -568,6 +574,19 @@ export class QueryHistoryManager extends DisposableObject { } } + async handleCancel( + singleItem: FullQueryInfo, + multiSelect: FullQueryInfo[] + ) { + const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect); + + (finalMultiSelect || [finalSingleItem]).forEach((item) => { + if (item.status === QueryStatus.InProgress) { + item.cancel(); + } + }); + } + async handleShowQueryText( singleItem: FullQueryInfo, multiSelect: FullQueryInfo[] diff --git a/extensions/ql-vscode/src/query-results.ts b/extensions/ql-vscode/src/query-results.ts index ed7b324db9f..d3ad46c69b2 100644 --- a/extensions/ql-vscode/src/query-results.ts +++ b/extensions/ql-vscode/src/query-results.ts @@ -1,4 +1,4 @@ -import { env } from 'vscode'; +import { CancellationTokenSource, env } from 'vscode'; import { QueryWithResults, tmpDir, QueryEvaluationInfo } from './run-queries'; import * as messages from './pure/messages'; @@ -180,10 +180,15 @@ export class FullQueryInfo { constructor( public readonly initialInfo: InitialQueryInfo, private readonly config: QueryHistoryConfig, + private readonly source: CancellationTokenSource ) { /**/ } + cancel() { + this.source.cancel(); + } + get startTime() { return this.initialInfo.start.toLocaleString(env.language); } diff --git a/extensions/ql-vscode/src/run-queries.ts b/extensions/ql-vscode/src/run-queries.ts index 233f9bcb7d3..185fedd6da6 100644 --- a/extensions/ql-vscode/src/run-queries.ts +++ b/extensions/ql-vscode/src/run-queries.ts @@ -695,7 +695,8 @@ let queryId = 0; export async function createInitialQueryInfo( selectedQueryUri: Uri | undefined, databaseInfo: DatabaseInfo, - isQuickEval: boolean, range?: Range + isQuickEval: boolean, + range?: Range ): Promise { // Determine which query to run, based on the selection and the active editor. const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, isQuickEval, range); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts index c3e39aae89b..c673fb56dfb 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts @@ -515,7 +515,8 @@ describe('query-history', () => { start: new Date(), queryPath: 'hucairz' } as InitialQueryInfo, - configListener + configListener, + {} as vscode.CancellationTokenSource ); if (queryWitbResults) { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-results.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/query-results.test.ts index 20c7de7660b..26fab2de853 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-results.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/query-results.test.ts @@ -12,6 +12,7 @@ import { EvaluationResult, QueryResultType } from '../../pure/messages'; import { SortDirection, SortedResultSetInfo } from '../../pure/interface-types'; import { CodeQLCliServer, SourceInfo } from '../../cli'; import { env } from 'process'; +import { CancellationTokenSource } from 'vscode'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -266,7 +267,8 @@ describe('query-results', () => { start: new Date(), queryPath: 'path/to/hucairz' } as InitialQueryInfo, - mockQueryHistoryConfig() + mockQueryHistoryConfig(), + {} as CancellationTokenSource ); if (queryWitbResults) {