Files
azuredatastudio/extensions/query-history/src/main.ts
2023-03-08 10:51:14 -08:00

148 lines
5.9 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { DOUBLE_CLICK_ACTION_CONFIG_SECTION, ITEM_SELECTED_COMMAND_ID, QUERY_HISTORY_CONFIG_SECTION } from './constants';
import { QueryHistoryItem } from './queryHistoryItem';
import { QueryHistoryProvider, setLoadingContext } from './queryHistoryProvider';
import { promises as fs } from 'fs';
import { sendSettingChangedEvent, TelemetryActions, TelemetryReporter, TelemetryViews } from './telemetry';
type DoubleClickAction = 'open' | 'run';
const DEFAULT_DOUBLECLICK_ACTION: DoubleClickAction = 'open';
let doubleClickAction: string = DEFAULT_DOUBLECLICK_ACTION;
let lastSelectedItem: { item: QueryHistoryItem | undefined, time: number | undefined } = {
item: undefined,
time: undefined
};
/**
* The time in ms between clicks to count as a double click on our tree view items
*/
const DOUBLE_CLICK_TIMEOUT_MS = 500;
export async function activate(context: vscode.ExtensionContext): Promise<void> {
// Create the global storage folder now for storing the query history persistance file
const storageUri = context.globalStorageUri;
try {
await fs.mkdir(storageUri.fsPath);
} catch (err) {
if (err.code !== 'EEXIST') {
TelemetryReporter.sendErrorEvent2(TelemetryViews.QueryHistory, 'CreatingStorageFolder', err);
console.error(`Error creating query history global storage folder ${context.globalStorageUri.fsPath}. ${err}`);
}
}
await setLoadingContext(true);
const treeDataProvider = new QueryHistoryProvider(context, storageUri);
context.subscriptions.push(treeDataProvider);
const treeView = vscode.window.createTreeView('queryHistory', {
treeDataProvider,
canSelectMany: false
});
context.subscriptions.push(treeView);
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => {
if (e.affectsConfiguration(`${QUERY_HISTORY_CONFIG_SECTION}.${DOUBLE_CLICK_ACTION_CONFIG_SECTION}`)) {
const newDoubleClickAction = getDoubleClickAction();
if (newDoubleClickAction !== doubleClickAction) {
sendSettingChangedEvent('DoubleClickAction', doubleClickAction, newDoubleClickAction);
}
doubleClickAction = newDoubleClickAction;
}
}));
// This is an internal-only command so not adding to package.json
context.subscriptions.push(vscode.commands.registerCommand(ITEM_SELECTED_COMMAND_ID, async (selectedItem: QueryHistoryItem) => {
// VS Code doesn't provide a native way to detect a double-click so we track it ourselves by keeping track of the last item clicked and
// when it was clicked to compare, then if a click happens on the same element quickly enough we trigger the configured action
const clickTime = new Date().getTime();
if (lastSelectedItem.item === selectedItem && lastSelectedItem.time && (clickTime - lastSelectedItem.time) < DOUBLE_CLICK_TIMEOUT_MS) {
TelemetryReporter.sendActionEvent(TelemetryViews.QueryHistory, TelemetryActions.DoubleClick, doubleClickAction);
switch (doubleClickAction) {
case 'run':
await runQuery(selectedItem);
break;
case 'open':
default:
await openQuery(selectedItem);
break;
}
// Clear out the last selected item so we don't run the command again on a 3rd click
lastSelectedItem = {
item: undefined,
time: undefined
};
} else {
// Update the last selected item since we didn't run a command
lastSelectedItem = {
item: selectedItem,
time: clickTime
};
}
}));
context.subscriptions.push(vscode.commands.registerCommand('queryHistory.open', async (item: QueryHistoryItem) => {
return openQuery(item);
}));
context.subscriptions.push(vscode.commands.registerCommand('queryHistory.run', async (item: QueryHistoryItem) => {
return runQuery(item);
}));
context.subscriptions.push(vscode.commands.registerCommand('queryHistory.delete', (item: QueryHistoryItem) => {
return treeDataProvider.deleteItem(item);
}));
context.subscriptions.push(vscode.commands.registerCommand('queryHistory.clear', () => {
return treeDataProvider.clearAll();
}));
context.subscriptions.push(vscode.commands.registerCommand('queryHistory.disableCapture', async () => {
return treeDataProvider.setCaptureEnabled(false);
}));
context.subscriptions.push(vscode.commands.registerCommand('queryHistory.enableCapture', async () => {
return treeDataProvider.setCaptureEnabled(true);
}));
context.subscriptions.push(vscode.commands.registerCommand('queryHistory.openStorageFolder', async () => {
return vscode.env.openExternal(storageUri);
}));
context.subscriptions.push(TelemetryReporter);
}
async function openQuery(item: QueryHistoryItem): Promise<void> {
try {
await azdata.queryeditor.openQueryDocument(
{
content: item.queryText
}, item.connectionProfile?.providerId);
} catch (err) {
TelemetryReporter.sendErrorEvent2(TelemetryViews.QueryHistory, 'OpenQuery');
}
}
async function runQuery(item: QueryHistoryItem): Promise<void> {
let step = 'OpenDoc';
try {
const doc = await azdata.queryeditor.openQueryDocument(
{
content: item.queryText
}, item.connectionProfile?.providerId);
if (item.connectionProfile) {
step = 'ConnectWithProfile';
await doc.connect(item.connectionProfile);
} else {
step = 'ConnectWithoutProfile';
await azdata.queryeditor.connect(doc.uri, '');
}
step = 'Run';
azdata.queryeditor.runQuery(doc.uri);
} catch (err) {
TelemetryReporter.createErrorEvent2(TelemetryViews.QueryHistory, 'RunQuery', err)
.withAdditionalProperties({ step })
.send();
}
}
export function getDoubleClickAction(): string {
return vscode.workspace.getConfiguration(QUERY_HISTORY_CONFIG_SECTION).get<string>(DOUBLE_CLICK_ACTION_CONFIG_SECTION, DEFAULT_DOUBLECLICK_ACTION);
}