Skip to content

Commit 0e064f1

Browse files
author
Eric Amodio
committed
Reworks git abstraction and acccess
Adds commands module Adds git commit message to blame hover decorator
1 parent 9964ea6 commit 0e064f1

File tree

7 files changed

+180
-129
lines changed

7 files changed

+180
-129
lines changed

src/codeLensProvider.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
11
'use strict';
22
import {CancellationToken, CodeLens, CodeLensProvider, commands, ExtensionContext, Location, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri} from 'vscode';
33
import {Commands, VsCodeCommands, WorkspaceState} from './constants';
4-
import GitBlameProvider, {IGitBlame, IGitBlameCommit} from './gitBlameProvider';
4+
import GitProvider, {IGitBlame, IGitBlameCommit} from './gitProvider';
55
import * as moment from 'moment';
66

77
export class GitBlameCodeLens extends CodeLens {
8-
constructor(private blameProvider: GitBlameProvider, public fileName: string, public blameRange: Range, range: Range) {
8+
constructor(private git: GitProvider, public fileName: string, public blameRange: Range, range: Range) {
99
super(range);
1010
}
1111

1212
getBlame(): Promise<IGitBlame> {
13-
return this.blameProvider.getBlameForRange(this.fileName, this.blameProvider.repoPath, this.blameRange);
14-
}
15-
16-
static toUri(lens: GitBlameCodeLens, repoPath: string, commit: IGitBlameCommit, index: number, commitCount: number): Uri {
17-
return GitBlameProvider.toBlameUri(repoPath, commit, lens.blameRange, index, commitCount);
13+
return this.git.getBlameForRange(this.fileName, this.blameRange);
1814
}
1915
}
2016

2117
export class GitHistoryCodeLens extends CodeLens {
2218
constructor(public repoPath: string, public fileName: string, range: Range) {
2319
super(range);
2420
}
25-
26-
// static toUri(lens: GitHistoryCodeLens, index: number): Uri {
27-
// return GitBlameProvider.toBlameUri(Object.assign({ repoPath: lens.repoPath, index: index, range: lens.blameRange, lines: lines }, line));
28-
// }
2921
}
3022

3123
export default class GitCodeLensProvider implements CodeLensProvider {
32-
constructor(context: ExtensionContext, public blameProvider: GitBlameProvider) { }
24+
constructor(context: ExtensionContext, private git: GitProvider) { }
3325

3426
provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable<CodeLens[]> {
35-
this.blameProvider.blameFile(document.fileName, this.blameProvider.repoPath);
27+
this.git.getBlameForFile(document.fileName);
3628

3729
return (commands.executeCommand(VsCodeCommands.ExecuteDocumentSymbolProvider, document.uri) as Promise<SymbolInformation[]>).then(symbols => {
3830
let lenses: CodeLens[] = [];
@@ -41,8 +33,8 @@ export default class GitCodeLensProvider implements CodeLensProvider {
4133
// Check if we have a lens for the whole document -- if not add one
4234
if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) {
4335
const docRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000));
44-
lenses.push(new GitBlameCodeLens(this.blameProvider, document.fileName, docRange, new Range(0, 0, 0, docRange.start.character)));
45-
lenses.push(new GitHistoryCodeLens(this.blameProvider.repoPath, document.fileName, new Range(0, 1, 0, docRange.start.character)));
36+
lenses.push(new GitBlameCodeLens(this.git, document.fileName, docRange, new Range(0, 0, 0, docRange.start.character)));
37+
lenses.push(new GitHistoryCodeLens(this.git.repoPath, document.fileName, new Range(0, 1, 0, docRange.start.character)));
4638
}
4739
return lenses;
4840
});
@@ -74,8 +66,8 @@ export default class GitCodeLensProvider implements CodeLensProvider {
7466
startChar += Math.floor(symbol.name.length / 2);
7567
}
7668

77-
lenses.push(new GitBlameCodeLens(this.blameProvider, document.fileName, symbol.location.range, line.range.with(new Position(line.range.start.line, startChar))));
78-
lenses.push(new GitHistoryCodeLens(this.blameProvider.repoPath, document.fileName, line.range.with(new Position(line.range.start.line, startChar + 1))));
69+
lenses.push(new GitBlameCodeLens(this.git, document.fileName, symbol.location.range, line.range.with(new Position(line.range.start.line, startChar))));
70+
lenses.push(new GitHistoryCodeLens(this.git.repoPath, document.fileName, line.range.with(new Position(line.range.start.line, startChar + 1))));
7971
}
8072

