Skip to content

Commit 68c1720

Browse files
committed
Adds a file history explorer view
1 parent d6c8406 commit 68c1720

File tree

8 files changed

+263
-9
lines changed

8 files changed

+263
-9
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
111111

112112
### Navigate and Explore
113113

114-
- Adds a `Git Stashes` explorer to the Explorer activity ([optional](#git-stashes-explorer-settings), off by default)
114+
- Adds a [customizable](#git-file-history-explorer-settings) `Git File History` explorer to the Explorer activity -- currently [insiders](#insiders) only
115+
116+
- Adds a [customizable](#git-stashes-explorer-settings) `Git Stashes` explorer to the Explorer activity
115117

116118
![Git Stashes explorer](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-git-stashes.png)
117119

@@ -289,6 +291,12 @@ GitLens is highly customizable and provides many configuration settings to allow
289291
|`gitlens.codeLens.customLocationSymbols`|Specifies the set of document symbols where Git code lens will be shown in the document
290292
|`gitlens.codeLens.perLanguageLocations`|Specifies where Git code lens will be shown in the document for the specified languages
291293

294+
### Git File History Explorer Settings
295+
296+
|Name | Description
297+
|-----|------------
298+
|`gitlens.fileHistoryExplorer.commitFormat`|Specifies the format of committed changes in the `Git File History` explorer <br />Available tokens<br /> ${id} - commit id<br /> ${author} - commit author<br /> ${message} - commit message<br /> ${ago} - relative commit date (e.g. 1 day ago)<br /> ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)<br /> ${authorAgo} - commit author, relative commit date<br />See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
299+
292300
### Git Stashes Explorer Settings
293301

294302
|Name | Description

package.json

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,11 @@
413413
"default": null,
414414
"description": "Specifies how all absolute dates will be formatted by default\nSee https://momentjs.com/docs/#/displaying/format/ for valid formats"
415415
},
416+
"gitlens.fileHistoryExplorer.commitFormat": {
417+
"type": "string",
418+
"default": "${message} \u00a0\u2022\u00a0 ${authorAgo} \u00a0\u2022\u00a0 ${id}",
419+
"description": "Specifies the format of committed changes in the `Git File History` explorer\nAvailable tokens\n ${id} - commit id\n ${author} - commit author\n ${message} - commit message\n ${ago} - relative commit date (e.g. 1 day ago)\n ${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)\n ${authorAgo} - commit author, relative commit date\nSee https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting"
420+
},
416421
"gitlens.stashExplorer.stashFormat": {
417422
"type": "string",
418423
"default": "${message}",
@@ -979,6 +984,40 @@
979984
"light": "images/light/icon-refresh.svg"
980985
}
981986
},
987+
{
988+
"command": "gitlens.fileHistoryExplorer.refresh",
989+
"title": "Refresh",
990+
"category": "GitLens",
991+
"icon": {
992+
"dark": "images/dark/icon-refresh.svg",
993+
"light": "images/light/icon-refresh.svg"
994+
}
995+
},
996+
{
997+
"command": "gitlens.fileHistoryExplorer.openChanges",
998+
"title": "Open Changes",
999+
"category": "GitLens"
1000+
},
1001+
{
1002+
"command": "gitlens.fileHistoryExplorer.openFile",
1003+
"title": "Open File",
1004+
"category": "GitLens"
1005+
},
1006+
{
1007+
"command": "gitlens.fileHistoryExplorer.openFileRevision",
1008+
"title": "Open File Revision",
1009+
"category": "GitLens"
1010+
},
1011+
{
1012+
"command": "gitlens.fileHistoryExplorer.openFileInRemote",
1013+
"title": "Open File in Remote",
1014+
"category": "GitLens"
1015+
},
1016+
{
1017+
"command": "gitlens.fileHistoryExplorer.openFileRevisionInRemote",
1018+
"title": "Open File Revision in Remote",
1019+
"category": "GitLens"
1020+
},
9821021
{
9831022
"command": "gitlens.stashExplorer.refresh",
9841023
"title": "Refresh",
@@ -1163,6 +1202,29 @@
11631202
"command": "gitlens.gitExplorer.refresh",
11641203
"when": "false"
11651204
},
1205+
{
1206+
"command": "gitlens.fileHistoryExplorer.refresh",
1207+
"when": "false"
1208+
},
1209+
{
1210+
"command": "gitlens.fileHistoryExplorer.openChanges",
1211+
"when": "false"
1212+
},
1213+
{
1214+
"command": "gitlens.fileHistoryExplorer.openFile",
1215+
"when": "false"
1216+
},
1217+
{
1218+
"command": "gitlens.fileHistoryExplorer.openFileRevision",
1219+
"when": "false"
1220+
},
1221+
{
1222+
"command": "gitlens.fileHistoryExplorer.openFileInRemote",
1223+
"when": "false"
1224+
},
1225+
{
1226+
"command": "gitlens.fileHistoryExplorer.openFileRevisionInRemote",
1227+
"when": "false"
11661228
},
11671229
{
11681230
"command": "gitlens.stashExplorer.refresh",
@@ -1374,6 +1436,11 @@
13741436
"when": "gitlens:enabled && view == gitlens.gitExplorer",
13751437
"group": "navigation"
13761438
},
1439+
{
1440+
"command": "gitlens.fileHistoryExplorer.refresh",
1441+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer",
1442+
"group": "navigation"
1443+
},
13771444
{
13781445
"command": "gitlens.stashSave",
13791446
"when": "gitlens:enabled && view == gitlens.stashExplorer",
@@ -1406,6 +1473,41 @@
14061473
"when": "gitlens:enabled && view == gitlens.gitExplorer && viewItem == commit-file",
14071474
"group": "2_gitlens@2"
14081475
},
1476+
{
1477+
"command": "gitlens.fileHistoryExplorer.openChanges",
1478+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
1479+
"group": "1_gitlens@1"
1480+
},
1481+
{
1482+
"command": "gitlens.diffWithWorking",
1483+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
1484+
"group": "1_gitlens@2"
1485+
},
1486+
{
1487+
"command": "gitlens.fileHistoryExplorer.openFile",
1488+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
1489+
"group": "2_gitlens@1"
1490+
},
1491+
{
1492+
"command": "gitlens.fileHistoryExplorer.openFileRevision",
1493+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
1494+
"group": "2_gitlens@2"
1495+
},
1496+
{
1497+
"command": "gitlens.fileHistoryExplorer.openFileInRemote",
1498+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
1499+
"group": "2_gitlens@3"
1500+
},
1501+
{
1502+
"command": "gitlens.fileHistoryExplorer.openFileRevisionInRemote",
1503+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
1504+
"group": "2_gitlens@4"
1505+
},
1506+
{
1507+
"command": "gitlens.showQuickCommitDetails",
1508+
"when": "gitlens:enabled && view == gitlens.fileHistoryExplorer && viewItem == commit-file",
1509+
"group": "3_gitlens@1"
1510+
},
14091511
{
14101512
"command": "gitlens.stashApply",
14111513
"when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == stash-commit",
@@ -1537,6 +1639,11 @@
15371639
],
15381640
"views": {
15391641
"explorer": [
1642+
{
1643+
"id": "gitlens.fileHistoryExplorer",
1644+
"name": "Git File History",
1645+
"when": "gitlens:enabled && config.gitlens.insiders"
1646+
},
15401647
{
15411648
"id": "gitlens.stashExplorer",
15421649
"name": "Git Stashes",

src/commands/showQuickCommitDetails.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use strict';
22
import { Strings } from '../system';
33
import { commands, TextEditor, Uri, window } from 'vscode';
4-
import { ActiveEditorCachedCommand, Commands, getCommandUri } from './common';
4+
import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri } from './common';
55
import { GlyphChars } from '../constants';
6+
import { CommitNode } from '../views/explorerNodes';
67
import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService';
78
import { Logger } from '../logger';
89
import { CommandQuickPickItem, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks';
@@ -24,6 +25,18 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
2425
super(Commands.ShowQuickCommitDetails);
2526
}
2627

28+
protected async preExecute(context: CommandContext, ...args: any[]): Promise<any> {
29+
if (context.type === 'view') {
30+
if (context.node instanceof CommitNode) {
31+
args = [{ sha: context.node.uri.sha, commit: context.node.commit }];
32+
}
33+
else {
34+
args = [{ sha: context.node.uri.sha }];
35+
}
36+
}
37+
return this.execute(context.editor, context.uri, ...args);
38+
}
39+
2740
async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickCommitDetailsCommandArgs = {}) {
2841
uri = getCommandUri(uri, editor);
2942
if (uri === undefined) return undefined;

src/configuration.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,12 @@ export interface IConfig {
296296

297297
defaultDateFormat: string | null;
298298

299+
fileHistoryExplorer: {
300+
commitFormat: string;
301+
// commitFileFormat: string;
302+
// dateFormat: string | null;
303+
};
304+
299305
gitExplorer: {
300306
commitFormat: string;
301307
commitFileFormat: string;

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { CodeLensController } from './codeLensController';
2121
import { CurrentLineController, LineAnnotationType } from './currentLineController';
2222
import { GitContentProvider } from './gitContentProvider';
2323
// import { GitExplorer } from './views/gitExplorer';
24+
import { FileHistoryExplorer } from './views/fileHistoryExplorer';
2425
import { StashExplorer } from './views/stashExplorer';
2526
import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider';
2627
import { GitContextTracker, GitService } from './gitService';
@@ -96,6 +97,7 @@ export async function activate(context: ExtensionContext) {
9697
// const explorer = new GitExplorer(context, git);
9798
// context.subscriptions.push(window.registerTreeDataProvider('gitlens.gitExplorer', explorer));
9899

100+
context.subscriptions.push(window.registerTreeDataProvider('gitlens.fileHistoryExplorer', new FileHistoryExplorer(context, git)));
99101
context.subscriptions.push(window.registerTreeDataProvider('gitlens.stashExplorer', new StashExplorer(context, git)));
100102

101103
context.subscriptions.push(commands.registerTextEditorCommand('gitlens.computingFileAnnotations', () => { }));

src/views/commitNode.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
import { Iterables } from '../system';
3-
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
3+
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
4+
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
45
import { CommitFileNode } from './commitFileNode';
56
import { ExplorerNode, ResourceType } from './explorerNode';
67
import { CommitFormatter, GitCommit, GitService, GitUri } from '../gitService';
@@ -14,6 +15,8 @@ export class CommitNode extends ExplorerNode {
1415
}
1516

1617
async getChildren(): Promise<ExplorerNode[]> {
18+
if (this.commit.type === 'file') Promise.resolve([]);
19+
1720
const log = await this.git.getLogForRepo(this.commit.repoPath, this.commit.sha, 1);
1821
if (log === undefined) return [];
1922

@@ -24,14 +27,40 @@ export class CommitNode extends ExplorerNode {
2427
}
2528

2629
getTreeItem(): TreeItem {
27-
const label = CommitFormatter.fromTemplate(this.template, this.commit, this.git.config.defaultDateFormat);
30+
const item = new TreeItem(CommitFormatter.fromTemplate(this.template, this.commit, this.git.config.defaultDateFormat));
31+
if (this.commit.type === 'file') {
32+
item.collapsibleState = TreeItemCollapsibleState.None;
33+
item.command = this.getCommand();
34+
item.contextValue = 'commit-file';
35+
}
36+
else {
37+
item.collapsibleState = TreeItemCollapsibleState.Collapsed;
38+
item.contextValue = this.resourceType;
39+
}
2840

29-
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
30-
item.contextValue = this.resourceType;
3141
item.iconPath = {
3242
dark: this.context.asAbsolutePath('images/dark/icon-commit.svg'),
3343
light: this.context.asAbsolutePath('images/light/icon-commit.svg')
3444
};
45+
3546
return item;
3647
}
48+
49+
getCommand(): Command | undefined {
50+
return {
51+
title: 'Compare File with Previous',
52+
command: Commands.DiffWithPrevious,
53+
arguments: [
54+
new GitUri(this.uri, this.commit),
55+
{
56+
commit: this.commit,
57+
line: 0,
58+
showOptions: {
59+
preserveFocus: true,
60+
preview: true
61+
}
62+
} as DiffWithPreviousCommandArgs
63+
]
64+
};
65+
}
3766
}

src/views/fileHistoryExplorer.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict';
2+
// import { Arrays } from '../system';
3+
import { commands, Event, EventEmitter, ExtensionContext, TextEditor, TreeDataProvider, TreeItem, Uri, window } from 'vscode';
4+
import { Commands, DiffWithPreviousCommandArgs, openEditor, OpenFileInRemoteCommandArgs } from '../commands';
5+
import { UriComparer } from '../comparers';
6+
import { CommitNode, ExplorerNode, FileHistoryNode, TextExplorerNode } from './explorerNodes';
7+
import { GitService, GitUri } from '../gitService';
8+
9+
export * from './explorerNodes';
10+
11+
export class FileHistoryExplorer implements TreeDataProvider<ExplorerNode> {
12+
13+
private _node?: ExplorerNode;
14+
15+
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>();
16+
public get onDidChangeTreeData(): Event<ExplorerNode> {
17+
return this._onDidChangeTreeData.event;
18+
}
19+
20+
constructor(private context: ExtensionContext, private git: GitService) {
21+
commands.registerCommand('gitlens.fileHistoryExplorer.refresh', this.refresh, this);
22+
commands.registerCommand('gitlens.fileHistoryExplorer.openChanges', this.openChanges, this);
23+
commands.registerCommand('gitlens.fileHistoryExplorer.openFile', this.openFile, this);
24+
commands.registerCommand('gitlens.fileHistoryExplorer.openFileRevision', this.openFileRevision, this);
25+
commands.registerCommand('gitlens.fileHistoryExplorer.openFileInRemote', this.openFileInRemote, this);
26+
commands.registerCommand('gitlens.fileHistoryExplorer.openFileRevisionInRemote', this.openFileRevisionInRemote, this);
27+
28+
context.subscriptions.push(window.onDidChangeActiveTextEditor(this.onActiveEditorChanged, this));
29+
30+
this._node = this.getRootNode(window.activeTextEditor);
31+
}
32+
33+
async getTreeItem(node: ExplorerNode): Promise<TreeItem> {
34+
return node.getTreeItem();
35+
}
36+
37+
async getChildren(node?: ExplorerNode): Promise<ExplorerNode[]> {
38+
if (this._node === undefined) return [new TextExplorerNode('No active file')];
39+
if (node === undefined) return this._node.getChildren();
40+
return node.getChildren();
41+
}
42+
43+
private getRootNode(editor: TextEditor | undefined): ExplorerNode | undefined {
44+
if (window.visibleTextEditors.length === 0) return undefined;
45+
if (editor === undefined) return this._node;
46+
47+
const uri = this.git.getGitUriForFile(editor.document.uri) || new GitUri(editor.document.uri, { repoPath: this.git.repoPath, fileName: editor.document.uri.fsPath });
48+
if (UriComparer.equals(uri, this._node && this._node.uri)) return this._node;
49+
50+
return new FileHistoryNode(uri, this.context, this.git);
51+
}
52+
53+
private onActiveEditorChanged(editor: TextEditor | undefined) {
54+
const node = this.getRootNode(editor);
55+
if (node === this._node) return;
56+
57+
this.refresh();
58+
}
59+
60+
refresh(node?: ExplorerNode) {
61+
this._node = node || this.getRootNode(window.activeTextEditor);
62+
this._onDidChangeTreeData.fire();
63+
}
64+
65+
private openChanges(node: CommitNode) {
66+
const command = node.getCommand();
67+
if (command === undefined || command.arguments === undefined) return;
68+
69+
const [uri, args] = command.arguments as [Uri, DiffWithPreviousCommandArgs];
70+
args.showOptions!.preview = false;
71+
return commands.executeCommand(command.command, uri, args);
72+
}
73+
74+
private openFile(node: CommitNode) {
75+
return openEditor(node.uri, { preserveFocus: true, preview: false });
76+
}
77+
78+
private openFileRevision(node: CommitNode) {
79+
return openEditor(GitService.toGitContentUri(node.uri), { preserveFocus: true, preview: false });
80+
}
81+
82+
private async openFileInRemote(node: CommitNode) {
83+
return commands.executeCommand(Commands.OpenFileInRemote, node.commit.uri, { range: false } as OpenFileInRemoteCommandArgs);
84+
}
85+
86+
private async openFileRevisionInRemote(node: CommitNode) {
87+
return commands.executeCommand(Commands.OpenFileInRemote, new GitUri(node.commit.uri, node.commit), { range: false } as OpenFileInRemoteCommandArgs);
88+
}
89+
}

0 commit comments

Comments
 (0)