Add language filter panel.

This commit is contained in:
Anders Starcke Henriksen
2023-10-10 13:53:13 +02:00
parent 4ee86c15ad
commit c668b39b30
7 changed files with 173 additions and 0 deletions

View File

@@ -581,6 +581,10 @@
"command": "codeQL.copyVersion",
"title": "CodeQL: Copy Version Information"
},
{
"command": "codeQLLanguageSelection.setSelectedItem",
"title": "Select"
},
{
"command": "codeQLQueries.runLocalQueryFromQueriesPanel",
"title": "Run local query",
@@ -1163,6 +1167,11 @@
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canImportCodeSearch/",
"group": "2_qlContextMenu@1"
},
{
"command": "codeQLLanguageSelection.setSelectedItem",
"when": "view == codeQLLanguageSelection && viewItem =~ /canBeSelected/",
"group": "inline"
},
{
"command": "codeQLDatabases.setCurrentDatabase",
"group": "inline",
@@ -1511,6 +1520,10 @@
{
"command": "codeQL.openModelEditor"
},
{
"command": "codeQLLanguageSelection.setSelectedItem",
"when": "false"
},
{
"command": "codeQLQueries.runLocalQueryContextMenu",
"when": "false"
@@ -1977,6 +1990,11 @@
},
"views": {
"ql-container": [
{
"id": "codeQLLanguageSelection",
"name": "Language",
"when": "config.codeQL.canary && config.codeQL.showLanguageFilter"
},
{
"id": "codeQLDatabases",
"name": "Databases"

View File

@@ -12,6 +12,7 @@ import type {
} from "../variant-analysis/shared/variant-analysis";
import type { QLDebugConfiguration } from "../debugger/debug-configuration";
import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
import type { LanguageSelectionTreeViewItem } from "../language-selection-panel/language-selection-data-provider";
// A command function matching the signature that VS Code calls when
// a command is invoked from a context menu on a TreeView with
@@ -198,6 +199,13 @@ export type QueryHistoryCommands = {
"codeQL.exportSelectedVariantAnalysisResults": () => Promise<void>;
};
// Commands user for the language selector panel
export type LanguageSelectionCommands = {
"codeQLLanguageSelection.setSelectedItem": (
item: LanguageSelectionTreeViewItem,
) => Promise<void>;
};
// Commands used for the local databases panel
export type LocalDatabasesCommands = {
// Command palette commands

View File

@@ -136,6 +136,7 @@ import { NewQueryRunner, QueryRunner, QueryServerClient } from "./query-server";
import { QueriesModule } from "./queries-panel/queries-module";
import { OpenReferencedFileCodeLensProvider } from "./local-queries/open-referenced-file-code-lens-provider";
import { LanguageContextStore } from "./language-context-store";
import { LanguageSelectionPanel } from "./language-selection-panel/language-selection-panel";
/**
* extension.ts
@@ -779,6 +780,10 @@ async function activateWithInstalledDistribution(
void extLogger.log("Initializing language context.");
const languageContext = new LanguageContextStore(app);
void extLogger.log("Initializing language selector.");
const languageSelectionPanel = new LanguageSelectionPanel(languageContext);
ctx.subscriptions.push(languageSelectionPanel);
void extLogger.log("Initializing database panel.");
const databaseUI = new DatabaseUI(
app,
@@ -1016,6 +1021,7 @@ async function activateWithInstalledDistribution(
...getPackagingCommands({
cliServer,
}),
...languageSelectionPanel.getCommands(),
...modelEditorModule.getCommands(),
...evalLogViewer.getCommands(),
...summaryLanguageSupport.getCommands(),

View File

@@ -46,4 +46,11 @@ export class LanguageContextStore extends DisposableObject {
public shouldInclude(language: QueryLanguage | undefined): boolean {
return this.languageFilter === "All" || this.languageFilter === language;
}
public selectedLanguage(language: QueryLanguage | undefined): boolean {
return (
(this.languageFilter === "All" && language === undefined) ||
this.languageFilter === language
);
}
}

View File

@@ -0,0 +1,93 @@
import { DisposableObject } from "../common/disposable-object";
import { LanguageContextStore } from "../language-context-store";
import {
Event,
EventEmitter,
ThemeIcon,
TreeDataProvider,
TreeItem,
} from "vscode";
import {
QueryLanguage,
getLanguageDisplayName,
} from "../common/query-language";
const ALL_LANGUAGE_SELECTION_OPTIONS = [
undefined, // All langauges
QueryLanguage.Cpp,
QueryLanguage.CSharp,
QueryLanguage.Go,
QueryLanguage.Java,
QueryLanguage.Javascript,
QueryLanguage.Python,
QueryLanguage.Ruby,
QueryLanguage.Swift,
];
// A tree view items consisting of of a language (or undefined for all languages
// and a boolean indicating whether it is selected or not.
export class LanguageSelectionTreeViewItem extends TreeItem {
constructor(
public readonly language: QueryLanguage | undefined,
public readonly selected: boolean = false,
) {
const label = language ? getLanguageDisplayName(language) : "All languages";
super(label);
this.iconPath = selected ? new ThemeIcon("check") : undefined;
this.contextValue = selected ? undefined : "canBeSelected";
}
}
export class LanguageSelectionTreeDataProvider
extends DisposableObject
implements TreeDataProvider<LanguageSelectionTreeViewItem>
{
private treeItems: LanguageSelectionTreeViewItem[];
private readonly onDidChangeTreeDataEmitter = this.push(
new EventEmitter<void>(),
);
public constructor(private readonly languageContext: LanguageContextStore) {
super();
this.treeItems = this.createTree(languageContext);
// If the language context changes, we need to update the tree.
this.push(
this.languageContext.onLanguageContextChanged(() => {
this.treeItems = this.createTree(languageContext);
this.onDidChangeTreeDataEmitter.fire();
}),
);
}
public get onDidChangeTreeData(): Event<void> {
return this.onDidChangeTreeDataEmitter.event;
}
public getTreeItem(item: LanguageSelectionTreeViewItem): TreeItem {
return item;
}
public getChildren(
item?: LanguageSelectionTreeViewItem,
): LanguageSelectionTreeViewItem[] {
if (!item) {
return this.treeItems;
} else {
return [];
}
}
private createTree(
languageContext: LanguageContextStore,
): LanguageSelectionTreeViewItem[] {
return ALL_LANGUAGE_SELECTION_OPTIONS.map((language) => {
return new LanguageSelectionTreeViewItem(
language,
languageContext.selectedLanguage(language),
);
});
}
}

View File

@@ -0,0 +1,40 @@
import { DisposableObject } from "../common/disposable-object";
import { window } from "vscode";
import {
LanguageSelectionTreeDataProvider,
LanguageSelectionTreeViewItem,
} from "./language-selection-data-provider";
import { LanguageContextStore } from "../language-context-store";
import { LanguageSelectionCommands } from "../common/commands";
// This panel allows the selection of a single language, that will
// then filter all other relevant views (e.g. db panel, query history).
export class LanguageSelectionPanel extends DisposableObject {
constructor(private readonly languageContext: LanguageContextStore) {
super();
const dataProvider = new LanguageSelectionTreeDataProvider(languageContext);
const treeView = window.createTreeView("codeQLLanguageSelection", {
treeDataProvider: dataProvider,
});
this.push(treeView);
}
public getCommands(): LanguageSelectionCommands {
return {
"codeQLLanguageSelection.setSelectedItem":
this.handleSetSelectedLanguage.bind(this),
};
}
private async handleSetSelectedLanguage(
item: LanguageSelectionTreeViewItem,
): Promise<void> {
if (item.language) {
await this.languageContext.setLanguageContext(item.language);
} else {
await this.languageContext.clearLanguageContext();
}
}
}

View File

@@ -38,6 +38,7 @@ describe("commands declared in package.json", () => {
expect(title).toBeDefined();
commandTitles[command] = title!;
} else if (
command.match(/^codeQLLanguageSelection\./) ||
command.match(/^codeQLDatabases\./) ||
command.match(/^codeQLQueries\./) ||
command.match(/^codeQLVariantAnalysisRepositories\./) ||