8173
resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable<CodeLens> {

src/commands.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict'
2+
import {commands, Disposable, Position, Range, Uri, window} from 'vscode';
3+
import {Commands, VsCodeCommands} from './constants';
4+
import GitProvider from './gitProvider';
5+
6+
abstract class Command extends Disposable {
7+
private _subscriptions: Disposable;
8+
9+
constructor(command: Commands) {
10+
super(() => this.dispose());
11+
this._subscriptions = commands.registerCommand(command, this.execute.bind(this));
12+
}
13+
14+
dispose() {
15+
this._subscriptions && this._subscriptions.dispose();
16+
super.dispose();
17+
}
18+
19+
abstract execute(...args): any;
20+
}
21+
22+
export class BlameCommand extends Command {
23+
constructor(private git: GitProvider) {
24+
super(Commands.ShowBlameHistory);
25+
}
26+
27+
execute(uri?: Uri, range?: Range, position?: Position) {
28+
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
29+
if (!uri) {
30+
const doc = window.activeTextEditor && window.activeTextEditor.document;
31+
if (doc) {
32+
uri = doc.uri;
33+
range = doc.validateRange(new Range(0, 0, 1000000, 1000000));
34+
position = doc.validateRange(new Range(0, 0, 0, 1000000)).start;
35+
}
36+
37+
if (!uri) return;
38+
}
39+
40+
return this.git.getBlameLocations(uri.path, range).then(locations => {
41+
return commands.executeCommand(VsCodeCommands.ShowReferences, uri, position, locations);
42+
});
43+
}
44+
}

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use strict'
2+
13
export type WorkspaceState = 'repoPath';
24
export const WorkspaceState = {
35
RepoPath: 'repoPath' as WorkspaceState

src/contentProvider.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
'use strict';
22
import {Disposable, EventEmitter, ExtensionContext, OverviewRulerLane, Range, TextEditor, TextEditorDecorationType, TextDocumentContentProvider, Uri, window, workspace} from 'vscode';
33
import {DocumentSchemes, WorkspaceState} from './constants';
4-
import {gitGetVersionText} from './git';
5-
import GitBlameProvider, {IGitBlameUriData} from './gitBlameProvider';
4+
import GitProvider, {IGitBlameUriData} from './gitProvider';
65
import * as moment from 'moment';
76

87
export default class GitBlameContentProvider implements TextDocumentContentProvider {
@@ -12,7 +11,7 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi
1211
private _onDidChange = new EventEmitter<Uri>();
1312
//private _subscriptions: Disposable;
1413

15-
constructor(context: ExtensionContext, public blameProvider: GitBlameProvider) {
14+
constructor(context: ExtensionContext, private git: GitProvider) {
1615
this._blameDecoration = window.createTextEditorDecorationType({
1716
dark: {
1817
backgroundColor: 'rgba(255, 255, 255, 0.15)',
@@ -48,11 +47,11 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi
4847
}
4948

5049
provideTextDocumentContent(uri: Uri): string | Thenable<string> {
51-
const data = GitBlameProvider.fromBlameUri(uri);
50+
const data = this.git.fromBlameUri(uri);
5251

5352
//const editor = this._findEditor(Uri.file(join(data.repoPath, data.file)));
5453

55-
return gitGetVersionText(data.originalFileName || data.fileName, this.blameProvider.repoPath, data.sha).then(text => {
54+
return this.git.getVersionedFileText(data.originalFileName || data.fileName, data.sha).then(text => {
5655
this.update(uri);
5756

5857
// TODO: This only works on the first load -- not after since it is cached
@@ -64,7 +63,7 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi
6463
return text;
6564
});
6665

67-
// return gitGetVersionFile(data.file, this.repoPath, data.sha).then(dst => {
66+
// return this.git.getVersionedFile(data.fileName, data.sha).then(dst => {
6867
// let uri = Uri.parse(`file:${dst}`)
6968
// return workspace.openTextDocument(uri).then(doc => {
7069
// this.update(uri);
@@ -87,19 +86,21 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi
8786
// Needs to be on a timer for some reason because we won't find the editor otherwise -- is there an event?
8887
let handle = setInterval(() => {
8988
let editor = this._findEditor(uri);
90-
if (editor) {
91-
clearInterval(handle);
92-
this.blameProvider.getBlameForShaRange(data.fileName, this.blameProvider.repoPath, data.sha, data.range).then(blame => {
93-
if (blame.lines.length) {
94-
editor.setDecorations(this._blameDecoration, blame.lines.map(l => {
95-
return {
96-
range: editor.document.validateRange(new Range(l.originalLine, 0, l.originalLine, 1000000)),
97-
hoverMessage: `${moment(blame.commit.date).format('MMMM Do, YYYY hh:MMa')}\n${blame.commit.author}\n${l.sha}`
98-
};
99-
}));
100-
}
101-
});
102-
}
89+
if (!editor) return;
90+
91+
clearInterval(handle);
92+
this.git.getBlameForShaRange(data.fileName, data.sha, data.range).then(blame => {
93+
if (!blame.lines.length) return;
94+
95+
this.git.getCommitMessage(data.sha).then(msg => {
96+
editor.setDecorations(this._blameDecoration, blame.lines.map(l => {
97+
return {
98+
range: editor.document.validateRange(new Range(l.originalLine, 0, l.originalLine, 1000000)),
99+
hoverMessage: `${msg}\n${blame.commit.author}\n${moment(blame.commit.date).format('MMMM Do, YYYY hh:MM a')}\n${l.sha}`
100+
};
101+
}));
102+
})
103+
});
103104
}, 200);
104105
}
105106

src/extension.ts

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
2-
import {CodeLens, commands, DocumentSelector, ExtensionContext, languages, Position, Range, Uri, window, workspace} from 'vscode';
2+
import {CodeLens, DocumentSelector, ExtensionContext, languages, workspace} from 'vscode';
33
import GitCodeLensProvider, {GitBlameCodeLens} from './codeLensProvider';
44
import GitContentProvider from './contentProvider';
5-
import {gitRepoPath} from './git';
6-
import GitBlameProvider from './gitBlameProvider';
7-
import {Commands, VsCodeCommands, WorkspaceState} from './constants';
5+
import GitProvider from './gitProvider';
6+
import {BlameCommand} from './commands';
7+
import {WorkspaceState} from './constants';
88

99
// this method is called when your extension is activated
1010
export function activate(context: ExtensionContext) {
@@ -16,35 +16,19 @@ export function activate(context: ExtensionContext) {
1616
}
1717

1818
console.log(`GitLens active: ${workspace.rootPath}`);
19-
gitRepoPath(workspace.rootPath).then(repoPath => {
20-
context.workspaceState.update(WorkspaceState.RepoPath, repoPath);
21-
22-
const blameProvider = new GitBlameProvider(context);
23-
context.subscriptions.push(blameProvider);
2419

25-
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, blameProvider)));
20+
const git = new GitProvider(context);
21+
context.subscriptions.push(git);
2622

27-
context.subscriptions.push(commands.registerCommand(Commands.ShowBlameHistory, (uri?: Uri, range?: Range, position?: Position) => {
28-
// If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file)
29-
if (!uri) {
30-
const doc = window.activeTextEditor && window.activeTextEditor.document;
31-
if (doc) {
32-
uri = doc.uri;
33-
range = doc.validateRange(new Range(0, 0, 1000000, 1000000));
34-
position = doc.validateRange(new Range(0, 0, 0, 1000000)).start;
35-
}
23+
git.getRepoPath(workspace.rootPath).then(repoPath => {
24+
context.workspaceState.update(WorkspaceState.RepoPath, repoPath);
3625

37-
if (!uri) return;
38-
}
26+
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider(context, git)));
3927

40-
console.log(uri.path, blameProvider.repoPath, range, position);
41-
return blameProvider.getBlameLocations(uri.path, blameProvider.repoPath, range).then(locations => {
42-
return commands.executeCommand(VsCodeCommands.ShowReferences, uri, position, locations);
43-
});
44-
}));
28+
context.subscriptions.push(new BlameCommand(git));
4529

4630
const selector: DocumentSelector = { scheme: 'file' };
47-
context.subscriptions.push(languages.registerCodeLensProvider(selector, new GitCodeLensProvider(context, blameProvider)));
31+
context.subscriptions.push(languages.registerCodeLensProvider(selector, new GitCodeLensProvider(context, git)));
4832
}).catch(reason => console.warn(reason));
4933
}
5034

src/git.ts

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,70 @@ import * as fs from 'fs';
44
import * as tmp from 'tmp';
55
import {spawnPromise} from 'spawn-rx';
66

7-
export function gitNormalizePath(fileName: string, repoPath: string) {
8-
fileName = fileName.replace(/\\/g, '/');
9-
return isAbsolute(fileName) ? relative(repoPath, fileName) : fileName;
10-
}
11-
12-
export function gitRepoPath(cwd) {
13-
return gitCommand(cwd, 'rev-parse', '--show-toplevel').then(data => data.replace(/\r?\n|\r/g, ''));
7+
function gitCommand(cwd: string, ...args) {
8+
return spawnPromise('git', args, { cwd: cwd });
149
}
1510

16-
export function gitBlame(fileName: string, repoPath: string) {
17-
fileName = gitNormalizePath(fileName, repoPath);
11+
export default class Git {
12+
static normalizePath(fileName: string, repoPath: string) {
13+
fileName = fileName.replace(/\\/g, '/');
14+
return isAbsolute(fileName) ? relative(repoPath, fileName) : fileName;
15+
}
1816

19-
console.log('git', 'blame', '-fnw', '--root', '--', fileName);
20-
return gitCommand(repoPath, 'blame', '-fnw', '--root', '--', fileName);
21-
// .then(s => { console.log(s); return s; })
22-
// .catch(ex => console.error(ex));
23-
}
17+
static repoPath(cwd: string) {
18+
return gitCommand(cwd, 'rev-parse', '--show-toplevel').then(data => data.replace(/\r?\n|\r/g, ''));
19+
}
2420

25-
export function gitGetVersionFile(fileName: string, repoPath: string, sha: string) {
26-
return new Promise<string>((resolve, reject) => {
27-
gitGetVersionText(fileName, repoPath, sha).then(data => {
28-
let ext = extname(fileName);
29-
tmp.file({ prefix: `${basename(fileName, ext)}-${sha}_`, postfix: ext }, (err, destination, fd, cleanupCallback) => {
30-
if (err) {
31-
reject(err);
32-
return;
33-
}
21+
static blame(fileName: string, repoPath: string) {
22+
fileName = Git.normalizePath(fileName, repoPath);
3423

35-
console.log("File: ", destination);
36-
console.log("Filedescriptor: ", fd);
24+
console.log('git', 'blame', '-fnw', '--root', '--', fileName);
25+
return gitCommand(repoPath, 'blame', '-fnw', '--root', '--', fileName);
26+
// .then(s => { console.log(s); return s; })
27+
// .catch(ex => console.error(ex));
28+
}
3729

38-
fs.appendFile(destination, data, err => {
30+
static getVersionedFile(fileName: string, repoPath: string, sha: string) {
31+
return new Promise<string>((resolve, reject) => {
32+
Git.getVersionedFileText(fileName, repoPath, sha).then(data => {
33+
let ext = extname(fileName);
34+
tmp.file({ prefix: `${basename(fileName, ext)}-${sha}_`, postfix: ext }, (err, destination, fd, cleanupCallback) => {
3935
if (err) {
4036
reject(err);
4137
return;
4238
}
43-
resolve(destination);
39+
40+
console.log("File: ", destination);
41+
console.log("Filedescriptor: ", fd);
42+
43+
fs.appendFile(destination, data, err => {
44+
if (err) {
45+
reject(err);
46+
return;
47+
}
48+
resolve(destination);
49+
});
4450
});
4551
});
4652
});
47-
});
48-
}
53+
}
4954

50-
export function gitGetVersionText(fileName: string, repoPath: string, sha: string) {
51-
fileName = gitNormalizePath(fileName, repoPath);
52-
sha = sha.replace('^', '');
55+
static getVersionedFileText(fileName: string, repoPath: string, sha: string) {
56+
fileName = Git.normalizePath(fileName, repoPath);
57+
sha = sha.replace('^', '');
5358

54-
console.log('git', 'show', `${sha}:${fileName}`);
55-
return gitCommand(repoPath, 'show', `${sha}:${fileName}`);
56-
// .then(s => { console.log(s); return s; })
57-
// .catch(ex => console.error(ex));
58-
}
59+
console.log('git', 'show', `${sha}:${fileName}`);
60+
return gitCommand(repoPath, 'show', `${sha}:${fileName}`);
61+
// .then(s => { console.log(s); return s; })
62+
// .catch(ex => console.error(ex));
63+
}
5964

60-
function gitCommand(cwd: string, ...args) {
61-
return spawnPromise('git', args, { cwd: cwd });
65+
static getCommitMessage(sha: string, repoPath: string) {
66+
sha = sha.replace('^', '');
67+
68+
console.log('git', 'show', '-s', '--format=%B', sha);
69+
return gitCommand(repoPath, 'show', '-s', '--format=%B', sha);
70+
// .then(s => { console.log(s); return s; })
71+
// .catch(ex => console.error(ex));
72+
}
6273
}

0 commit comments

Comments
 (0)