- 'use strict';
- import { Iterables } from './system';
- import { DecorationInstanceRenderOptions, DecorationOptions, Disposable, ExtensionContext, Range, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
- import BlameAnnotationFormatter, { BlameAnnotationFormat, cssIndent, defaultShaLength, defaultAuthorLength } from './blameAnnotationFormatter';
- import { blameDecoration, highlightDecoration } from './blameAnnotationController';
- import { TextDocumentComparer } from './comparers';
- import { BlameAnnotationStyle, IBlameConfig } from './configuration';
- import GitProvider, { GitUri, IGitBlame } from './gitProvider';
- import WhitespaceController from './whitespaceController';
-
- export class BlameAnnotationProvider extends Disposable {
-
- public document: TextDocument;
-
- private _blame: Promise<IGitBlame>;
- private _config: IBlameConfig;
- private _disposable: Disposable;
- private _uri: GitUri;
-
- constructor(context: ExtensionContext, private git: GitProvider, private whitespaceController: WhitespaceController | undefined, public editor: TextEditor) {
- super(() => this.dispose());
-
- this.document = this.editor.document;
- this._uri = GitUri.fromUri(this.document.uri, this.git);
-
- this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
-
- this._config = workspace.getConfiguration('gitlens').get<IBlameConfig>('blame');
-
- const subscriptions: Disposable[] = [];
-
- subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
-
- this._disposable = Disposable.from(...subscriptions);
- }
-
- async dispose() {
- if (this.editor) {
- this.editor.setDecorations(blameDecoration, []);
- highlightDecoration && this.editor.setDecorations(highlightDecoration, []);
- }
-
- // HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- restore whitespace
- this.whitespaceController && await this.whitespaceController.restore();
-
- this._disposable && this._disposable.dispose();
- }
-
- private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent) {
- if (!TextDocumentComparer.equals(this.document, e.textEditor && e.textEditor.document)) return;
-
- return this.setSelection(e.selections[0].active.line);
- }
-
- async supportsBlame(): Promise<boolean> {
- const blame = await this._blame;
- return !!(blame && blame.lines.length);
- }
-
- async provideBlameAnnotation(shaOrLine?: string | number): Promise<boolean> {
- const blame = await this._blame;
- if (!blame || !blame.lines.length) return false;
-
- // HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
- if (this._config.annotation.style !== BlameAnnotationStyle.Trailing) {
- this.whitespaceController && await this.whitespaceController.override();
- }
-
- let blameDecorationOptions: DecorationOptions[] | undefined;
- switch (this._config.annotation.style) {
- case BlameAnnotationStyle.Compact:
- blameDecorationOptions = this._getCompactGutterDecorations(blame);
- break;
- case BlameAnnotationStyle.Expanded:
- blameDecorationOptions = this._getExpandedGutterDecorations(blame, false);
- break;
- case BlameAnnotationStyle.Trailing:
- blameDecorationOptions = this._getExpandedGutterDecorations(blame, true);
- break;
- }
-
- if (blameDecorationOptions) {
- this.editor.setDecorations(blameDecoration, blameDecorationOptions);
- }
-
- this._setSelection(blame, shaOrLine);
- return true;
- }
-
- async setSelection(shaOrLine?: string | number) {
- const blame = await this._blame;
- if (!blame || !blame.lines.length) return;
-
- return this._setSelection(blame, shaOrLine);
- }
-
- private _setSelection(blame: IGitBlame, shaOrLine?: string | number) {
- if (!highlightDecoration) return;
-
- const offset = this._uri.offset;
-
- let sha: string;
- if (typeof shaOrLine === 'string') {
- sha = shaOrLine;
- }
- else if (typeof shaOrLine === 'number') {
- const line = shaOrLine - offset;
- if (line >= 0) {
- const commitLine = blame.lines[line];
- sha = commitLine && commitLine.sha;
- }
- }
- else {
- sha = Iterables.first(blame.commits.values()).sha;
- }
-
- if (!sha) {
- this.editor.setDecorations(highlightDecoration, []);
- return;
- }
-
- const highlightDecorationRanges = blame.lines
- .filter(l => l.sha === sha)
- .map(l => this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)));
-
- this.editor.setDecorations(highlightDecoration, highlightDecorationRanges);
- }
-
- private _getCompactGutterDecorations(blame: IGitBlame): DecorationOptions[] {
- const offset = this._uri.offset;
-
- let count = 0;
- let lastSha: string;
- return blame.lines.map(l => {
- let commit = blame.commits.get(l.sha);
-
- let color: string;
- if (commit.isUncommitted) {
- color = 'rgba(0, 188, 242, 0.6)';
- }
- else {
- color = l.previousSha ? '#999999' : '#6b6b6b';
- }
-
- let gutter = '';
- if (lastSha !== l.sha) {
- count = -1;
- }
-
- const isEmptyOrWhitespace = this.document.lineAt(l.line).isEmptyOrWhitespace;
- if (!isEmptyOrWhitespace) {
- switch (++count) {
- case 0:
- gutter = commit.sha.substring(0, defaultShaLength);
- break;
- case 1:
- gutter = `${cssIndent} ${BlameAnnotationFormatter.getAuthor(this._config, commit, defaultAuthorLength, true)}`;
- break;
- case 2:
- gutter = `${cssIndent} ${BlameAnnotationFormatter.getDate(this._config, commit, 'MM/DD/YYYY', true, true)}`;
- break;
- default:
- gutter = `${cssIndent}`;
- break;
- }
- }
-
- const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit);
-
- // Escape single quotes because for some reason that breaks the ::before or ::after element
- // https://github.com/Microsoft/vscode/issues/19922 remove once this is released
- gutter = gutter.replace(/\'/g, '\\\'');
-
- lastSha = l.sha;
-
- return {
- range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)),
- hoverMessage: hoverMessage,
- renderOptions: {
- before: {
- color: color,
- contentText: gutter,
- width: '11em'
- }
- }
- } as DecorationOptions;
- });
- }
-
- private _getExpandedGutterDecorations(blame: IGitBlame, trailing: boolean = false): DecorationOptions[] {
- const offset = this._uri.offset;
-
- let width = 0;
- if (!trailing) {
- if (this._config.annotation.sha) {
- width += 5;
- }
- if (this._config.annotation.date && this._config.annotation.date !== 'off') {
- if (width > 0) {
- width += 7;
- }
- else {
- width += 6;
- }
-
- if (this._config.annotation.date === 'relative') {
- width += 2;
- }
- }
- if (this._config.annotation.author) {
- if (width > 5 + 6) {
- width += 12;
- }
- else if (width > 0) {
- width += 11;
- }
- else {
- width += 10;
- }
- }
- if (this._config.annotation.message) {
- if (width > 5 + 6 + 10) {
- width += 21;
- }
- else if (width > 5 + 6) {
- width += 21;
- }
- else if (width > 0) {
- width += 21;
- }
- else {
- width += 19;
- }
- }
- }
-
- return blame.lines.map(l => {
- let commit = blame.commits.get(l.sha);
-
- let color: string;
- if (commit.isUncommitted) {
- color = 'rgba(0, 188, 242, 0.6)';
- }
- else {
- if (trailing) {
- color = l.previousSha ? 'rgba(153, 153, 153, 0.5)' : 'rgba(107, 107, 107, 0.5)';
- }
- else {
- color = l.previousSha ? 'rgb(153, 153, 153)' : 'rgb(107, 107, 107)';
- }
- }
-
- const format = trailing ? BlameAnnotationFormat.Unconstrained : BlameAnnotationFormat.Constrained;
- // Escape single quotes because for some reason that breaks the ::before or ::after element
- // https://github.com/Microsoft/vscode/issues/19922 remove once this is released
- const gutter = BlameAnnotationFormatter.getAnnotation(this._config, commit, format).replace(/\'/g, '\\\'');
- const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit);
-
- let renderOptions: DecorationInstanceRenderOptions;
- if (trailing) {
- renderOptions = {
- after: {
- color: color,
- contentText: gutter
- }
- } as DecorationInstanceRenderOptions;
- }
- else {
- renderOptions = {
- before: {
- color: color,
- contentText: gutter,
- width: `${width}em`
- }
- } as DecorationInstanceRenderOptions;
- }
-
- return {
- range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)),
- hoverMessage: hoverMessage,
- renderOptions: renderOptions
- } as DecorationOptions;
- });
- }
- }
|