mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-01-16 01:25:42 -05:00
Adds new trailing annotation mode
Adds message setting to annotations Adds active line annotations & setting
This commit is contained in:
36
package.json
36
package.json
@@ -47,24 +47,40 @@
|
||||
"default": "expanded",
|
||||
"enum": [
|
||||
"compact",
|
||||
"expanded"
|
||||
"expanded",
|
||||
"trailing"
|
||||
],
|
||||
"description": "Specifies the style of the blame annotations. `compact` - groups annotations to limit the repetition and also adds author and date when possible. `expanded` - shows an annotation on every line"
|
||||
"description": "Specifies the style of the blame annotations. `compact` - groups annotations to limit the repetition and also adds author and date when possible. `expanded` - shows an annotation before every line. `trailing` - shows an annotation after every line"
|
||||
},
|
||||
"gitlens.blame.annotation.sha": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether the commit sha will be shown in the blame annotations. Applies only to the `expanded` annotation style"
|
||||
"description": "Specifies whether the commit sha will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
},
|
||||
"gitlens.blame.annotation.author": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether the committer will be shown in the blame annotations. Applies only to the `expanded` annotation style"
|
||||
"description": "Specifies whether the committer will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
},
|
||||
"gitlens.blame.annotation.date": {
|
||||
"type": "string",
|
||||
"default": "off",
|
||||
"enum": [
|
||||
"off",
|
||||
"relative",
|
||||
"absolute"
|
||||
],
|
||||
"description": "Specifies whether and how the commit date will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
},
|
||||
"gitlens.blame.annotation.message": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Specifies whether the commit date will be shown in the blame annotations. Applies only to the `expanded` annotation style"
|
||||
"description": "Specifies whether the commit message will be shown in the blame annotations. Applies only to the `expanded` & `trailing` annotation styles"
|
||||
},
|
||||
"gitlens.blame.annotation.activeLine.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether to show a trailing blame annotation (sha and commit message) of the active line"
|
||||
},
|
||||
"gitlens.codeLens.visibility": {
|
||||
"type": "string",
|
||||
@@ -183,6 +199,11 @@
|
||||
],
|
||||
"description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker"
|
||||
},
|
||||
"gitlens.menus.diff.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether diff commands will be added to the context menus"
|
||||
},
|
||||
"gitlens.statusBar.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -201,11 +222,6 @@
|
||||
],
|
||||
"description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker"
|
||||
},
|
||||
"gitlens.menus.diff.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Specifies whether diff commands will be added to the context menus"
|
||||
},
|
||||
"gitlens.advanced.caching.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
||||
129
src/blameAnnotationFormatter.ts
Normal file
129
src/blameAnnotationFormatter.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
'use strict';
|
||||
import { IBlameConfig } from './configuration';
|
||||
import { GitCommit, IGitBlame, IGitCommitLine } from './gitProvider';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export const defaultShaLength = 8;
|
||||
export const defaultAbsoluteDateLength = 10;
|
||||
export const defaultRelativeDateLength = 13;
|
||||
export const defaultAuthorLength = 16;
|
||||
export const defaultMessageLength = 32;
|
||||
|
||||
export enum BlameAnnotationFormat {
|
||||
Constrained,
|
||||
Unconstrained
|
||||
}
|
||||
|
||||
export default class BlameAnnotationFormatter {
|
||||
|
||||
static getAnnotation(config: IBlameConfig, commit: GitCommit, format: BlameAnnotationFormat) {
|
||||
const sha = commit.sha.substring(0, defaultShaLength);
|
||||
const message = this.getMessage(config, commit, format === BlameAnnotationFormat.Unconstrained ? 0 : defaultMessageLength);
|
||||
|
||||
if (format === BlameAnnotationFormat.Unconstrained) {
|
||||
const authorAndDate = this.getAuthorAndDate(config, commit, 'MMMM Do, YYYY h:MMa');
|
||||
if (config.annotation.sha) {
|
||||
return `${sha}${(authorAndDate ? `\\00a0\\2022\\00a0 ${authorAndDate}` : '')}${(message ? `\\00a0\\2022\\00a0 ${message}` : '')}`;
|
||||
}
|
||||
|
||||
if (config.annotation.author || config.annotation.date) {
|
||||
return `${authorAndDate}${(message ? `\\00a0\\2022\\00a0 ${message}` : '')}`;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
const author = this.getAuthor(config, commit, defaultAuthorLength);
|
||||
const date = this.getDate(config, commit, 'MM/DD/YYYY', true);
|
||||
if (config.annotation.sha) {
|
||||
return `${sha}${(author ? `\\00a0\\2022\\00a0 ${author}` : '')}${(date ? `\\00a0\\2022\\00a0 ${date}` : '')}${(message ? `\\00a0\\2022\\00a0 ${message}` : '')}`;
|
||||
}
|
||||
|
||||
if (config.annotation.author) {
|
||||
return `${author}${(date ? `\\00a0\\2022\\00a0 ${date}` : '')}${(message ? `\\00a0\\2022\\00a0 ${message}` : '')}`;
|
||||
}
|
||||
|
||||
if (config.annotation.date) {
|
||||
return `${date}${(message ? `\\00a0\\2022\\00a0 ${message}` : '')}`;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
static getAnnotationHover(config: IBlameConfig, line: IGitCommitLine, commit: GitCommit, blame?: IGitBlame): string | Array<string> {
|
||||
if (commit.isUncommitted) {
|
||||
let previous = blame && blame.commits.get(commit.previousSha);
|
||||
if (previous) {
|
||||
return [
|
||||
'Uncommitted changes',
|
||||
`_${previous.sha}_ - ${previous.message}`,
|
||||
`${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MMa')}`
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'Uncommitted changes',
|
||||
`_${line.previousSha}_`
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
`_${commit.sha}_ - ${commit.message}`,
|
||||
`${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MMa')}`
|
||||
];
|
||||
}
|
||||
|
||||
static getAuthorAndDate(config: IBlameConfig, commit: GitCommit, format?: string/*, truncate: boolean = false*/, force: boolean = false) {
|
||||
if (!force && !config.annotation.author && (!config.annotation.date || config.annotation.date === 'off')) return '';
|
||||
|
||||
if (!config.annotation.author) {
|
||||
return this.getDate(config, commit, format); //, truncate);
|
||||
}
|
||||
|
||||
if (!config.annotation.date || config.annotation.date === 'off') {
|
||||
return this.getAuthor(config, commit); //, truncate ? defaultAuthorLength : 0);
|
||||
}
|
||||
|
||||
return `${this.getAuthor(config, commit)}, ${this.getDate(config, commit, format)}`;
|
||||
}
|
||||
|
||||
static getAuthor(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||
if (!force && !config.annotation.author) return '';
|
||||
|
||||
const author = commit.isUncommitted ? 'Uncommitted' : commit.author;
|
||||
if (!truncateTo) return author;
|
||||
|
||||
if (author.length > truncateTo) {
|
||||
return `${author.substring(0, truncateTo - 1)}\\2026`;
|
||||
}
|
||||
|
||||
return author + '\\00a0'.repeat(truncateTo - author.length);
|
||||
}
|
||||
|
||||
static getDate(config: IBlameConfig, commit: GitCommit, format?: string, truncate: boolean = false, force: boolean = false) {
|
||||
if (!force && (!config.annotation.date || config.annotation.date === 'off')) return '';
|
||||
|
||||
const date = config.annotation.date === 'relative'
|
||||
? moment(commit.date).fromNow()
|
||||
: moment(commit.date).format(format);
|
||||
if (!truncate) return date;
|
||||
|
||||
const truncateTo = config.annotation.date === 'relative' ? defaultRelativeDateLength : defaultAbsoluteDateLength;
|
||||
if (date.length > truncateTo) {
|
||||
return `${date.substring(0, truncateTo - 1)}\\2026`;
|
||||
}
|
||||
|
||||
return date + '\\00a0'.repeat(truncateTo - date.length);
|
||||
}
|
||||
|
||||
static getMessage(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||
if (!force && !config.annotation.message) return '';
|
||||
|
||||
let message = commit.message;
|
||||
if (truncateTo && message.length > truncateTo) {
|
||||
return `${message.substring(0, truncateTo - 1)}\\2026`;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
'use strict';
|
||||
import { Iterables } from './system';
|
||||
import { DecorationOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { DecorationInstanceRenderOptions, DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, OverviewRulerLane, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import BlameAnnotationFormatter, { BlameAnnotationFormat, defaultShaLength, defaultAuthorLength } from './blameAnnotationFormatter';
|
||||
import { TextDocumentComparer } from './comparers';
|
||||
import { BlameAnnotationStyle, IBlameConfig } from './configuration';
|
||||
import GitProvider, { GitCommit, GitUri, IGitBlame } from './gitProvider';
|
||||
import GitProvider, { GitUri, IGitBlame } from './gitProvider';
|
||||
import WhitespaceController from './whitespaceController';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const blameDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
||||
before: {
|
||||
margin: '0 1.75em 0 0'
|
||||
},
|
||||
after: {
|
||||
margin: '0 0 0 4em'
|
||||
}
|
||||
});
|
||||
} as DecorationRenderOptions);
|
||||
|
||||
let highlightDecoration: TextEditorDecorationType;
|
||||
|
||||
@@ -87,7 +90,9 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
if (!blame || !blame.lines.length) return false;
|
||||
|
||||
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- override whitespace (turn off)
|
||||
this.whitespaceController && await this.whitespaceController.override();
|
||||
if (this._config.annotation.style !== BlameAnnotationStyle.Trailing) {
|
||||
this.whitespaceController && await this.whitespaceController.override();
|
||||
}
|
||||
|
||||
let blameDecorationOptions: DecorationOptions[] | undefined;
|
||||
switch (this._config.annotation.style) {
|
||||
@@ -95,7 +100,10 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
blameDecorationOptions = this._getCompactGutterDecorations(blame);
|
||||
break;
|
||||
case BlameAnnotationStyle.Expanded:
|
||||
blameDecorationOptions = this._getExpandedGutterDecorations(blame);
|
||||
blameDecorationOptions = this._getExpandedGutterDecorations(blame, false);
|
||||
break;
|
||||
case BlameAnnotationStyle.Trailing:
|
||||
blameDecorationOptions = this._getExpandedGutterDecorations(blame, true);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -149,21 +157,17 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
let count = 0;
|
||||
let lastSha: string;
|
||||
return blame.lines.map(l => {
|
||||
let color = l.previousSha ? '#999999' : '#6b6b6b';
|
||||
let commit = blame.commits.get(l.sha);
|
||||
let hoverMessage: string | Array<string> = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MMa')}`];
|
||||
|
||||
let color: string;
|
||||
if (commit.isUncommitted) {
|
||||
color = 'rgba(0, 188, 242, 0.6)';
|
||||
|
||||
let previous = blame.commits.get(commit.previousSha);
|
||||
if (previous) {
|
||||
hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MMa')}`];
|
||||
}
|
||||
else {
|
||||
hoverMessage = ['Uncommitted changes', `_${l.previousSha}_`];
|
||||
}
|
||||
}
|
||||
else {
|
||||
color = l.previousSha ? '#999999' : '#6b6b6b';
|
||||
}
|
||||
|
||||
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit, blame);
|
||||
|
||||
let gutter = '';
|
||||
if (lastSha !== l.sha) {
|
||||
@@ -174,13 +178,13 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
if (!isEmptyOrWhitespace) {
|
||||
switch (++count) {
|
||||
case 0:
|
||||
gutter = commit.sha.substring(0, 8);
|
||||
gutter = commit.sha.substring(0, defaultShaLength);
|
||||
break;
|
||||
case 1:
|
||||
gutter = `\\02759\\00a0 ${this._getAuthor(commit, 17, true)}`;
|
||||
gutter = `\\02759\\00a0 ${BlameAnnotationFormatter.getAuthor(this._config, commit, defaultAuthorLength, true)}`;
|
||||
break;
|
||||
case 2:
|
||||
gutter = `\\02759\\00a0 ${this._getDate(commit, true)}`;
|
||||
gutter = `\\02759\\00a0 ${BlameAnnotationFormatter.getDate(this._config, commit, 'MM/DD/YYYY', true, true)}`;
|
||||
break;
|
||||
default:
|
||||
gutter = '\\02759';
|
||||
@@ -188,94 +192,118 @@ export class BlameAnnotationProvider extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
// Escape single quotes because for some reason that breaks the ::before or ::after element
|
||||
gutter = gutter.replace(/\'/g, '\\\'');
|
||||
|
||||
lastSha = l.sha;
|
||||
|
||||
return {
|
||||
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 0)),
|
||||
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' } }
|
||||
renderOptions: {
|
||||
before: {
|
||||
color: color,
|
||||
contentText: gutter,
|
||||
width: '11em'
|
||||
}
|
||||
}
|
||||
} as DecorationOptions;
|
||||
});
|
||||
}
|
||||
|
||||
private _getExpandedGutterDecorations(blame: IGitBlame): DecorationOptions[] {
|
||||
private _getExpandedGutterDecorations(blame: IGitBlame, trailing: boolean = false): DecorationOptions[] {
|
||||
const offset = this._uri.offset;
|
||||
|
||||
let width = 0;
|
||||
if (this._config.annotation.sha) {
|
||||
width += 5;
|
||||
}
|
||||
if (this._config.annotation.date) {
|
||||
if (width > 0) {
|
||||
width += 7;
|
||||
if (!trailing) {
|
||||
if (this._config.annotation.sha) {
|
||||
width += 5;
|
||||
}
|
||||
else {
|
||||
width += 6;
|
||||
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;
|
||||
if (this._config.annotation.author) {
|
||||
if (width > 5 + 6) {
|
||||
width += 12;
|
||||
}
|
||||
else if (width > 0) {
|
||||
width += 11;
|
||||
}
|
||||
else {
|
||||
width += 10;
|
||||
}
|
||||
}
|
||||
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 color = l.previousSha ? '#999999' : '#6b6b6b';
|
||||
let commit = blame.commits.get(l.sha);
|
||||
let hoverMessage: string | Array<string> = [`_${l.sha}_ - ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY h:MMa')}`];
|
||||
|
||||
let color: string;
|
||||
if (commit.isUncommitted) {
|
||||
color = 'rgba(0, 188, 242, 0.6)';
|
||||
|
||||
let previous = blame.commits.get(commit.previousSha);
|
||||
if (previous) {
|
||||
hoverMessage = ['Uncommitted changes', `_${previous.sha}_ - ${previous.message}`, `${previous.author}, ${moment(previous.date).format('MMMM Do, YYYY h:MMa')}`];
|
||||
}
|
||||
else {
|
||||
if (trailing) {
|
||||
color = l.previousSha ? 'rgba(153, 153, 153, 0.5)' : 'rgba(107, 107, 107, 0.5)';
|
||||
}
|
||||
else {
|
||||
hoverMessage = ['Uncommitted changes', `_${l.previousSha}_`];
|
||||
color = l.previousSha ? 'rgb(153, 153, 153)' : 'rgb(107, 107, 107)';
|
||||
}
|
||||
}
|
||||
|
||||
const gutter = this._getGutter(commit);
|
||||
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(this._config, l, commit, blame);
|
||||
|
||||
const format = trailing ? BlameAnnotationFormat.Unconstrained : BlameAnnotationFormat.Constrained;
|
||||
// Escape single quotes because for some reason that breaks the ::before or ::after element
|
||||
const gutter = BlameAnnotationFormatter.getAnnotation(this._config, commit, format).replace(/\'/g, '\\\'');
|
||||
|
||||
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, 0)),
|
||||
range: this.editor.document.validateRange(new Range(l.line + offset, 0, l.line + offset, 1000000)),
|
||||
hoverMessage: hoverMessage,
|
||||
renderOptions: { before: { color: color, contentText: gutter, width: `${width}em` } }
|
||||
renderOptions: renderOptions
|
||||
} as DecorationOptions;
|
||||
});
|
||||
}
|
||||
|
||||
private _getAuthor(commit: GitCommit, max: number = 17, force: boolean = false) {
|
||||
if (!force && !this._config.annotation.author) return '';
|
||||
let author = commit.isUncommitted ? 'Uncommitted' : commit.author;
|
||||
if (author.length > max) {
|
||||
return `${author.substring(0, max - 1)}\\2026`;
|
||||
}
|
||||
return author;
|
||||
}
|
||||
|
||||
private _getDate(commit: GitCommit, force?: boolean) {
|
||||
if (!force && !this._config.annotation.date) return '';
|
||||
return moment(commit.date).format('MM/DD/YYYY');
|
||||
}
|
||||
|
||||
private _getGutter(commit: GitCommit) {
|
||||
const author = this._getAuthor(commit);
|
||||
const date = this._getDate(commit);
|
||||
if (this._config.annotation.sha) {
|
||||
return `${commit.sha.substring(0, 8)}${(date ? `\\00a0\\2022\\00a0 ${date}` : '')}${(author ? `\\00a0\\2022\\00a0 ${author}` : '')}`;
|
||||
}
|
||||
else if (this._config.annotation.date) {
|
||||
return `${date}${(author ? `\\00a0\\2022\\00a0 ${author}` : '')}`;
|
||||
}
|
||||
else {
|
||||
return author;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,28 @@
|
||||
'use strict';
|
||||
import { Objects } from './system';
|
||||
import { Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import { TextDocumentComparer } from './comparers';
|
||||
import { IConfig, StatusBarCommand } from './configuration';
|
||||
import GitProvider, { GitCommit, GitUri, IGitBlame } from './gitProvider';
|
||||
import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditorDecorationType, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||
import BlameAnnotationFormatter, { BlameAnnotationFormat } from './blameAnnotationFormatter';
|
||||
import { TextEditorComparer } from './comparers';
|
||||
import { IBlameConfig, IConfig, StatusBarCommand } from './configuration';
|
||||
import { DocumentSchemes } from './constants';
|
||||
import GitProvider, { GitCommit, GitUri, IGitBlame, IGitCommitLine } from './gitProvider';
|
||||
import { Logger } from './logger';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
|
||||
after: {
|
||||
margin: '0 0 0 4em'
|
||||
}
|
||||
} as DecorationRenderOptions);
|
||||
|
||||
export default class BlameStatusBarController extends Disposable {
|
||||
|
||||
private _activeEditorLineDisposable: Disposable | undefined;
|
||||
private _blame: Promise<IGitBlame> | undefined;
|
||||
private _config: IConfig;
|
||||
private _disposable: Disposable;
|
||||
private _document: TextDocument | undefined;
|
||||
private _editor: TextEditor | undefined;
|
||||
private _statusBarItem: StatusBarItem | undefined;
|
||||
private _statusBarDisposable: Disposable | undefined;
|
||||
private _uri: GitUri;
|
||||
private _useCaching: boolean;
|
||||
|
||||
@@ -31,7 +39,9 @@ export default class BlameStatusBarController extends Disposable {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._statusBarDisposable && this._statusBarDisposable.dispose();
|
||||
this._editor && this._editor.setDecorations(activeLineDecoration, []);
|
||||
|
||||
this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose();
|
||||
this._statusBarItem && this._statusBarItem.dispose();
|
||||
this._disposable && this._disposable.dispose();
|
||||
}
|
||||
@@ -39,12 +49,12 @@ export default class BlameStatusBarController extends Disposable {
|
||||
private _onConfigure() {
|
||||
const config = workspace.getConfiguration('').get<IConfig>('gitlens');
|
||||
|
||||
if (!Objects.areEquivalent(config.statusBar, this._config && this._config.statusBar)) {
|
||||
this._statusBarDisposable && this._statusBarDisposable.dispose();
|
||||
this._statusBarItem && this._statusBarItem.dispose();
|
||||
let changed: boolean = false;
|
||||
|
||||
if (!Objects.areEquivalent(config.statusBar, this._config && this._config.statusBar)) {
|
||||
changed = true;
|
||||
if (config.statusBar.enabled) {
|
||||
this._statusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 1000);
|
||||
this._statusBarItem = this._statusBarItem || window.createStatusBarItem(StatusBarAlignment.Right, 1000);
|
||||
switch (config.statusBar.command) {
|
||||
case StatusBarCommand.ToggleCodeLens:
|
||||
if (config.codeLens.visibility !== 'ondemand') {
|
||||
@@ -53,35 +63,59 @@ export default class BlameStatusBarController extends Disposable {
|
||||
break;
|
||||
}
|
||||
this._statusBarItem.command = config.statusBar.command;
|
||||
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
|
||||
|
||||
this._statusBarDisposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
else {
|
||||
this._statusBarDisposable = undefined;
|
||||
else if (!config.statusBar.enabled && this._statusBarItem) {
|
||||
this._statusBarItem.dispose();
|
||||
this._statusBarItem = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Objects.areEquivalent(config.blame.annotation.activeLine, this._config && this._config.blame.annotation.activeLine)) {
|
||||
changed = true;
|
||||
if (!config.blame.annotation.activeLine.enabled && this._editor) {
|
||||
this._editor.setDecorations(activeLineDecoration, []);
|
||||
}
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
|
||||
if (!changed) return;
|
||||
|
||||
let trackActiveLine = config.statusBar.enabled || config.blame.annotation.activeLine.enabled;
|
||||
if (trackActiveLine && !this._activeEditorLineDisposable) {
|
||||
const subscriptions: Disposable[] = [];
|
||||
|
||||
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
|
||||
subscriptions.push(window.onDidChangeTextEditorSelection(this._onActiveSelectionChanged, this));
|
||||
|
||||
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
|
||||
}
|
||||
else if (!trackActiveLine && this._activeEditorLineDisposable) {
|
||||
this._activeEditorLineDisposable.dispose();
|
||||
this._activeEditorLineDisposable = undefined;
|
||||
}
|
||||
|
||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||
}
|
||||
|
||||
private async _onActiveTextEditorChanged(e: TextEditor): Promise<void> {
|
||||
if (!e || !e.document || e.document.isUntitled || (e.viewColumn === undefined && !this.git.hasGitUriForFile(e))) {
|
||||
this.clear();
|
||||
const previousEditor = this._editor;
|
||||
previousEditor && previousEditor.setDecorations(activeLineDecoration, []);
|
||||
|
||||
if (!e || !e.document || e.document.isUntitled ||
|
||||
(e.document.uri.scheme !== DocumentSchemes.File && e.document.uri.scheme !== DocumentSchemes.Git) ||
|
||||
(e.viewColumn === undefined && !this.git.hasGitUriForFile(e))) {
|
||||
this.clear(e);
|
||||
|
||||
this._editor = undefined;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._document = e.document;
|
||||
this._uri = GitUri.fromUri(this._document.uri, this.git);
|
||||
this._editor = e;
|
||||
this._uri = GitUri.fromUri(e.document.uri, this.git);
|
||||
const maxLines = this._config.advanced.caching.statusBar.maxLines;
|
||||
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || this._document.lineCount <= maxLines);
|
||||
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || e.document.lineCount <= maxLines);
|
||||
if (this._useCaching) {
|
||||
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
|
||||
}
|
||||
@@ -89,29 +123,31 @@ export default class BlameStatusBarController extends Disposable {
|
||||
this._blame = undefined;
|
||||
}
|
||||
|
||||
return this._showBlame(e.selection.active.line);
|
||||
return await this._showBlame(e.selection.active.line, e);
|
||||
}
|
||||
|
||||
private async _onActiveSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
|
||||
if (!TextDocumentComparer.equals(this._document, e.textEditor && e.textEditor.document)) return;
|
||||
if (!TextEditorComparer.equals(e.textEditor, this._editor)) return;
|
||||
|
||||
return this._showBlame(e.selections[0].active.line);
|
||||
return await this._showBlame(e.selections[0].active.line, e.textEditor);
|
||||
}
|
||||
|
||||
private async _showBlame(line: number) {
|
||||
private async _showBlame(line: number, editor: TextEditor) {
|
||||
line = line - this._uri.offset;
|
||||
|
||||
let commitLine: IGitCommitLine;
|
||||
let commit: GitCommit;
|
||||
if (line >= 0) {
|
||||
if (this._useCaching) {
|
||||
const blame = await this._blame;
|
||||
const blame = this._blame && await this._blame;
|
||||
if (!blame || !blame.lines.length) {
|
||||
this.clear();
|
||||
this.clear(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const sha = blame.lines[line].sha;
|
||||
commitLine = blame.lines[line];
|
||||
const sha = commitLine.sha;
|
||||
commit = blame.commits.get(sha);
|
||||
}
|
||||
catch (ex) {
|
||||
@@ -121,48 +157,81 @@ export default class BlameStatusBarController extends Disposable {
|
||||
}
|
||||
else {
|
||||
const blameLine = await this.git.getBlameForLine(this._uri.fsPath, line, this._uri.sha, this._uri.repoPath);
|
||||
commitLine = blameLine && blameLine.line;
|
||||
commit = blameLine && blameLine.commit;
|
||||
}
|
||||
}
|
||||
|
||||
if (commit) {
|
||||
this.show(commit);
|
||||
this.show(commit, commitLine, editor);
|
||||
}
|
||||
else {
|
||||
this.clear();
|
||||
this.clear(editor);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
clear(editor: TextEditor, previousEditor?: TextEditor) {
|
||||
editor && editor.setDecorations(activeLineDecoration, []);
|
||||
|
||||
this._statusBarItem && this._statusBarItem.hide();
|
||||
this._document = undefined;
|
||||
this._blame = undefined;
|
||||
}
|
||||
|
||||
show(commit: GitCommit) {
|
||||
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`;
|
||||
show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
|
||||
if (this._config.statusBar.enabled) {
|
||||
this._statusBarItem.text = `$(git-commit) ${commit.author}, ${moment(commit.date).fromNow()}`;
|
||||
|
||||
switch (this._config.statusBar.command) {
|
||||
case StatusBarCommand.BlameAnnotate:
|
||||
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
|
||||
break;
|
||||
case StatusBarCommand.ShowBlameHistory:
|
||||
this._statusBarItem.tooltip = 'Open Blame History';
|
||||
break;
|
||||
case StatusBarCommand.ShowFileHistory:
|
||||
this._statusBarItem.tooltip = 'Open File History';
|
||||
break;
|
||||
case StatusBarCommand.DiffWithPrevious:
|
||||
this._statusBarItem.tooltip = 'Compare to Previous Commit';
|
||||
break;
|
||||
case StatusBarCommand.ToggleCodeLens:
|
||||
this._statusBarItem.tooltip = 'Toggle Blame CodeLens';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickFileHistory:
|
||||
this._statusBarItem.tooltip = 'View Git File History';
|
||||
break;
|
||||
switch (this._config.statusBar.command) {
|
||||
case StatusBarCommand.BlameAnnotate:
|
||||
this._statusBarItem.tooltip = 'Toggle Blame Annotations';
|
||||
break;
|
||||
case StatusBarCommand.ShowBlameHistory:
|
||||
this._statusBarItem.tooltip = 'Open Blame History';
|
||||
break;
|
||||
case StatusBarCommand.ShowFileHistory:
|
||||
this._statusBarItem.tooltip = 'Open File History';
|
||||
break;
|
||||
case StatusBarCommand.DiffWithPrevious:
|
||||
this._statusBarItem.tooltip = 'Compare to Previous Commit';
|
||||
break;
|
||||
case StatusBarCommand.ToggleCodeLens:
|
||||
this._statusBarItem.tooltip = 'Toggle Blame CodeLens';
|
||||
break;
|
||||
case StatusBarCommand.ShowQuickFileHistory:
|
||||
this._statusBarItem.tooltip = 'View Git File History';
|
||||
break;
|
||||
}
|
||||
|
||||
this._statusBarItem.show();
|
||||
}
|
||||
|
||||
this._statusBarItem.show();
|
||||
if (this._config.blame.annotation.activeLine.enabled) {
|
||||
const offset = this._uri.offset;
|
||||
|
||||
const config = {
|
||||
annotation: {
|
||||
sha: true,
|
||||
author: this._config.statusBar.enabled ? false : this._config.blame.annotation.author,
|
||||
date: this._config.statusBar.enabled ? 'off' : this._config.blame.annotation.date,
|
||||
message: true
|
||||
}
|
||||
} as IBlameConfig;
|
||||
|
||||
// Escape single quotes because for some reason that breaks the ::before or ::after element
|
||||
const annotation = BlameAnnotationFormatter.getAnnotation(config, commit, BlameAnnotationFormat.Unconstrained).replace(/\'/g, '\\\'');
|
||||
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(config, blameLine, commit);
|
||||
|
||||
const decorationOptions = {
|
||||
range: editor.document.validateRange(new Range(blameLine.line + offset, 1000000, blameLine.line + offset, 1000000)),
|
||||
hoverMessage: hoverMessage,
|
||||
renderOptions: {
|
||||
after: {
|
||||
color: 'rgba(153, 153, 153, 0.3)',
|
||||
contentText: annotation
|
||||
}
|
||||
} as DecorationInstanceRenderOptions
|
||||
} as DecorationOptions;
|
||||
|
||||
editor.setDecorations(activeLineDecoration, [decorationOptions]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
'use strict';
|
||||
import { Commands } from './constants';
|
||||
|
||||
export type BlameAnnotationStyle = 'compact' | 'expanded';
|
||||
export type BlameAnnotationStyle = 'compact' | 'expanded' | 'trailing';
|
||||
export const BlameAnnotationStyle = {
|
||||
Compact: 'compact' as BlameAnnotationStyle,
|
||||
Expanded: 'expanded' as BlameAnnotationStyle
|
||||
Expanded: 'expanded' as BlameAnnotationStyle,
|
||||
Trailing: 'trailing' as BlameAnnotationStyle
|
||||
};
|
||||
|
||||
export interface IBlameConfig {
|
||||
@@ -12,7 +13,11 @@ export interface IBlameConfig {
|
||||
style: BlameAnnotationStyle;
|
||||
sha: boolean;
|
||||
author: boolean;
|
||||
date: boolean;
|
||||
date: 'off' | 'relative' | 'absolute';
|
||||
message: boolean;
|
||||
activeLine: {
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user