Add the commandRunner
The commandRunner wraps all vscode command registrations. It provides uniform error handling and an optional progress monitor. In general, progress monitors should only be created by the commandRunner and passed through to the locations that use it.
This commit is contained in:
@@ -3,7 +3,6 @@ import {
|
||||
ExtensionContext,
|
||||
TreeDataProvider,
|
||||
EventEmitter,
|
||||
commands,
|
||||
Event,
|
||||
ProviderResult,
|
||||
TreeItemCollapsibleState,
|
||||
@@ -19,6 +18,7 @@ import { DatabaseItem } from './databases';
|
||||
import { UrlValue, BqrsId } from './bqrs-cli-types';
|
||||
import { showLocation } from './interface-utils';
|
||||
import { isStringLoc, isWholeFileLoc, isLineColumnLoc } from './bqrs-utils';
|
||||
import { commandRunner } from './helpers';
|
||||
|
||||
|
||||
export interface AstItem {
|
||||
@@ -45,10 +45,10 @@ class AstViewerDataProvider implements TreeDataProvider<AstItem> {
|
||||
this._onDidChangeTreeData.event;
|
||||
|
||||
constructor() {
|
||||
commands.registerCommand('codeQLAstViewer.gotoCode',
|
||||
async (item: AstItem) => {
|
||||
await showLocation(item.fileLocation);
|
||||
});
|
||||
commandRunner('codeQLAstViewer.gotoCode',
|
||||
async (item: AstItem) => {
|
||||
await showLocation(item.fileLocation);
|
||||
});
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
@@ -109,7 +109,7 @@ export class AstViewer {
|
||||
showCollapseAll: true
|
||||
});
|
||||
|
||||
commands.registerCommand('codeQLAstViewer.clear', () => {
|
||||
commandRunner('codeQLAstViewer.clear', async () => {
|
||||
this.clear();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { decodeSourceArchiveUri, zipArchiveScheme } from '../archive-filesystem-provider';
|
||||
import { ColumnKindCode, EntityValue, getResultSetSchema } from '../bqrs-cli-types';
|
||||
import { ColumnKindCode, EntityValue, getResultSetSchema, ResultSetSchema } from '../bqrs-cli-types';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { DatabaseManager, DatabaseItem } from '../databases';
|
||||
import fileRangeFromURI from './fileRangeFromURI';
|
||||
import * as messages from '../messages';
|
||||
import { QueryServerClient } from '../queryserver-client';
|
||||
import { QueryWithResults, compileAndRunQueryAgainstDatabase } from '../run-queries';
|
||||
import { ProgressCallback } from '../helpers';
|
||||
import { KeyType } from './keyType';
|
||||
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
|
||||
|
||||
@@ -28,6 +29,8 @@ export interface FullLocationLink extends vscode.LocationLink {
|
||||
* @param dbm The database manager
|
||||
* @param uriString The selected source file and location
|
||||
* @param keyType The contextual query type to run
|
||||
* @param progress A progress callback
|
||||
* @param token A CancellationToken
|
||||
* @param filter A function that will filter extraneous results
|
||||
*/
|
||||
export async function getLocationsForUriString(
|
||||
@@ -36,37 +39,42 @@ export async function getLocationsForUriString(
|
||||
dbm: DatabaseManager,
|
||||
uriString: string,
|
||||
keyType: KeyType,
|
||||
progress: ProgressCallback,
|
||||
token: vscode.CancellationToken,
|
||||
filter: (src: string, dest: string) => boolean
|
||||
): Promise<FullLocationLink[]> {
|
||||
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString));
|
||||
const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme });
|
||||
|
||||
const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
|
||||
if (db) {
|
||||
const qlpack = await qlpackOfDatabase(cli, db);
|
||||
if (qlpack === undefined) {
|
||||
throw new Error('Can\'t infer qlpack from database source archive');
|
||||
}
|
||||
const links: FullLocationLink[] = [];
|
||||
for (const query of await resolveQueries(cli, qlpack, keyType)) {
|
||||
const templates: messages.TemplateDefinitions = {
|
||||
[TEMPLATE_NAME]: {
|
||||
values: {
|
||||
tuples: [[{
|
||||
stringValue: uri.pathWithinSourceArchive
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates);
|
||||
if (results.result.resultType == messages.QueryResultType.SUCCESS) {
|
||||
links.push(...await getLinksFromResults(results, cli, db, filter));
|
||||
}
|
||||
}
|
||||
return links;
|
||||
} else {
|
||||
if (!db) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const qlpack = await qlpackOfDatabase(cli, db);
|
||||
if (qlpack === undefined) {
|
||||
throw new Error('Can\'t infer qlpack from database source archive');
|
||||
}
|
||||
const templates = createTemplates(uri.pathWithinSourceArchive);
|
||||
|
||||
const links: FullLocationLink[] = [];
|
||||
for (const query of await resolveQueries(cli, qlpack, keyType)) {
|
||||
const results = await compileAndRunQueryAgainstDatabase(
|
||||
cli,
|
||||
qs,
|
||||
db,
|
||||
false,
|
||||
vscode.Uri.file(query),
|
||||
progress,
|
||||
token,
|
||||
templates
|
||||
);
|
||||
|
||||
if (results.result.resultType == messages.QueryResultType.SUCCESS) {
|
||||
links.push(...await getLinksFromResults(results, cli, db, filter));
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
async function getLinksFromResults(
|
||||
@@ -79,10 +87,7 @@ async function getLinksFromResults(
|
||||
const bqrsPath = results.query.resultsPaths.resultsPath;
|
||||
const info = await cli.bqrsInfo(bqrsPath);
|
||||
const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info);
|
||||
if (selectInfo && selectInfo.columns.length == 3
|
||||
&& selectInfo.columns[0].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[1].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[2].kind == ColumnKindCode.STRING) {
|
||||
if (isValidSelect(selectInfo)) {
|
||||
// TODO: Page this
|
||||
const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME);
|
||||
for (const tuple of allTuples.tuples) {
|
||||
@@ -101,3 +106,22 @@ async function getLinksFromResults(
|
||||
}
|
||||
return localLinks;
|
||||
}
|
||||
|
||||
function createTemplates(path: string): messages.TemplateDefinitions {
|
||||
return {
|
||||
[TEMPLATE_NAME]: {
|
||||
values: {
|
||||
tuples: [[{
|
||||
stringValue: path
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function isValidSelect(selectInfo: ResultSetSchema | undefined) {
|
||||
return selectInfo && selectInfo.columns.length == 3
|
||||
&& selectInfo.columns[0].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[1].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[2].kind == ColumnKindCode.STRING;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as vscode from 'vscode';
|
||||
import { decodeSourceArchiveUri, zipArchiveScheme } from '../archive-filesystem-provider';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { DatabaseManager } from '../databases';
|
||||
import { CachedOperation } from '../helpers';
|
||||
import { CachedOperation, ProgressCallback, withProgress } from '../helpers';
|
||||
import * as messages from '../messages';
|
||||
import { QueryServerClient } from '../queryserver-client';
|
||||
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from '../run-queries';
|
||||
@@ -44,14 +44,22 @@ export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvide
|
||||
}
|
||||
|
||||
private async getDefinitions(uriString: string): Promise<vscode.LocationLink[]> {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
this.qs,
|
||||
this.dbm,
|
||||
uriString,
|
||||
KeyType.DefinitionQuery,
|
||||
(src, _dest) => src === uriString
|
||||
);
|
||||
return await withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
cancellable: true,
|
||||
title: 'Finding definitions'
|
||||
}, async (progress, token) => {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
this.qs,
|
||||
this.dbm,
|
||||
uriString,
|
||||
KeyType.DefinitionQuery,
|
||||
progress,
|
||||
token,
|
||||
(src, _dest) => src === uriString
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,14 +91,22 @@ export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider
|
||||
}
|
||||
|
||||
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
this.qs,
|
||||
this.dbm,
|
||||
uriString,
|
||||
KeyType.ReferenceQuery,
|
||||
(_src, dest) => dest === uriString
|
||||
);
|
||||
return await withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
cancellable: true,
|
||||
title: 'Finding references'
|
||||
}, async (progress, token) => {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
this.qs,
|
||||
this.dbm,
|
||||
uriString,
|
||||
KeyType.DefinitionQuery,
|
||||
progress,
|
||||
token,
|
||||
(src, _dest) => src === uriString
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +117,10 @@ export class TemplatePrintAstProvider {
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
|
||||
// Note: progress and token are only used if a cached value is not available
|
||||
private progress: ProgressCallback,
|
||||
private token: vscode.CancellationToken
|
||||
) {
|
||||
this.cache = new CachedOperation<QueryWithResults | undefined>(this.getAst.bind(this));
|
||||
}
|
||||
@@ -157,12 +177,15 @@ export class TemplatePrintAstProvider {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return await compileAndRunQueryAgainstDatabase(
|
||||
this.cli,
|
||||
this.qs,
|
||||
db,
|
||||
false,
|
||||
vscode.Uri.file(query),
|
||||
this.progress,
|
||||
this.token,
|
||||
templates
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import * as unzipper from 'unzipper';
|
||||
import { zip } from 'zip-a-folder';
|
||||
import {
|
||||
Uri,
|
||||
ProgressOptions,
|
||||
ProgressLocation,
|
||||
CancellationToken,
|
||||
commands,
|
||||
window,
|
||||
} from 'vscode';
|
||||
@@ -14,8 +13,6 @@ import * as path from 'path';
|
||||
import { DatabaseManager, DatabaseItem } from './databases';
|
||||
import {
|
||||
ProgressCallback,
|
||||
showAndLogErrorMessage,
|
||||
withProgress,
|
||||
showAndLogInformationMessage,
|
||||
} from './helpers';
|
||||
import { logger } from './logging';
|
||||
@@ -28,42 +25,32 @@ import { logger } from './logging';
|
||||
*/
|
||||
export async function promptImportInternetDatabase(
|
||||
databasesManager: DatabaseManager,
|
||||
storagePath: string
|
||||
storagePath: string,
|
||||
progress: ProgressCallback,
|
||||
_: CancellationToken,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
let item: DatabaseItem | undefined = undefined;
|
||||
|
||||
try {
|
||||
const databaseUrl = await window.showInputBox({
|
||||
prompt: 'Enter URL of zipfile of database to download',
|
||||
});
|
||||
if (databaseUrl) {
|
||||
validateHttpsUrl(databaseUrl);
|
||||
|
||||
const progressOptions: ProgressOptions = {
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Adding database from URL',
|
||||
cancellable: false,
|
||||
};
|
||||
await withProgress(
|
||||
progressOptions,
|
||||
async (progress) =>
|
||||
(item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
databasesManager,
|
||||
storagePath,
|
||||
progress
|
||||
))
|
||||
);
|
||||
if (item) {
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database downloaded and imported successfully.');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
const databaseUrl = await window.showInputBox({
|
||||
prompt: 'Enter URL of zipfile of database to download',
|
||||
});
|
||||
if (!databaseUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateHttpsUrl(databaseUrl);
|
||||
|
||||
const item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
databasesManager,
|
||||
storagePath,
|
||||
progress
|
||||
);
|
||||
|
||||
if (item) {
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database downloaded and imported successfully.');
|
||||
}
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,49 +63,37 @@ export async function promptImportInternetDatabase(
|
||||
*/
|
||||
export async function promptImportLgtmDatabase(
|
||||
databasesManager: DatabaseManager,
|
||||
storagePath: string
|
||||
storagePath: string,
|
||||
progress: ProgressCallback,
|
||||
_: CancellationToken
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
let item: DatabaseItem | undefined = undefined;
|
||||
|
||||
try {
|
||||
const lgtmUrl = await window.showInputBox({
|
||||
prompt:
|
||||
'Enter the project URL on LGTM (e.g., https://lgtm.com/projects/g/github/codeql)',
|
||||
});
|
||||
if (!lgtmUrl) {
|
||||
return;
|
||||
}
|
||||
if (looksLikeLgtmUrl(lgtmUrl)) {
|
||||
const databaseUrl = await convertToDatabaseUrl(lgtmUrl);
|
||||
if (databaseUrl) {
|
||||
const progressOptions: ProgressOptions = {
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Adding database from LGTM',
|
||||
cancellable: false,
|
||||
};
|
||||
await withProgress(
|
||||
progressOptions,
|
||||
async (progress) =>
|
||||
(item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
databasesManager,
|
||||
storagePath,
|
||||
progress
|
||||
))
|
||||
);
|
||||
if (item) {
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database downloaded and imported successfully.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
|
||||
}
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
const lgtmUrl = await window.showInputBox({
|
||||
prompt:
|
||||
'Enter the project URL on LGTM (e.g., https://lgtm.com/projects/g/github/codeql)',
|
||||
});
|
||||
if (!lgtmUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
return item;
|
||||
if (looksLikeLgtmUrl(lgtmUrl)) {
|
||||
const databaseUrl = await convertToDatabaseUrl(lgtmUrl);
|
||||
if (databaseUrl) {
|
||||
const item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
databasesManager,
|
||||
storagePath,
|
||||
progress
|
||||
);
|
||||
if (item) {
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database downloaded and imported successfully.');
|
||||
}
|
||||
return item;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,37 +106,30 @@ export async function promptImportLgtmDatabase(
|
||||
export async function importArchiveDatabase(
|
||||
databaseUrl: string,
|
||||
databasesManager: DatabaseManager,
|
||||
storagePath: string
|
||||
storagePath: string,
|
||||
progress: ProgressCallback,
|
||||
_: CancellationToken,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
let item: DatabaseItem | undefined = undefined;
|
||||
try {
|
||||
const progressOptions: ProgressOptions = {
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Importing database from archive',
|
||||
cancellable: false,
|
||||
};
|
||||
await withProgress(
|
||||
progressOptions,
|
||||
async (progress) =>
|
||||
(item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
databasesManager,
|
||||
storagePath,
|
||||
progress
|
||||
))
|
||||
const item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
databasesManager,
|
||||
storagePath,
|
||||
progress
|
||||
);
|
||||
if (item) {
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database unzipped and imported successfully.');
|
||||
}
|
||||
return item;
|
||||
} catch (e) {
|
||||
if (e.message.includes('unexpected end of file')) {
|
||||
showAndLogErrorMessage('Database is corrupt or too large. Try unzipping outside of VS Code and importing the unzipped folder instead.');
|
||||
throw new Error('Database is corrupt or too large. Try unzipping outside of VS Code and importing the unzipped folder instead.');
|
||||
} else {
|
||||
showAndLogErrorMessage(e.message);
|
||||
// delegate
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as path from 'path';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import {
|
||||
commands,
|
||||
Event,
|
||||
EventEmitter,
|
||||
ExtensionContext,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
Uri,
|
||||
window,
|
||||
env,
|
||||
ProgressLocation
|
||||
} from 'vscode';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
@@ -21,9 +21,14 @@ import {
|
||||
DatabaseManager,
|
||||
getUpgradesDirectories,
|
||||
} from './databases';
|
||||
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from './helpers';
|
||||
import {
|
||||
commandRunner,
|
||||
getOnDiskWorkspaceFolders,
|
||||
ProgressCallback,
|
||||
showAndLogErrorMessage
|
||||
} from './helpers';
|
||||
import { logger } from './logging';
|
||||
import { clearCacheInDatabase, UserCancellationException } from './run-queries';
|
||||
import { clearCacheInDatabase } from './run-queries';
|
||||
import * as qsClient from './queryserver-client';
|
||||
import { upgradeDatabase } from './upgrades';
|
||||
import {
|
||||
@@ -31,6 +36,7 @@ import {
|
||||
promptImportInternetDatabase,
|
||||
promptImportLgtmDatabase,
|
||||
} from './databaseFetcher';
|
||||
import { CancellationToken } from 'vscode-jsonrpc';
|
||||
|
||||
type ThemableIconPath = { light: string; dark: string } | string;
|
||||
|
||||
@@ -227,83 +233,104 @@ export class DatabaseUI extends DisposableObject {
|
||||
|
||||
logger.log('Registering database panel commands.');
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQL.setCurrentDatabase',
|
||||
this.handleSetCurrentDatabase
|
||||
this.handleSetCurrentDatabase,
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Importing database from archive',
|
||||
cancellable: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQL.upgradeCurrentDatabase',
|
||||
this.handleUpgradeCurrentDatabase
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand('codeQL.clearCache', this.handleClearCache)
|
||||
commandRunner(
|
||||
'codeQL.clearCache',
|
||||
this.handleClearCache,
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Clearing Cache',
|
||||
cancellable: false,
|
||||
})
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.chooseDatabaseFolder',
|
||||
this.handleChooseDatabaseFolder
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.chooseDatabaseArchive',
|
||||
this.handleChooseDatabaseArchive
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.chooseDatabaseInternet',
|
||||
this.handleChooseDatabaseInternet
|
||||
this.handleChooseDatabaseInternet,
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Adding database from URL',
|
||||
cancellable: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.chooseDatabaseLgtm',
|
||||
this.handleChooseDatabaseLgtm
|
||||
)
|
||||
this.handleChooseDatabaseLgtm,
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Adding database from LGTM',
|
||||
cancellable: false,
|
||||
})
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.setCurrentDatabase',
|
||||
this.handleMakeCurrentDatabase
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.sortByName',
|
||||
this.handleSortByName
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.sortByDateAdded',
|
||||
this.handleSortByDateAdded
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.removeDatabase',
|
||||
this.handleRemoveDatabase
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.upgradeDatabase',
|
||||
this.handleUpgradeDatabase
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.renameDatabase',
|
||||
this.handleRenameDatabase
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLDatabases.openDatabaseFolder',
|
||||
this.handleOpenFolder
|
||||
)
|
||||
@@ -316,37 +343,53 @@ export class DatabaseUI extends DisposableObject {
|
||||
await this.databaseManager.setCurrentDatabaseItem(databaseItem);
|
||||
};
|
||||
|
||||
handleChooseDatabaseFolder = async (): Promise<DatabaseItem | undefined> => {
|
||||
handleChooseDatabaseFolder = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
): Promise<DatabaseItem | undefined> => {
|
||||
try {
|
||||
return await this.chooseAndSetDatabase(true);
|
||||
return await this.chooseAndSetDatabase(true, progress, token);
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
handleChooseDatabaseArchive = async (): Promise<DatabaseItem | undefined> => {
|
||||
handleChooseDatabaseArchive = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
): Promise<DatabaseItem | undefined> => {
|
||||
try {
|
||||
return await this.chooseAndSetDatabase(false);
|
||||
return await this.chooseAndSetDatabase(false, progress, token);
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
handleChooseDatabaseInternet = async (): Promise<
|
||||
handleChooseDatabaseInternet = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
): Promise<
|
||||
DatabaseItem | undefined
|
||||
> => {
|
||||
return await promptImportInternetDatabase(
|
||||
this.databaseManager,
|
||||
this.storagePath
|
||||
this.storagePath,
|
||||
progress,
|
||||
token
|
||||
);
|
||||
};
|
||||
|
||||
handleChooseDatabaseLgtm = async (): Promise<DatabaseItem | undefined> => {
|
||||
handleChooseDatabaseLgtm = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
): Promise<DatabaseItem | undefined> => {
|
||||
return await promptImportLgtmDatabase(
|
||||
this.databaseManager,
|
||||
this.storagePath
|
||||
this.storagePath,
|
||||
progress,
|
||||
token
|
||||
);
|
||||
};
|
||||
|
||||
@@ -377,116 +420,115 @@ export class DatabaseUI extends DisposableObject {
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
multiSelect: DatabaseItem[] | undefined
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) => this.handleUpgradeDatabase(dbItem, []))
|
||||
);
|
||||
}
|
||||
if (this.queryServer === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but there is no running query server.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (databaseItem === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but no database was provided.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (databaseItem.contents === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but database contents could not be found.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (databaseItem.contents.dbSchemeUri === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but database has no schema.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for upgrade scripts in any workspace folders available
|
||||
const searchPath: string[] = getOnDiskWorkspaceFolders();
|
||||
|
||||
const upgradeInfo = await this.cliserver.resolveUpgrades(
|
||||
databaseItem.contents.dbSchemeUri.fsPath,
|
||||
searchPath
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) => this.handleUpgradeDatabase(dbItem, []))
|
||||
);
|
||||
|
||||
const { scripts, finalDbscheme } = upgradeInfo;
|
||||
|
||||
if (finalDbscheme === undefined) {
|
||||
logger.log('Could not determine target dbscheme to upgrade to.');
|
||||
return;
|
||||
}
|
||||
const targetDbSchemeUri = Uri.file(finalDbscheme);
|
||||
|
||||
await upgradeDatabase(
|
||||
this.queryServer,
|
||||
databaseItem,
|
||||
targetDbSchemeUri,
|
||||
getUpgradesDirectories(scripts)
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof UserCancellationException) {
|
||||
logger.log(e.message);
|
||||
} else throw e;
|
||||
}
|
||||
if (this.queryServer === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but there is no running query server.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (databaseItem === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but no database was provided.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (databaseItem.contents === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but database contents could not be found.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (databaseItem.contents.dbSchemeUri === undefined) {
|
||||
logger.log(
|
||||
'Received request to upgrade database, but database has no schema.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for upgrade scripts in any workspace folders available
|
||||
const searchPath: string[] = getOnDiskWorkspaceFolders();
|
||||
|
||||
const upgradeInfo = await this.cliserver.resolveUpgrades(
|
||||
databaseItem.contents.dbSchemeUri.fsPath,
|
||||
searchPath
|
||||
);
|
||||
|
||||
const { scripts, finalDbscheme } = upgradeInfo;
|
||||
|
||||
if (finalDbscheme === undefined) {
|
||||
logger.log('Could not determine target dbscheme to upgrade to.');
|
||||
return;
|
||||
}
|
||||
const targetDbSchemeUri = Uri.file(finalDbscheme);
|
||||
|
||||
await upgradeDatabase(
|
||||
this.queryServer,
|
||||
databaseItem,
|
||||
targetDbSchemeUri,
|
||||
getUpgradesDirectories(scripts)
|
||||
);
|
||||
};
|
||||
|
||||
private handleClearCache = async (): Promise<void> => {
|
||||
private handleClearCache = async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> => {
|
||||
if (
|
||||
this.queryServer !== undefined &&
|
||||
this.databaseManager.currentDatabaseItem !== undefined
|
||||
) {
|
||||
await clearCacheInDatabase(
|
||||
this.queryServer,
|
||||
this.databaseManager.currentDatabaseItem
|
||||
this.databaseManager.currentDatabaseItem,
|
||||
progress,
|
||||
token
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private handleSetCurrentDatabase = async (
|
||||
uri: Uri
|
||||
): Promise<DatabaseItem | undefined> => {
|
||||
uri: Uri,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
// Assume user has selected an archive if the file has a .zip extension
|
||||
if (uri.path.endsWith('.zip')) {
|
||||
return await importArchiveDatabase(
|
||||
await importArchiveDatabase(
|
||||
uri.toString(true),
|
||||
this.databaseManager,
|
||||
this.storagePath
|
||||
this.storagePath,
|
||||
progress,
|
||||
token
|
||||
);
|
||||
} else {
|
||||
await this.setCurrentDatabase(uri);
|
||||
}
|
||||
|
||||
return await this.setCurrentDatabase(uri);
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(
|
||||
// rethrow and let this be handled by default error handling.
|
||||
throw new Error(
|
||||
`Could not set database to ${path.basename(uri.fsPath)}. Reason: ${
|
||||
e.message
|
||||
}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
private handleRemoveDatabase = (
|
||||
private handleRemoveDatabase = async (
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined
|
||||
): void => {
|
||||
try {
|
||||
if (multiSelect?.length) {
|
||||
multiSelect.forEach((dbItem) =>
|
||||
this.databaseManager.removeDatabaseItem(dbItem)
|
||||
);
|
||||
} else {
|
||||
this.databaseManager.removeDatabaseItem(databaseItem);
|
||||
}
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
): Promise<void> => {
|
||||
if (multiSelect?.length) {
|
||||
multiSelect.forEach((dbItem) =>
|
||||
this.databaseManager.removeDatabaseItem(dbItem)
|
||||
);
|
||||
} else {
|
||||
this.databaseManager.removeDatabaseItem(databaseItem);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -494,19 +536,15 @@ export class DatabaseUI extends DisposableObject {
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined
|
||||
): Promise<void> => {
|
||||
try {
|
||||
this.assertSingleDatabase(multiSelect);
|
||||
this.assertSingleDatabase(multiSelect);
|
||||
|
||||
const newName = await window.showInputBox({
|
||||
prompt: 'Choose new database name',
|
||||
value: databaseItem.name,
|
||||
});
|
||||
const newName = await window.showInputBox({
|
||||
prompt: 'Choose new database name',
|
||||
value: databaseItem.name,
|
||||
});
|
||||
|
||||
if (newName) {
|
||||
this.databaseManager.renameDatabaseItem(databaseItem, newName);
|
||||
}
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
if (newName) {
|
||||
this.databaseManager.renameDatabaseItem(databaseItem, newName);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -514,16 +552,12 @@ export class DatabaseUI extends DisposableObject {
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) => env.openExternal(dbItem.databaseUri))
|
||||
);
|
||||
} else {
|
||||
await env.openExternal(databaseItem.databaseUri);
|
||||
}
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
if (multiSelect?.length) {
|
||||
await Promise.all(
|
||||
multiSelect.map((dbItem) => env.openExternal(dbItem.databaseUri))
|
||||
);
|
||||
} else {
|
||||
await env.openExternal(databaseItem.databaseUri);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -532,9 +566,12 @@ export class DatabaseUI extends DisposableObject {
|
||||
* current database, ask the user for one, and return that, or
|
||||
* undefined if they cancel.
|
||||
*/
|
||||
public async getDatabaseItem(): Promise<DatabaseItem | undefined> {
|
||||
public async getDatabaseItem(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
if (this.databaseManager.currentDatabaseItem === undefined) {
|
||||
await this.chooseAndSetDatabase(false);
|
||||
await this.chooseAndSetDatabase(false, progress, token);
|
||||
}
|
||||
|
||||
return this.databaseManager.currentDatabaseItem;
|
||||
@@ -557,7 +594,9 @@ export class DatabaseUI extends DisposableObject {
|
||||
* operation was canceled.
|
||||
*/
|
||||
private async chooseAndSetDatabase(
|
||||
byFolder: boolean
|
||||
byFolder: boolean,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const uri = await chooseDatabaseDir(byFolder);
|
||||
|
||||
@@ -575,7 +614,9 @@ export class DatabaseUI extends DisposableObject {
|
||||
return await importArchiveDatabase(
|
||||
uri.toString(true),
|
||||
this.databaseManager,
|
||||
this.storagePath
|
||||
this.storagePath,
|
||||
progress,
|
||||
token
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CancellationToken,
|
||||
commands,
|
||||
Disposable,
|
||||
ExtensionContext,
|
||||
@@ -18,7 +19,12 @@ import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
|
||||
import { AstViewer } from './astViewer';
|
||||
import * as archiveFilesystemProvider from './archive-filesystem-provider';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { DistributionConfigListener, MAX_QUERIES, QueryHistoryConfigListener, QueryServerConfigListener } from './config';
|
||||
import {
|
||||
DistributionConfigListener,
|
||||
MAX_QUERIES,
|
||||
QueryHistoryConfigListener,
|
||||
QueryServerConfigListener
|
||||
} from './config';
|
||||
import * as languageSupport from './languageSupport';
|
||||
import { DatabaseManager } from './databases';
|
||||
import { DatabaseUI } from './databases-ui';
|
||||
@@ -47,7 +53,7 @@ import { QueryHistoryManager } from './query-history';
|
||||
import { CompletedQuery } from './query-results';
|
||||
import * as qsClient from './queryserver-client';
|
||||
import { displayQuickQuery } from './quick-query';
|
||||
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './run-queries';
|
||||
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal } from './run-queries';
|
||||
import { QLTestAdapterFactory } from './test-adapter';
|
||||
import { TestUIService } from './test-ui';
|
||||
import { CompareInterfaceManager } from './compare/compare-interface';
|
||||
@@ -86,7 +92,7 @@ let isInstallingOrUpdatingDistribution = false;
|
||||
*
|
||||
* @param excludedCommands List of commands for which we should not register error stubs.
|
||||
*/
|
||||
function registerErrorStubs(excludedCommands: string[], stubGenerator: (command: string) => () => void): void {
|
||||
function registerErrorStubs(excludedCommands: string[], stubGenerator: (command: string) => () => Promise<void>): void {
|
||||
// Remove existing stubs
|
||||
errorStubs.forEach(stub => stub.dispose());
|
||||
|
||||
@@ -101,7 +107,7 @@ function registerErrorStubs(excludedCommands: string[], stubGenerator: (command:
|
||||
|
||||
stubbedCommands.forEach(command => {
|
||||
if (excludedCommands.indexOf(command) === -1) {
|
||||
errorStubs.push(commands.registerCommand(command, stubGenerator(command)));
|
||||
errorStubs.push(helpers.commandRunner(command, stubGenerator(command)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -119,9 +125,9 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
|
||||
|
||||
const shouldUpdateOnNextActivationKey = 'shouldUpdateOnNextActivation';
|
||||
|
||||
registerErrorStubs([checkForUpdatesCommand], command => () => {
|
||||
registerErrorStubs([checkForUpdatesCommand], command => (async () => {
|
||||
helpers.showAndLogErrorMessage(`Can't execute ${command}: waiting to finish loading CodeQL CLI.`);
|
||||
});
|
||||
}));
|
||||
|
||||
interface DistributionUpdateConfig {
|
||||
isUserInitiated: boolean;
|
||||
@@ -163,6 +169,8 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
|
||||
title: progressTitle,
|
||||
cancellable: false,
|
||||
};
|
||||
|
||||
// Avoid using commandRunner here because this function might be called upon extension activation
|
||||
await helpers.withProgress(progressOptions, progress =>
|
||||
distributionManager.installExtensionManagedDistributionRelease(result.updatedRelease, progress));
|
||||
|
||||
@@ -276,7 +284,7 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
|
||||
shouldDisplayMessageWhenNoUpdates: false,
|
||||
allowAutoUpdating: true
|
||||
})));
|
||||
ctx.subscriptions.push(commands.registerCommand(checkForUpdatesCommand, () => installOrUpdateThenTryActivate({
|
||||
ctx.subscriptions.push(helpers.commandRunner(checkForUpdatesCommand, () => installOrUpdateThenTryActivate({
|
||||
isUserInitiated: true,
|
||||
shouldDisplayMessageWhenNoUpdates: true,
|
||||
allowAutoUpdating: true
|
||||
@@ -389,40 +397,30 @@ async function activateWithInstalledDistribution(
|
||||
|
||||
async function compileAndRunQuery(
|
||||
quickEval: boolean,
|
||||
selectedQuery: Uri | undefined
|
||||
selectedQuery: Uri | undefined,
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<void> {
|
||||
if (qs !== undefined) {
|
||||
try {
|
||||
const dbItem = await databaseUI.getDatabaseItem();
|
||||
if (dbItem === undefined) {
|
||||
throw new Error('Can\'t run query without a selected database');
|
||||
}
|
||||
const info = await compileAndRunQueryAgainstDatabase(
|
||||
cliServer,
|
||||
qs,
|
||||
dbItem,
|
||||
quickEval,
|
||||
selectedQuery
|
||||
);
|
||||
const item = qhm.addQuery(info);
|
||||
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
|
||||
// The call to showResults potentially creates SARIF file;
|
||||
// Update the tree item context value to allow viewing that
|
||||
// SARIF file from context menu.
|
||||
await qhm.updateTreeItemContextValue(item);
|
||||
} catch (e) {
|
||||
if (e instanceof UserCancellationException) {
|
||||
if (e.silent) {
|
||||
logger.log(e.message);
|
||||
} else {
|
||||
helpers.showAndLogWarningMessage(e.message);
|
||||
}
|
||||
} else if (e instanceof Error) {
|
||||
helpers.showAndLogErrorMessage(e.message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
const dbItem = await databaseUI.getDatabaseItem(progress, token);
|
||||
if (dbItem === undefined) {
|
||||
throw new Error('Can\'t run query without a selected database');
|
||||
}
|
||||
const info = await compileAndRunQueryAgainstDatabase(
|
||||
cliServer,
|
||||
qs,
|
||||
dbItem,
|
||||
quickEval,
|
||||
selectedQuery,
|
||||
progress,
|
||||
token
|
||||
);
|
||||
const item = qhm.addQuery(info);
|
||||
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
|
||||
// The call to showResults potentially creates SARIF file;
|
||||
// Update the tree item context value to allow viewing that
|
||||
// SARIF file from context menu.
|
||||
await qhm.updateTreeItemContextValue(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,53 +459,99 @@ async function activateWithInstalledDistribution(
|
||||
|
||||
logger.log('Registering top-level command palette commands.');
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQL.runQuery',
|
||||
async (uri: Uri | undefined) => await compileAndRunQuery(false, uri)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
'codeQL.runQueries',
|
||||
async (_: Uri | undefined, multi: Uri[]) => {
|
||||
const maxQueryCount = MAX_QUERIES.getValue() as number;
|
||||
try {
|
||||
const [files, dirFound] = await gatherQlFiles(multi.map(uri => uri.fsPath));
|
||||
if (files.length > maxQueryCount) {
|
||||
throw new Error(`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`);
|
||||
}
|
||||
// warn user and display selected files when a directory is selected because some ql
|
||||
// files may be hidden from the user.
|
||||
if (dirFound) {
|
||||
const fileString = files.map(file => path.basename(file)).join(', ');
|
||||
const res = await helpers.showBinaryChoiceDialog(
|
||||
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`
|
||||
);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const queryUris = files.map(path => Uri.parse(`file:${path}`, true));
|
||||
await Promise.all(queryUris.map(uri => compileAndRunQuery(false, uri)));
|
||||
} catch (e) {
|
||||
helpers.showAndLogErrorMessage(e.message);
|
||||
}
|
||||
async (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined
|
||||
) => await compileAndRunQuery(false, uri, progress, token),
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Running query',
|
||||
cancellable: true
|
||||
}
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQL.runQueries',
|
||||
async (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
_: Uri | undefined,
|
||||
multi: Uri[]
|
||||
) => {
|
||||
const maxQueryCount = MAX_QUERIES.getValue() as number;
|
||||
const [files, dirFound] = await gatherQlFiles(multi.map(uri => uri.fsPath));
|
||||
if (files.length > maxQueryCount) {
|
||||
throw new Error(`You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`);
|
||||
}
|
||||
// warn user and display selected files when a directory is selected because some ql
|
||||
// files may be hidden from the user.
|
||||
if (dirFound) {
|
||||
const fileString = files.map(file => path.basename(file)).join(', ');
|
||||
const res = await helpers.showBinaryChoiceDialog(
|
||||
`You are about to run ${files.length} queries: ${fileString} Do you want to continue?`
|
||||
);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const queryUris = files.map(path => Uri.parse(`file:${path}`, true));
|
||||
|
||||
// Use a wrapped progress so that messages appear with the queries remaining in it.
|
||||
let queriesRemaining = queryUris.length;
|
||||
function wrappedProgress(update: helpers.ProgressUpdate) {
|
||||
const message = queriesRemaining > 1
|
||||
? `${queriesRemaining} remaining. ${update.message}`
|
||||
: update.message;
|
||||
progress({
|
||||
...update,
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
wrappedProgress({
|
||||
maxStep: queryUris.length,
|
||||
step: queryUris.length - queriesRemaining,
|
||||
message: ''
|
||||
});
|
||||
await Promise.all(queryUris.map(async uri =>
|
||||
compileAndRunQuery(false, uri, wrappedProgress, token)
|
||||
.then(() => queriesRemaining--)
|
||||
));
|
||||
},
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Running queries',
|
||||
cancellable: true
|
||||
})
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
helpers.commandRunner(
|
||||
'codeQL.quickEval',
|
||||
async (uri: Uri | undefined) => await compileAndRunQuery(true, uri)
|
||||
async (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined
|
||||
) => await compileAndRunQuery(true, uri, progress, token),
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Running query',
|
||||
cancellable: true
|
||||
})
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
helpers.commandRunner('codeQL.quickQuery', async (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken
|
||||
) =>
|
||||
displayQuickQuery(ctx, cliServer, databaseUI, progress, token)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand('codeQL.quickQuery', async () =>
|
||||
displayQuickQuery(ctx, cliServer, databaseUI)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand('codeQL.restartQueryServer', async () => {
|
||||
helpers.commandRunner('codeQL.restartQueryServer', async () => {
|
||||
await qs.restartQueryServer();
|
||||
helpers.showAndLogInformationMessage('CodeQL Query Server restarted.', {
|
||||
outputLogger: queryServerLogger,
|
||||
@@ -515,24 +559,45 @@ async function activateWithInstalledDistribution(
|
||||
})
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand('codeQL.chooseDatabaseFolder', () =>
|
||||
databaseUI.handleChooseDatabaseFolder()
|
||||
helpers.commandRunner('codeQL.chooseDatabaseFolder', (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken
|
||||
) =>
|
||||
databaseUI.handleChooseDatabaseFolder(progress, token)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand('codeQL.chooseDatabaseArchive', () =>
|
||||
databaseUI.handleChooseDatabaseArchive()
|
||||
helpers.commandRunner('codeQL.chooseDatabaseArchive', (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken
|
||||
) =>
|
||||
databaseUI.handleChooseDatabaseArchive(progress, token)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand('codeQL.chooseDatabaseLgtm', () =>
|
||||
databaseUI.handleChooseDatabaseLgtm()
|
||||
)
|
||||
helpers.commandRunner('codeQL.chooseDatabaseLgtm', (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken
|
||||
) =>
|
||||
databaseUI.handleChooseDatabaseLgtm(progress, token),
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Adding database from LGTM',
|
||||
cancellable: false,
|
||||
})
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commands.registerCommand('codeQL.chooseDatabaseInternet', () =>
|
||||
databaseUI.handleChooseDatabaseInternet()
|
||||
)
|
||||
helpers.commandRunner('codeQL.chooseDatabaseInternet', (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken
|
||||
) =>
|
||||
databaseUI.handleChooseDatabaseInternet(progress, token),
|
||||
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
title: 'Adding database from URL',
|
||||
cancellable: false,
|
||||
})
|
||||
);
|
||||
|
||||
logger.log('Starting language server.');
|
||||
@@ -544,18 +609,26 @@ async function activateWithInstalledDistribution(
|
||||
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
|
||||
new TemplateQueryDefinitionProvider(cliServer, qs, dbm)
|
||||
);
|
||||
|
||||
languages.registerReferenceProvider(
|
||||
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
|
||||
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
|
||||
);
|
||||
|
||||
const astViewer = new AstViewer(ctx);
|
||||
ctx.subscriptions.push(commands.registerCommand('codeQL.viewAst', async () => {
|
||||
const ast = await new TemplatePrintAstProvider(cliServer, qs, dbm)
|
||||
ctx.subscriptions.push(helpers.commandRunner('codeQL.viewAst', async (
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken
|
||||
) => {
|
||||
const ast = await new TemplatePrintAstProvider(cliServer, qs, dbm, progress, token)
|
||||
.provideAst(window.activeTextEditor?.document);
|
||||
if (ast) {
|
||||
astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);
|
||||
}
|
||||
}, {
|
||||
location: ProgressLocation.Notification,
|
||||
cancellable: true,
|
||||
title: 'Calculate AST'
|
||||
}));
|
||||
|
||||
logger.log('Successfully finished extension initialization.');
|
||||
|
||||
@@ -7,11 +7,23 @@ import {
|
||||
ExtensionContext,
|
||||
ProgressOptions,
|
||||
window as Window,
|
||||
workspace
|
||||
workspace,
|
||||
commands,
|
||||
Disposable
|
||||
} from 'vscode';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { logger } from './logging';
|
||||
|
||||
export class UserCancellationException extends Error {
|
||||
/**
|
||||
* @param message The error message
|
||||
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
|
||||
*/
|
||||
constructor(message?: string, public readonly silent = false) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProgressUpdate {
|
||||
/**
|
||||
* The current step
|
||||
@@ -29,18 +41,34 @@ export interface ProgressUpdate {
|
||||
|
||||
export type ProgressCallback = (p: ProgressUpdate) => void;
|
||||
|
||||
export type ProgressTask<R> = (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
...args: any[]
|
||||
) => Thenable<R>;
|
||||
|
||||
type NoProgressTask = ((...args: any[]) => Promise<any>);
|
||||
|
||||
/**
|
||||
* This mediates between the kind of progress callbacks we want to
|
||||
* write (where we *set* current progress position and give
|
||||
* `maxSteps`) and the kind vscode progress api expects us to write
|
||||
* (which increment progress by a certain amount out of 100%)
|
||||
* (which increment progress by a certain amount out of 100%).
|
||||
*
|
||||
* Where possible, the `commandRunner` function below should be used
|
||||
* instead of this function. The commandRunner is meant for wrapping
|
||||
* top-level commands and provides error handling and other support
|
||||
* automatically.
|
||||
*
|
||||
* Only use this function if you need a progress monitor and the
|
||||
* control flow does not always come from a command (eg- during
|
||||
* extension activation, or from an internal language server
|
||||
* request).
|
||||
*/
|
||||
export function withProgress<R>(
|
||||
options: ProgressOptions,
|
||||
task: (
|
||||
progress: (p: ProgressUpdate) => void,
|
||||
token: CancellationToken
|
||||
) => Thenable<R>
|
||||
task: ProgressTask<R>,
|
||||
...args: any[]
|
||||
): Thenable<R> {
|
||||
let progressAchieved = 0;
|
||||
return Window.withProgress(options,
|
||||
@@ -50,10 +78,58 @@ export function withProgress<R>(
|
||||
const increment = 100 * (step - progressAchieved) / maxStep;
|
||||
progressAchieved = step;
|
||||
progress.report({ message, increment });
|
||||
}, token);
|
||||
}, token, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic wrapper for commands. This wrapper adds error handling and progress monitoring
|
||||
* for any command.
|
||||
*
|
||||
* There are two ways to invoke the command task: with or without a progress monitor
|
||||
* If progressOptions are passed in, then the command task will run with a progress monitor
|
||||
* Otherwise, no progress monitor will be used.
|
||||
*
|
||||
* If a task is run with a progress monitor, the first two arguments to the task are always
|
||||
* the progress callback, and the cancellation token. And this is followed by any extra command arguments
|
||||
* (eg- selection, multiselection, ...).
|
||||
*
|
||||
* If there is no progress monitor, then only extra command arguments are passed in.
|
||||
*
|
||||
* @param commandId The ID of the command to register.
|
||||
* @param task The task to run. If passing taskOptions, then this task must be a `ProgressTask`.
|
||||
* @param progressOptions Optional argument. If present, then the task is run with a progress monitor
|
||||
* and cancellation token, otherwise it is run with no arguments.
|
||||
*/
|
||||
export function commandRunner<R>(
|
||||
commandId: string,
|
||||
task: ProgressTask<R> | NoProgressTask,
|
||||
progressOptions?: ProgressOptions
|
||||
): Disposable {
|
||||
return commands.registerCommand(commandId, async (...args: any[]) => {
|
||||
try {
|
||||
if (progressOptions) {
|
||||
await withProgress(progressOptions, task as ProgressTask<R>, ...args);
|
||||
} else {
|
||||
await (task as ((...args: any[]) => Promise<any>))(...args);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof UserCancellationException) {
|
||||
// User has cancelled this action manually
|
||||
if (e.silent) {
|
||||
logger.log(e.message);
|
||||
} else {
|
||||
showAndLogWarningMessage(e.message);
|
||||
}
|
||||
} else if (e instanceof Error) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error message and log it to the console
|
||||
*
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
RawResultsSortState,
|
||||
} from './interface-types';
|
||||
import { Logger } from './logging';
|
||||
import { commandRunner } from './helpers';
|
||||
import * as messages from './messages';
|
||||
import { CompletedQuery, interpretResults } from './query-results';
|
||||
import { QueryInfo, tmpDir } from './run-queries';
|
||||
@@ -121,13 +122,13 @@ export class InterfaceManager extends DisposableObject {
|
||||
);
|
||||
logger.log('Registering path-step navigation commands.');
|
||||
this.push(
|
||||
vscode.commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLQueryResults.nextPathStep',
|
||||
this.navigatePathStep.bind(this, 1)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
vscode.commands.registerCommand(
|
||||
commandRunner(
|
||||
'codeQLQueryResults.previousPathStep',
|
||||
this.navigatePathStep.bind(this, -1)
|
||||
)
|
||||
@@ -145,7 +146,7 @@ export class InterfaceManager extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
navigatePathStep(direction: number): void {
|
||||
async navigatePathStep(direction: number): Promise<void> {
|
||||
this.postMessage({ t: 'navigatePath', direction });
|
||||
}
|
||||
|
||||
|
||||
@@ -209,51 +209,51 @@ export class QueryHistoryManager {
|
||||
});
|
||||
logger.log('Registering query history panel commands.');
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.openQuery',
|
||||
this.handleOpenQuery.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.removeHistoryItem',
|
||||
this.handleRemoveHistoryItem.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.setLabel',
|
||||
this.handleSetLabel.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.compareWith',
|
||||
this.handleCompareWith.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.showQueryLog',
|
||||
this.handleShowQueryLog.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.showQueryText',
|
||||
this.handleShowQueryText.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.viewSarif',
|
||||
this.handleViewSarif.bind(this)
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
helpers.commandRunner(
|
||||
'codeQLQueryHistory.itemClicked',
|
||||
async (item) => {
|
||||
async (item: CompletedQuery) => {
|
||||
return this.handleItemClicked(item, [item]);
|
||||
}
|
||||
)
|
||||
@@ -424,22 +424,18 @@ export class QueryHistoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const queryName = singleItem.queryName.endsWith('.ql')
|
||||
? singleItem.queryName
|
||||
: singleItem.queryName + '.ql';
|
||||
const params = new URLSearchParams({
|
||||
isQuickEval: String(!!singleItem.query.quickEvalPosition),
|
||||
queryText: encodeURIComponent(await this.getQueryText(singleItem)),
|
||||
});
|
||||
const uri = vscode.Uri.parse(
|
||||
`codeql:${singleItem.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);
|
||||
}
|
||||
const queryName = singleItem.queryName.endsWith('.ql')
|
||||
? singleItem.queryName
|
||||
: singleItem.queryName + '.ql';
|
||||
const params = new URLSearchParams({
|
||||
isQuickEval: String(!!singleItem.query.quickEvalPosition),
|
||||
queryText: encodeURIComponent(await this.getQueryText(singleItem)),
|
||||
});
|
||||
const uri = vscode.Uri.parse(
|
||||
`codeql:${singleItem.query.queryID}-${queryName}?${params.toString()}`
|
||||
);
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
await vscode.window.showTextDocument(doc, { preview: false });
|
||||
}
|
||||
|
||||
async handleViewSarif(
|
||||
@@ -450,20 +446,16 @@ export class QueryHistoryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const hasInterpretedResults = await singleItem.query.canHaveInterpretedResults();
|
||||
if (hasInterpretedResults) {
|
||||
await this.tryOpenExternalFile(
|
||||
singleItem.query.resultsPaths.interpretedResultsPath
|
||||
);
|
||||
} else {
|
||||
const label = singleItem.getLabel();
|
||||
helpers.showAndLogInformationMessage(
|
||||
`Query ${label} has no interpreted results.`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
helpers.showAndLogErrorMessage(e.message);
|
||||
const hasInterpretedResults = await singleItem.query.canHaveInterpretedResults();
|
||||
if (hasInterpretedResults) {
|
||||
await this.tryOpenExternalFile(
|
||||
singleItem.query.resultsPaths.interpretedResultsPath
|
||||
);
|
||||
} else {
|
||||
const label = singleItem.getLabel();
|
||||
helpers.showAndLogInformationMessage(
|
||||
`Query ${label} has no interpreted results.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as path from 'path';
|
||||
import { ExtensionContext, window as Window, workspace, Uri } from 'vscode';
|
||||
import { CancellationToken, ExtensionContext, window as Window, workspace, Uri } from 'vscode';
|
||||
import { ErrorCodes, ResponseError } from 'vscode-languageclient';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { DatabaseUI } from './databases-ui';
|
||||
import * as helpers from './helpers';
|
||||
import { logger } from './logging';
|
||||
import { UserCancellationException } from './run-queries';
|
||||
|
||||
const QUICK_QUERIES_DIR_NAME = 'quick-queries';
|
||||
const QUICK_QUERY_QUERY_NAME = 'quick-query.ql';
|
||||
@@ -48,7 +47,13 @@ function getQuickQueriesDir(ctx: ExtensionContext): string {
|
||||
/**
|
||||
* Show a buffer the user can enter a simple query into.
|
||||
*/
|
||||
export async function displayQuickQuery(ctx: ExtensionContext, cliServer: CodeQLCliServer, databaseUI: DatabaseUI) {
|
||||
export async function displayQuickQuery(
|
||||
ctx: ExtensionContext,
|
||||
cliServer: CodeQLCliServer,
|
||||
databaseUI: DatabaseUI,
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken
|
||||
) {
|
||||
|
||||
function updateQuickQueryDir(queriesDir: string, index: number, len: number) {
|
||||
workspace.updateWorkspaceFolders(
|
||||
@@ -94,7 +99,7 @@ export async function displayQuickQuery(ctx: ExtensionContext, cliServer: CodeQL
|
||||
updateQuickQueryDir(queriesDir, index, 1);
|
||||
|
||||
// We're going to infer which qlpack to use from the current database
|
||||
const dbItem = await databaseUI.getDatabaseItem();
|
||||
const dbItem = await databaseUI.getDatabaseItem(progress, token);
|
||||
if (dbItem === undefined) {
|
||||
throw new Error('Can\'t start quick query without a selected database');
|
||||
}
|
||||
@@ -116,7 +121,7 @@ export async function displayQuickQuery(ctx: ExtensionContext, cliServer: CodeQL
|
||||
|
||||
// TODO: clean up error handling for top-level commands like this
|
||||
catch (e) {
|
||||
if (e instanceof UserCancellationException) {
|
||||
if (e instanceof helpers.UserCancellationException) {
|
||||
logger.log(e.message);
|
||||
}
|
||||
else if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {
|
||||
|
||||
@@ -2,7 +2,14 @@ import * as crypto from 'crypto';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as tmp from 'tmp';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
CancellationToken,
|
||||
ConfigurationTarget,
|
||||
TextDocument,
|
||||
TextEditor,
|
||||
Uri,
|
||||
window
|
||||
} from 'vscode';
|
||||
import { ErrorCodes, ResponseError } from 'vscode-languageclient';
|
||||
|
||||
import * as cli from './cli';
|
||||
@@ -34,16 +41,6 @@ export const tmpDirDisposal = {
|
||||
}
|
||||
};
|
||||
|
||||
export class UserCancellationException extends Error {
|
||||
/**
|
||||
* @param message The error message
|
||||
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
|
||||
*/
|
||||
constructor(message?: string, public readonly silent = false) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of evaluation-time information about a query,
|
||||
* including the query itself, and where we have decided to put
|
||||
@@ -55,7 +52,7 @@ export class QueryInfo {
|
||||
|
||||
readonly compiledQueryPath: string;
|
||||
readonly resultsPaths: ResultsPaths;
|
||||
readonly dataset: vscode.Uri; // guarantee the existence of a well-defined dataset dir at this point
|
||||
readonly dataset: Uri; // guarantee the existence of a well-defined dataset dir at this point
|
||||
readonly queryID: number;
|
||||
|
||||
constructor(
|
||||
@@ -80,6 +77,8 @@ export class QueryInfo {
|
||||
|
||||
async run(
|
||||
qs: qsClient.QueryServerClient,
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<messages.EvaluationResult> {
|
||||
let result: messages.EvaluationResult | null = null;
|
||||
|
||||
@@ -87,7 +86,7 @@ export class QueryInfo {
|
||||
|
||||
const queryToRun: messages.QueryToRun = {
|
||||
resultsPath: this.resultsPaths.resultsPath,
|
||||
qlo: vscode.Uri.file(this.compiledQueryPath).toString(),
|
||||
qlo: Uri.file(this.compiledQueryPath).toString(),
|
||||
allowUnknownTemplates: true,
|
||||
templateValues: this.templates,
|
||||
id: callbackId,
|
||||
@@ -105,13 +104,7 @@ export class QueryInfo {
|
||||
useSequenceHint: false
|
||||
};
|
||||
try {
|
||||
await helpers.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Running Query',
|
||||
cancellable: true,
|
||||
}, (progress, token) => {
|
||||
return qs.sendRequest(messages.runQueries, params, token, progress);
|
||||
});
|
||||
await qs.sendRequest(messages.runQueries, params, token, progress);
|
||||
} finally {
|
||||
qs.unRegisterCallback(callbackId);
|
||||
}
|
||||
@@ -126,6 +119,8 @@ export class QueryInfo {
|
||||
|
||||
async compile(
|
||||
qs: qsClient.QueryServerClient,
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<messages.CompilationMessage[]> {
|
||||
let compiled: messages.CheckQueryResult | undefined;
|
||||
try {
|
||||
@@ -150,13 +145,7 @@ export class QueryInfo {
|
||||
target,
|
||||
};
|
||||
|
||||
compiled = await helpers.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Compiling Query',
|
||||
cancellable: true,
|
||||
}, (progress, token) => {
|
||||
return qs.sendRequest(messages.compileQuery, params, token, progress);
|
||||
});
|
||||
compiled = await qs.sendRequest(messages.compileQuery, params, token, progress);
|
||||
} finally {
|
||||
qs.logger.log(' - - - COMPILATION DONE - - - ');
|
||||
}
|
||||
@@ -192,7 +181,10 @@ export interface QueryWithResults {
|
||||
}
|
||||
|
||||
export async function clearCacheInDatabase(
|
||||
qs: qsClient.QueryServerClient, dbItem: DatabaseItem
|
||||
qs: qsClient.QueryServerClient,
|
||||
dbItem: DatabaseItem,
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
): Promise<messages.ClearCacheResult> {
|
||||
if (dbItem.contents === undefined) {
|
||||
throw new Error('Can\'t clear the cache in an invalid database.');
|
||||
@@ -208,13 +200,7 @@ export async function clearCacheInDatabase(
|
||||
db,
|
||||
};
|
||||
|
||||
return helpers.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Clearing Cache',
|
||||
cancellable: false,
|
||||
}, (progress, token) =>
|
||||
qs.sendRequest(messages.clearCache, params, token, progress)
|
||||
);
|
||||
return qs.sendRequest(messages.clearCache, params, token, progress);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +236,7 @@ async function convertToQlPath(filePath: string): Promise<string> {
|
||||
|
||||
|
||||
/** Gets the selected position within the given editor. */
|
||||
async function getSelectedPosition(editor: vscode.TextEditor): Promise<messages.Position> {
|
||||
async function getSelectedPosition(editor: TextEditor): Promise<messages.Position> {
|
||||
const pos = editor.selection.start;
|
||||
const posEnd = editor.selection.end;
|
||||
// Convert from 0-based to 1-based line and column numbers.
|
||||
@@ -306,7 +292,7 @@ async function checkDbschemeCompatibility(
|
||||
await upgradeDatabase(
|
||||
qs,
|
||||
query.dbItem,
|
||||
vscode.Uri.file(finalDbscheme),
|
||||
Uri.file(finalDbscheme),
|
||||
getUpgradesDirectories(scripts)
|
||||
);
|
||||
}
|
||||
@@ -321,7 +307,7 @@ async function checkDbschemeCompatibility(
|
||||
* @returns true if we should save changes and false if we should continue without saving changes.
|
||||
* @throws UserCancellationException if we should abort whatever operation triggered this prompt
|
||||
*/
|
||||
async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<boolean> {
|
||||
async function promptUserToSaveChanges(document: TextDocument): Promise<boolean> {
|
||||
if (document.isDirty) {
|
||||
if (config.AUTOSAVE_SETTING.getValue()) {
|
||||
return true;
|
||||
@@ -332,14 +318,14 @@ async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<b
|
||||
const noItem = { title: 'No (run anyway)', isCloseAffordance: false };
|
||||
const cancelItem = { title: 'Cancel', isCloseAffordance: true };
|
||||
const message = 'Query file has unsaved changes. Save now?';
|
||||
const chosenItem = await vscode.window.showInformationMessage(
|
||||
const chosenItem = await window.showInformationMessage(
|
||||
message,
|
||||
{ modal: true },
|
||||
yesItem, alwaysItem, noItem, cancelItem
|
||||
);
|
||||
|
||||
if (chosenItem === alwaysItem) {
|
||||
await config.AUTOSAVE_SETTING.updateValue(true, vscode.ConfigurationTarget.Workspace);
|
||||
await config.AUTOSAVE_SETTING.updateValue(true, ConfigurationTarget.Workspace);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -348,7 +334,7 @@ async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<b
|
||||
}
|
||||
|
||||
if (chosenItem === cancelItem) {
|
||||
throw new UserCancellationException('Query run cancelled.', true);
|
||||
throw new helpers.UserCancellationException('Query run cancelled.', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,11 +357,11 @@ type SelectedQuery = {
|
||||
* @param selectedResourceUri The selected resource when the command was run.
|
||||
* @param quickEval Whether the command being run is `Quick Evaluation`.
|
||||
*/
|
||||
export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | undefined, quickEval: boolean): Promise<SelectedQuery> {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
export async function determineSelectedQuery(selectedResourceUri: Uri | undefined, quickEval: boolean): Promise<SelectedQuery> {
|
||||
const editor = window.activeTextEditor;
|
||||
|
||||
// Choose which QL file to use.
|
||||
let queryUri: vscode.Uri;
|
||||
let queryUri: Uri;
|
||||
if (selectedResourceUri === undefined) {
|
||||
// No resource was passed to the command handler, so obtain it from the active editor.
|
||||
// This usually happens when the command is called from the Command Palette.
|
||||
@@ -437,7 +423,9 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
qs: qsClient.QueryServerClient,
|
||||
db: DatabaseItem,
|
||||
quickEval: boolean,
|
||||
selectedQueryUri: vscode.Uri | undefined,
|
||||
selectedQueryUri: Uri | undefined,
|
||||
progress: helpers.ProgressCallback,
|
||||
token: CancellationToken,
|
||||
templates?: messages.TemplateDefinitions,
|
||||
): Promise<QueryWithResults> {
|
||||
if (!db.contents || !db.contents.dbSchemeUri) {
|
||||
@@ -497,7 +485,7 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
|
||||
let errors;
|
||||
try {
|
||||
errors = await query.compile(qs);
|
||||
errors = await query.compile(qs, progress, token);
|
||||
} catch (e) {
|
||||
if (e instanceof ResponseError && e.code == ErrorCodes.RequestCancelled) {
|
||||
return createSyntheticResult(query, db, historyItemOptions, 'Query cancelled', messages.QueryResultType.CANCELLATION);
|
||||
@@ -507,7 +495,7 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
}
|
||||
|
||||
if (errors.length == 0) {
|
||||
const result = await query.run(qs);
|
||||
const result = await query.run(qs, progress, token);
|
||||
if (result.resultType !== messages.QueryResultType.SUCCESS) {
|
||||
const message = result.message || 'Failed to run query';
|
||||
logger.log(message);
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as helpers from './helpers';
|
||||
import { logger } from './logging';
|
||||
import * as messages from './messages';
|
||||
import * as qsClient from './queryserver-client';
|
||||
import { upgradesTmpDir, UserCancellationException } from './run-queries';
|
||||
import { upgradesTmpDir } from './run-queries';
|
||||
|
||||
/**
|
||||
* Maximum number of lines to include from database upgrade message,
|
||||
@@ -101,7 +101,7 @@ async function checkAndConfirmDatabaseUpgrade(
|
||||
return params;
|
||||
}
|
||||
else {
|
||||
throw new UserCancellationException('User cancelled the database upgrade.');
|
||||
throw new helpers.UserCancellationException('User cancelled the database upgrade.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,9 @@ async function checkAndConfirmDatabaseUpgrade(
|
||||
* Reports errors during compilation and evaluation of upgrades to the user.
|
||||
*/
|
||||
export async function upgradeDatabase(
|
||||
qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]
|
||||
qs: qsClient.QueryServerClient,
|
||||
db: DatabaseItem, targetDbScheme: vscode.Uri,
|
||||
upgradesDirectories: vscode.Uri[]
|
||||
): Promise<messages.RunUpgradeResult | undefined> {
|
||||
const upgradeParams = await checkAndConfirmDatabaseUpgrade(qs, db, targetDbScheme, upgradesDirectories);
|
||||
|
||||
@@ -155,6 +157,7 @@ export async function upgradeDatabase(
|
||||
async function checkDatabaseUpgrade(
|
||||
qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams
|
||||
): Promise<messages.CheckUpgradeResult> {
|
||||
// Avoid using commandRunner here because this function might be called upon extension activation
|
||||
return helpers.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Checking for database upgrades',
|
||||
@@ -170,6 +173,7 @@ async function compileDatabaseUpgrade(
|
||||
upgradeTempDir: upgradesTmpDir.name
|
||||
};
|
||||
|
||||
// Avoid using commandRunner here because this function might be called upon extension activation
|
||||
return helpers.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Compiling database upgrades',
|
||||
@@ -195,6 +199,7 @@ async function runDatabaseUpgrade(
|
||||
toRun: upgrades
|
||||
};
|
||||
|
||||
// Avoid using commandRunner here because this function might be called upon extension activation
|
||||
return helpers.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Running database upgrades',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { commands, TreeDataProvider, window } from 'vscode';
|
||||
import { TreeDataProvider, window } from 'vscode';
|
||||
import { DisposableObject } from './disposable-object';
|
||||
import { commandRunner } from '../helpers';
|
||||
|
||||
/**
|
||||
* A VS Code service that interacts with the UI, including handling commands.
|
||||
@@ -16,7 +17,7 @@ export class UIService extends DisposableObject {
|
||||
* @remarks The command handler is automatically unregistered when the service is disposed.
|
||||
*/
|
||||
protected registerCommand(command: string, callback: (...args: any[]) => any): void {
|
||||
this.push(commands.registerCommand(command, callback, this));
|
||||
this.push(commandRunner(command, callback.bind(this)));
|
||||
}
|
||||
|
||||
protected registerTreeDataProvider<T>(viewId: string, treeDataProvider: TreeDataProvider<T>):
|
||||
@@ -24,4 +25,4 @@ export class UIService extends DisposableObject {
|
||||
|
||||
this.push(window.registerTreeDataProvider<T>(viewId, treeDataProvider));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user