Merge pull request #3015 from github/koesie10/reveal-file-in-queries-panel
Reveal opened files in queries panel
This commit is contained in:
@@ -1,21 +1,103 @@
|
||||
import { DisposableObject } from "../common/disposable-object";
|
||||
import { QueryTreeDataProvider } from "./query-tree-data-provider";
|
||||
import { QueryDiscovery } from "./query-discovery";
|
||||
import { window } from "vscode";
|
||||
import { TextEditor, TreeView, window } from "vscode";
|
||||
import { App } from "../common/app";
|
||||
import { QueryTreeViewItem } from "./query-tree-view-item";
|
||||
|
||||
export class QueriesPanel extends DisposableObject {
|
||||
private readonly dataProvider: QueryTreeDataProvider;
|
||||
private readonly treeView: TreeView<QueryTreeViewItem>;
|
||||
|
||||
public constructor(
|
||||
queryDiscovery: QueryDiscovery,
|
||||
readonly app: App,
|
||||
) {
|
||||
super();
|
||||
|
||||
const dataProvider = new QueryTreeDataProvider(queryDiscovery, app);
|
||||
this.dataProvider = new QueryTreeDataProvider(queryDiscovery, app);
|
||||
|
||||
const treeView = window.createTreeView("codeQLQueries", {
|
||||
treeDataProvider: dataProvider,
|
||||
this.treeView = window.createTreeView("codeQLQueries", {
|
||||
treeDataProvider: this.dataProvider,
|
||||
});
|
||||
this.push(this.treeView);
|
||||
|
||||
this.subscribeToTreeSelectionEvents();
|
||||
}
|
||||
|
||||
private subscribeToTreeSelectionEvents(): void {
|
||||
// Keep track of whether the user has changed their text editor while
|
||||
// the tree view was not visible. If so, we will focus the text editor
|
||||
// in the tree view when it becomes visible.
|
||||
let changedTextEditor: TextEditor | undefined = undefined;
|
||||
|
||||
window.onDidChangeActiveTextEditor((textEditor) => {
|
||||
if (!this.treeView.visible) {
|
||||
changedTextEditor = textEditor;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the changedTextEditor variable so we don't try to show it when
|
||||
// the tree view becomes next visible.
|
||||
changedTextEditor = undefined;
|
||||
|
||||
if (!textEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
void this.revealTextEditor(textEditor);
|
||||
});
|
||||
|
||||
this.treeView.onDidChangeVisibility((e) => {
|
||||
if (!e.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!changedTextEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
void this.revealTextEditor(changedTextEditor);
|
||||
});
|
||||
|
||||
// If there is an active text editor when activating the extension, we want to show it in the tree view.
|
||||
if (window.activeTextEditor) {
|
||||
// We need to wait for the data provider to load its data. Without this, we will end up in a situation
|
||||
// where we're trying to show an item that does not exist yet since the query discoverer has not yet
|
||||
// finished running.
|
||||
const initialEventDisposable = this.dataProvider.onDidChangeTreeData(
|
||||
() => {
|
||||
if (window.activeTextEditor && this.treeView.visible) {
|
||||
void this.revealTextEditor(window.activeTextEditor);
|
||||
}
|
||||
|
||||
// We only want to listen to this event once, so dispose of the listener to unsubscribe.
|
||||
initialEventDisposable.dispose();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private revealTextEditor(textEditor: TextEditor): void {
|
||||
const filePath = textEditor.document.uri.fsPath;
|
||||
|
||||
const item = this.dataProvider.getTreeItemByPath(filePath);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.treeView.selection.length === 1 &&
|
||||
this.treeView.selection[0].path === item.path
|
||||
) {
|
||||
// The item is already selected
|
||||
return;
|
||||
}
|
||||
|
||||
void this.treeView.reveal(item, {
|
||||
select: true,
|
||||
focus: false,
|
||||
});
|
||||
this.push(treeView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { DisposableObject } from "../common/disposable-object";
|
||||
import { FileTreeNode } from "../common/file-tree-nodes";
|
||||
import { App } from "../common/app";
|
||||
import { containsPath } from "../common/files";
|
||||
|
||||
export interface QueryDiscoverer {
|
||||
readonly buildQueryTree: () => Array<FileTreeNode<string>> | undefined;
|
||||
@@ -41,6 +42,54 @@ export class QueryTreeDataProvider
|
||||
return this.onDidChangeTreeDataEmitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific tree view item by its path. If it's not found, returns undefined.
|
||||
*
|
||||
* @param path The path to retrieve the item for.
|
||||
*/
|
||||
public getTreeItemByPath(path: string): QueryTreeViewItem | undefined {
|
||||
const itemPath = this.findItemPath(path, this.queryTreeItems);
|
||||
if (!itemPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return itemPath[itemPath.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a specific tree view item by path.
|
||||
*
|
||||
* @param path The path to find the item for.
|
||||
* @param items The items to search.
|
||||
* @param currentPath The current path to the item.
|
||||
* @return The path to the tree view item, or undefined if it could not be found. The last item in the
|
||||
* array is the item itself.
|
||||
*/
|
||||
private findItemPath(
|
||||
path: string,
|
||||
items: QueryTreeViewItem[],
|
||||
currentPath: QueryTreeViewItem[] = [],
|
||||
): QueryTreeViewItem[] | undefined {
|
||||
const relevantItems = items.filter((item) => containsPath(item.path, path));
|
||||
|
||||
const matchingItem = relevantItems.find((item) => item.path === path);
|
||||
if (matchingItem) {
|
||||
return [...currentPath, matchingItem];
|
||||
}
|
||||
|
||||
for (const item of relevantItems) {
|
||||
const childItem = this.findItemPath(path, item.children, [
|
||||
...currentPath,
|
||||
item,
|
||||
]);
|
||||
if (childItem) {
|
||||
return childItem;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private createTree(): QueryTreeViewItem[] {
|
||||
const queryTree = this.queryDiscoverer.buildQueryTree();
|
||||
if (queryTree === undefined) {
|
||||
@@ -95,4 +144,14 @@ export class QueryTreeDataProvider
|
||||
return item.children;
|
||||
}
|
||||
}
|
||||
|
||||
public getParent(item: QueryTreeViewItem): QueryTreeViewItem | undefined {
|
||||
const itemPath = this.findItemPath(item.path, this.queryTreeItems);
|
||||
if (!itemPath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// The item itself is last in the last, so the parent is the second last item.
|
||||
return itemPath[itemPath.length - 2];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as vscode from "vscode";
|
||||
export class QueryTreeViewItem extends vscode.TreeItem {
|
||||
constructor(
|
||||
name: string,
|
||||
public readonly path: string | undefined,
|
||||
public readonly path: string,
|
||||
public readonly children: QueryTreeViewItem[],
|
||||
) {
|
||||
super(name);
|
||||
|
||||
Reference in New Issue
Block a user