Browse Source

Adds Tags view

main
Eric Amodio 4 years ago
parent
commit
7b24d7fe27
9 changed files with 495 additions and 28 deletions
  1. +188
    -10
      package.json
  2. +3
    -0
      src/config.ts
  3. +11
    -0
      src/container.ts
  4. +6
    -6
      src/views/branchesView.ts
  5. +2
    -2
      src/views/nodes/branchesNode.ts
  6. +3
    -2
      src/views/nodes/tagNode.ts
  7. +7
    -6
      src/views/nodes/tagsNode.ts
  8. +271
    -0
      src/views/tagsView.ts
  9. +4
    -2
      src/views/viewBase.ts

+ 188
- 10
package.json View File

@ -1525,7 +1525,7 @@
"gitlens.views.branches.files.compact": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to compact (flatten) unnecessary file nesting in the _Branches_ view. Only applies when `#gitlens.views.repositories.files.layout#` is set to `tree` or `auto`",
"markdownDescription": "Specifies whether to compact (flatten) unnecessary file nesting in the _Branches_ view. Only applies when `#gitlens.views.branches.files.layout#` is set to `tree` or `auto`",
"scope": "window"
},
"gitlens.views.branches.files.layout": {
@ -1661,7 +1661,7 @@
"gitlens.views.history.files.compact": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to compact (flatten) unnecessary file nesting in the _History_ view. Only applies when `#gitlens.views.repositories.files.layout#` is set to `tree` or `auto`",
"markdownDescription": "Specifies whether to compact (flatten) unnecessary file nesting in the _History_ view. Only applies when `#gitlens.views.history.files.layout#` is set to `tree` or `auto`",
"scope": "window"
},
"gitlens.views.history.files.layout": {
@ -1941,6 +1941,54 @@
"markdownDescription": "Specifies the description format of the status of a working or committed file in the views. See [_File Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#file-tokens) in the GitLens docs",
"scope": "window"
},
"gitlens.views.tags.avatars": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to show avatar images instead of commit (or status) icons in the _Tags_ view",
"scope": "window"
},
"gitlens.views.tags.branches.layout": {
"type": "string",
"default": "tree",
"enum": [
"list",
"tree"
],
"enumDescriptions": [
"Displays branches as a list",
"Displays branches as a tree when branch names contain slashes `/`"
],
"markdownDescription": "Specifies how the _Tags_ view will display branches",
"scope": "window"
},
"gitlens.views.tags.files.compact": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to compact (flatten) unnecessary file nesting in the _Tags_ view. Only applies when `#gitlens.views.tags.files.layout#` is set to `tree` or `auto`",
"scope": "window"
},
"gitlens.views.tags.files.layout": {
"type": "string",
"default": "auto",
"enum": [
"auto",
"list",
"tree"
],
"enumDescriptions": [
"Automatically switches between displaying files as a `tree` or `list` based on the `#gitlens.views.tags.files.threshold#` value and the number of files at each nesting level",
"Displays files as a list",
"Displays files as a tree"
],
"markdownDescription": "Specifies how the _Tags_ view will display files",
"scope": "window"
},
"gitlens.views.tags.files.threshold": {
"type": "number",
"default": 5,
"markdownDescription": "Specifies when to switch between displaying files as a `tree` or `list` based on the number of files in a nesting level in the _Tags_ view. Only applies when `#gitlens.views.tags.files.layout#` is set to `auto`",
"scope": "window"
},
"gitlens.advanced.abbreviatedShaLength": {
"type": "number",
"default": "7",
@ -3242,14 +3290,14 @@
"icon": "$(refresh)"
},
{
"command": "gitlens.views.branches.setBranchesLayoutToList",
"title": "Toggle Branch Layout (Tree)",
"command": "gitlens.views.branches.setLayoutToList",
"title": "Toggle Layout (Tree)",
"category": "GitLens",
"icon": "$(list-tree)"
},
{
"command": "gitlens.views.branches.setBranchesLayoutToTree",
"title": "Toggle Branch Layout (List)",
"command": "gitlens.views.branches.setLayoutToTree",
"title": "Toggle Layout (List)",
"category": "GitLens",
"icon": "$(list-flat)"
},
@ -3605,6 +3653,60 @@
"category": "GitLens"
},
{
"command": "gitlens.views.tags.copy",
"title": "Copy",
"category": "GitLens"
},
{
"command": "gitlens.views.tags.refresh",
"title": "Refresh",
"category": "GitLens",
"icon": "$(refresh)"
},
{
"command": "gitlens.views.tags.setLayoutToList",
"title": "Toggle Layout (Tree)",
"category": "GitLens",
"icon": "$(list-tree)"
},
{
"command": "gitlens.views.tags.setLayoutToTree",
"title": "Toggle Layout (List)",
"category": "GitLens",
"icon": "$(list-flat)"
},
{
"command": "gitlens.views.tags.setFilesLayoutToAuto",
"title": "Toggle File Layout (Tree)",
"category": "GitLens",
"icon": "$(list-tree)"
},
{
"command": "gitlens.views.tags.setFilesLayoutToList",
"title": "Toggle File Layout (Auto)",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-view-auto.svg",
"light": "images/light/icon-view-auto.svg"
}
},
{
"command": "gitlens.views.tags.setFilesLayoutToTree",
"title": "Toggle File Layout (List)",
"category": "GitLens",
"icon": "$(list-flat)"
},
{
"command": "gitlens.views.tags.setShowAvatarsOn",
"title": "Show Avatars",
"category": "GitLens"
},
{
"command": "gitlens.views.tags.setShowAvatarsOff",
"title": "Hide Avatars",
"category": "GitLens"
},
{
"command": "gitlens.views.dismissNode",
"title": "Dismiss",
"category": "GitLens",
@ -4279,11 +4381,11 @@
"when": "false"
},
{
"command": "gitlens.views.branches.setBranchesLayoutToList",
"command": "gitlens.views.branches.setLayoutToList",
"when": "false"
},
{
"command": "gitlens.views.branches.setBranchesLayoutToTree",
"command": "gitlens.views.branches.setLayoutToTree",
"when": "false"
},
{
@ -4519,6 +4621,42 @@
"when": "false"
},
{
"command": "gitlens.views.tags.copy",
"when": "false"
},
{
"command": "gitlens.views.tags.refresh",
"when": "false"
},
{
"command": "gitlens.views.tags.setLayoutToList",
"when": "false"
},
{
"command": "gitlens.views.tags.setLayoutToTree",
"when": "false"
},
{
"command": "gitlens.views.tags.setFilesLayoutToAuto",
"when": "false"
},
{
"command": "gitlens.views.tags.setFilesLayoutToList",
"when": "false"
},
{
"command": "gitlens.views.tags.setFilesLayoutToTree",
"when": "false"
},
{
"command": "gitlens.views.tags.setShowAvatarsOn",
"when": "false"
},
{
"command": "gitlens.views.tags.setShowAvatarsOff",
"when": "false"
},
{
"command": "gitlens.views.dismissNode",
"when": "false"
},
@ -5151,12 +5289,12 @@
"group": "9_gitlens@-1"
},
{
"command": "gitlens.views.branches.setBranchesLayoutToList",
"command": "gitlens.views.branches.setLayoutToList",
"when": "view =~ /gitlens\\.views\\.branches/ && config.gitlens.views.branches.branches.layout == tree",
"group": "navigation@1"
},
{
"command": "gitlens.views.branches.setBranchesLayoutToTree",
"command": "gitlens.views.branches.setLayoutToTree",
"when": "view =~ /gitlens\\.views\\.branches/ && config.gitlens.views.branches.branches.layout == list",
"group": "navigation@1"
},
@ -5219,6 +5357,46 @@
"command": "gitlens.views.history.setShowAvatarsOff",
"when": "view =~ /^gitlens\\.views\\.history/ && config.gitlens.views.history.avatars",
"group": "1_gitlens@0"
},
{
"command": "gitlens.views.tags.setLayoutToList",
"when": "view =~ /gitlens\\.views\\.tags/ && config.gitlens.views.tags.branches.layout == tree",
"group": "navigation@1"
},
{
"command": "gitlens.views.tags.setLayoutToTree",
"when": "view =~ /gitlens\\.views\\.tags/ && config.gitlens.views.tags.branches.layout == list",
"group": "navigation@1"
},
{
"command": "gitlens.views.tags.setFilesLayoutToList",
"when": "view =~ /^gitlens\\.views\\.tags/ && config.gitlens.views.tags.files.layout == auto",
"group": "navigation@13"
},
{
"command": "gitlens.views.tags.setFilesLayoutToTree",
"when": "view =~ /^gitlens\\.views\\.tags/ && config.gitlens.views.tags.files.layout == list",
"group": "navigation@13"
},
{
"command": "gitlens.views.tags.setFilesLayoutToAuto",
"when": "view =~ /^gitlens\\.views\\.tags/ && config.gitlens.views.tags.files.layout == tree",
"group": "navigation@13"
},
{
"command": "gitlens.views.tags.refresh",
"when": "view =~ /^gitlens\\.views\\.tags/",
"group": "navigation@99"
},
{
"command": "gitlens.views.tags.setShowAvatarsOn",
"when": "view =~ /^gitlens\\.views\\.tags/ && !config.gitlens.views.tags.avatars",
"group": "1_gitlens@0"
},
{
"command": "gitlens.views.tags.setShowAvatarsOff",
"when": "view =~ /^gitlens\\.views\\.tags/ && config.gitlens.views.tags.avatars",
"group": "1_gitlens@0"
}
],
"view/item/context": [

+ 3
- 0
src/config.ts View File

@ -551,6 +551,9 @@ export interface StashesViewConfig {
export interface TagsViewConfig {
avatars: boolean;
branches: {
layout: ViewBranchesLayout;
};
files: ViewsFilesConfig;
}

+ 11
- 0
src/container.ts View File

@ -30,6 +30,7 @@ import { HistoryView } from './views/historyView';
import { LineHistoryView } from './views/lineHistoryView';
import { RepositoriesView } from './views/repositoriesView';
import { SearchView } from './views/searchView';
import { TagsView } from './views/tagsView';
import { ViewCommands } from './views/viewCommands';
import { VslsController } from './vsls/vsls';
import { RebaseEditorProvider } from './webviews/rebaseEditor';
@ -66,6 +67,7 @@ export class Container {
context.subscriptions.push((this._branchesView = new BranchesView()));
context.subscriptions.push((this._historyView = new HistoryView()));
context.subscriptions.push((this._tagsView = new TagsView()));
if (config.views.compare.enabled) {
context.subscriptions.push((this._compareView = new CompareView()));
@ -312,6 +314,15 @@ export class Container {
return this._statusBarController;
}
private static _tagsView: TagsView | undefined;
static get tagsView() {
if (this._tagsView === undefined) {
this._context.subscriptions.push((this._tagsView = new TagsView()));
}
return this._tagsView;
}
private static _tracker: GitDocumentTracker;
static get tracker() {
return this._tracker;

+ 6
- 6
src/views/branchesView.ts View File

@ -39,7 +39,7 @@ import { debug, gate } from '../system';
import { ViewBase } from './viewBase';
export class BranchesRepositoryNode extends SubscribeableViewNode<BranchesView> {
private child: ViewNode | undefined;
private child: BranchesNode | undefined;
constructor(
uri: GitUri,
@ -169,13 +169,13 @@ export class BranchesView extends ViewBase
);
commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this);
commands.registerCommand(
this.getQualifiedCommand('setBranchesLayoutToList'),
() => this.setBranchesLayout(ViewBranchesLayout.List),
this.getQualifiedCommand('setLayoutToList'),
() => this.setLayout(ViewBranchesLayout.List),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setBranchesLayoutToTree'),
() => this.setBranchesLayout(ViewBranchesLayout.Tree),
this.getQualifiedCommand('setLayoutToTree'),
() => this.setLayout(ViewBranchesLayout.Tree),
this,
);
commands.registerCommand(
@ -389,7 +389,7 @@ export class BranchesView extends ViewBase
);
}
private setBranchesLayout(layout: ViewBranchesLayout) {
private setLayout(layout: ViewBranchesLayout) {
return configuration.updateEffective('views', this.configKey, 'branches', 'layout', layout);
}

+ 2
- 2
src/views/nodes/branchesNode.ts View File

@ -13,7 +13,7 @@ import { RepositoryNode } from './repositoryNode';
import { Arrays, debug, gate } from '../../system';
import { ContextValues, ViewNode } from './viewNode';
export class BranchesNode extends ViewNode<RepositoriesView | BranchesView> {
export class BranchesNode extends ViewNode<BranchesView | RepositoriesView> {
static key = ':branches';
static getId(repoPath: string): string {
return `${RepositoryNode.getId(repoPath)}${this.key}`;
@ -23,7 +23,7 @@ export class BranchesNode extends ViewNode {
constructor(
uri: GitUri,
view: RepositoriesView | BranchesView,
view: BranchesView | RepositoriesView,
parent: ViewNode,
public readonly repo: Repository,
) {

+ 3
- 2
src/views/nodes/tagNode.ts View File

@ -13,14 +13,15 @@ import { ContextValues, PageableViewNode, ViewNode, ViewRefNode } from './viewNo
import { emojify } from '../../emojis';
import { RepositoryNode } from './repositoryNode';
import { GlyphChars } from '../../constants';
import { TagsView } from '../tagsView';
export class TagNode extends ViewRefNode<RepositoriesView, GitTagReference> implements PageableViewNode {
export class TagNode extends ViewRefNode<TagsView | RepositoriesView, GitTagReference> implements PageableViewNode {
static key = ':tag';
static getId(repoPath: string, name: string): string {
return `${RepositoryNode.getId(repoPath)}${this.key}(${name})`;
}
constructor(uri: GitUri, view: RepositoriesView, parent: ViewNode, public readonly tag: GitTag) {
constructor(uri: GitUri, view: TagsView | RepositoriesView, parent: ViewNode, public readonly tag: GitTag) {
super(uri, view, parent);
}

+ 7
- 6
src/views/nodes/tagsNode.ts View File

@ -1,23 +1,24 @@
'use strict';
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { BranchOrTagFolderNode } from './branchOrTagFolderNode';
import { MessageNode } from './common';
import { ViewBranchesLayout } from '../../configuration';
import { Repository } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { Arrays } from '../../system';
import { RepositoriesView } from '../repositoriesView';
import { BranchOrTagFolderNode } from './branchOrTagFolderNode';
import { MessageNode } from './common';
import { RepositoryNode } from './repositoryNode';
import { Arrays } from '../../system';
import { TagNode } from './tagNode';
import { TagsView } from '../tagsView';
import { ContextValues, ViewNode } from './viewNode';
import { RepositoryNode } from './repositoryNode';
export class TagsNode extends ViewNode<RepositoriesView> {
export class TagsNode extends ViewNode<TagsView | RepositoriesView> {
static key = ':tags';
static getId(repoPath: string): string {
return `${RepositoryNode.getId(repoPath)}${this.key}`;
}
constructor(uri: GitUri, view: RepositoriesView, parent: ViewNode, public readonly repo: Repository) {
constructor(uri: GitUri, view: TagsView | RepositoriesView, parent: ViewNode, public readonly repo: Repository) {
super(uri, view, parent);
}

+ 271
- 0
src/views/tagsView.ts View File

@ -0,0 +1,271 @@
'use strict';
import {
CancellationToken,
commands,
ConfigurationChangeEvent,
ProgressLocation,
TreeItem,
TreeItemCollapsibleState,
window,
} from 'vscode';
import { configuration, TagsViewConfig, ViewBranchesLayout, ViewFilesLayout } from '../configuration';
import { Container } from '../container';
import { GitReference, GitTagReference, Repository, RepositoryChange, RepositoryChangeEvent } from '../git/git';
import { GitUri } from '../git/gitUri';
import {
BranchOrTagFolderNode,
ContextValues,
MessageNode,
RepositoriesNode,
RepositoryNode,
SubscribeableViewNode,
TagsNode,
unknownGitUri,
ViewNode,
} from './nodes';
import { debug, gate } from '../system';
import { ViewBase } from './viewBase';
export class TagsRepositoryNode extends SubscribeableViewNode<TagsView> {
private child: TagsNode | undefined;
constructor(
uri: GitUri,
view: TagsView,
parent: ViewNode,
public readonly repo: Repository,
private readonly root: boolean,
) {
super(uri, view, parent);
}
async getChildren(): Promise<ViewNode[]> {
if (this.child == null) {
this.child = new TagsNode(this.uri, this.view, this, this.repo);
void this.ensureSubscription();
}
return this.child.getChildren();
}
getTreeItem(): TreeItem {
const item = new TreeItem(
this.repo.formattedName ?? this.uri.repoPath ?? '',
TreeItemCollapsibleState.Expanded,
);
item.contextValue = ContextValues.RepositoryFolder;
void this.ensureSubscription();
return item;
}
@gate()
@debug()
async refresh(reset: boolean = false) {
await this.child?.triggerChange(reset);
await this.ensureSubscription();
}
@debug()
protected subscribe() {
return this.repo.onDidChange(this.onRepositoryChanged, this);
}
@debug({
args: {
0: (e: RepositoryChangeEvent) =>
`{ repository: ${e.repository ? e.repository.name : ''}, changes: ${e.changes.join()} }`,
},
})
private onRepositoryChanged(e: RepositoryChangeEvent) {
if (e.changed(RepositoryChange.Closed)) {
this.dispose();
void this.parent?.triggerChange(true);
return;
}
if (e.changed(RepositoryChange.Tags)) {
void this.triggerChange(true);
if (this.root) {
void this.parent?.triggerChange(true);
}
}
}
}
export class TagsViewNode extends ViewNode<TagsView> {
private children: TagsRepositoryNode[] | undefined;
constructor(view: TagsView) {
super(unknownGitUri, view);
}
async getChildren(): Promise<ViewNode[]> {
if (this.children != null) {
for (const child of this.children) {
child.dispose?.();
}
this.children = undefined;
}
const repositories = await Container.git.getOrderedRepositories();
if (repositories.length === 0) return [new MessageNode(this.view, this, 'No tags could be found.')];
const root = repositories.length === 1;
this.children = repositories.map(
r => new TagsRepositoryNode(GitUri.fromRepoPath(r.path), this.view, this, r, root),
);
if (root) {
const [child] = this.children;
const tags = await child.repo.getTags();
this.view.description = tags.length === 0 ? undefined : `(${tags.length})`;
return child.getChildren();
}
return this.children;
}
getTreeItem(): TreeItem {
const item = new TreeItem('Tags', TreeItemCollapsibleState.Expanded);
return item;
}
}
export class TagsView extends ViewBase<TagsViewNode, TagsViewConfig> {
protected readonly configKey = 'tags';
constructor() {
super('gitlens.views.tags', 'Tags');
}
getRoot() {
return new TagsViewNode(this);
}
protected registerCommands() {
void Container.viewCommands;
commands.registerCommand(
this.getQualifiedCommand('copy'),
() => commands.executeCommand('gitlens.views.copy', this.selection),
this,
);
commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this);
commands.registerCommand(
this.getQualifiedCommand('setLayoutToList'),
() => this.setLayout(ViewBranchesLayout.List),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setLayoutToTree'),
() => this.setLayout(ViewBranchesLayout.Tree),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setFilesLayoutToAuto'),
() => this.setFilesLayout(ViewFilesLayout.Auto),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setFilesLayoutToList'),
() => this.setFilesLayout(ViewFilesLayout.List),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setFilesLayoutToTree'),
() => this.setFilesLayout(ViewFilesLayout.Tree),
this,
);
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOn'), () => this.setShowAvatars(true), this);
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOff'), () => this.setShowAvatars(false), this);
}
protected filterConfigurationChanged(e: ConfigurationChangeEvent) {
const changed = super.filterConfigurationChanged(e);
if (
!changed &&
!configuration.changed(e, 'defaultDateFormat') &&
!configuration.changed(e, 'defaultDateSource') &&
!configuration.changed(e, 'defaultDateStyle') &&
!configuration.changed(e, 'defaultGravatarsStyle') &&
!configuration.changed(e, 'sortTagsBy')
) {
return false;
}
return true;
}
protected onConfigurationChanged(e: ConfigurationChangeEvent) {
if (configuration.initializing(e)) {
this.initialize(undefined, { showCollapseAll: true });
}
if (!configuration.initializing(e) && this._root != null) {
void this.refresh(true);
}
}
findTag(tag: GitTagReference, token?: CancellationToken) {
const repoNodeId = RepositoryNode.getId(tag.repoPath);
return this.findNode((n: any) => n.tag !== undefined && n.tag.ref === tag.ref, {
allowPaging: true,
maxDepth: 5,
canTraverse: n => {
// Only search for tag nodes in the same repo within TagsNode
if (n instanceof RepositoriesNode) return true;
if (n instanceof RepositoryNode || n instanceof TagsNode || n instanceof BranchOrTagFolderNode) {
return n.id.startsWith(repoNodeId);
}
return false;
},
token: token,
});
}
@gate(() => '')
revealTag(
tag: GitTagReference,
options?: {
select?: boolean;
focus?: boolean;
expand?: boolean | number;
},
) {
return window.withProgress(
{
location: ProgressLocation.Notification,
title: `Revealing ${GitReference.toString(tag, { icon: false })} in the Repositories view...`,
cancellable: true,
},
async (progress, token) => {
const node = await this.findTag(tag, token);
if (node === undefined) return node;
await this.ensureRevealNode(node, options);
return node;
},
);
}
private setLayout(layout: ViewBranchesLayout) {
return configuration.updateEffective('views', this.configKey, 'branches', 'layout', layout);
}
private setFilesLayout(layout: ViewFilesLayout) {
return configuration.updateEffective('views', this.configKey, 'files', 'layout', layout);
}
private setShowAvatars(enabled: boolean) {
return configuration.updateEffective('views', this.configKey, 'avatars', enabled);
}
}

+ 4
- 2
src/views/viewBase.ts View File

@ -46,6 +46,7 @@ import { PageableViewNode, ViewNode } from './nodes';
import { RepositoriesView } from './repositoriesView';
import { SearchView } from './searchView';
import { debug, Functions, log, Promises, Strings } from '../system';
import { TagsView } from './tagsView';
export type View =
| BranchesView
@ -54,8 +55,9 @@ export type View =
| HistoryView
| LineHistoryView
| RepositoriesView
| SearchView;
export type ViewsWithFiles = BranchesView | CompareView | HistoryView | RepositoriesView | SearchView;
| SearchView
| TagsView;
export type ViewsWithFiles = BranchesView | CompareView | HistoryView | RepositoriesView | SearchView | TagsView;
export interface TreeViewNodeStateChangeEvent<T> extends TreeViewExpansionEvent<T> {
state: TreeItemCollapsibleState;

Loading…
Cancel
Save