Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.20.0

* Add file filter functionality to tree view

## 1.19.0

* Add GitHub PR comparison command [#130](https://github.com/letmaik/vscode-git-tree-compare/pull/130)
Expand Down
22 changes: 22 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,18 @@
"icon": "$(search)",
"category": "Git Tree Compare"
},
{
"command": "gitTreeCompare.filterFiles",
"title": "Filter Files",
"icon": "$(filter)",
"category": "Git Tree Compare"
},
{
"command": "gitTreeCompare.clearFilter",
"title": "Clear Filter",
"icon": "$(clear-all)",
"category": "Git Tree Compare"
},
{
"command": "gitTreeCompare.copyPath",
"title": "Copy Path",
Expand Down Expand Up @@ -318,6 +330,16 @@
"when": "view == gitTreeCompare",
"group": "2_files"
},
{
"command": "gitTreeCompare.filterFiles",
"when": "view == gitTreeCompare",
"group": "navigation@4"
},
{
"command": "gitTreeCompare.clearFilter",
"when": "view == gitTreeCompare && gitTreeCompare.isFiltered",
"group": "navigation@5"
},
{
"submenu": "gitTreeCompare.viewAndSort",
"when": "view == gitTreeCompare",
Expand Down
7 changes: 7 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ export function activate(context: ExtensionContext) {
commands.registerCommand(NAMESPACE + '.searchChanges', () => {
runAfterInit(() => provider!.searchChanges());
});
commands.registerCommand(NAMESPACE + '.filterFiles', () => {
runAfterInit(() => provider!.filterFiles());
});
commands.registerCommand(NAMESPACE + '.clearFilter', () => {
runAfterInit(() => provider!.clearFilter());
});
commands.registerCommand(NAMESPACE + '.copyPath', node => {
runAfterInit(() => provider!.copyPath(node));
});
Expand Down Expand Up @@ -127,6 +133,7 @@ export function activate(context: ExtensionContext) {

// Set initial context for menu enablement (starts in tree view mode)
commands.executeCommand('setContext', NAMESPACE + '.viewAsList', false);
commands.executeCommand('setContext', NAMESPACE + '.isFiltered', false);

provider = new GitTreeCompareProvider(git, gitApi, outputChannel, context.globalState, context.asAbsolutePath);

Expand Down
107 changes: 97 additions & 10 deletions src/treeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
private repository: Repository | undefined;
private baseRef: string;
private viewAsList = false;
private searchFilter: string | undefined;

// Static state of repository
private workspaceFolder: string;
Expand Down Expand Up @@ -240,16 +241,28 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
this.updateTreeRootFolder();
this.log('Using repository: ' + this.repoRoot);

const repoName = path.basename(repoRoot);
this.treeView.title = repoName;
this.updateTreeTitle();
}

private updateTreeTitle() {
if (!this.repository) {
this.treeView.title = 'none';
return;
}
const repoName = path.basename(this.repoRoot);
if (this.searchFilter) {
this.treeView.title = `${repoName} (filtered)`;
} else {
this.treeView.title = repoName;
}
}

async unsetRepository() {
this.repository = undefined;
this._onDidChangeTreeData.fire();
this.log('No repository selected');

this.treeView.title = 'none';
this.updateTreeTitle();
}

async changeRepository(repositoryRoot: string) {
Expand All @@ -264,6 +277,8 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
return;
}
this.checkboxStates.clear();
this.searchFilter = undefined;
this.updateFilterContext();
this._onDidChangeTreeData.fire();
}

Expand Down Expand Up @@ -831,6 +846,36 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
}
}

private matchesFilter(filePath: string, relPathBase: string): boolean {
if (!this.searchFilter) {
return true;
}
const fileName = path.basename(filePath);
const relativePath = path.relative(relPathBase, filePath);
const searchLower = this.searchFilter.toLowerCase();
return fileName.toLowerCase().includes(searchLower) ||
relativePath.toLowerCase().includes(searchLower);
}

