Browse Source

Adds filtering support to comparisons

main
Eric Amodio 3 years ago
parent
commit
0f2a815d95
7 changed files with 211 additions and 25 deletions
  1. +10
    -0
      CHANGELOG.md
  2. +56
    -0
      package.json
  3. +10
    -7
      src/views/nodes/compareBranchNode.ts
  4. +1
    -1
      src/views/nodes/compareResultsNode.ts
  5. +97
    -9
      src/views/nodes/resultsFilesNode.ts
  6. +30
    -1
      src/views/searchAndCompareView.ts
  7. +7
    -7
      src/views/viewCommands.ts

+ 10
- 0
CHANGELOG.md View File

@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- Adds the ability to filter comparisons to show only either the left-side or right-side file differences
### Changed
- Reverses the resulting comparison of the _Compare with HEAD_, _Compare with Working_, and _Compare with Upstream_ commands in the views
## [11.4.1] - 2021-04-14
### Added

+ 56
- 0
package.json View File

@ -5058,6 +5058,23 @@
}
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOnLeft",
"title": "Show Left-side Files Only",
"category": "GitLens",
"enablement": "viewItem =~ /gitlens:results:files\\b(?!.*?\\b\\+filtered~left\\b)/"
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOnRight",
"title": "Show Right-side Files Only",
"category": "GitLens",
"enablement": "viewItem =~ /gitlens:results:files\\b(?!.*?\\b\\+filtered~right\\b)/"
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOff",
"title": "Clear Filter",
"category": "GitLens"
},
{
"command": "gitlens.views.stashes.copy",
"title": "Copy",
"category": "GitLens"
@ -6477,6 +6494,18 @@
"when": "false"
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOnLeft",
"when": "false"
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOnRight",
"when": "false"
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOff",
"when": "false"
},
{
"command": "gitlens.views.stashes.copy",
"when": "false"
},
@ -8483,6 +8512,11 @@
"group": "inline@1"
},
{
"submenu": "gitlens/view/searchAndCompare/comparison/filter",
"when": "viewItem =~ /gitlens:results:files\\b(?=.*?\\b\\+filterable\\b)/",
"group": "inline@1"
},
{
"command": "gitlens.views.refreshNode",
"when": "viewItem =~ /gitlens:compare:(branch(?=.*?\\b\\+comparing\\b)|results(?!:))\\b/",
"group": "inline@97"
@ -9070,6 +9104,23 @@
"when": "view =~ /^gitlens\\.views\\.searchAndCompare\\b/",
"group": "navigation@11"
}
],
"gitlens/view/searchAndCompare/comparison/filter": [
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOff",
"when": "viewItem =~ /gitlens:results:files\\b(?=.*?\\b\\+filtered\\b)/",
"group": "navigation@1"
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOnLeft",
"when1": "viewItem =~ /gitlens:results:files\\b(?!.*?\\b\\+filtered~left\\b)/",
"group": "navigation_1@1"
},
{
"command": "gitlens.views.searchAndCompare.setFilesFilterOnRight",
"when1": "viewItem =~ /gitlens:results:files\\b(?!.*?\\b\\+filtered~right\\b)/",
"group": "navigation_1@2"
}
]
},
"submenus": [
@ -9133,6 +9184,11 @@
"id": "gitlens/view/searchAndCompare/new",
"label": "New Search or Compare",
"icon": "$(add)"
},
{
"id": "gitlens/view/searchAndCompare/comparison/filter",
"label": "Filter",
"icon": "$(filter)"
}
],
"keybindings": [

+ 10
- 7
src/views/nodes/compareBranchNode.ts View File

@ -140,14 +140,19 @@ export class CompareBranchNode extends ViewNode
let state: TreeItemCollapsibleState;
let label;
let description;
let tooltip;
if (this._compareWith == null) {
label = `Compare ${this.branch.name}${
this.compareWithWorkingTree ? ' (working)' : ''
label = `Compare ${
this.compareWithWorkingTree ? 'Working Tree' : this.branch.name
} with <branch, tag, or ref>`;
state = TreeItemCollapsibleState.None;
tooltip = `Click to compare ${
this.compareWithWorkingTree ? 'Working Tree' : this.branch.name
} with a branch, tag, or ref`;
} else {
label = `Compare ${this.branch.name}${this.compareWithWorkingTree ? ' (working)' : ''}`;
description = `with ${GitRevision.shorten(this._compareWith.ref, {
label = `Compare ${
this.compareWithWorkingTree ? 'Working Tree' : this.branch.name
} with ${GitRevision.shorten(this._compareWith.ref, {
strings: { working: 'Working Tree' },
})}`;
state = TreeItemCollapsibleState.Collapsed;
@ -169,9 +174,7 @@ export class CompareBranchNode extends ViewNode
item.description = description;
item.iconPath = new ThemeIcon('git-compare');
item.id = this.id;
item.tooltip = `Click to compare ${this.branch.name}${this.compareWithWorkingTree ? ' (working)' : ''} with${
GlyphChars.Ellipsis
}`;
item.tooltip = tooltip;
return item;
}

+ 1
- 1
src/views/nodes/compareResultsNode.ts View File

@ -151,7 +151,7 @@ export class CompareResultsNode extends ViewNode {
const item = new TreeItem(
`Comparing ${
this._ref.label ?? GitRevision.shorten(this._ref.ref, { strings: { working: 'Working Tree' } })
} to ${
} with ${
this._compareWith.label ??
GitRevision.shorten(this._compareWith.ref, { strings: { working: 'Working Tree' } })
}`,

+ 97
- 9
src/views/nodes/resultsFilesNode.ts View File

@ -1,7 +1,8 @@
'use strict';
import * as paths from 'path';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { ViewFilesLayout } from '../../configuration';
import { Container } from '../../container';
import { GitFile } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { Arrays, debug, gate, Iterables, Promises, Strings } from '../../system';
@ -13,6 +14,10 @@ import { ContextValues, ViewNode } from './viewNode';
export interface FilesQueryResults {
label: string;
files: GitFile[] | undefined;
filtered?: {
filter: 'left' | 'right';
files: GitFile[];
};
}
export class ResultsFilesNode extends ViewNode<ViewsWithCommits> {
@ -37,8 +42,30 @@ export class ResultsFilesNode extends ViewNode {
return `${this.parent!.id}:results:files`;
}
private _filter: 'left' | 'right' | false = false;
get filter(): 'left' | 'right' | false {
return this._filter;
}
set filter(value: 'left' | 'right' | false) {
if (this._filter === value) return;
this._filter = value;
this._filterResults = undefined;
void this.triggerChange(false);
}
get filterable(): boolean {
return this.filtered || (this.ref1 !== this.ref2 && this.direction === undefined);
}
get filtered(): boolean {
return Boolean(this.filter);
}
async getChildren(): Promise<ViewNode[]> {
const { files } = await this.getFilesQueryResults();
const results = await this.getFilesQueryResults();
const files = (this.filtered ? results.filtered?.files : undefined) ?? results.files;
if (files == null) return [];
let children: FileNode[] = [
@ -71,11 +98,20 @@ export class ResultsFilesNode extends ViewNode {
async getTreeItem(): Promise<TreeItem> {
let label;
let files;
let icon;
let files: GitFile[] | undefined;
let state;
try {
({ label, files } = await Promises.cancellable(this.getFilesQueryResults(), 100));
const results = await Promises.cancellable(this.getFilesQueryResults(), 100);
label = results.label;
files = (this.filtered ? results.filtered?.files : undefined) ?? results.files;
if (this.filtered && results.filtered == null) {
label = 'files changed';
icon = new ThemeIcon('ellipsis');
}
state =
files == null || files.length === 0
? TreeItemCollapsibleState.None
@ -84,16 +120,24 @@ export class ResultsFilesNode extends ViewNode {
: TreeItemCollapsibleState.Collapsed;
} catch (ex) {
if (ex instanceof Promises.CancellationError) {
ex.promise.then(() => this.triggerChange(false));
ex.promise.then(() => setTimeout(() => this.triggerChange(false), 0));
}
label = 'files changed';
icon = new ThemeIcon('ellipsis');
// Need to use Collapsed before we have results or the item won't show up in the view until the children are awaited
// https://github.com/microsoft/vscode/issues/54806 & https://github.com/microsoft/vscode/issues/62214
state = TreeItemCollapsibleState.Collapsed;
}
const item = new TreeItem(label ?? 'files changed', state);
item.contextValue = ContextValues.ResultsFiles;
const item = new TreeItem(
`${this.filtered && files != null ? `Showing ${files.length} of ` : ''}${label}`,
state,
);
item.iconPath = icon;
item.contextValue = `${ContextValues.ResultsFiles}${this.filterable ? '+filterable' : ''}${
this.filtered ? `+filtered~${this.filter}` : ''
}`;
item.id = this.id;
return item;
@ -104,16 +148,60 @@ export class ResultsFilesNode extends ViewNode {
refresh(reset: boolean = false) {
if (!reset) return;
this._filterResults = undefined;
this._filesQueryResults = this._filesQuery();
}
private _filesQueryResults: Promise<FilesQueryResults> | undefined;
private _filterResults: Promise<void> | undefined;
getFilesQueryResults() {
async getFilesQueryResults() {
if (this._filesQueryResults === undefined) {
this._filesQueryResults = this._filesQuery();
}
return this._filesQueryResults;
const results = await this._filesQueryResults;
if (
results.files == null ||
!this.filterable ||
this.filter === false ||
results.filtered?.filter === this.filter
) {
return results;
}
if (this._filterResults === undefined) {
this._filterResults = this.filterResults(this.filter, results);
}
await this._filterResults;
return results;
}
private async filterResults(filter: 'left' | 'right', results: FilesQueryResults) {
let filterTo: Set<string> | undefined;
const ref = this.filter === 'left' ? this.ref2 : this.ref1;
const mergeBase = await Container.git.getMergeBase(this.repoPath, this.ref1 || 'HEAD', this.ref2 || 'HEAD');
if (mergeBase != null) {
const files = await Container.git.getDiffStatus(this.uri.repoPath!, `${mergeBase}..${ref}`);
if (files != null) {
filterTo = new Set<string>(files.map(f => f.fileName));
}
} else {
const commit = await Container.git.getCommit(this.uri.repoPath!, ref || 'HEAD');
if (commit?.files != null) {
filterTo = new Set<string>(commit.files.map(f => f.fileName));
}
}
if (filterTo == null) return;
results.filtered = {
filter: filter,
files: results.files!.filter(f => filterTo!.has(f.fileName)),
};
}
}

+ 30
- 1
src/views/searchAndCompareView.ts View File

@ -4,7 +4,14 @@ import { configuration, SearchAndCompareViewConfig, ViewFilesLayout } from '../c
import { ContextKeys, NamedRef, PinnedItem, PinnedItems, setContext, WorkspaceState } from '../constants';
import { Container } from '../container';
import { GitLog, GitRevision, SearchPattern } from '../git/git';
import { CompareResultsNode, ContextValues, SearchResultsNode, unknownGitUri, ViewNode } from './nodes';
import {
CompareResultsNode,
ContextValues,
ResultsFilesNode,
SearchResultsNode,
unknownGitUri,
ViewNode,
} from './nodes';
import { debug, gate, Iterables, log, Promises } from '../system';
import { ViewBase } from './viewBase';
import { ComparePickerNode } from './nodes/comparePickerNode';
@ -296,6 +303,22 @@ export class SearchAndCompareView extends ViewBase
commands.registerCommand(this.getQualifiedCommand('swapComparison'), this.swapComparison, this);
commands.registerCommand(this.getQualifiedCommand('selectForCompare'), this.selectForCompare, this);
commands.registerCommand(this.getQualifiedCommand('compareWithSelected'), this.compareWithSelected, this);
commands.registerCommand(
this.getQualifiedCommand('setFilesFilterOnLeft'),
n => this.setFilesFilter(n, 'left'),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setFilesFilterOnRight'),
n => this.setFilesFilter(n, 'right'),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setFilesFilterOff'),
n => this.setFilesFilter(n, false),
this,
);
}
protected filterConfigurationChanged(e: ConfigurationChangeEvent) {
@ -484,6 +507,12 @@ export class SearchAndCompareView extends ViewBase
return node.pin();
}
private setFilesFilter(node: ResultsFilesNode, filter: 'left' | 'right' | false) {
if (!(node instanceof ResultsFilesNode)) return;
node.filter = filter;
}
private swapComparison(node: CompareResultsNode) {
if (!(node instanceof CompareResultsNode)) return undefined;

+ 7
- 7
src/views/viewCommands.ts View File

@ -176,13 +176,13 @@ export class ViewCommands {
commands.registerCommand('gitlens.views.unstageFile', this.unstageFile, this);
commands.registerCommand('gitlens.views.compareAncestryWithWorking', this.compareAncestryWithWorking, this);
commands.registerCommand('gitlens.views.compareWithHead', this.compareWithHead, this);
commands.registerCommand('gitlens.views.compareWithHead', this.compareHeadWith, this);
commands.registerCommand('gitlens.views.compareWithUpstream', this.compareWithUpstream, this);
commands.registerCommand('gitlens.views.compareWithSelected', this.compareWithSelected, this);
commands.registerCommand('gitlens.views.selectForCompare', this.selectForCompare, this);
commands.registerCommand('gitlens.views.compareFileWithSelected', this.compareFileWithSelected, this);
commands.registerCommand('gitlens.views.selectFileForCompare', this.selectFileForCompare, this);
commands.registerCommand('gitlens.views.compareWithWorking', this.compareWithWorking, this);
commands.registerCommand('gitlens.views.compareWithWorking', this.compareWorkingWith, this);
commands.registerCommand(
'gitlens.views.setBranchComparisonToWorking',
@ -703,10 +703,10 @@ export class ViewCommands {
}
@debug()
private compareWithHead(node: ViewRefNode) {
private compareHeadWith(node: ViewRefNode) {
if (!(node instanceof ViewRefNode)) return Promise.resolve();
return Container.searchAndCompareView.compare(node.repoPath, node.ref, 'HEAD');
return Container.searchAndCompareView.compare(node.repoPath, 'HEAD', node.ref);
}
@debug()
@ -714,14 +714,14 @@ export class ViewCommands {
if (!(node instanceof BranchNode)) return Promise.resolve();
if (node.branch.upstream == null) return Promise.resolve();
return Container.searchAndCompareView.compare(node.repoPath, node.branch.upstream.name, node.ref);
return Container.searchAndCompareView.compare(node.repoPath, node.ref, node.branch.upstream.name);
}
@debug()
private compareWithWorking(node: ViewRefNode) {
private compareWorkingWith(node: ViewRefNode) {
if (!(node instanceof ViewRefNode)) return Promise.resolve();
return Container.searchAndCompareView.compare(node.repoPath, node.ref, '');
return Container.searchAndCompareView.compare(node.repoPath, '', node.ref);
}
@debug()

Loading…
Cancel
Save