Browse Source

Combines File & Line History views

main
Eric Amodio 4 years ago
parent
commit
7d047ea04a
10 changed files with 166 additions and 77 deletions
  1. +42
    -12
      package.json
  2. +1
    -0
      src/constants.ts
  3. +1
    -1
      src/git/git.ts
  4. +40
    -14
      src/views/fileHistoryView.ts
  5. +4
    -7
      src/views/lineHistoryView.ts
  6. +5
    -6
      src/views/nodes/fileHistoryNode.ts
  7. +3
    -3
      src/views/nodes/fileHistoryTrackerNode.ts
  8. +11
    -11
      src/views/nodes/lineHistoryNode.ts
  9. +5
    -4
      src/views/nodes/lineHistoryTrackerNode.ts
  10. +54
    -19
      src/views/viewBase.ts

+ 42
- 12
package.json View File

@ -1606,7 +1606,7 @@
},
"gitlens.views.lineHistory.enabled": {
"type": "boolean",
"default": true,
"default": false,
"markdownDescription": "Specifies whether to show the _Line History_ view",
"scope": "window"
},
@ -3170,17 +3170,29 @@
},
{
"command": "gitlens.views.fileHistory.setEditorFollowingOn",
"title": "Resume File Tracking",
"title": "Unpin the Current File History",
"category": "GitLens",
"icon": "$(eye-closed)"
"icon": "$(pinned)"
},
{
"command": "gitlens.views.fileHistory.setEditorFollowingOff",
"title": "Pause File Tracking",
"title": "Pin the Current File History",
"category": "GitLens",
"icon": "$(pin)"
},
{
"command": "gitlens.views.fileHistory.setCursorFollowingOn",
"title": "Follow the Cursor",
"category": "GitLens",
"icon": "$(eye)"
},
{
"command": "gitlens.views.fileHistory.setCursorFollowingOff",
"title": "Unfollow the Cursor",
"category": "GitLens",
"icon": "$(eye-closed)"
},
{
"command": "gitlens.views.fileHistory.setRenameFollowingOn",
"title": "Follow Renames",
"category": "GitLens",
@ -3226,15 +3238,15 @@
},
{
"command": "gitlens.views.lineHistory.setEditorFollowingOn",
"title": "Resume Line Tracking",
"title": "Unpin the Current File History",
"category": "GitLens",
"icon": "$(eye-closed)"
"icon": "$(pinned)"
},
{
"command": "gitlens.views.lineHistory.setEditorFollowingOff",
"title": "Pause Line Tracking",
"title": "Pin the Current File History",
"category": "GitLens",
"icon": "$(eye)"
"icon": "$(pin)"
},
{
"command": "gitlens.views.lineHistory.setRenameFollowingOn",
@ -4101,6 +4113,14 @@
"when": "false"
},
{
"command": "gitlens.views.fileHistory.setCursorFollowingOn",
"when": "false"
},
{
"command": "gitlens.views.fileHistory.setCursorFollowingOff",
"when": "false"
},
{
"command": "gitlens.views.fileHistory.setRenameFollowingOn",
"when": "false"
},
@ -4652,9 +4672,19 @@
"group": "navigation@10"
},
{
"command": "gitlens.views.fileHistory.setCursorFollowingOn",
"when": "view =~ /^gitlens\\.views\\.fileHistory:/ && !gitlens:views:fileHistory:cursorFollowing",
"group": "navigation@11"
},
{
"command": "gitlens.views.fileHistory.setCursorFollowingOff",
"when": "view =~ /^gitlens\\.views\\.fileHistory:/ && gitlens:views:fileHistory:cursorFollowing",
"group": "navigation@11"
},
{
"command": "gitlens.views.fileHistory.changeBase",
"when": "view =~ /^gitlens\\.views\\.fileHistory:/",
"group": "navigation@11"
"group": "navigation@12"
},
{
"command": "gitlens.views.fileHistory.refresh",
@ -6082,7 +6112,7 @@
"when": "config.gitlens.views.lineHistory.enabled && config.gitlens.views.lineHistory.location == gitlens",
"contextualTitle": "GitLens",
"icon": "images/views/history.svg",
"visibility": "collapsed"
"visibility": "hidden"
},
{
"id": "gitlens.views.compare:gitlens",
@ -6122,7 +6152,7 @@
"when": "gitlens:enabled && config.gitlens.views.lineHistory.enabled && config.gitlens.views.lineHistory.location == explorer",
"contextualTitle": "GitLens",
"icon": "images/views/history.svg",
"visibility": "collapsed"
"visibility": "hidden"
},
{
"id": "gitlens.views.compare:explorer",
@ -6162,7 +6192,7 @@
"when": "gitlens:enabled && config.gitlens.views.lineHistory.enabled && config.gitlens.views.lineHistory.location == scm",
"contextualTitle": "GitLens",
"icon": "images/views/history.svg",
"visibility": "collapsed"
"visibility": "hidden"
},
{
"id": "gitlens.views.compare:scm",

+ 1
- 0
src/constants.ts View File

@ -41,6 +41,7 @@ export enum CommandContext {
ViewsCanCompare = 'gitlens:views:canCompare',
ViewsCanCompareFile = 'gitlens:views:canCompare:file',
ViewsCompareKeepResults = 'gitlens:views:compare:keepResults',
ViewsFileHistoryCursorFollowing = 'gitlens:views:fileHistory:cursorFollowing',
ViewsFileHistoryEditorFollowing = 'gitlens:views:fileHistory:editorFollowing',
ViewsLineHistoryEditorFollowing = 'gitlens:views:lineHistory:editorFollowing',
ViewsRepositoriesAutoRefresh = 'gitlens:views:repositories:autoRefresh',

+ 1
- 1
src/git/git.ts View File

@ -781,7 +781,7 @@ export namespace Git {
params.push('--numstat', '--summary');
}
} else {
// Don't include --name-status or -s because Git won't honor it
// Don't include `--name-status`, `--numstat`, or `--summary` because they aren't supported with `-L`
params.push(`-L ${startLine},${endLine == null ? startLine : endLine}:${file}`);
}
}

