diff --git a/extensions/ql-vscode/src/compare/compare-interface.ts b/extensions/ql-vscode/src/compare/compare-interface.ts index 32c8d135a..37aeb2435 100644 --- a/extensions/ql-vscode/src/compare/compare-interface.ts +++ b/extensions/ql-vscode/src/compare/compare-interface.ts @@ -95,7 +95,7 @@ export class CompareInterfaceManager extends DisposableObject { currentResultSetName: currentResultSetName, rows, message, - datebaseUri: to.initialInfo.databaseInfo.databaseUri, + databaseUri: to.initialInfo.databaseInfo.databaseUri, }); } } diff --git a/extensions/ql-vscode/src/compare/view/Compare.tsx b/extensions/ql-vscode/src/compare/view/Compare.tsx index c880d7be3..b81356724 100644 --- a/extensions/ql-vscode/src/compare/view/Compare.tsx +++ b/extensions/ql-vscode/src/compare/view/Compare.tsx @@ -17,7 +17,7 @@ const emptyComparison: SetComparisonsMessage = { columns: [], commonResultSetNames: [], currentResultSetName: '', - datebaseUri: '', + databaseUri: '', message: 'Empty comparison' }; diff --git a/extensions/ql-vscode/src/compare/view/CompareTable.tsx b/extensions/ql-vscode/src/compare/view/CompareTable.tsx index 3039b7cfe..ada4627b8 100644 --- a/extensions/ql-vscode/src/compare/view/CompareTable.tsx +++ b/extensions/ql-vscode/src/compare/view/CompareTable.tsx @@ -76,7 +76,7 @@ export default function CompareTable(props: Props) { schemaName={comparison.currentResultSetName} preventSort={true} /> - {createRows(rows.from, comparison.datebaseUri)} + {createRows(rows.from, comparison.databaseUri)} @@ -86,7 +86,7 @@ export default function CompareTable(props: Props) { schemaName={comparison.currentResultSetName} preventSort={true} /> - {createRows(rows.to, comparison.datebaseUri)} + {createRows(rows.to, comparison.databaseUri)} diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index e859c121b..8232e652b 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -501,8 +501,6 @@ async function activateWithInstalledDistribution( const initialInfo = await createInitialQueryInfo(selectedQuery, databaseInfo, quickEval, range); const item = new FullQueryInfo(initialInfo, queryHistoryConfigurationListener); qhm.addQuery(item); - await qhm.refreshTreeView(item); - try { const completedQueryInfo = await compileAndRunQueryAgainstDatabase( cliServer, @@ -520,7 +518,7 @@ async function activateWithInstalledDistribution( item.failureReason = e.message; throw e; } finally { - await qhm.refreshTreeView(item); + qhm.refreshTreeView(); } } } diff --git a/extensions/ql-vscode/src/interface.ts b/extensions/ql-vscode/src/interface.ts index 16b2a5a2f..78efd3a9f 100644 --- a/extensions/ql-vscode/src/interface.ts +++ b/extensions/ql-vscode/src/interface.ts @@ -33,7 +33,7 @@ import { Logger } from './logging'; import * as messages from './pure/messages'; import { commandRunner } from './commandRunner'; import { CompletedQueryInfo, interpretResults } from './query-results'; -import { QueryEvaluatonInfo, tmpDir } from './run-queries'; +import { QueryEvaluationInfo, tmpDir } from './run-queries'; import { parseSarifLocation, parseSarifPlainTextMessage } from './pure/sarif-utils'; import { WebviewReveal, @@ -644,7 +644,7 @@ export class InterfaceManager extends DisposableObject { } private async interpretResultsInfo( - query: QueryEvaluatonInfo, + query: QueryEvaluationInfo, sortState: InterpretedResultsSortState | undefined ): Promise { if ( diff --git a/extensions/ql-vscode/src/pure/interface-types.ts b/extensions/ql-vscode/src/pure/interface-types.ts index 9e9ce2aa5..bb75686ad 100644 --- a/extensions/ql-vscode/src/pure/interface-types.ts +++ b/extensions/ql-vscode/src/pure/interface-types.ts @@ -316,7 +316,7 @@ export interface SetComparisonsMessage { readonly currentResultSetName: string; readonly rows: QueryCompareResult | undefined; readonly message: string | undefined; - readonly datebaseUri: string; + readonly databaseUri: string; } export enum DiffKind { diff --git a/extensions/ql-vscode/src/query-history.ts b/extensions/ql-vscode/src/query-history.ts index 2c9bdd800..a91311d07 100644 --- a/extensions/ql-vscode/src/query-history.ts +++ b/extensions/ql-vscode/src/query-history.ts @@ -96,9 +96,6 @@ export class HistoryTreeDataProvider extends DisposableObject { private localSuccessIconPath: string; - /** - * When not undefined, must be reference-equal to an item in `this.databases`. - */ private current: FullQueryInfo | undefined; constructor(extensionPath: string) { @@ -152,8 +149,8 @@ export class HistoryTreeDataProvider extends DisposableObject { element?: FullQueryInfo ): ProviderResult { return element ? [] : this.history.sort((h1, h2) => { - const q1 = h1.completedQuery; - const q2 = h2.completedQuery; + const resultCount1 = h1.completedQuery?.resultCount ?? -1; + const resultCount2 = h2.completedQuery?.resultCount ?? -1; switch (this.sortOrder) { case SortOrder.NameAsc: @@ -165,9 +162,15 @@ export class HistoryTreeDataProvider extends DisposableObject { case SortOrder.DateDesc: return h2.initialInfo.start.getTime() - h1.initialInfo.start.getTime(); case SortOrder.CountAsc: - return (!q1 || !q2) ? 0 : q1.resultCount - q2.resultCount; + // If the result counts are equal, sort by name. + return resultCount1 - resultCount2 === 0 + ? h1.label.localeCompare(h2.label, env.language) + : resultCount1 - resultCount2; case SortOrder.CountDesc: - return (!q1 || !q2) ? 0 : q2.resultCount - q1.resultCount; + // If the result counts are equal, sort by name. + return resultCount2 - resultCount1 === 0 + ? h2.label.localeCompare(h1.label, env.language) + : resultCount2 - resultCount1; default: assertNever(this.sortOrder); } @@ -183,8 +186,8 @@ export class HistoryTreeDataProvider extends DisposableObject { } pushQuery(item: FullQueryInfo): void { - this.current = item; this.history.push(item); + this.setCurrentItem(item); this.refresh(); } @@ -193,16 +196,17 @@ export class HistoryTreeDataProvider extends DisposableObject { } remove(item: FullQueryInfo) { - if (this.current === item) { - this.current = undefined; + const isCurrent = this.current === item; + if (isCurrent) { + this.setCurrentItem(); } const index = this.history.findIndex((i) => i === item); if (index >= 0) { this.history.splice(index, 1); - if (this.current === undefined && this.history.length > 0) { + if (isCurrent && this.history.length > 0) { // Try to keep a current item, near the deleted item if there // are any available. - this.current = this.history[Math.min(index, this.history.length - 1)]; + this.setCurrentItem(this.history[Math.min(index, this.history.length - 1)]); } this.refresh(); } @@ -212,8 +216,8 @@ export class HistoryTreeDataProvider extends DisposableObject { return this.history; } - refresh(item?: FullQueryInfo) { - this._onDidChangeTreeData.fire(item); + refresh() { + this._onDidChangeTreeData.fire(undefined); } public get sortOrder() { @@ -267,11 +271,13 @@ export class QueryHistoryManager extends DisposableObject { this.updateTreeViewSelectionIfVisible() ) ); - // Don't allow the selection to become empty this.push( this.treeView.onDidChangeSelection(async (ev) => { - if (ev.selection.length == 0) { + if (ev.selection.length === 0) { + // Don't allow the selection to become empty this.updateTreeViewSelectionIfVisible(); + } else { + this.treeDataProvider.setCurrentItem(ev.selection[0]); } this.updateCompareWith(ev.selection); }) @@ -433,12 +439,15 @@ export class QueryHistoryManager extends DisposableObject { const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect); (finalMultiSelect || [finalSingleItem]).forEach((item) => { - this.treeDataProvider.remove(item); - item.completedQuery?.dispose(); + // TODO: Removing in progress queries is not supported yet + if (item.status !== QueryStatus.InProgress) { + this.treeDataProvider.remove(item); + item.completedQuery?.dispose(); + } }); const current = this.treeDataProvider.getCurrent(); if (current !== undefined) { - await this.treeView.reveal(current); + await this.treeView.reveal(current, { select: true }); await this.invokeCallbackOn(current); } } @@ -484,12 +493,7 @@ export class QueryHistoryManager extends DisposableObject { if (response !== undefined) { // Interpret empty string response as 'go back to using default' singleItem.initialInfo.userSpecifiedLabel = response === '' ? undefined : response; - if (this.treeDataProvider.sortOrder === SortOrder.NameAsc || - this.treeDataProvider.sortOrder === SortOrder.NameDesc) { - this.treeDataProvider.refresh(); - } else { - this.treeDataProvider.refresh(singleItem); - } + this.treeDataProvider.refresh(); } } @@ -685,7 +689,7 @@ export class QueryHistoryManager extends DisposableObject { // We must fire the onDidChangeTreeData event to ensure the current element can be selected // using `reveal` if the tree view was not visible when the current element was added. this.treeDataProvider.refresh(); - void this.treeView.reveal(current); + void this.treeView.reveal(current, { select: true }); } } } @@ -826,13 +830,19 @@ the file in the file explorer and dragging it into the workspace.` singleItem: FullQueryInfo, multiSelect: FullQueryInfo[] ): { finalSingleItem: FullQueryInfo; finalMultiSelect: FullQueryInfo[] } { - if (singleItem === undefined && (multiSelect === undefined || multiSelect.length === 0 || multiSelect[0] === undefined)) { + if (!singleItem && !multiSelect?.[0]) { const selection = this.treeView.selection; - if (selection) { + const current = this.treeDataProvider.getCurrent(); + if (selection?.length) { return { finalSingleItem: selection[0], finalMultiSelect: selection }; + } else if (current) { + return { + finalSingleItem: current, + finalMultiSelect: [current] + }; } } return { @@ -841,7 +851,7 @@ the file in the file explorer and dragging it into the workspace.` }; } - async refreshTreeView(item: FullQueryInfo): Promise { - this.treeDataProvider.refresh(item); + refreshTreeView(): void { + this.treeDataProvider.refresh(); } } diff --git a/extensions/ql-vscode/src/query-results.ts b/extensions/ql-vscode/src/query-results.ts index 888282af3..ed7b324db 100644 --- a/extensions/ql-vscode/src/query-results.ts +++ b/extensions/ql-vscode/src/query-results.ts @@ -1,6 +1,6 @@ import { env } from 'vscode'; -import { QueryWithResults, tmpDir, QueryEvaluatonInfo } from './run-queries'; +import { QueryWithResults, tmpDir, QueryEvaluationInfo } from './run-queries'; import * as messages from './pure/messages'; import * as cli from './cli'; import * as sarif from 'sarif'; @@ -39,7 +39,7 @@ export enum QueryStatus { } export class CompletedQueryInfo implements QueryWithResults { - readonly query: QueryEvaluatonInfo; + readonly query: QueryEvaluationInfo; readonly result: messages.EvaluationResult; readonly logFileLocation?: string; resultCount: number; diff --git a/extensions/ql-vscode/src/run-queries.ts b/extensions/ql-vscode/src/run-queries.ts index 500740102..446ff3201 100644 --- a/extensions/ql-vscode/src/run-queries.ts +++ b/extensions/ql-vscode/src/run-queries.ts @@ -53,7 +53,7 @@ export const tmpDirDisposal = { * temporary files associated with it, such as the compiled query * output and results. */ -export class QueryEvaluatonInfo { +export class QueryEvaluationInfo { readonly compiledQueryPath: string; readonly dilPath: string; readonly csvPath: string; @@ -266,7 +266,7 @@ export class QueryEvaluatonInfo { export interface QueryWithResults { - readonly query: QueryEvaluatonInfo; + readonly query: QueryEvaluationInfo; readonly result: messages.EvaluationResult; readonly logFileLocation?: string; readonly dispose: () => void; @@ -351,7 +351,7 @@ async function getSelectedPosition(editor: TextEditor, range?: Range): Promise { @@ -393,7 +393,7 @@ async function checkDbschemeCompatibility( } } -function reportNoUpgradePath(query: QueryEvaluatonInfo) { +function reportNoUpgradePath(query: QueryEvaluationInfo) { throw new Error(`Query ${query.program.queryPath} expects database scheme ${query.queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`); } @@ -403,7 +403,7 @@ function reportNoUpgradePath(query: QueryEvaluatonInfo) { async function compileNonDestructiveUpgrade( qs: qsClient.QueryServerClient, upgradeTemp: tmp.DirectoryResult, - query: QueryEvaluatonInfo, + query: QueryEvaluationInfo, progress: ProgressCallback, token: CancellationToken, ): Promise { @@ -614,7 +614,7 @@ export async function compileAndRunQueryAgainstDatabase( } } - const query = new QueryEvaluatonInfo(initialInfo.id, qlProgram, db, packConfig.dbscheme, initialInfo.quickEvalPosition, metadata, templates); + const query = new QueryEvaluationInfo(initialInfo.id, qlProgram, db, packConfig.dbscheme, initialInfo.quickEvalPosition, metadata, templates); const upgradeDir = await tmp.dir({ dir: upgradesTmpDir.name, unsafeCleanup: true }); try { @@ -716,7 +716,7 @@ const compilationFailedErrorTail = ' compilation failed. Please make sure there ' and choose CodeQL Query Server from the dropdown.'; function createSyntheticResult( - query: QueryEvaluatonInfo, + query: QueryEvaluationInfo, message: string, resultType: number ): QueryWithResults { 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 84e1017d5..cb3fc3f6e 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 @@ -6,7 +6,7 @@ import * as sinon from 'sinon'; import * as chaiAsPromised from 'chai-as-promised'; import { logger } from '../../logging'; import { QueryHistoryManager, HistoryTreeDataProvider } from '../../query-history'; -import { QueryEvaluatonInfo, QueryWithResults } from '../../run-queries'; +import { QueryEvaluationInfo, QueryWithResults } from '../../run-queries'; import { QueryHistoryConfigListener } from '../../config'; import * as messages from '../../pure/messages'; import { QueryServerClient } from '../../queryserver-client'; @@ -379,7 +379,7 @@ describe('query-history', () => { return { query: { hasInterpretedResults: () => Promise.resolve(hasInterpretedResults) - } as QueryEvaluatonInfo, + } as QueryEvaluationInfo, result: { resultType: didRunSuccessfully ? messages.QueryResultType.SUCCESS 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 757667ae9..20c7de766 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 @@ -6,7 +6,7 @@ import 'sinon-chai'; import * as sinon from 'sinon'; import * as chaiAsPromised from 'chai-as-promised'; import { FullQueryInfo, InitialQueryInfo, interpretResults } from '../../query-results'; -import { QueryEvaluatonInfo, QueryWithResults, tmpDir } from '../../run-queries'; +import { QueryEvaluationInfo, QueryWithResults, tmpDir } from '../../run-queries'; import { QueryHistoryConfig } from '../../config'; import { EvaluationResult, QueryResultType } from '../../pure/messages'; import { SortDirection, SortedResultSetInfo } from '../../pure/interface-types'; @@ -248,7 +248,7 @@ describe('query-results', () => { resultsPath: '/a/b/c', interpretedResultsPath: '/d/e/f' } - } as QueryEvaluatonInfo, + } as QueryEvaluationInfo, result: { evaluationTime: 12340, resultType: didRunSuccessfully diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts index 06df24c17..e75437045 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts @@ -5,7 +5,7 @@ import 'sinon-chai'; import * as sinon from 'sinon'; import * as chaiAsPromised from 'chai-as-promised'; -import { QueryEvaluatonInfo } from '../../run-queries'; +import { QueryEvaluationInfo } from '../../run-queries'; import { QlProgram, Severity, compileQuery } from '../../pure/messages'; import { DatabaseItem } from '../../databases'; @@ -13,7 +13,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; describe('run-queries', () => { - it('should create a QueryEvaluatonInfo', () => { + it('should create a QueryEvaluationInfo', () => { const info = createMockQueryInfo(); const queryID = info.queryID; @@ -86,7 +86,7 @@ describe('run-queries', () => { let queryNum = 0; function createMockQueryInfo() { - return new QueryEvaluatonInfo( + return new QueryEvaluationInfo( queryNum++, 'my-program' as unknown as QlProgram, {