Skip to content

Commit fb33879

Browse files
authored
Merge pull request #3015 from github/koesie10/reveal-file-in-queries-panel
Reveal opened files in queries panel
2 parents 2ebccd5 + 3a07fa9 commit fb33879

File tree

3 files changed

+147
-6
lines changed

3 files changed

+147
-6
lines changed
Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,103 @@
11
import { DisposableObject } from "../common/disposable-object";
22
import { QueryTreeDataProvider } from "./query-tree-data-provider";
33
import { QueryDiscovery } from "./query-discovery";
4-
import { window } from "vscode";
4+
import { TextEditor, TreeView, window } from "vscode";
55
import { App } from "../common/app";
6+
import { QueryTreeViewItem } from "./query-tree-view-item";
67

78
export class QueriesPanel extends DisposableObject {
9+
private readonly dataProvider: QueryTreeDataProvider;
10+
private readonly treeView: TreeView<QueryTreeViewItem>;
11+
812
public constructor(
913
queryDiscovery: QueryDiscovery,
1014
readonly app: App,
1115
) {
1216
super();
1317

14-
const dataProvider = new QueryTreeDataProvider(queryDiscovery, app);
18+
this.dataProvider = new QueryTreeDataProvider(queryDiscovery, app);
19+
20+
this.treeView = window.createTreeView("codeQLQueries", {
21+
treeDataProvider: this.dataProvider,
22+
});
23+
this.push(this.treeView);
24+
25+
this.subscribeToTreeSelectionEvents();
26+
}
27+
28+
private subscribeToTreeSelectionEvents(): void {
29+
// Keep track of whether the user has changed their text editor while
30+
// the tree view was not visible. If so, we will focus the text editor
31+
// in the tree view when it becomes visible.
32+
let changedTextEditor: TextEditor | undefined = undefined;
33+
34+
window.onDidChangeActiveTextEditor((textEditor) => {
35+
if (!this.treeView.visible) {
36+
changedTextEditor = textEditor;
37+
38+
return;
39+
}
40+
41+
// Reset the changedTextEditor variable so we don't try to show it when
42+
// the tree view becomes next visible.
43+
changedTextEditor = undefined;
44+
45+
if (!textEditor) {
46+
return;
47+
}
48+
49+
void this.revealTextEditor(textEditor);
50+
});
51+
52+
this.treeView.onDidChangeVisibility((e) => {
53+
if (!e.visible) {
54+
return;
55+
}
56+
57+
if (!changedTextEditor) {
58+
return;
59+
}
60+
61+
void this.revealTextEditor(changedTextEditor);
62+
});
63+
64+
// If there is an active text editor when activating the extension, we want to show it in the tree view.
65+
if (window.activeTextEditor) {
66+
// We need to wait for the data provider to load its data. Without this, we will end up in a situation
67+
// where we're trying to show an item that does not exist yet since the query discoverer has not yet
68+
// finished running.
69+
const initialEventDisposable = this.dataProvider.onDidChangeTreeData(
70+
() => {
71+
if (window.activeTextEditor && this.treeView.visible) {
72+
void this.revealTextEditor(window.activeTextEditor);
73+
}
74+
75+
// We only want to listen to this event once, so dispose of the listener to unsubscribe.
76+
initialEventDisposable.dispose();
77+
},
78+
);
79+
}
80+
}
81+
82+
private revealTextEditor(textEditor: TextEditor): void {
83+
const filePath = textEditor.document.uri.fsPath;
84+
85+
const item = this.dataProvider.getTreeItemByPath(filePath);
86+
if (!item) {
87+
return;
88+
}
89+
90+
if (
91+
this.treeView.selection.length === 1 &&
92+
this.treeView.selection[0].path === item.path
93+
) {
94+
// The item is already selected
95+
return;
96+
}
1597

16-
const treeView = window.createTreeView("codeQLQueries", {
17-
treeDataProvider: dataProvider,
98+
void this.treeView.reveal(item, {
99+
select: true,
100+
focus: false,
18101
});
19-
this.push(treeView);
20102
}
21103
}

extensions/ql-vscode/src/queries-panel/query-tree-data-provider.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { DisposableObject } from "../common/disposable-object";
88
import { FileTreeNode } from "../common/file-tree-nodes";
99
import { App } from "../common/app";
10+
import { containsPath } from "../common/files";
1011

1112
export interface QueryDiscoverer {
1213
readonly buildQueryTree: () => Array<FileTreeNode<string>> | undefined;
@@ -41,6 +42,54 @@ export class QueryTreeDataProvider
4142
return this.onDidChangeTreeDataEmitter.event;
4243
}
4344

45+
/**
46+
* Retrieves a specific tree view item by its path. If it's not found, returns undefined.
47+
*
48+
* @param path The path to retrieve the item for.
49+
*/
50+
public getTreeItemByPath(path: string): QueryTreeViewItem | undefined {
51+
const itemPath = this.findItemPath(path, this.queryTreeItems);
52+
if (!itemPath) {
53+
return undefined;
54+
}
55+
56+
return itemPath[itemPath.length - 1];
57+
}
58+
59+
/**
60+
* Find a specific tree view item by path.
61+
*
62+
* @param path The path to find the item for.
63+
* @param items The items to search.
64+
* @param currentPath The current path to the item.
65+
* @return The path to the tree view item, or undefined if it could not be found. The last item in the
66+
* array is the item itself.
67+
*/
68+
private findItemPath(
69+
path: string,
70+
items: QueryTreeViewItem[],
71+
currentPath: QueryTreeViewItem[] = [],
72+
): QueryTreeViewItem[] | undefined {
73+
const relevantItems = items.filter((item) => containsPath(item.path, path));
74+
75+
const matchingItem = relevantItems.find((item) => item.path === path);
76+
if (matchingItem) {
77+
return [...currentPath, matchingItem];
78+
}
79+
80+
for (const item of relevantItems) {
81+
const childItem = this.findItemPath(path, item.children, [
82+
...currentPath,
83+
item,
84+
]);
85+
if (childItem) {
86+
return childItem;
87+
}
88+
}
89+
90+
return undefined;
91+
}
92+
4493
private createTree(): QueryTreeViewItem[] {
4594
const queryTree = this.queryDiscoverer.buildQueryTree();
4695
if (queryTree === undefined) {
@@ -95,4 +144,14 @@ export class QueryTreeDataProvider
95144
return item.children;
96145
}
97146
}
147+
148+
public getParent(item: QueryTreeViewItem): QueryTreeViewItem | undefined {
149+
const itemPath = this.findItemPath(item.path, this.queryTreeItems);
150+
if (!itemPath) {
151+
return undefined;
152+
}
153+
154+
// The item itself is last in the last, so the parent is the second last item.
155+
return itemPath[itemPath.length - 2];
156+
}
98157
}

extensions/ql-vscode/src/queries-panel/query-tree-view-item.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as vscode from "vscode";
33
export class QueryTreeViewItem extends vscode.TreeItem {
44
constructor(
55
name: string,
6-
public readonly path: string | undefined,
6+
public readonly path: string,
77
public readonly children: QueryTreeViewItem[],
88
) {
99
super(name);

0 commit comments

Comments
 (0)