+ 40
- 14
src/views/fileHistoryView.ts View File

@ -4,16 +4,16 @@ import { configuration, FileHistoryViewConfig, ViewsConfig } from '../configurat
import { CommandContext, setCommandContext } from '../constants';
import { Container } from '../container';
import { GitUri } from '../git/gitUri';
import { FileHistoryTrackerNode } from './nodes';
import { FileHistoryTrackerNode, LineHistoryTrackerNode } from './nodes';
import { ViewBase } from './viewBase';
export class FileHistoryView extends ViewBase<FileHistoryTrackerNode> {
export class FileHistoryView extends ViewBase<FileHistoryTrackerNode | LineHistoryTrackerNode> {
constructor() {
super('gitlens.views.fileHistory', 'File History');
}
getRoot() {
return new FileHistoryTrackerNode(this);
return this._followCursor ? new LineHistoryTrackerNode(this) : new FileHistoryTrackerNode(this);
}
protected get location(): string {
@ -31,6 +31,16 @@ export class FileHistoryView extends ViewBase {
commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this);
commands.registerCommand(this.getQualifiedCommand('changeBase'), () => this.changeBase(), this);
commands.registerCommand(
this.getQualifiedCommand('setCursorFollowingOn'),
() => this.setCursorFollowing(true),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setCursorFollowingOff'),
() => this.setCursorFollowing(false),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setEditorFollowingOn'),
() => this.setEditorFollowing(true),
this,
@ -87,14 +97,15 @@ export class FileHistoryView extends ViewBase {
}
if (configuration.changed(e, 'views', 'fileHistory', 'enabled')) {
void setCommandContext(CommandContext.ViewsFileHistoryEditorFollowing, true);
void setCommandContext(CommandContext.ViewsFileHistoryEditorFollowing, this._followEditor);
void setCommandContext(CommandContext.ViewsFileHistoryCursorFollowing, this._followCursor);
}
if (configuration.changed(e, 'views', 'fileHistory', 'location')) {
this.initialize(this.config.location);
}
if (!configuration.initializing(e) && this._root !== undefined) {
if (!configuration.initializing(e) && this._root != null) {
void this.refresh(true);
}
}
@ -104,24 +115,39 @@ export class FileHistoryView extends ViewBase {
}
async showHistoryForUri(uri: GitUri, baseRef?: string) {
const root = this.ensureRoot();
this.setCursorFollowing(false);
this.setEditorFollowing(false);
await root.showHistoryForUri(uri, baseRef);
const root = this.ensureRoot(true);
if (root instanceof FileHistoryTrackerNode) {
await root.showHistoryForUri(uri, baseRef);
}
return this.show();
}
private changeBase() {
if (this._root !== undefined) {
void this._root.changeBase();
}
void this._root?.changeBase();
}
private _followCursor: boolean = false;
private setCursorFollowing(enabled: boolean) {
this._followCursor = enabled;
void setCommandContext(CommandContext.ViewsFileHistoryCursorFollowing, enabled);
const root = this.ensureRoot(true);
root.setEditorFollowing(this._followEditor);
void root.ensureSubscription();
void this.refresh(true);
this.titleContext = this._followCursor ? this.titleContext : undefined;
}
private _followEditor: boolean = true;
private setEditorFollowing(enabled: boolean) {
this._followEditor = enabled;
void setCommandContext(CommandContext.ViewsFileHistoryEditorFollowing, enabled);
if (this._root !== undefined) {
this._root.setEditorFollowing(enabled);
}
this._root?.setEditorFollowing(enabled);
this.description = enabled ? '' : ' (pinned)';
}
private setRenameFollowing(enabled: boolean) {

+ 4
- 7
src/views/lineHistoryView.ts View File

@ -82,7 +82,7 @@ export class LineHistoryView extends ViewBase {
this.initialize(this.config.location);
}
if (!configuration.initializing(e) && this._root !== undefined) {
if (!configuration.initializing(e) && this._root != null) {
void this.refresh(true);
}
}
@ -92,16 +92,13 @@ export class LineHistoryView extends ViewBase {
}
private changeBase() {
if (this._root !== undefined) {
void this._root.changeBase();
}
void this._root?.changeBase();
}
private setEditorFollowing(enabled: boolean) {
void setCommandContext(CommandContext.ViewsLineHistoryEditorFollowing, enabled);
if (this._root !== undefined) {
this._root.setEditorFollowing(enabled);
}
this._root?.setEditorFollowing(enabled);
this.description = enabled ? '' : ' (pinned)';
}
private setRenameFollowing(enabled: boolean) {

+ 5
- 6
src/views/nodes/fileHistoryNode.ts View File

@ -1,5 +1,5 @@
'use strict';
import { Disposable, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Container } from '../../container';
import {
GitLog,
@ -92,9 +92,8 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
);
item.contextValue = ResourceType.FileHistory;
item.description = this.uri.directory;
item.iconPath = new ThemeIcon('history');
item.tooltip = `History of ${this.uri.fileName}\n${this.uri.directory}/${
this.uri.sha === undefined ? '' : `\n\n${this.uri.sha}`
this.uri.sha == null ? '' : `\n\n${this.uri.sha}`
}`;
void this.ensureSubscription();
@ -105,7 +104,7 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
@debug()
protected async subscribe() {
const repo = await Container.git.getRepository(this.uri);
if (repo === undefined) return undefined;
if (repo == null) return undefined;
const subscription = Disposable.from(
repo.onDidChange(this.onRepoChanged, this),
@ -146,7 +145,7 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
private _log: GitLog | undefined;
private async getLog() {
if (this._log === undefined) {
if (this._log == null) {
this._log = await Container.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, {
limit: this.limit ?? this.view.config.defaultItemLimit,
ref: this.uri.sha,
@ -163,7 +162,7 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
limit: number | undefined = this.view.getNodeLastKnownLimit(this);
async showMore(limit?: number | { until?: any }) {
let log = await this.getLog();
if (log === undefined || !log.hasMore) return;
if (log == null || !log.hasMore) return;
log = await log.more?.(limit ?? this.view.config.pageItemLimit);
if (this._log === log) return;

+ 3
- 3
src/views/nodes/fileHistoryTrackerNode.ts View File

@ -29,15 +29,15 @@ export class FileHistoryTrackerNode extends SubscribeableViewNode
@debug()
private resetChild() {
if (this._child === undefined) return;
if (this._child == null) return;
this._child.dispose();
this._child = undefined;
}
getChildren(): ViewNode[] {
if (this._child === undefined) {
if (this._fileUri === undefined && this.uri === unknownGitUri) {
if (this._child == null) {
if (this._fileUri == null && this.uri === unknownGitUri) {
return [
new MessageNode(
this.view,

+ 11
- 11
src/views/nodes/lineHistoryNode.ts View File

@ -1,5 +1,5 @@
'use strict';
import { Disposable, Selection, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Disposable, Selection, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Container } from '../../container';
import {
GitCommitType,
@ -57,7 +57,7 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
const blame = this._editorContents
? await Container.git.getBlameForRangeContents(this.uri, selection, this._editorContents)
: await Container.git.getBlameForRange(this.uri, selection);
if (blame !== undefined) {
if (blame != null) {
for (const commit of blame.commits.values()) {
if (!commit.isUncommitted) continue;
@ -84,7 +84,7 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
workingTreeStatus: status?.workingTreeStatus ?? '?',
};
if (status?.workingTreeStatus !== undefined && status?.indexStatus !== undefined) {
if (status?.workingTreeStatus != null && status?.indexStatus != null) {
let uncommitted = new GitLogCommit(
GitCommitType.LogFile,
this.uri.repoPath!,
@ -142,9 +142,9 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
const uncommitted = new GitLogCommit(
GitCommitType.LogFile,
this.uri.repoPath!,
status?.workingTreeStatus !== undefined
status?.workingTreeStatus != null
? GitRevision.uncommitted
: status?.indexStatus !== undefined
: status?.indexStatus != null
? GitRevision.uncommittedStaged
: commit.sha,
'You',
@ -177,7 +177,7 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
}
const log = await this.getLog(selection);
if (log !== undefined) {
if (log != null) {
children.push(
...insertDateMarkers(
Iterables.filterMap(
@ -216,20 +216,20 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
);
item.contextValue = ResourceType.LineHistory;
item.description = this.uri.directory;
item.iconPath = new ThemeIcon('history');
item.tooltip = `History of ${this.uri.fileName}${lines}\n${this.uri.directory}/${
this.uri.sha === undefined ? '' : `\n\n${this.uri.sha}`
this.uri.sha == null ? '' : `\n\n${this.uri.sha}`
}`;
void this.ensureSubscription();
this.view.titleContext = lines;
return item;
}
@debug()
protected async subscribe() {
const repo = await Container.git.getRepository(this.uri);
if (repo === undefined) return undefined;
if (repo == null) return undefined;
const subscription = Disposable.from(
repo.onDidChange(this.onRepoChanged, this),
@ -268,7 +268,7 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
private _log: GitLog | undefined;
private async getLog(selection?: Selection) {
if (this._log === undefined) {
if (this._log == null) {
this._log = await Container.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, {
limit: this.limit ?? this.view.config.defaultItemLimit,
ref: this.uri.sha,
@ -286,7 +286,7 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
limit: number | undefined = this.view.getNodeLastKnownLimit(this);
async showMore(limit?: number | { until?: any }) {
let log = await this.getLog();
if (log === undefined || !log.hasMore) return;
if (log == null || !log.hasMore) return;
log = await log.more?.(limit ?? this.view.config.pageItemLimit);
if (this._log === log) return;

+ 5
- 4
src/views/nodes/lineHistoryTrackerNode.ts View File

@ -3,6 +3,7 @@ import { Selection, TreeItem, TreeItemCollapsibleState, window } from 'vscode';
import { MessageNode } from './common';
import { UriComparer } from '../../comparers';
import { Container } from '../../container';
import { FileHistoryView } from '../fileHistoryView';
import { GitReference } from '../../git/git';
import { GitCommitish, GitUri } from '../../git/gitUri';
import { LineHistoryView } from '../lineHistoryView';
@ -13,13 +14,13 @@ import { debug, Functions, gate, log } from '../../system';
import { LinesChangeEvent } from '../../trackers/gitLineTracker';
import { ResourceType, SubscribeableViewNode, unknownGitUri, ViewNode } from './viewNode';
export class LineHistoryTrackerNode extends SubscribeableViewNode<LineHistoryView> {
export class LineHistoryTrackerNode extends SubscribeableViewNode<LineHistoryView | FileHistoryView> {
private _base: string | undefined;
private _child: LineHistoryNode | undefined;
private _editorContents: string | undefined;
private _selection: Selection | undefined;
constructor(view: LineHistoryView) {
constructor(view: LineHistoryView | FileHistoryView) {
super(unknownGitUri, view);
}
@ -31,14 +32,14 @@ export class LineHistoryTrackerNode extends SubscribeableViewNode
@debug()
private resetChild() {
if (this._child === undefined) return;
if (this._child == null) return;
this._child.dispose();
this._child = undefined;
}
getChildren(): ViewNode[] {
if (this._child === undefined) {
if (this._child == null) {
if (this.uri === unknownGitUri) {
return [
new MessageNode(

+ 54
- 19
src/views/viewBase.ts View File

@ -17,6 +17,7 @@ import {
window,
} from 'vscode';
import { configuration } from '../configuration';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { Logger } from '../logger';
import { debug, Functions, log, Promises, Strings } from '../system';
@ -62,7 +63,7 @@ export abstract class ViewBase> implements TreeData
const item = await fn.apply(this, [node]);
const parent = node.getParent();
if (parent !== undefined) {
if (parent != null) {
item.tooltip = `${
item.tooltip ?? item.label
}\n\nDBG:\nnode: ${node.toString()}\nparent: ${parent.toString()}\ncontext: ${item.contextValue}`;
@ -85,6 +86,41 @@ export abstract class ViewBase> implements TreeData
this._disposable?.dispose();
}
private _title: string | undefined;
get title(): string | undefined {
return this._title;
}
set title(value: string | undefined) {
this._title = value;
this.updateTitle();
}
private _titleContext: string | undefined;
get titleContext(): string | undefined {
return this._titleContext;
}
set titleContext(value: string | undefined) {
this._titleContext = value;
this.updateTitle();
}
private _description: string | undefined;
get description(): string | undefined {
return this._description;
}
set description(value: string | undefined) {
this._description = value;
this.updateTitle();
}
private updateTitle() {
if (this._tree == null) return;
this._tree.title = `${this.title}${this.titleContext ? ` ${GlyphChars.Dot} ${this.titleContext}` : ''}${
this.description ? ` ${this.description}` : ''
}`;
}
getQualifiedCommand(command: string) {
return `${this.id}.${command}`;
}
@ -111,10 +147,11 @@ export abstract class ViewBase> implements TreeData
this._tree.onDidCollapseElement(this.onElementCollapsed, this),
this._tree.onDidExpandElement(this.onElementExpanded, this),
);
this._title = this._tree.title;
}
protected ensureRoot() {
if (this._root === undefined) {
protected ensureRoot(force: boolean = false) {
if (this._root == null || force) {
this._root = this.getRoot();
}
@ -122,7 +159,7 @@ export abstract class ViewBase> implements TreeData
}
getChildren(node?: ViewNode): ViewNode[] | Promise<ViewNode[]> {
if (node !== undefined) return node.getChildren();
if (node != null) return node.getChildren();
const root = this.ensureRoot();
return root.getChildren();
@ -149,13 +186,13 @@ export abstract class ViewBase> implements TreeData
}
get selection(): ViewNode[] {
if (this._tree === undefined || this._root === undefined) return [];
if (this._tree == null || this._root == null) return [];
return this._tree.selection;
}
get visible(): boolean {
return this._tree !== undefined ? this._tree.visible : false;
return this._tree != null ? this._tree.visible : false;
}
async findNode(
@ -205,7 +242,7 @@ export abstract class ViewBase> implements TreeData
const cc = Logger.getCorrelationContext();
// If we have no root (e.g. never been initialized) force it so the tree will load properly
if (this._root === undefined) {
if (this._root == null) {
await this.show();
}
@ -246,7 +283,7 @@ export abstract class ViewBase> implements TreeData
if (token?.isCancellationRequested) return undefined;
node = queue.shift();
if (node === undefined) {
if (node == null) {
depth++;
queue.push(undefined);
@ -256,7 +293,7 @@ export abstract class ViewBase> implements TreeData
}
if (predicate(node)) return node;
if (canTraverse !== undefined) {
if (canTraverse != null) {
const traversable = canTraverse(node);
if (Promises.is(traversable)) {
if (!(await traversable)) continue;
@ -270,7 +307,7 @@ export abstract class ViewBase> implements TreeData
if (PageableViewNode.is(node)) {
let child = children.find(predicate);
if (child !== undefined) return child;
if (child != null) return child;
if (allowPaging && node.hasMore) {
while (true) {
@ -287,7 +324,7 @@ export abstract class ViewBase> implements TreeData
);
child = pagedChildren.find(predicate);
if (child !== undefined) return child;
if (child != null) return child;
if (!node.hasMore) break;
}
@ -305,9 +342,7 @@ export abstract class ViewBase> implements TreeData
@debug()
async refresh(reset: boolean = false) {
if (this._root?.refresh != null) {
await this._root.refresh(reset);
}
await this._root?.refresh?.(reset);
this.triggerNodeChange();
}
@ -316,7 +351,7 @@ export abstract class ViewBase> implements TreeData
args: { 0: (n: ViewNode) => n.toString() },
})
async refreshNode(node: ViewNode, reset: boolean = false) {
if (node.refresh !== undefined) {
if (node.refresh != null) {
const cancel = await node.refresh(reset);
if (cancel === true) return;
}
@ -335,7 +370,7 @@ export abstract class ViewBase> implements TreeData
expand?: boolean | number;
},
) {
if (this._tree === undefined) return;
if (this._tree == null) return;
try {
await this._tree.reveal(node, options);
@ -385,7 +420,7 @@ export abstract class ViewBase> implements TreeData
@debug({
args: {
0: (n: ViewNode & PageableViewNode) => n.toString(),
3: (n?: ViewNode) => (n === undefined ? '' : n.toString()),
3: (n?: ViewNode) => (n == null ? '' : n.toString()),
},
})
async showMoreNodeChildren(
@ -393,7 +428,7 @@ export abstract class ViewBase> implements TreeData
limit: number | { until: any } | undefined,
previousNode?: ViewNode,
) {
if (previousNode !== undefined) {
if (previousNode != null) {
void (await this.reveal(previousNode, { select: true }));
}
@ -406,6 +441,6 @@ export abstract class ViewBase> implements TreeData
})
triggerNodeChange(node?: ViewNode) {
// Since the root node won't actually refresh, force everything
this._onDidChangeTreeData.fire(node !== undefined && node !== this._root ? node : undefined);
this._onDidChangeTreeData.fire(node != null && node !== this._root ? node : undefined);
}
}

Loading…
Cancel
Save