private folderHasMatchingFiles(folder: string, useFilesOutsideTreeRoot: boolean): boolean {
if (!this.searchFilter) {
return true;
}
const files = useFilesOutsideTreeRoot ? this.filesOutsideTreeRoot : this.filesInsideTreeRoot;
const relPathBase = useFilesOutsideTreeRoot ? this.repoRoot : this.treeRoot;

for (const [folderPath, fileEntries] of files.entries()) {
if (folderPath === folder || folderPath.startsWith(folder + path.sep)) {
for (const file of fileEntries) {
if (this.matchesFilter(file.dstAbsPath, relPathBase)) {
return true;
}
}
}
}
return false;
}

private getFileSystemEntries(folder: string, useFilesOutsideTreeRoot: boolean): FileSystemElement[] {
const entries: FileSystemElement[] = [];
const files = useFilesOutsideTreeRoot ? this.filesOutsideTreeRoot : this.filesInsideTreeRoot;
Expand All @@ -849,14 +894,19 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
for (const folder2 of folders) {
const fileEntries = files.get(folder2)!;
for (const file of fileEntries) {
const dstRelPath = path.relative(relPathBase, file.dstAbsPath);
entries.push(new FileElement(file.srcAbsPath, file.dstAbsPath, dstRelPath, file.status, file.isSubmodule));
if (this.matchesFilter(file.dstAbsPath, relPathBase)) {
const dstRelPath = path.relative(relPathBase, file.dstAbsPath);
entries.push(new FileElement(file.srcAbsPath, file.dstAbsPath, dstRelPath, file.status, file.isSubmodule));
}
}
}
} else if (this.compactFolders) {
// add direct subfolders and apply compaction
for (const folder2 of files.keys()) {
if (path.dirname(folder2) === folder) {
if (!this.folderHasMatchingFiles(folder2, useFilesOutsideTreeRoot)) {
continue;
}
let compactedPath = folder2;
// not very efficient, needs a better data structure
outer: while (true) {
Expand Down Expand Up @@ -891,9 +941,11 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
// add direct subfolders
for (const folder2 of files.keys()) {
if (path.dirname(folder2) === folder) {
const label = path.basename(folder2);
entries.push(new FolderElement(
label, folder2, useFilesOutsideTreeRoot));
if (this.folderHasMatchingFiles(folder2, useFilesOutsideTreeRoot)) {
const label = path.basename(folder2);
entries.push(new FolderElement(
label, folder2, useFilesOutsideTreeRoot));
}
}
}
entries.sort((a, b) => path.basename(a.dstAbsPath).localeCompare(path.basename(b.dstAbsPath)));
Expand All @@ -905,8 +957,10 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
// there are no files within treeRoot, therefore, this is guarded
if (fileEntries) {
for (const file of fileEntries) {
const dstRelPath = path.relative(relPathBase, file.dstAbsPath);
entries.push(new FileElement(file.srcAbsPath, file.dstAbsPath, dstRelPath, file.status, file.isSubmodule));
if (this.matchesFilter(file.dstAbsPath, relPathBase)) {
const dstRelPath = path.relative(relPathBase, file.dstAbsPath);
entries.push(new FileElement(file.srcAbsPath, file.dstAbsPath, dstRelPath, file.status, file.isSubmodule));
}
}
}

Expand Down Expand Up @@ -1514,6 +1568,39 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
});
}

async filterFiles() {
const searchTerm = await window.showInputBox({
prompt: 'Enter text to filter files (leave empty to show all)',
placeHolder: 'Filter by filename or path...',
value: this.searchFilter || ''
});

if (searchTerm === undefined) {
return;
}

this.searchFilter = searchTerm.trim() || undefined;
this.updateTreeTitle();
this.updateFilterContext();
this.log(this.searchFilter ? `Filtering files by: ${this.searchFilter}` : 'Cleared file filter');
this._onDidChangeTreeData.fire();
}

clearFilter() {
if (!this.searchFilter) {
return;
}
this.searchFilter = undefined;
this.updateTreeTitle();
this.updateFilterContext();
this.log('Cleared file filter');
this._onDidChangeTreeData.fire();
}

private updateFilterContext() {
commands.executeCommand('setContext', NAMESPACE + '.isFiltered', !!this.searchFilter);
}

async copyPath(fileEntry: FileElement) {
const diffStatus = this.getDiffStatus(fileEntry);
if (!diffStatus) {
Expand Down