Skip to content

Commit ff36088

Browse files
authored
Merge pull request #3012 from github/koesie10/use-selected-queries-item
Create new queries in selected folder of queries panel
2 parents 234498a + f0f5538 commit ff36088

File tree

6 files changed

+283
-33
lines changed

6 files changed

+283
-33
lines changed

extensions/ql-vscode/src/extension.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,11 @@ async function activateWithInstalledDistribution(
802802
);
803803
ctx.subscriptions.push(databaseUI);
804804

805-
QueriesModule.initialize(app, languageContext, cliServer);
805+
const queriesModule = QueriesModule.initialize(
806+
app,
807+
languageContext,
808+
cliServer,
809+
);
806810

807811
void extLogger.log("Initializing evaluator log viewer.");
808812
const evalLogViewer = new EvalLogViewer();
@@ -941,6 +945,10 @@ async function activateWithInstalledDistribution(
941945
);
942946
ctx.subscriptions.push(localQueries);
943947

948+
queriesModule.onDidChangeSelection((event) =>
949+
localQueries.setSelectedQueryTreeViewItems(event.selection),
950+
);
951+
944952
void extLogger.log("Initializing debugger factory.");
945953
ctx.subscriptions.push(
946954
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: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import { join } from "path";
2-
import { Uri, workspace, window as Window } from "vscode";
1+
import { basename, 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";
66
import { QueryLanguage } from "../common/query-language";
7-
import {
8-
getFirstWorkspaceFolder,
9-
isFolderAlreadyInWorkspace,
10-
} from "../common/vscode/workspace-folders";
7+
import { getFirstWorkspaceFolder } from "../common/vscode/workspace-folders";
118
import { getErrorMessage } from "../common/helpers-pure";
129
import { QlPackGenerator } from "./qlpack-generator";
1310
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
@@ -24,8 +21,9 @@ import {
2421
isCodespacesTemplate,
2522
setQlPackLocation,
2623
} from "../config";
27-
import { existsSync } from "fs-extra";
24+
import { lstat, pathExists } from "fs-extra";
2825
import { askForLanguage } from "../codeql-cli/query-language";
26+
import { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
2927

3028
type QueryLanguagesToDatabaseMap = Record<string, string>;
3129

@@ -51,6 +49,7 @@ export class SkeletonQueryWizard {
5149
private readonly logger: BaseLogger,
5250
private readonly databaseManager: DatabaseManager,
5351
private readonly databaseStoragePath: string | undefined,
52+
private readonly selectedItems: readonly QueryTreeViewItem[],
5453
private language: QueryLanguage | undefined = undefined,
5554
) {}
5655

@@ -70,9 +69,9 @@ export class SkeletonQueryWizard {
7069

7170
this.qlPackStoragePath = await this.determineStoragePath();
7271

73-
const skeletonPackAlreadyExists =
74-
existsSync(join(this.qlPackStoragePath, this.folderName)) ||
75-
isFolderAlreadyInWorkspace(this.folderName);
72+
const skeletonPackAlreadyExists = await pathExists(
73+
join(this.qlPackStoragePath, this.folderName),
74+
);
7675

7776
if (skeletonPackAlreadyExists) {
7877
// just create a new example query file in skeleton QL pack
@@ -109,7 +108,41 @@ export class SkeletonQueryWizard {
109108
});
110109
}
111110

112-
public async determineStoragePath() {
111+
public async determineStoragePath(): Promise<string> {
112+
if (this.selectedItems.length === 0) {
113+
return this.determineRootStoragePath();
114+
}
115+
116+
const storagePath = await this.determineStoragePathFromSelection();
117+
118+
// If the user has selected a folder or file within a folder that matches the current
119+
// folder name, we should create a query rather than a query pack
120+
if (basename(storagePath) === this.folderName) {
121+
return dirname(storagePath);
122+
}
123+
124+
return storagePath;
125+
}
126+
127+
private async determineStoragePathFromSelection(): Promise<string> {
128+
// Just like VS Code's "New File" command, if the user has selected multiple files/folders in the queries panel,
129+
// we will create the new file in the same folder as the first selected item.
130+
// See https://github.com/microsoft/vscode/blob/a8b7239d0311d4915b57c837972baf4b01394491/src/vs/workbench/contrib/files/browser/fileActions.ts#L893-L900
131+
const selectedItem = this.selectedItems[0];
132+
133+
const path = selectedItem.path;
134+
135+
// We use stat to protect against outdated query tree items
136+
const fileStat = await lstat(path);
137+
138+
if (fileStat.isDirectory()) {
139+
return path;
140+
}
141+
142+
return dirname(path);
143+
}
144+
145+
public async determineRootStoragePath() {
113146
const firstStorageFolder = getFirstWorkspaceFolder();
114147

115148
if (isCodespacesTemplate()) {
@@ -118,7 +151,7 @@ export class SkeletonQueryWizard {
118151

119152
let storageFolder = getQlPackLocation();
120153

121-
if (storageFolder === undefined || !existsSync(storageFolder)) {
154+
if (storageFolder === undefined || !(await pathExists(storageFolder))) {
122155
storageFolder = await Window.showInputBox({
123156
title:
124157
"Please choose a folder in which to create your new query pack. You can change this in the extension settings.",
@@ -131,7 +164,7 @@ export class SkeletonQueryWizard {
131164
throw new UserCancellationException("No storage folder entered.");
132165
}
133166

134-
if (!existsSync(storageFolder)) {
167+
if (!(await pathExists(storageFolder))) {
135168
throw new UserCancellationException(
136169
"Invalid folder. Must be a folder that already exists.",
137170
);
@@ -208,7 +241,7 @@ export class SkeletonQueryWizard {
208241
await qlPackGenerator.createExampleQlFile(this.fileName);
209242
} catch (e: unknown) {
210243
void this.logger.log(
211-
`Could not create skeleton QL pack: ${getErrorMessage(e)}`,
244+
`Could not create query example file: ${getErrorMessage(e)}`,
212245
);
213246
}
214247
}

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)