ソースを参照

Adds comparison pinning

main
Eric Amodio 5年前
コミット
71e5c5d5a9
9個のファイルの変更241行の追加46行の削除
  1. +6
    -0
      CHANGELOG.md
  2. +62
    -7
      package.json
  3. +16
    -0
      src/constants.ts
  4. +54
    -3
      src/views/compareView.ts
  5. +18
    -8
      src/views/nodes/compareNode.ts
  6. +75
    -18
      src/views/nodes/compareResultsNode.ts
  7. +5
    -6
      src/views/nodes/viewNode.ts
  8. +2
    -1
      src/views/searchView.ts
  9. +3
    -3
      src/views/viewCommands.ts

+ 6
- 0
CHANGELOG.md ファイルの表示

@ -4,6 +4,12 @@ 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 pinning of comparisons in the _Compare_ view — pinned comparisons will persist across reloads
## [9.3.0] - 2019-01-02
### Added

+ 62
- 7
package.json ファイルの表示

@ -2703,6 +2703,24 @@
}
},
{
"command": "gitlens.views.compare.pinComparision",
"title": "Pin Comparision",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-pin.svg",
"light": "images/light/icon-pin.svg"
}
},
{
"command": "gitlens.views.compare.unpinComparision",
"title": "Unpin Comparision",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-pinned.svg",
"light": "images/light/icon-pinned.svg"
}
},
{
"command": "gitlens.views.compare.swapComparision",
"title": "Swap Comparision",
"category": "GitLens",
@ -2788,7 +2806,11 @@
{
"command": "gitlens.views.refreshNode",
"title": "Refresh",
"category": "GitLens"
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-refresh.svg",
"light": "images/light/icon-refresh.svg"
}
}
],
"menus": {
@ -3346,6 +3368,14 @@
"when": "false"
},
{
"command": "gitlens.views.compare.pinComparision",
"when": "false"
},
{
"command": "gitlens.views.compare.unpinComparision",
"when": "false"
},
{
"command": "gitlens.views.compare.swapComparision",
"when": "false"
},
@ -4364,27 +4394,52 @@
},
{
"command": "gitlens.views.dismissNode",
"when": "viewItem =~ /gitlens:(compare:picker:ref|compare|search)\\b(?!:(commits|files))/",
"group": "inline@2"
"when": "viewItem =~ /gitlens:(compare:picker:ref|compare:results\\b(?!.*?\\+pinned\\b.*?)|search)\\b(?!:(commits|files))/",
"group": "inline@99"
},
{
"command": "gitlens.views.dismissNode",
"when": "viewItem =~ /gitlens:(compare:picker:ref|compare|search)\\b(?!:(commits|files))/",
"when": "viewItem =~ /gitlens:(compare:picker:ref|compare:results\\b(?!.*?\\+pinned\\b.*?)|search)\\b(?!:(commits|files))/",
"group": "1_gitlens@1"
},
{
"command": "gitlens.views.compare.swapComparision",
"when": "viewItem == gitlens:compare:results",
"when": "viewItem =~ /gitlens:compare:results\\b/",
"group": "inline@1"
},
{
"command": "gitlens.views.compare.pinComparision",
"when": "viewItem =~ /gitlens:compare:results\\b(?!.*?\\+pinned\\b.*?)/",
"group": "inline@2"
},
{
"command": "gitlens.views.compare.unpinComparision",
"when": "viewItem =~ /gitlens:compare:results\\b.*?\\+pinned\\b.*?/",
"group": "inline@2"
},
{
"command": "gitlens.views.refreshNode",
"when": "viewItem =~ /gitlens:compare:results\\b/",
"group": "inline@3"
},
{
"command": "gitlens.views.compare.swapComparision",
"when": "viewItem == gitlens:compare:results",
"when": "viewItem =~ /gitlens:compare:results\\b/",
"group": "2_gitlens@1"
},
{
"command": "gitlens.views.compare.pinComparision",
"when": "viewItem =~ /gitlens:compare:results\\b(?!.*?\\+pinned\\b.*?)/",
"group": "3_gitlens@1"
},
{
"command": "gitlens.views.compare.unpinComparision",
"when": "viewItem =~ /gitlens:compare:results\\b.*?\\+pinned\\b.*?/",
"group": "3_gitlens@1"
},
{
"command": "gitlens.views.openDirectoryDiff",
"when": "viewItem == gitlens:compare:results",
"when": "viewItem =~ /gitlens:compare:results\\b/",
"group": "7_gitlens@1"
},
{

+ 16
- 0
src/constants.ts ファイルの表示

@ -128,6 +128,21 @@ export const ImageMimetypes: { [key: string]: string } = {
'.bmp': 'image/bmp'
};
export interface NamedRef {
label?: string;
ref: string;
}
export interface PinnedComparison {
path: string;
ref1: NamedRef;
ref2: NamedRef;
}
export interface PinnedComparisons {
[id: string]: PinnedComparison;
}
export interface StarredBranches {
[id: string]: boolean;
}
@ -138,6 +153,7 @@ export interface StarredRepositories {
export enum WorkspaceState {
DefaultRemote = 'gitlens:remote:default',
PinnedComparisons = 'gitlens:pinned:comparisons',
StarredBranches = 'gitlens:starred:branches',
StarredRepositories = 'gitlens:starred:repositories',
ViewsCompareKeepResults = 'gitlens:views:compare:keepResults',

+ 54
- 3
src/views/compareView.ts ファイルの表示

@ -1,9 +1,16 @@
'use strict';
import { commands, ConfigurationChangeEvent } from 'vscode';
import { CompareViewConfig, configuration, ViewFilesLayout, ViewsConfig } from '../configuration';
import { CommandContext, setCommandContext, WorkspaceState } from '../constants';
import {
CommandContext,
NamedRef,
PinnedComparison,
PinnedComparisons,
setCommandContext,
WorkspaceState
} from '../constants';
import { Container } from '../container';
import { CompareNode, CompareResultsNode, NamedRef, ViewNode } from './nodes';
import { CompareNode, CompareResultsNode, nodeSupportsConditionalDismissal, ViewNode } from './nodes';
import { ViewBase } from './viewBase';
export class CompareView extends ViewBase<CompareNode> {
@ -46,6 +53,9 @@ export class CompareView extends ViewBase {
() => this.setKeepResults(false),
this
);
commands.registerCommand(this.getQualifiedCommand('pinComparision'), this.pinComparision, this);
commands.registerCommand(this.getQualifiedCommand('unpinComparision'), this.unpinComparision, this);
commands.registerCommand(this.getQualifiedCommand('swapComparision'), this.swapComparision, this);
commands.registerCommand(this.getQualifiedCommand('selectForCompare'), this.selectForCompare, this);
@ -86,6 +96,7 @@ export class CompareView extends ViewBase {
dismissNode(node: ViewNode) {
if (this._root === undefined) return;
if (nodeSupportsConditionalDismissal(node) && node.canDismiss() === false) return;
this._root.dismiss(node);
}
@ -111,6 +122,34 @@ export class CompareView extends ViewBase {
void root.selectForCompare(repoPath, ref);
}
getPinnedComparisons() {
const pinned = Container.context.workspaceState.get<PinnedComparisons>(WorkspaceState.PinnedComparisons);
if (pinned == null) return [];
return Object.values(pinned).map(p => new CompareResultsNode(this, p.path, p.ref1, p.ref2, true));
}
async updatePinnedComparison(id: string, pin?: PinnedComparison) {
let pinned = Container.context.workspaceState.get<PinnedComparisons>(WorkspaceState.PinnedComparisons);
if (pinned == null) {
pinned = Object.create(null);
}
if (pin !== undefined) {
pinned![id] = {
path: pin.path,
ref1: pin.ref1,
ref2: pin.ref2
};
}
else {
const { [id]: _, ...rest } = pinned!;
pinned = rest;
}
await Container.context.workspaceState.update(WorkspaceState.PinnedComparisons, pinned);
}
private async addResults(results: ViewNode) {
if (!this.visible) {
void (await this.show());
@ -131,9 +170,21 @@ export class CompareView extends ViewBase {
setCommandContext(CommandContext.ViewsCompareKeepResults, enabled);
}
private async pinComparision(node: ViewNode) {
if (!(node instanceof CompareResultsNode)) return;
return node.pin();
}
private swapComparision(node: ViewNode) {
if (!(node instanceof CompareResultsNode)) return;
node.swap();
return node.swap();
}
private async unpinComparision(node: ViewNode) {
if (!(node instanceof CompareResultsNode)) return;
return node.unpin();
}
}

+ 18
- 8
src/views/nodes/compareNode.ts ファイルの表示

@ -1,14 +1,14 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { getRepoPathOrPrompt } from '../../commands';
import { CommandContext, GlyphChars, setCommandContext } from '../../constants';
import { CommandContext, GlyphChars, NamedRef, setCommandContext } from '../../constants';
import { GitService } from '../../git/gitService';
import { BranchesAndTagsQuickPick, CommandQuickPickItem } from '../../quickpicks';
import { debug, Functions, gate, log } from '../../system';
import { CompareView } from '../compareView';
import { MessageNode } from './common';
import { ComparePickerNode } from './comparePickerNode';
import { NamedRef, ResourceType, unknownGitUri, ViewNode } from './viewNode';
import { ResourceType, unknownGitUri, ViewNode } from './viewNode';
interface RepoRef {
label: string;
@ -34,17 +34,21 @@ export class CompareNode extends ViewNode {
// Not really sure why I can't reuse this node -- but if I do the Tree errors out with an id already exists error
this._comparePickerNode = new ComparePickerNode(this.view, this);
this._children = [this._comparePickerNode];
const pinned = this.view.getPinnedComparisons();
if (pinned.length !== 0) {
this._children.push(...pinned);
}
}
else if (
this._selectedRef !== undefined &&
(this._comparePickerNode === undefined || !this._children.includes(this._comparePickerNode))
) {
else if (this._comparePickerNode === undefined || !this._children.includes(this._comparePickerNode)) {
// Not really sure why I can't reuse this node -- but if I do the Tree errors out with an id already exists error
this._comparePickerNode = new ComparePickerNode(this.view, this);
this._children.splice(0, 0, this._comparePickerNode);
const node = this._comparePickerNode;
setImmediate(() => this.view.reveal(node, { focus: false, select: true }));
if (this._selectedRef !== undefined) {
const node = this._comparePickerNode;
setImmediate(() => this.view.reveal(node, { focus: false, select: true }));
}
}
return this._children;
@ -62,6 +66,12 @@ export class CompareNode extends ViewNode {
if (this._children.length !== 0 && replace) {
this._children.length = 0;
this._children.push(results);
// Re-add the pinned comparisons
const pinned = this.view.getPinnedComparisons();
if (pinned.length !== 0) {
this._children.push(...pinned);
}
}
else {
if (this._comparePickerNode !== undefined) {

+ 75
- 18
src/views/nodes/compareResultsNode.ts ファイルの表示

@ -1,32 +1,39 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NamedRef, PinnedComparisons, WorkspaceState } from '../../constants';
import { Container } from '../../container';
import { GitService, GitUri } from '../../git/gitService';
import { Strings } from '../../system';
import { ViewWithFiles } from '../viewBase';
import { log, Strings } from '../../system';
import { CompareView } from '../compareView';
import { CommitsQueryResults, ResultsCommitsNode } from './resultsCommitsNode';
import { ResultsFilesNode } from './resultsFilesNode';
import { NamedRef, ResourceType, ViewNode } from './viewNode';
import { ResourceType, ViewNode } from './viewNode';
export class CompareResultsNode extends ViewNode<ViewWithFiles> {
export class CompareResultsNode extends ViewNode<CompareView> {
constructor(
view: ViewWithFiles,
view: CompareView,
public readonly repoPath: string,
ref1: NamedRef,
ref2: NamedRef
private _ref1: NamedRef,
private _ref2: NamedRef,
private _pinned: boolean = false
) {
super(GitUri.fromRepoPath(repoPath), view);
}
get label() {
return `Comparing ${this._ref1.label ||
GitService.shortenSha(this._ref1.ref, { working: 'Working Tree' })} to ${this._ref2.label ||
GitService.shortenSha(this._ref2.ref, { working: 'Working Tree' })}`;
}
this._ref1 = ref1;
this._ref2 = ref2;
get pinned(): boolean {
return this._pinned;
}
private _ref1: NamedRef;
get ref1(): NamedRef {
return this._ref1;
}
private _ref2: NamedRef;
get ref2(): NamedRef {
return this._ref2;
}
@ -45,23 +52,69 @@ export class CompareResultsNode extends ViewNode {
description = (repo && repo.formattedName) || this.uri.repoPath;
}
const item = new TreeItem(
`Comparing ${this._ref1.label ||
GitService.shortenSha(this._ref1.ref, { working: 'Working Tree' })} to ${this._ref2.label ||
GitService.shortenSha(this._ref2.ref, { working: 'Working Tree' })}`,
TreeItemCollapsibleState.Collapsed
);
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.contextValue = ResourceType.CompareResults;
if (this._pinned) {
item.contextValue += '+pinned';
}
item.description = description;
if (this._pinned) {
item.iconPath = {
dark: Container.context.asAbsolutePath(`images/dark/icon-pinned.svg`),
light: Container.context.asAbsolutePath(`images/light/icon-pinned.svg`)
};
}
return item;
}
swap() {
canDismiss(): boolean {
return !this._pinned;
}
@log()
async pin() {
if (this._pinned) return;
await this.view.updatePinnedComparison(this.getPinnableId(), {
path: this.repoPath,
ref1: this.ref1,
ref2: this.ref2
});
this._pinned = true;
void this.triggerChange();
}
@log()
async unpin() {
if (!this._pinned) return;
await this.view.updatePinnedComparison(this.getPinnableId());
this._pinned = false;
void this.triggerChange();
}
@log()
async swap() {
// Save the current id so we can update it later
const currentId = this.getPinnableId();
const ref1 = this._ref1;
this._ref1 = this._ref2;
this._ref2 = ref1;
// If we were pinned, remove the existing pin and save a new one
if (this._pinned) {
await this.view.updatePinnedComparison(currentId);
await this.view.updatePinnedComparison(this.getPinnableId(), {
path: this.repoPath,
ref1: this.ref1,
ref2: this.ref2
});
}
this.view.triggerNodeChange(this);
}
@ -81,4 +134,8 @@ export class CompareResultsNode extends ViewNode {
log: log
};
}
private getPinnableId() {
return Strings.sha1(`${this.repoPath}|${this.ref1.ref}|${this.ref2.ref}`);
}
}

+ 5
- 6
src/views/nodes/viewNode.ts ファイルの表示

@ -45,11 +45,6 @@ export enum ResourceType {
Tags = 'gitlens:tags'
}
export interface NamedRef {
label?: string;
ref: string;
}
export const unknownGitUri = new GitUri();
export interface ViewNode {
@ -226,6 +221,10 @@ export abstract class SubscribeableViewNode extends V
}
}
export function canDismissNode(view: View): view is View & { dismissNode(node: ViewNode): void } {
export function nodeSupportsConditionalDismissal(node: ViewNode): node is ViewNode & { canDismiss(): boolean } {
return typeof (node as any).canDismiss === 'function';
}
export function viewSupportsNodeDismissal(view: View): view is View & { dismissNode(node: ViewNode): void } {
return typeof (view as any).dismissNode === 'function';
}

+ 2
- 1
src/views/searchView.ts ファイルの表示

@ -5,7 +5,7 @@ import { CommandContext, setCommandContext, WorkspaceState } from '../constants'
import { Container } from '../container';
import { GitLog, GitRepoSearchBy } from '../git/gitService';
import { Functions, Strings } from '../system';
import { SearchNode, SearchResultsCommitsNode, ViewNode } from './nodes';
import { nodeSupportsConditionalDismissal, SearchNode, SearchResultsCommitsNode, ViewNode } from './nodes';
import { ViewBase } from './viewBase';
interface SearchQueryResult {
@ -89,6 +89,7 @@ export class SearchView extends ViewBase {
dismissNode(node: ViewNode) {
if (this._root === undefined) return;
if (nodeSupportsConditionalDismissal(node) && node.canDismiss() === false) return;
this._root.dismiss(node);
}

+ 3
- 3
src/views/viewCommands.ts ファイルの表示

@ -20,7 +20,6 @@ import { Arrays } from '../system';
import {
BranchNode,
BranchTrackingStatusNode,
canDismissNode,
CommitFileNode,
CommitNode,
FolderNode,
@ -32,7 +31,8 @@ import {
StatusFileNode,
TagNode,
ViewNode,
ViewRefNode
ViewRefNode,
viewSupportsNodeDismissal
} from './nodes';
export interface RefreshNodeCommandArgs {
@ -64,7 +64,7 @@ export class ViewCommands implements Disposable {
);
commands.registerCommand(
'gitlens.views.dismissNode',
(node: ViewNode) => canDismissNode(node.view) && node.view.dismissNode(node),
(node: ViewNode) => viewSupportsNodeDismissal(node.view) && node.view.dismissNode(node),
this
);

読み込み中…
キャンセル
保存