Skip to content

Commit f083393

Browse files
author
Eric Amodio
committed
Adds full blame UI support
1 parent f4d3d17 commit f083393

File tree

7 files changed

+459
-30
lines changed

7 files changed

+459
-30
lines changed

.vscode/settings.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
{
33
"files.exclude": {
44
"out": false, // set this to true to hide the "out" folder with the compiled JS files
5-
"node_modules": false
5+
"node_modules": true,
6+
"typings": true
67
},
78
"search.exclude": {
89
"out": true, // set this to false to include "out" folder in search results
9-
"node_modules": true
10+
"node_modules": true,
11+
"typings": true
1012
},
1113
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
1214
}

src/commands.ts

Lines changed: 216 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
'use strict'
2-
import {commands, Disposable, Position, Range, Uri, window} from 'vscode';
2+
import {commands, DecorationOptions, Disposable, OverviewRulerLane, Position, Range, TextEditorDecorationType, Uri, window} from 'vscode';
33
import {Commands, VsCodeCommands} from './constants';
44
import GitProvider from './gitProvider';
5+
import GitBlameController from './gitBlameController';
56
import {basename} from 'path';
7+
import * as moment from 'moment';
68

79
abstract class Command extends Disposable {
810
private _subscriptions: Disposable;
@@ -14,36 +16,233 @@ abstract class Command extends Disposable {
1416

1517
dispose() {
1618
this._subscriptions && this._subscriptions.dispose();
17-
super.dispose();
1819
}
1920

2021
abstract execute(...args): any;
2122
}
2223

2324
export class BlameCommand extends Command {
24-
constructor(private git: GitProvider) {
25+
constructor(private git: GitProvider, private blameController: GitBlameController) {
2526
super(Commands.ShowBlameHistory);
2627
}
2728

28-
execute(uri?: Uri, range?: Range, position?: Position) {
29-
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
30-
if (!uri) {
31-
const doc = window.activeTextEditor && window.activeTextEditor.document;
32-
if (doc) {
33-
uri = doc.uri;
34-
range = doc.validateRange(new Range(0, 0, 1000000, 1000000));
35-
position = doc.validateRange(new Range(0, 0, 0, 1000000)).start;
36-
}
37-
38-
if (!uri) return;
29+
execute(uri?: Uri, range?: Range, sha?: string) {
30+
const editor = window.activeTextEditor;
31+
if (!editor) return;
32+
33+
if (!range) {
34+
range = editor.document.validateRange(new Range(0, 0, 1000000, 1000000));
3935
}
4036

41-
return this.git.getBlameLocations(uri.path, range).then(locations => {
42-
return commands.executeCommand(VsCodeCommands.ShowReferences, uri, position, locations);
43-
});
37+
if (sha) {
38+
return this.blameController.toggleBlame(editor, sha);
39+
}
40+
41+
const activeLine = editor.selection.active.line;
42+
return this.git.getBlameForLine(editor.document.fileName, activeLine)
43+
.then(blame => this.blameController.toggleBlame(editor, blame.commit.sha));
4444
}
4545
}
4646

47+
// export class BlameCommand extends Command {
48+
// // static Colors: Array<Array<number>> = [ [255, 152, 0], [255, 87, 34], [121, 85, 72], [158, 158, 158], [96, 125, 139], [244, 67, 54], [233, 30, 99], [156, 39, 176], [103, 58, 183] ];
49+
// // private _decorations: TextEditorDecorationType[] = [];
50+
51+
// constructor(private git: GitProvider, private blameDecoration: TextEditorDecorationType, private highlightDecoration: TextEditorDecorationType) {
52+
// super(Commands.ShowBlameHistory);
53+
54+
// // BlameCommand.Colors.forEach(c => {
55+
// // this._decorations.push(window.createTextEditorDecorationType({
56+
// // dark: {
57+
// // backgroundColor: `rgba(${c[0]}, ${c[1]}, ${c[2]}, 0.15)`,
58+
// // //gutterIconPath: context.asAbsolutePath('images/blame-dark.png'),
59+
// // overviewRulerColor: `rgba(${c[0]}, ${c[1]}, ${c[2]}, 0.75)`,
60+
// // },
61+
// // //light: {
62+
// // //backgroundColor: 'rgba(0, 0, 0, 0.15)',
63+
// // //gutterIconPath: context.asAbsolutePath('images/blame-light.png'),
64+
// // //overviewRulerColor: c //'rgba(0, 0, 0, 0.75)',
65+
// // //},
66+
// // // before: {
67+
// // // margin: '0 1em 0 0'
68+
// // // },
69+
// // // after: {
70+
// // // margin: '0 0 0 2em'
71+
// // // },
72+
// // //gutterIconSize: 'contain',
73+
// // overviewRulerLane: OverviewRulerLane.Right,
74+
// // //isWholeLine: true
75+
// // }));
76+
// // });
77+
// }
78+
79+
// execute(uri?: Uri, range?: Range, position?: Position) {
80+
// const editor = window.activeTextEditor;
81+
// if (!editor) {
82+
// return;
83+
// }
84+
85+
// editor.setDecorations(this.blameDecoration, []);
86+
// editor.setDecorations(this.highlightDecoration, []);
87+
88+
// const highlightDecorationRanges: Array<Range> = [];
89+
// const blameDecorationOptions: Array<DecorationOptions> = [];
90+
91+
// this.git.getBlameForRange(uri.path, range).then(blame => {
92+
// if (!blame.lines.length) return;
93+
94+
// const commits = Array.from(blame.commits.values());
95+
// const recentCommit = commits.sort((a, b) => b.date.getTime() - a.date.getTime())[0];
96+
97+
// return this.git.getCommitMessages(uri.path)
98+
// .then(msgs => {
99+
// commits.forEach(c => {
100+
// c.message = msgs.get(c.sha.substring(0, c.sha.length - 1));
101+
// });
102+
103+
// blame.lines.forEach(l => {
104+
// if (l.sha === recentCommit.sha) {
105+
// highlightDecorationRanges.push(editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)));
106+
// }
107+
108+
// const c = blame.commits.get(l.sha);
109+
// blameDecorationOptions.push({
110+
// range: editor.document.validateRange(new Range(l.line, 0, l.line, 0)),
111+
// hoverMessage: `${c.sha}: ${c.message}\n${c.author}, ${moment(c.date).format('MMMM Do, YYYY hh:MM a')}`,
112+
// renderOptions: {
113+
// // dark: {
114+
// // backgroundColor: `rgba(255, 255, 255, ${alphas.get(l.sha)})`
115+
// // },
116+
// before: {
117+
// //border: '1px solid gray',
118+
// //color: 'rgb(128, 128, 128)',
119+
// contentText: `${l.sha}`,
120+
// // margin: '0 1em 0 0',
121+
// // width: '5em'
122+
// }
123+
// // after: {
124+
// // contentText: `${c.author}, ${moment(c.date).format('MMMM Do, YYYY hh:MM a')}`,
125+
// // //color: 'rbg(128, 128, 128)',
126+
// // margin: '0 0 0 2em'
127+
// // }
128+
// }
129+
// });
130+
// });
131+
// });
132+
133+
// // Array.from(blame.commits.values()).forEach((c, i) => {
134+
// // if (i == 0) {
135+
// // highlightDecorationRanges = blame.lines
136+
// // .filter(l => l.sha === c.sha)
137+
// // .map(l => editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)));
138+
// // }
139+
140+
// // blameDecorationOptions.push(blame.lines
141+
// // .filter(l => l.sha === c.sha)
142+
// // .map(l => {
143+
// // return {
144+
// // range: editor.document.validateRange(new Range(l.line, 0, l.line, 6)),
145+
// // hoverMessage: `${c.author}\n${moment(c.date).format('MMMM Do, YYYY hh:MM a')}\n${l.sha}`,
146+
// // renderOptions: {
147+
// // // dark: {
148+
// // // backgroundColor: `rgba(255, 255, 255, ${alphas.get(l.sha)})`
149+
// // // },
150+
// // before: {
151+
// // //border: '1px solid gray',
152+
// // //color: 'rgb(128, 128, 128)',
153+
// // contentText: `${l.sha}`,
154+
// // // margin: '0 1em 0 0',
155+
// // // width: '5em'
156+
// // }
157+
// // // after: {
158+
// // // contentText: `${c.author}, ${moment(c.date).format('MMMM Do, YYYY hh:MM a')}`,
159+
// // // //color: 'rbg(128, 128, 128)',
160+
// // // margin: '0 0 0 2em'
161+
// // // }
162+
// // }
163+
// // };
164+
// // }));
165+
// // });
166+
// })
167+
// .then(() => {
168+
// editor.setDecorations(this.blameDecoration, blameDecorationOptions);
169+
// editor.setDecorations(this.highlightDecoration, highlightDecorationRanges);
170+
// });
171+
172+
// // this._decorations.forEach(d => editor.setDecorations(d, []));
173+
// // this.git.getBlameForRange(uri.path, range).then(blame => {
174+
// // if (!blame.lines.length) return;
175+
176+
// // Array.from(blame.commits.values()).forEach((c, i) => {
177+
// // editor.setDecorations(this._decorations[i], blame.lines.filter(l => l.sha === c.sha).map(l => {
178+
// // const commit = c; //blame.commits.get(l.sha);
179+
// // return {
180+
// // range: editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)),
181+
// // hoverMessage: `${commit.author}\n${moment(commit.date).format('MMMM Do, YYYY hh:MM a')}\n${l.sha}`,
182+
// // renderOptions: {
183+
// // // dark: {
184+
// // // backgroundColor: `rgba(255, 255, 255, ${alphas.get(l.sha)})`
185+
// // // },
186+
// // before: {
187+
// // color: 'rgb(128, 128, 128)',
188+
// // contentText: `${l.sha}`,
189+
// // //border: '1px solid gray',
190+
// // width: '5em',
191+
// // margin: '0 1em 0 0'
192+
// // },
193+
// // after: {
194+
// // contentText: `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY hh:MM a')}`,
195+
// // //color: 'rbg(128, 128, 128)',
196+
// // margin: '0 0 0 2em'
197+
// // }
198+
// // }
199+
// // };
200+
// // }));
201+
// // });
202+
203+
// // //this.git.getCommitMessage(data.sha).then(msg => {
204+
// // // editor.setDecorations(this._blameDecoration, blame.lines.map(l => {
205+
// // // const commit = blame.commits.get(l.sha);
206+
// // // return {
207+
// // // range: editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)),
208+
// // // hoverMessage: `${commit.author}\n${moment(commit.date).format('MMMM Do, YYYY hh:MM a')}\n${l.sha}`,
209+
// // // renderOptions: {
210+
// // // // dark: {
211+
// // // // backgroundColor: `rgba(255, 255, 255, ${alphas.get(l.sha)})`
212+
// // // // },
213+
// // // before: {
214+
// // // contentText: `${l.sha}`,
215+
// // // margin: '0 0 0 -10px'
216+
// // // },
217+
// // // after: {
218+
// // // contentText: `${l.sha}`,
219+
// // // color: 'rbg(128, 128, 128)',
220+
// // // margin: '0 20px 0 0'
221+
// // // }
222+
// // // }
223+
// // // };
224+
// // // }));
225+
// // //})
226+
// // });
227+
228+
// // // If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
229+
// // if (!uri) {
230+
// // const doc = window.activeTextEditor && window.activeTextEditor.document;
231+
// // if (doc) {
232+
// // uri = doc.uri;
233+
// // range = doc.validateRange(new Range(0, 0, 1000000, 1000000));
234+
// // position = doc.validateRange(new Range(0, 0, 0, 1000000)).start;
235+
// // }
236+
237+
// // if (!uri) return;
238+
// // }
239+
240+
// // return this.git.getBlameLocations(uri.path, range).then(locations => {
241+
// // return commands.executeCommand(VsCodeCommands.ShowReferences, uri, position, locations);
242+
// // });
243+
// }
244+
// }
245+
47246
export class DiffWithPreviousCommand extends Command {
48247
constructor(private git: GitProvider) {
49248
super(Commands.DiffWithPrevious);

src/extension.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use strict';
2-
import {CodeLens, DocumentSelector, ExtensionContext, extensions, languages, workspace} from 'vscode';
2+
import {CodeLens, DocumentSelector, ExtensionContext, extensions, languages, OverviewRulerLane, window, workspace} from 'vscode';
33
import GitCodeLensProvider from './gitCodeLensProvider';
44
import GitBlameCodeLensProvider from './gitBlameCodeLensProvider';
55
import GitBlameContentProvider from './gitBlameContentProvider';
6+
import GitBlameController from './gitBlameController';
67
import GitProvider from './gitProvider';
78
import {BlameCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands';
89
import {WorkspaceState} from './constants';
@@ -28,7 +29,11 @@ export function activate(context: ExtensionContext) {
2829
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitBlameContentProvider.scheme, new GitBlameContentProvider(context, git)));
2930
context.subscriptions.push(languages.registerCodeLensProvider(GitCodeLensProvider.selector, new GitCodeLensProvider(context, git)));
3031
context.subscriptions.push(languages.registerCodeLensProvider(GitBlameCodeLensProvider.selector, new GitBlameCodeLensProvider(context, git)));
31-
context.subscriptions.push(new BlameCommand(git));
32+
33+
const blameController = new GitBlameController(context, git);
34+
context.subscriptions.push(blameController);
35+
36+
context.subscriptions.push(new BlameCommand(git, blameController));
3237
context.subscriptions.push(new DiffWithPreviousCommand(git));
3338
context.subscriptions.push(new DiffWithWorkingCommand(git));
3439
}).catch(reason => console.warn(reason));

src/git.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ export default class Git {
2222
fileName = Git.normalizePath(fileName, repoPath);
2323

2424
if (sha) {
25-
console.log('git', 'blame', '-fnw', '--root', `${sha}^`, '--', fileName);
25+
console.log('git', 'blame', '-fn', '--root', `${sha}^`, '--', fileName);
2626
return gitCommand(repoPath, 'blame', '-fnw', '--root', `${sha}^`, '--', fileName);
2727
}
2828

29-
console.log('git', 'blame', '-fnw', '--root', '--', fileName);
30-
return gitCommand(repoPath, 'blame', '-fnw', '--root', '--', fileName);
29+
console.log('git', 'blame', '-fn', '--root', '--', fileName);
30+
return gitCommand(repoPath, 'blame', '-fn', '--root', '--', fileName);
3131
// .then(s => { console.log(s); return s; })
3232
// .catch(ex => console.error(ex));
3333
}
@@ -75,4 +75,13 @@ export default class Git {
7575
// .then(s => { console.log(s); return s; })
7676
// .catch(ex => console.error(ex));
7777
}
78+
79+
static getCommitMessages(fileName: string, repoPath: string) {
80+
fileName = Git.normalizePath(fileName, repoPath);
81+
82+
console.log('git', 'log', '--oneline', '--', fileName);
83+
return gitCommand(repoPath, 'log', '--oneline', '--', fileName);
84+
// .then(s => { console.log(s); return s; })
85+
// .catch(ex => console.error(ex));
86+
}
7887
}

0 commit comments

Comments
 (0)