From 1576c08fa8f07788a6c6b79672edcd800d6b49eb Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 26 Aug 2016 18:27:38 -0400 Subject: [PATCH] Reworked git access Cleaned up the blame hightlights (wip) --- blame.png => images/blame-dark.png | Bin 24733 -> 24729 bytes images/blame-light.png | Bin 0 -> 24723 bytes package.json | 5 +- src/codeLensProvider.ts | 100 ++++++++++++++--------------- src/contentProvider.ts | 48 ++++++-------- src/git.ts | 75 ++++------------------ src/gitBlameUri.ts | 27 ++++++++ typings/spawn-rx.d.ts | 13 ++++ 8 files changed, 125 insertions(+), 143 deletions(-) rename blame.png => images/blame-dark.png (97%) create mode 100644 images/blame-light.png create mode 100644 src/gitBlameUri.ts create mode 100644 typings/spawn-rx.d.ts diff --git a/blame.png b/images/blame-dark.png similarity index 97% rename from blame.png rename to images/blame-dark.png index 22f8bcd53767b413a3498fd74adf6085a1e3383b..a76cb1e2fe71f50c2cd8753e701bf7c6b67df7a3 100644 GIT binary patch delta 228 zcmbPxka6Zg#tpLk+(u?0hNe~~W>%(#lQsCQk@=hR_|J2xBwAV;CYczT>Y5m)ndq9B zSeol5B_^lnnkE~VSQwh6B&VhsZBCRD5++LJ86-kSn8UX z8XDvkTf9Sq`k&bAaVo+T^?hagocL*%hDip8d)I N1fH&bF6*2UngH2sL+Jnj diff --git a/images/blame-light.png b/images/blame-light.png new file mode 100644 index 0000000000000000000000000000000000000000..02b1947b8e7200666652a02b5c9c8057bdb99ecf GIT binary patch literal 24723 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 21936f2..6b75076 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,11 @@ "postinstall": "node ./node_modules/vscode/bin/install && tsc" }, "dependencies": { - "tmp": "^0.0.28" + "tmp": "^0.0.28", + "spawn-rx": "^2.0.1" }, "devDependencies": { "typescript": "^1.8.10", - "vscode": "^0.11.15" + "vscode": "^0.11.17" } } \ No newline at end of file diff --git a/src/codeLensProvider.ts b/src/codeLensProvider.ts index 96738ec..ae489c5 100644 --- a/src/codeLensProvider.ts +++ b/src/codeLensProvider.ts @@ -2,7 +2,7 @@ import {CancellationToken, CodeLens, CodeLensProvider, commands, Location, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri} from 'vscode'; import {Commands, VsCodeCommands} from './constants'; import {IGitBlameLine, gitBlame} from './git'; -import {toGitBlameUri} from './contentProvider'; +import {toGitBlameUri} from './gitBlameUri'; import * as moment from 'moment'; export class GitBlameCodeLens extends CodeLens { @@ -34,7 +34,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable { // TODO: Should I wait here? - let blame = gitBlame(document.fileName); + const blame = gitBlame(document.fileName); return (commands.executeCommand(VsCodeCommands.ExecuteDocumentSymbolProvider, document.uri) as Promise).then(symbols => { let lenses: CodeLens[] = []; @@ -44,6 +44,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) { const docRange = document.validateRange(new Range(0, 1000000, 1000000, 1000000)); lenses.push(new GitBlameCodeLens(blame, this.repoPath, document.fileName, docRange, new Range(0, 0, 0, docRange.start.character))); + lenses.push(new GitHistoryCodeLens(this.repoPath, document.fileName, docRange.with(new Position(docRange.start.line, docRange.start.character + 1)))); } return lenses; }); @@ -66,67 +67,66 @@ export default class GitCodeLensProvider implements CodeLensProvider { return; } - var line = document.lineAt(symbol.location.range.start); + const line = document.lineAt(symbol.location.range.start); lenses.push(new GitBlameCodeLens(blame, this.repoPath, document.fileName, symbol.location.range, line.range.with(new Position(line.range.start.line, line.firstNonWhitespaceCharacterIndex)))); lenses.push(new GitHistoryCodeLens(this.repoPath, document.fileName, line.range.with(new Position(line.range.start.line, line.firstNonWhitespaceCharacterIndex + 1)))); } resolveCodeLens(lens: CodeLens, token: CancellationToken): Thenable { - if (lens instanceof GitBlameCodeLens) { - return lens.getBlameLines().then(lines => { - if (!lines.length) { - console.error('No blame lines found', lens); - throw new Error('No blame lines found'); - } + if (lens instanceof GitBlameCodeLens) return this._resolveGitBlameCodeLens(lens, token); + if (lens instanceof GitHistoryCodeLens) return this._resolveGitHistoryCodeLens(lens, token); + } - let recentLine = lines[0]; + _resolveGitBlameCodeLens(lens: GitBlameCodeLens, token: CancellationToken): Thenable { + return lens.getBlameLines().then(lines => { + if (!lines.length) { + console.error('No blame lines found', lens); + throw new Error('No blame lines found'); + } - let locations: Location[] = []; - if (lines.length > 1) { - let sorted = lines.sort((a, b) => b.date.getTime() - a.date.getTime()); - recentLine = sorted[0]; + let recentLine = lines[0]; - console.log(lens.fileName, 'Blame lines:', sorted); + let locations: Location[] = []; + if (lines.length > 1) { + let sorted = lines.sort((a, b) => b.date.getTime() - a.date.getTime()); + recentLine = sorted[0]; - let map: Map = new Map(); - sorted.forEach(l => { - let item = map.get(l.sha); - if (item) { - item.push(l); - } else { - map.set(l.sha, [l]); - } - }); + console.log(lens.fileName, 'Blame lines:', sorted); - Array.from(map.values()).forEach((lines, i) => { - const uri = GitBlameCodeLens.toUri(lens, i + 1, lines[0], lines); - lines.forEach(l => { - locations.push(new Location(uri, new Position(l.originalLine, 0))); - }); - }); + let map: Map = new Map(); + sorted.forEach(l => { + let item = map.get(l.sha); + if (item) { + item.push(l); + } else { + map.set(l.sha, [l]); + } + }); - //locations = Array.from(map.values()).map((l, i) => new Location(GitBlameCodeLens.toUri(lens, i, l[0], l), new Position(l[0].originalLine, 0)));//lens.range.start)) - } else { - locations = [new Location(GitBlameCodeLens.toUri(lens, 1, recentLine, lines), lens.range.start)]; - } + Array.from(map.values()).forEach((lines, i) => { + const uri = GitBlameCodeLens.toUri(lens, i + 1, lines[0], lines); + lines.forEach(l => locations.push(new Location(uri, new Position(l.originalLine, 0)))); + }); + } else { + locations = [new Location(GitBlameCodeLens.toUri(lens, 1, recentLine, lines), lens.range.start)]; + } - lens.command = { - title: `${recentLine.author}, ${moment(recentLine.date).fromNow()}`, - command: Commands.ShowBlameHistory, - arguments: [Uri.file(lens.fileName), lens.range.start, locations] - }; - return lens; - }).catch(ex => Promise.reject(ex)); // TODO: Figure out a better way to stop the codelens from appearing - } - - // TODO: Play with this more -- get this to open the correct diff to the right place - if (lens instanceof GitHistoryCodeLens) { lens.command = { - title: `View Diff`, - command: 'git.viewFileHistory', // viewLineHistory - arguments: [Uri.file(lens.fileName)] + title: `${recentLine.author}, ${moment(recentLine.date).fromNow()}`, + command: Commands.ShowBlameHistory, + arguments: [Uri.file(lens.fileName), lens.range.start, locations] }; - return Promise.resolve(lens); - } + return lens; + }).catch(ex => Promise.reject(ex)); // TODO: Figure out a better way to stop the codelens from appearing + } + + _resolveGitHistoryCodeLens(lens: GitHistoryCodeLens, token: CancellationToken): Thenable { + // TODO: Play with this more -- get this to open the correct diff to the right place + lens.command = { + title: `View History`, + command: 'git.viewFileHistory', // viewLineHistory + arguments: [Uri.file(lens.fileName)] + }; + return Promise.resolve(lens); } } diff --git a/src/contentProvider.ts b/src/contentProvider.ts index 0847eb1..b5ef73b 100644 --- a/src/contentProvider.ts +++ b/src/contentProvider.ts @@ -1,8 +1,8 @@ 'use strict'; import {Disposable, EventEmitter, ExtensionContext, OverviewRulerLane, Range, TextEditor, TextEditorDecorationType, TextDocumentContentProvider, Uri, window, workspace} from 'vscode'; import {DocumentSchemes} from './constants'; -import {gitGetVersionFile, gitGetVersionText, IGitBlameLine} from './git'; -import {basename, dirname, extname, join} from 'path'; +import {gitGetVersionText} from './git'; +import {fromGitBlameUri, IGitBlameUriData} from './gitBlameUri'; import * as moment from 'moment'; export default class GitBlameContentProvider implements TextDocumentContentProvider { @@ -10,21 +10,29 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi private _blameDecoration: TextEditorDecorationType; private _onDidChange = new EventEmitter(); - private _subscriptions: Disposable; + // private _subscriptions: Disposable; // private _dataMap: Map; constructor(context: ExtensionContext) { - // TODO: Light & Dark this._blameDecoration = window.createTextEditorDecorationType({ - backgroundColor: 'rgba(254, 220, 95, 0.15)', - gutterIconPath: context.asAbsolutePath('blame.png'), - overviewRulerColor: 'rgba(254, 220, 95, 0.60)', + dark: { + backgroundColor: 'rgba(255, 255, 255, 0.15)', + gutterIconPath: context.asAbsolutePath('images/blame-dark.png'), + overviewRulerColor: 'rgba(255, 255, 255, 0.75)', + }, + light: { + backgroundColor: 'rgba(0, 0, 0, 0.15)', + gutterIconPath: context.asAbsolutePath('images/blame-light.png'), + overviewRulerColor: 'rgba(0, 0, 0, 0.75)', + }, + gutterIconSize: 'contain', overviewRulerLane: OverviewRulerLane.Right, isWholeLine: true }); // this._dataMap = new Map(); // this._subscriptions = Disposable.from( + // window.onDidChangeActiveTextEditor(e => e ? console.log(e.document.uri) : console.log('active missing')), // workspace.onDidOpenTextDocument(d => { // let data = this._dataMap.get(d.uri.toString()); // if (!data) return; @@ -40,7 +48,7 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi dispose() { this._onDidChange.dispose(); - this._subscriptions && this._subscriptions.dispose(); + // this._subscriptions && this._subscriptions.dispose(); } get onDidChange() { @@ -57,7 +65,6 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi //const editor = this._findEditor(Uri.file(join(data.repoPath, data.file))); - //console.log('provideTextDocumentContent', uri, data); return gitGetVersionText(data.repoPath, data.sha, data.file).then(text => { this.update(uri); @@ -81,6 +88,7 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi private _findEditor(uri: Uri): TextEditor { let uriString = uri.toString(); + // TODO: This is a big hack :) const matcher = (e: any) => (e._documentData && e._documentData._uri && e._documentData._uri.toString()) === uriString; if (matcher(window.activeTextEditor)) { return window.activeTextEditor; @@ -89,6 +97,7 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi } private _tryAddBlameDecorations(uri: Uri, data: IGitBlameUriData) { + // Needs to be on a timer for some reason because we won't find the editor otherwise -- is there an event? let handle = setInterval(() => { let editor = this._findEditor(uri); if (editor) { @@ -96,7 +105,7 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi editor.setDecorations(this._blameDecoration, data.lines.map(l => { return { range: editor.document.validateRange(new Range(l.originalLine, 0, l.originalLine, 1000000)), - hoverMessage: `${moment(l.date).fromNow()}\n${l.author}\n${l.sha}` + hoverMessage: `${moment(l.date).format('MMMM Do, YYYY hh:MMa')}\n${l.author}\n${l.sha}` }; })); } @@ -106,23 +115,4 @@ export default class GitBlameContentProvider implements TextDocumentContentProvi // private _addBlameDecorations(editor: TextEditor, data: IGitBlameUriData) { // editor.setDecorations(this._blameDecoration, data.lines.map(l => editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)))); // } -} - -export interface IGitBlameUriData extends IGitBlameLine { - repoPath: string, - range: Range, - index: number, - lines: IGitBlameLine[] -} - -export function toGitBlameUri(data: IGitBlameUriData) { - let ext = extname(data.file); - let path = `${dirname(data.file)}/${data.sha}: ${basename(data.file, ext)}${ext}`; - return Uri.parse(`${DocumentSchemes.GitBlame}:${data.index}. ${moment(data.date).format('YYYY-MM-DD hh:MMa')} ${path}?${JSON.stringify(data)}`); -} - -export function fromGitBlameUri(uri: Uri): IGitBlameUriData { - let data = JSON.parse(uri.query); - data.range = new Range(data.range[0].line, data.range[0].character, data.range[1].line, data.range[1].character); - return data; } \ No newline at end of file diff --git a/src/git.ts b/src/git.ts index 18f0d3f..ace42be 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,8 +1,8 @@ 'use strict'; -import {spawn} from 'child_process'; import {basename, dirname, extname} from 'path'; import * as fs from 'fs'; import * as tmp from 'tmp'; +import {spawnPromise} from 'spawn-rx'; export declare interface IGitBlameLine { sha: string; @@ -14,28 +14,14 @@ export declare interface IGitBlameLine { code: string; } -export function gitRepoPath(cwd): Promise { - let data: Array = []; - const capture = input => data.push(input.toString().replace(/\r?\n|\r/g, '')); - const output = () => data[0]; - - return gitCommand(cwd, capture, output, 'rev-parse', '--show-toplevel'); - - // return new Promise((resolve, reject) => { - // gitCommand(cwd, capture, output, 'rev-parse', '--show-toplevel') - // .then(result => resolve(result[0])) - // .catch(reason => reject(reason)); - // }); +export function gitRepoPath(cwd) { + return gitCommand(cwd, 'rev-parse', '--show-toplevel').then(data => data.replace(/\r?\n|\r/g, '')); } -//const blameMatcher = /^(.*)\t\((.*)\t(.*)\t(.*?)\)(.*)$/gm; -//const blameMatcher = /^([0-9a-fA-F]{8})\s([\S]*)\s([0-9\S]+)\s\((.*?)\s([0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\s[-|+][0-9]{4})\s([0-9]+)\)(.*)$/gm; const blameMatcher = /^([0-9a-fA-F]{8})\s([\S]*)\s+([0-9\S]+)\s\((.*)\s([0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\s[-|+][0-9]{4})\s+([0-9]+)\)(.*)$/gm; -export function gitBlame(fileName: string): Promise { - let data: string = ''; - const capture = input => data += input.toString(); - const output = () => { +export function gitBlame(fileName: string) { + return gitCommand(dirname(fileName), 'blame', '-fnw', '--', fileName).then(data => { let lines: Array = []; let m: Array; while ((m = blameMatcher.exec(data)) != null) { @@ -50,18 +36,12 @@ export function gitBlame(fileName: string): Promise { }); } return lines; - }; - - return gitCommand(dirname(fileName), capture, output, 'blame', '-fnw', '--', fileName); + }); } -export function gitGetVersionFile(repoPath: string, sha: string, source: string): Promise { - let data: Array = []; - const capture = input => data.push(input); - const output = () => data; - +export function gitGetVersionFile(repoPath: string, sha: string, source: string): Promise { return new Promise((resolve, reject) => { - (gitCommand(repoPath, capture, output, 'show', `${sha}:${source.replace(/\\/g, '/')}`) as Promise>).then(o => { + gitCommand(repoPath, 'show', `${sha}:${source.replace(/\\/g, '/')}`).then(data => { let ext = extname(source); tmp.file({ prefix: `${basename(source, ext)}-${sha}_`, postfix: ext }, (err, destination, fd, cleanupCallback) => { if (err) { @@ -72,7 +52,7 @@ export function gitGetVersionFile(repoPath: string, sha: string, source: string) console.log("File: ", destination); console.log("Filedescriptor: ", fd); - fs.appendFile(destination, o.join(), err => { + fs.appendFile(destination, data, err => { if (err) { reject(err); return; @@ -84,39 +64,10 @@ export function gitGetVersionFile(repoPath: string, sha: string, source: string) }); } -export function gitGetVersionText(repoPath: string, sha: string, source: string): Promise { - let data: Array = []; - const capture = input => data.push(input.toString()); - const output = () => data; - - return new Promise((resolve, reject) => (gitCommand(repoPath, capture, output, 'show', `${sha}:${source.replace(/\\/g, '/')}`) as Promise>).then(o => resolve(o.join()))); +export function gitGetVersionText(repoPath: string, sha: string, source: string) { + return gitCommand(repoPath, 'show', `${sha}:${source.replace(/\\/g, '/')}`); } -function gitCommand(cwd: string, capture: (input: Buffer) => void, output: () => any, ...args): Promise { - return new Promise((resolve, reject) => { - let spawn = require('child_process').spawn; - let process = spawn('git', args, { cwd: cwd }); - - process.stdout.on('data', data => { - capture(data); - }); - - let errors: Array = []; - process.stderr.on('data', err => { - errors.push(err.toString()); - }); - - process.on('close', (exitCode, exitSignal) => { - if (exitCode && errors.length) { - reject(errors.toString()); - return; - } - - try { - resolve(output()); - } catch (ex) { - reject(ex); - } - }); - }); +function gitCommand(cwd: string, ...args) { + return spawnPromise('git', args, { cwd: cwd }); } \ No newline at end of file diff --git a/src/gitBlameUri.ts b/src/gitBlameUri.ts new file mode 100644 index 0000000..4504acf --- /dev/null +++ b/src/gitBlameUri.ts @@ -0,0 +1,27 @@ +import {Range, Uri} from 'vscode'; +import {DocumentSchemes} from './constants'; +import {IGitBlameLine} from './git'; +import {basename, dirname, extname} from 'path'; +import * as moment from 'moment'; + +export interface IGitBlameUriData extends IGitBlameLine { + repoPath: string, + range: Range, + index: number, + lines: IGitBlameLine[] +} + +export function toGitBlameUri(data: IGitBlameUriData) { + const pad = n => ("000" + n).slice(-3); + + let ext = extname(data.file); + let path = `${dirname(data.file)}/${data.sha}: ${basename(data.file, ext)}${ext}`; + // TODO: Need to specify an index here, since I can't control the sort order -- just alphabetic or by file location + return Uri.parse(`${DocumentSchemes.GitBlame}:${pad(data.index)}. ${data.author}, ${moment(data.date).format('MMM D, YYYY hh:MMa')} - ${path}?${JSON.stringify(data)}`); +} + +export function fromGitBlameUri(uri: Uri): IGitBlameUriData { + let data = JSON.parse(uri.query); + data.range = new Range(data.range[0].line, data.range[0].character, data.range[1].line, data.range[1].character); + return data; +} \ No newline at end of file diff --git a/typings/spawn-rx.d.ts b/typings/spawn-rx.d.ts new file mode 100644 index 0000000..a0ee414 --- /dev/null +++ b/typings/spawn-rx.d.ts @@ -0,0 +1,13 @@ +/// +declare module "spawn-rx" { + import { Observable } from 'rxjs/Observable'; + + namespace spawnrx { + function findActualExecutable(exe: string, args: Array): { cmd: string, args: Array }; + function spawnDetached(exe: string, params: Array, opts: Object): Observable; + function spawn(exe: string, params: Array, opts: Object): Observable; + function spawnDetachedPromise(exe: string, params: Array, opts: Object): Promise; + function spawnPromise(exe: string, params: Array, opts: Object): Promise; + } + export = spawnrx; +} \ No newline at end of file