mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-02-16 18:48:45 -05:00
Fixes issues with annotation character settings
Fixes #29 - Commit info tooltip duplicated for current line when blame is enabled Improves performance of navigating line when active line annotations & statusbar blame are enabled
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
Provides Git CodeLens information (most recent commit, # of authors), on-demand inline blame annotations, status bar blame information, file and blame history explorers, and commands to compare changes with the working tree or previous versions.
|
Provides Git CodeLens information (most recent commit, # of authors), on-demand inline blame annotations, status bar blame information, file and blame history explorers, and commands to compare changes with the working tree or previous versions.
|
||||||
|
|
||||||
---
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Provides (optional) **CodeLens** on code blocks:
|
- Provides (optional) **CodeLens** on code blocks:
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ export default class BlameAnnotationController extends Disposable {
|
|||||||
return provider.provideBlameAnnotation(shaOrLine);
|
return provider.provideBlameAnnotation(shaOrLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAnnotating(editor: TextEditor): boolean {
|
||||||
|
if (!editor || !editor.document) return false;
|
||||||
|
if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false;
|
||||||
|
|
||||||
|
return !!this._annotationProviders.get(editor.viewColumn || -1);
|
||||||
|
}
|
||||||
|
|
||||||
async toggleBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
|
async toggleBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
|
||||||
if (!editor || !editor.document) return false;
|
if (!editor || !editor.document) return false;
|
||||||
if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false;
|
if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false;
|
||||||
|
|||||||
@@ -9,16 +9,22 @@ export const defaultRelativeDateLength = 13;
|
|||||||
export const defaultAuthorLength = 16;
|
export const defaultAuthorLength = 16;
|
||||||
export const defaultMessageLength = 32;
|
export const defaultMessageLength = 32;
|
||||||
|
|
||||||
export let cssEllipse = '\\2026';
|
export let cssEllipse = '\\002026';
|
||||||
export let cssIndent = '\\2759';
|
export let cssIndent = '\\002759';
|
||||||
export let cssSeparator = '\\2022';
|
export let cssSeparator = '\\002022';
|
||||||
export let cssPadding = '\\00a0';
|
export let cssPadding = '\\0000a0';
|
||||||
|
|
||||||
|
let cssEllipseLength: number = 1;
|
||||||
|
|
||||||
|
const cssUnicodeMatcher = /\\[0-9a-fA-F]{1,6}/;
|
||||||
|
|
||||||
export function configureCssCharacters(config: IBlameConfig) {
|
export function configureCssCharacters(config: IBlameConfig) {
|
||||||
cssEllipse = config.annotation.characters.ellipse || cssEllipse;
|
cssEllipse = config.annotation.characters.ellipse || cssEllipse;
|
||||||
cssIndent = config.annotation.characters.indent || cssIndent;
|
cssIndent = config.annotation.characters.indent || cssIndent;
|
||||||
cssPadding = config.annotation.characters.padding || cssPadding;
|
cssPadding = config.annotation.characters.padding || cssPadding;
|
||||||
cssSeparator = config.annotation.characters.separator || cssSeparator;
|
cssSeparator = config.annotation.characters.separator || cssSeparator;
|
||||||
|
|
||||||
|
cssEllipseLength = cssUnicodeMatcher.test(cssEllipse) ? 1 : cssEllipse.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BlameAnnotationFormat {
|
export enum BlameAnnotationFormat {
|
||||||
@@ -35,10 +41,10 @@ export default class BlameAnnotationFormatter {
|
|||||||
if (format === BlameAnnotationFormat.Unconstrained) {
|
if (format === BlameAnnotationFormat.Unconstrained) {
|
||||||
const authorAndDate = this.getAuthorAndDate(config, commit, 'MMMM Do, YYYY h:MMa');
|
const authorAndDate = this.getAuthorAndDate(config, commit, 'MMMM Do, YYYY h:MMa');
|
||||||
if (config.annotation.sha) {
|
if (config.annotation.sha) {
|
||||||
message = `${sha}${(authorAndDate ? ` ${cssSeparator} ${authorAndDate}` : '')}${(message ? ` ${cssSeparator} ${message}` : '')}`;
|
message = `${sha}${(authorAndDate ? `${cssPadding}${cssSeparator}${cssPadding} ${authorAndDate}` : '')}${(message ? `${cssPadding}${cssSeparator}${cssPadding} ${message}` : '')}`;
|
||||||
}
|
}
|
||||||
else if (config.annotation.author || config.annotation.date) {
|
else if (config.annotation.author || config.annotation.date) {
|
||||||
message = `${authorAndDate}${(message ? ` ${cssSeparator} ${message}` : '')}`;
|
message = `${authorAndDate}${(message ? `${cssPadding}${cssSeparator}${cssPadding} ${message}` : '')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
@@ -47,13 +53,13 @@ export default class BlameAnnotationFormatter {
|
|||||||
const author = this.getAuthor(config, commit, defaultAuthorLength);
|
const author = this.getAuthor(config, commit, defaultAuthorLength);
|
||||||
const date = this.getDate(config, commit, 'MM/DD/YYYY', true);
|
const date = this.getDate(config, commit, 'MM/DD/YYYY', true);
|
||||||
if (config.annotation.sha) {
|
if (config.annotation.sha) {
|
||||||
message = `${sha}${(author ? ` ${cssSeparator} ${author}` : '')}${(date ? ` ${cssSeparator} ${date}` : '')}${(message ? ` ${cssSeparator} ${message}` : '')}`;
|
message = `${sha}${(author ? `${cssPadding}${cssSeparator}${cssPadding} ${author}` : '')}${(date ? `${cssPadding}${cssSeparator}${cssPadding} ${date}` : '')}${(message ? `${cssPadding}${cssSeparator}${cssPadding} ${message}` : '')}`;
|
||||||
}
|
}
|
||||||
else if (config.annotation.author) {
|
else if (config.annotation.author) {
|
||||||
message = `${author}${(date ? ` ${cssSeparator} ${date}` : '')}${(message ? ` ${cssSeparator} ${message}` : '')}`;
|
message = `${author}${(date ? `${cssPadding}${cssSeparator}${cssPadding} ${date}` : '')}${(message ? `${cssPadding}${cssSeparator}${cssPadding} ${message}` : '')}`;
|
||||||
}
|
}
|
||||||
else if (config.annotation.date) {
|
else if (config.annotation.date) {
|
||||||
message = `${date}${(message ? ` ${cssSeparator} ${message}` : '')}`;
|
message = `${date}${(message ? `${cssPadding}${cssSeparator}${cssPadding} ${message}` : '')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
@@ -62,7 +68,7 @@ export default class BlameAnnotationFormatter {
|
|||||||
static getAnnotationHover(config: IBlameConfig, line: IGitCommitLine, commit: GitCommit): string | Array<string> {
|
static getAnnotationHover(config: IBlameConfig, line: IGitCommitLine, commit: GitCommit): string | Array<string> {
|
||||||
const message = commit.message.replace(/\n/g, '\n\n');
|
const message = commit.message.replace(/\n/g, '\n\n');
|
||||||
if (commit.isUncommitted) {
|
if (commit.isUncommitted) {
|
||||||
return `\`${'0'.repeat(8)}\` __Uncommitted changes__ \n\n > ${message}`;
|
return `\`${'0'.repeat(8)}\` __Uncommitted changes__`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `\`${commit.sha}\` __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format('MMMM Do, YYYY h:MMa')})_ \n\n > ${message}`;
|
return `\`${commit.sha}\` __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format('MMMM Do, YYYY h:MMa')})_ \n\n > ${message}`;
|
||||||
@@ -85,11 +91,11 @@ export default class BlameAnnotationFormatter {
|
|||||||
static getAuthor(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
static getAuthor(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||||
if (!force && !config.annotation.author) return '';
|
if (!force && !config.annotation.author) return '';
|
||||||
|
|
||||||
const author = commit.isUncommitted ? 'Uncommitted' : commit.author;
|
const author = commit.isUncommitted ? 'Uncommited' : commit.author;
|
||||||
if (!truncateTo) return author;
|
if (!truncateTo) return author;
|
||||||
|
|
||||||
if (author.length > truncateTo) {
|
if (author.length > truncateTo) {
|
||||||
return `${author.substring(0, truncateTo - cssEllipse.length)}${cssEllipse}`;
|
return `${author.substring(0, truncateTo - cssEllipseLength)}${cssEllipse}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force) return author; // Don't pad when just asking for the value
|
if (force) return author; // Don't pad when just asking for the value
|
||||||
@@ -106,7 +112,7 @@ export default class BlameAnnotationFormatter {
|
|||||||
|
|
||||||
const truncateTo = config.annotation.date === 'relative' ? defaultRelativeDateLength : defaultAbsoluteDateLength;
|
const truncateTo = config.annotation.date === 'relative' ? defaultRelativeDateLength : defaultAbsoluteDateLength;
|
||||||
if (date.length > truncateTo) {
|
if (date.length > truncateTo) {
|
||||||
return `${date.substring(0, truncateTo - cssEllipse.length)}${cssEllipse}`;
|
return `${date.substring(0, truncateTo - cssEllipseLength)}${cssEllipse}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force) return date; // Don't pad when just asking for the value
|
if (force) return date; // Don't pad when just asking for the value
|
||||||
@@ -116,9 +122,9 @@ export default class BlameAnnotationFormatter {
|
|||||||
static getMessage(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
static getMessage(config: IBlameConfig, commit: GitCommit, truncateTo: number = 0, force: boolean = false) {
|
||||||
if (!force && !config.annotation.message) return '';
|
if (!force && !config.annotation.message) return '';
|
||||||
|
|
||||||
let message = commit.message;
|
let message = commit.isUncommitted ? 'Uncommited change' : commit.message;
|
||||||
if (truncateTo && message.length > truncateTo) {
|
if (truncateTo && message.length > truncateTo) {
|
||||||
return `${message.substring(0, truncateTo - cssEllipse.length)}${cssEllipse}`;
|
return `${message.substring(0, truncateTo - cssEllipseLength)}${cssEllipse}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { Objects } from './system';
|
import { Functions, Objects } from './system';
|
||||||
import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
import { DecorationOptions, DecorationInstanceRenderOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
|
||||||
|
import BlameAnnotationController from './blameAnnotationController';
|
||||||
import BlameAnnotationFormatter, { BlameAnnotationFormat } from './blameAnnotationFormatter';
|
import BlameAnnotationFormatter, { BlameAnnotationFormat } from './blameAnnotationFormatter';
|
||||||
import { TextDocumentComparer, TextEditorComparer } from './comparers';
|
import { TextDocumentComparer, TextEditorComparer } from './comparers';
|
||||||
import { IBlameConfig, IConfig, StatusBarCommand } from './configuration';
|
import { IBlameConfig, IConfig, StatusBarCommand } from './configuration';
|
||||||
@@ -19,15 +20,19 @@ export default class BlameStatusBarController extends Disposable {
|
|||||||
private _activeEditorLineDisposable: Disposable | undefined;
|
private _activeEditorLineDisposable: Disposable | undefined;
|
||||||
private _blame: Promise<IGitBlame> | undefined;
|
private _blame: Promise<IGitBlame> | undefined;
|
||||||
private _config: IConfig;
|
private _config: IConfig;
|
||||||
|
private _currentLine: number = -1;
|
||||||
private _disposable: Disposable;
|
private _disposable: Disposable;
|
||||||
private _editor: TextEditor | undefined;
|
private _editor: TextEditor | undefined;
|
||||||
|
private _showBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
|
||||||
private _statusBarItem: StatusBarItem | undefined;
|
private _statusBarItem: StatusBarItem | undefined;
|
||||||
private _uri: GitUri;
|
private _uri: GitUri;
|
||||||
private _useCaching: boolean;
|
private _useCaching: boolean;
|
||||||
|
|
||||||
constructor(context: ExtensionContext, private git: GitProvider) {
|
constructor(context: ExtensionContext, private git: GitProvider, private annotationController: BlameAnnotationController) {
|
||||||
super(() => this.dispose());
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
this._showBlameDebounced = Functions.debounce(this._showBlame, 50);
|
||||||
|
|
||||||
this._onConfigure();
|
this._onConfigure();
|
||||||
|
|
||||||
const subscriptions: Disposable[] = [];
|
const subscriptions: Disposable[] = [];
|
||||||
@@ -104,7 +109,9 @@ export default class BlameStatusBarController extends Disposable {
|
|||||||
this._onActiveTextEditorChanged(window.activeTextEditor);
|
this._onActiveTextEditorChanged(window.activeTextEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onActiveTextEditorChanged(e: TextEditor): Promise<void> {
|
private _onActiveTextEditorChanged(e: TextEditor) {
|
||||||
|
this._currentLine = -1;
|
||||||
|
|
||||||
const previousEditor = this._editor;
|
const previousEditor = this._editor;
|
||||||
previousEditor && previousEditor.setDecorations(activeLineDecoration, []);
|
previousEditor && previousEditor.setDecorations(activeLineDecoration, []);
|
||||||
|
|
||||||
@@ -129,21 +136,26 @@ export default class BlameStatusBarController extends Disposable {
|
|||||||
this._blame = undefined;
|
this._blame = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this._showBlame(e.selection.active.line, e);
|
this._showBlame(e.selection.active.line, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
|
private _onEditorSelectionChanged(e: TextEditorSelectionChangeEvent): void {
|
||||||
// Make sure this is for the editor we are tracking
|
// Make sure this is for the editor we are tracking
|
||||||
if (!TextEditorComparer.equals(e.textEditor, this._editor)) return;
|
if (!TextEditorComparer.equals(e.textEditor, this._editor)) return;
|
||||||
|
|
||||||
return await this._showBlame(e.selections[0].active.line, e.textEditor);
|
const line = e.selections[0].active.line;
|
||||||
|
if (line === this._currentLine) return;
|
||||||
|
this._currentLine = line;
|
||||||
|
|
||||||
|
this._showBlameDebounced(line, e.textEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onDocumentChanged(e: TextDocumentChangeEvent): Promise<void> {
|
private _onDocumentChanged(e: TextDocumentChangeEvent) {
|
||||||
// Make sure this is for the editor we are tracking
|
// Make sure this is for the editor we are tracking
|
||||||
if (!this._editor || !TextDocumentComparer.equals(e.document, this._editor.document)) return;
|
if (!this._editor || !TextDocumentComparer.equals(e.document, this._editor.document)) return;
|
||||||
|
this._currentLine = -1;
|
||||||
|
|
||||||
return await this._showBlame(this._editor.selections[0].active.line, this._editor);
|
this._showBlame(this._editor.selections[0].active.line, this._editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _showBlame(line: number, editor: TextEditor) {
|
private async _showBlame(line: number, editor: TextEditor) {
|
||||||
@@ -248,11 +260,21 @@ export default class BlameStatusBarController extends Disposable {
|
|||||||
const log = await this.git.getLogForFile(this._uri.fsPath, commit.sha, this._uri.repoPath, undefined, 1);
|
const log = await this.git.getLogForFile(this._uri.fsPath, commit.sha, this._uri.repoPath, undefined, 1);
|
||||||
logCommit = log && log.commits.get(commit.sha);
|
logCommit = log && log.commits.get(commit.sha);
|
||||||
}
|
}
|
||||||
const hoverMessage = BlameAnnotationFormatter.getAnnotationHover(config, blameLine, logCommit || commit);
|
|
||||||
|
let hoverMessage: string | string[];
|
||||||
|
if (activeLine !== 'inline') {
|
||||||
|
// If the messages match (or we couldn't find the log), then this is a possible duplicate annotation
|
||||||
|
const possibleDuplicate = !logCommit || logCommit.message === commit.message;
|
||||||
|
// If we don't have a possible dupe or we aren't showing annotations get the hover message
|
||||||
|
if (!possibleDuplicate || !this.annotationController.isAnnotating(editor)) {
|
||||||
|
hoverMessage = BlameAnnotationFormatter.getAnnotationHover(config, blameLine, logCommit || commit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let decorationOptions: DecorationOptions;
|
let decorationOptions: DecorationOptions;
|
||||||
switch (activeLine) {
|
switch (activeLine) {
|
||||||
case 'both':
|
case 'both':
|
||||||
|
case 'inline':
|
||||||
decorationOptions = {
|
decorationOptions = {
|
||||||
range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)),
|
range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)),
|
||||||
hoverMessage: hoverMessage,
|
hoverMessage: hoverMessage,
|
||||||
@@ -265,18 +287,6 @@ export default class BlameStatusBarController extends Disposable {
|
|||||||
} as DecorationOptions;
|
} as DecorationOptions;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'inline':
|
|
||||||
decorationOptions = {
|
|
||||||
range: editor.document.validateRange(new Range(blameLine.line + offset, 1000000, blameLine.line + offset, 1000000)),
|
|
||||||
renderOptions: {
|
|
||||||
after: {
|
|
||||||
color: 'rgba(153, 153, 153, 0.3)',
|
|
||||||
contentText: annotation
|
|
||||||
}
|
|
||||||
} as DecorationInstanceRenderOptions
|
|
||||||
} as DecorationOptions;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'hover':
|
case 'hover':
|
||||||
decorationOptions = {
|
decorationOptions = {
|
||||||
range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)),
|
range: editor.document.validateRange(new Range(blameLine.line + offset, 0, blameLine.line + offset, 1000000)),
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export async function activate(context: ExtensionContext) {
|
|||||||
const annotationController = new BlameAnnotationController(context, git);
|
const annotationController = new BlameAnnotationController(context, git);
|
||||||
context.subscriptions.push(annotationController);
|
context.subscriptions.push(annotationController);
|
||||||
|
|
||||||
const statusBarController = new BlameStatusBarController(context, git);
|
const statusBarController = new BlameStatusBarController(context, git, annotationController);
|
||||||
context.subscriptions.push(statusBarController);
|
context.subscriptions.push(statusBarController);
|
||||||
|
|
||||||
context.subscriptions.push(new DiffWithWorkingCommand(git));
|
context.subscriptions.push(new DiffWithWorkingCommand(git));
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ const _once = require('lodash.once');
|
|||||||
|
|
||||||
export interface IDeferred {
|
export interface IDeferred {
|
||||||
cancel(): void;
|
cancel(): void;
|
||||||
flush(): void;
|
flush(...args: any[]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Functions {
|
export namespace Functions {
|
||||||
export function debounce<T extends Function>(fn: T, wait?: number, options?: any): T & IDeferred {
|
export function debounce<T extends Function>(fn: T, wait?: number, options?: { leading?: boolean, maxWait?: number, trailing?: boolean }): T & IDeferred {
|
||||||
return _debounce(fn, wait, options);
|
return _debounce(fn, wait, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user