Browse Source

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
main
Eric Amodio 7 years ago
parent
commit
80aa43a84b
6 changed files with 62 additions and 40 deletions
  1. +0
    -1
      README.md
  2. +7
    -0
      src/blameAnnotationController.ts
  3. +21
    -15
      src/blameAnnotationFormatter.ts
  4. +31
    -21
      src/blameStatusBarController.ts
  5. +1
    -1
      src/extension.ts
  6. +2
    -2
      src/system/function.ts

+ 0
- 1
README.md View File

@ -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:

+ 7
- 0
src/blameAnnotationController.ts View File

@ -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;

+ 21
- 15
src/blameAnnotationFormatter.ts View File

@ -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 cssIndent = '\\2759';
export let cssSeparator = '\\2022';
export let cssPadding = '\\00a0';
export let cssEllipse = '\\002026';
export let cssIndent = '\\002759';
export let cssSeparator = '\\002022';
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)}\` &nbsp; __Uncommitted changes__ \n\n > ${message}`;
return `\`${'0'.repeat(8)}\` &nbsp; __Uncommitted changes__`;
} }
return `\`${commit.sha}\` &nbsp; __${commit.author}__, ${moment(commit.date).fromNow()} _(${moment(commit.date).format('MMMM Do, YYYY h:MMa')})_ \n\n > ${message}`; return `\`${commit.sha}\` &nbsp; __${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;

+ 31
- 21
src/blameStatusBarController.ts View File

@ -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)),

+ 1
- 1
src/extension.ts View File

@ -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));

+ 2
- 2
src/system/function.ts View File

@ -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);
} }

Loading…
Cancel
Save