Skip to content

Commit 68ab2fd

Browse files
committed
Create new queries in selected folder of queries panel
This will change the behavior of the "Create new query" command to create the new query in the same folder as the first selected item in the queries panel. If no items are selected, the behavior is the same as before. I've used events to communicate the selection from the queries panel to the local queries module. This is some more code and some extra complexity, but it ensures that we don't have a dependency from the local queries module to the queries panel module. This makes testing easier.
1 parent fb33879 commit 68ab2fd

File tree

6 files changed

+229
-16
lines changed

6 files changed

+229
-16
lines changed

extensions/ql-vscode/src/extension.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,11 @@ async function activateWithInstalledDistribution(
795795
);
796796
ctx.subscriptions.push(databaseUI);
797797

798-
QueriesModule.initialize(app, languageContext, cliServer);
798+
const queriesModule = QueriesModule.initialize(
799+
app,
800+
languageContext,
801+
cliServer,
802+
);
799803

800804
void extLogger.log("Initializing evaluator log viewer.");
801805
const evalLogViewer = new EvalLogViewer();
@@ -934,6 +938,10 @@ async function activateWithInstalledDistribution(
934938
);
935939
ctx.subscriptions.push(localQueries);
936940

941+
queriesModule.onDidChangeSelection((event) =>
942+
localQueries.setSelectedQueryTreeViewItems(event.selection),
943+
);
944+
937945
void extLogger.log("Initializing debugger factory.");
938946
ctx.subscriptions.push(
939947
new QLDebugAdapterDescriptorFactory(queryStorageDir, qs, localQueries),

extensions/ql-vscode/src/local-queries/local-queries.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export enum QuickEvalType {
6363
}
6464

6565
export class LocalQueries extends DisposableObject {
66+
private selectedQueryTreeViewItems: readonly QueryTreeViewItem[] = [];
67+
6668
public constructor(
6769
private readonly app: App,
6870
private readonly queryRunner: QueryRunner,
@@ -77,6 +79,12 @@ export class LocalQueries extends DisposableObject {
7779
super();
7880
}
7981

82+
public setSelectedQueryTreeViewItems(
83+
selection: readonly QueryTreeViewItem[],
84+
) {
85+
this.selectedQueryTreeViewItems = selection;
86+
}
87+
8088
public getCommands(): LocalQueryCommands {
8189
return {
8290
"codeQL.runQuery": this.runQuery.bind(this),
@@ -333,6 +341,7 @@ export class LocalQueries extends DisposableObject {
333341
this.app.logger,
334342
this.databaseManager,
335343
contextStoragePath,
344+
this.selectedQueryTreeViewItems,
336345
language,
337346
);
338347
await skeletonQueryWizard.execute();

extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { join } from "path";
2-
import { Uri, workspace, window as Window } from "vscode";
1+
import { dirname, join } from "path";
2+
import { Uri, window as Window, workspace } from "vscode";
33
import { CodeQLCliServer } from "../codeql-cli/cli";
44
import { BaseLogger } from "../common/logging";
55
import { Credentials } from "../common/authentication";
@@ -24,8 +24,9 @@ import {
2424
isCodespacesTemplate,
2525
setQlPackLocation,
2626
} from "../config";
27-
import { existsSync } from "fs-extra";
27+
import { lstat, pathExists } from "fs-extra";
2828
import { askForLanguage } from "../codeql-cli/query-language";
29+
import { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
2930

3031
type QueryLanguagesToDatabaseMap = Record<string, string>;
3132

@@ -51,6 +52,7 @@ export class SkeletonQueryWizard {
5152
private readonly logger: BaseLogger,
5253
private readonly databaseManager: DatabaseManager,
5354
private readonly databaseStoragePath: string | undefined,
55+
private readonly selectedItems: readonly QueryTreeViewItem[],
5456
private language: QueryLanguage | undefined = undefined,
5557
) {}
5658

@@ -71,7 +73,7 @@ export class SkeletonQueryWizard {
7173
this.qlPackStoragePath = await this.determineStoragePath();
7274

7375
const skeletonPackAlreadyExists =
74-
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
76+
(await pathExists(join(this.qlPackStoragePath, this.folderName))) ||
7577
isFolderAlreadyInWorkspace(this.folderName);
7678

7779
if (skeletonPackAlreadyExists) {
@@ -109,7 +111,29 @@ export class SkeletonQueryWizard {
109111
});
110112
}
111113

112-
public async determineStoragePath() {
114+
public async determineStoragePath(): Promise<string> {
115+
if (this.selectedItems.length === 0) {
116+
return this.determineRootStoragePath();
117+
}
118+
119+
// Just like VS Code's "New File" command, if the user has selected multiple files/folders in the queries panel,
120+
// we will create the new file in the same folder as the first selected item.
121+
// See https://github.com/microsoft/vscode/blob/a8b7239d0311d4915b57c837972baf4b01394491/src/vs/workbench/contrib/files/browser/fileActions.ts#L893-L900
122+
const selectedItem = this.selectedItems[0];
123+
124+
const path = selectedItem.path;
125+
126+
// We use stat to protect against outdated query tree items
127+
const fileStat = await lstat(path);
128+
129+
if (fileStat.isDirectory()) {
130+
return path;
131+
}
132+
133+
return dirname(path);
134+
}
135+
136+
public async determineRootStoragePath() {
113137
const firstStorageFolder = getFirstWorkspaceFolder();
114138

115139
if (isCodespacesTemplate()) {
@@ -118,7 +142,7 @@ export class SkeletonQueryWizard {
118142

119143
let storageFolder = getQlPackLocation();
120144

121-
if (storageFolder === undefined || !existsSync(storageFolder)) {
145+
if (storageFolder === undefined || !(await pathExists(storageFolder))) {
122146
storageFolder = await Window.showInputBox({
123147
title:
124148
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
@@ -131,7 +155,7 @@ export class SkeletonQueryWizard {
131155
throw new UserCancellationException("No storage folder entered.");
132156
}
133157

134-
if (!existsSync(storageFolder)) {
158+
if (!(await pathExists(storageFolder))) {
135159
throw new UserCancellationException(
136160
"Invalid folder. Must be a folder that already exists.",
137161
);

extensions/ql-vscode/src/queries-panel/queries-module.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,18 @@ import { QueriesPanel } from "./queries-panel";
77
import { QueryDiscovery } from "./query-discovery";
88
import { QueryPackDiscovery } from "./query-pack-discovery";
99
import { LanguageContextStore } from "../language-context-store";
10+
import { TreeViewSelectionChangeEvent } from "vscode";
11+
import { QueryTreeViewItem } from "./query-tree-view-item";
1012

1113
export class QueriesModule extends DisposableObject {
1214
private queriesPanel: QueriesPanel | undefined;
15+
private readonly onDidChangeSelectionEmitter = this.push(
16+
this.app.createEventEmitter<
17+
TreeViewSelectionChangeEvent<QueryTreeViewItem>
18+
>(),
19+
);
20+
21+
public readonly onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
1322

1423
private constructor(readonly app: App) {
1524
super();
@@ -52,6 +61,9 @@ export class QueriesModule extends DisposableObject {
5261
void queryDiscovery.initialRefresh();
5362

5463
this.queriesPanel = new QueriesPanel(queryDiscovery, app);
64+
this.queriesPanel.onDidChangeSelection((event) =>
65+
this.onDidChangeSelectionEmitter.fire(event),
66+
);
5567
this.push(this.queriesPanel);
5668
}
5769
}

extensions/ql-vscode/src/queries-panel/queries-panel.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { DisposableObject } from "../common/disposable-object";
22
import { QueryTreeDataProvider } from "./query-tree-data-provider";
33
import { QueryDiscovery } from "./query-discovery";
4-
import { TextEditor, TreeView, window } from "vscode";
4+
import {
5+
Event,
6+
TextEditor,
7+
TreeView,
8+
TreeViewSelectionChangeEvent,
9+
window,
10+
} from "vscode";
511
import { App } from "../common/app";
612
import { QueryTreeViewItem } from "./query-tree-view-item";
713

@@ -16,6 +22,7 @@ export class QueriesPanel extends DisposableObject {
1622
super();
1723

1824
this.dataProvider = new QueryTreeDataProvider(queryDiscovery, app);
25+
this.push(this.dataProvider);
1926

2027
this.treeView = window.createTreeView("codeQLQueries", {
2128
treeDataProvider: this.dataProvider,
@@ -25,6 +32,12 @@ export class QueriesPanel extends DisposableObject {
2532
this.subscribeToTreeSelectionEvents();
2633
}
2734

35+
public get onDidChangeSelection(): Event<
36+
TreeViewSelectionChangeEvent<QueryTreeViewItem>
37+
> {
38+
return this.treeView.onDidChangeSelection;
39+
}
40+
2841
private subscribeToTreeSelectionEvents(): void {
2942
// Keep track of whether the user has changed their text editor while
3043
// the tree view was not visible. If so, we will focus the text editor

0 commit comments

Comments
 (0)