Merge pull request #3015 from github/koesie10/reveal-file-in-queries-panel

Reveal opened files in queries panel
This commit is contained in:
Koen Vlaswinkel
2023-10-25 13:58:08 +02:00
committed by GitHub
3 changed files with 147 additions and 6 deletions

View File

@@ -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);
}
}

View File

@@ -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];
}
}

View File

@@ -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);