Attempts to fix #80 - on line with link, annotation gets underlined

This commit is contained in:
Eric Amodio
2017-06-06 13:29:38 -04:00
parent e5e582d300
commit d2dc172042
3 changed files with 434 additions and 430 deletions

View File

@@ -12,7 +12,8 @@ import { WhitespaceController } from './whitespaceController';
export const Decorations = { export const Decorations = {
annotation: window.createTextEditorDecorationType({ annotation: window.createTextEditorDecorationType({
isWholeLine: true isWholeLine: true,
textDecoration: 'none'
} as DecorationRenderOptions), } as DecorationRenderOptions),
highlight: undefined as TextEditorDecorationType | undefined highlight: undefined as TextEditorDecorationType | undefined
}; };
@@ -233,7 +234,7 @@ export class AnnotationController extends Disposable {
for (const [key, p] of this._annotationProviders) { for (const [key, p] of this._annotationProviders) {
if (!TextDocumentComparer.equals(p.document, e.document)) continue; if (!TextDocumentComparer.equals(p.document, e.document)) continue;
// TODO: Rework this once https://github.com/Microsoft/vscode/issues/27231 is released in v1.13 // TODO: Rework this once https://github.com/Microsoft/vscode/issues/27231 is released in v1.13
// We have to defer because isDirty is not reliable inside this event // We have to defer because isDirty is not reliable inside this event
setTimeout(() => { setTimeout(() => {
// If the document is dirty all is fine, just kick out since the GitContextTracker will handle it // If the document is dirty all is fine, just kick out since the GitContextTracker will handle it

View File

@@ -77,8 +77,7 @@ export class Annotations {
before: { before: {
...renderOptions.before, ...renderOptions.before,
...{ ...{
contentText: content, contentText: content
margin: '0 26px 0 0'
} }
}, },
dark: { dark: {
@@ -113,7 +112,9 @@ export class Annotations {
before: { before: {
borderStyle: borderStyle, borderStyle: borderStyle,
borderWidth: borderWidth, borderWidth: borderWidth,
height: cfgFileTheme.separateLines ? 'calc(100% - 1px)' : '100%' height: cfgFileTheme.separateLines ? 'calc(100% - 1px)' : '100%',
margin: '0 26px 0 0',
textDecoration: 'none'
}, },
dark: { dark: {
backgroundColor: cfgFileTheme.dark.backgroundColor || undefined, backgroundColor: cfgFileTheme.dark.backgroundColor || undefined,
@@ -123,7 +124,7 @@ export class Annotations {
backgroundColor: cfgFileTheme.light.backgroundColor || undefined, backgroundColor: cfgFileTheme.light.backgroundColor || undefined,
color: cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor color: cfgFileTheme.light.foregroundColor || themeDefaults.annotations.file.gutter.light.foregroundColor
} as DecorationInstanceRenderOptions } as DecorationInstanceRenderOptions
}; } as IRenderOptions;
} }
static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean): DecorationOptions { static hover(commit: GitCommit, renderOptions: IRenderOptions, heatmap: boolean): DecorationOptions {
@@ -142,7 +143,8 @@ export class Annotations {
borderWidth: '0 0 0 2px', borderWidth: '0 0 0 2px',
contentText: '\u200B', contentText: '\u200B',
height: cfgTheme.annotations.file.hover.separateLines ? 'calc(100% - 1px)' : '100%', height: cfgTheme.annotations.file.hover.separateLines ? 'calc(100% - 1px)' : '100%',
margin: '0 26px 0 0' margin: '0 26px 0 0',
textDecoration: 'none'
} }
} as IRenderOptions; } as IRenderOptions;
} }

View File

@@ -1,205 +1,206 @@
'use strict'; 'use strict';
import { Functions, Objects } from './system'; import { Functions, Objects } from './system';
import { DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode'; import { DecorationOptions, DecorationRenderOptions, Disposable, ExtensionContext, Range, StatusBarAlignment, StatusBarItem, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { AnnotationController } from './annotations/annotationController'; import { AnnotationController } from './annotations/annotationController';
import { Annotations, endOfLineIndex } from './annotations/annotations'; import { Annotations, endOfLineIndex } from './annotations/annotations';
import { Commands } from './commands'; import { Commands } from './commands';
import { TextEditorComparer } from './comparers'; import { TextEditorComparer } from './comparers';
import { FileAnnotationType, IConfig, LineAnnotationType, StatusBarCommand } from './configuration'; import { FileAnnotationType, IConfig, LineAnnotationType, StatusBarCommand } from './configuration';
import { DocumentSchemes, ExtensionKey } from './constants'; import { DocumentSchemes, ExtensionKey } from './constants';
import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitContextTracker, GitService, GitUri, IGitCommitLine } from './gitService'; import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitContextTracker, GitService, GitUri, IGitCommitLine } from './gitService';
const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({ const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
after: { after: {
margin: '0 0 0 4em' margin: '0 0 0 3em',
} textDecoration: 'none'
} as DecorationRenderOptions); }
} as DecorationRenderOptions);
export class CurrentLineController extends Disposable {
export class CurrentLineController extends Disposable {
private _activeEditorLineDisposable: Disposable | undefined;
private _blameable: boolean; private _activeEditorLineDisposable: Disposable | undefined;
private _config: IConfig; private _blameable: boolean;
private _currentLine: number = -1; private _config: IConfig;
private _disposable: Disposable; private _currentLine: number = -1;
private _editor: TextEditor | undefined; private _disposable: Disposable;
private _isAnnotating: boolean = false; private _editor: TextEditor | undefined;
private _statusBarItem: StatusBarItem | undefined; private _isAnnotating: boolean = false;
private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>; private _statusBarItem: StatusBarItem | undefined;
private _uri: GitUri; private _updateBlameDebounced: (line: number, editor: TextEditor) => Promise<void>;
private _uri: GitUri;
constructor(context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker, private annotationController: AnnotationController) {
super(() => this.dispose()); constructor(context: ExtensionContext, private git: GitService, private gitContextTracker: GitContextTracker, private annotationController: AnnotationController) {
super(() => this.dispose());
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 250);
this._updateBlameDebounced = Functions.debounce(this._updateBlame, 250);
this._onConfigurationChanged();
this._onConfigurationChanged();
const subscriptions: Disposable[] = [];
const subscriptions: Disposable[] = [];
subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this)); subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this));
subscriptions.push(annotationController.onDidToggleAnnotations(this._onAnnotationsToggled, this)); subscriptions.push(git.onDidChangeGitCache(this._onGitCacheChanged, this));
subscriptions.push(annotationController.onDidToggleAnnotations(this._onAnnotationsToggled, this));
this._disposable = Disposable.from(...subscriptions);
} this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this._clearAnnotations(this._editor, true); dispose() {
this._clearAnnotations(this._editor, true);
this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose();
this._statusBarItem && this._statusBarItem.dispose(); this._activeEditorLineDisposable && this._activeEditorLineDisposable.dispose();
this._disposable && this._disposable.dispose(); this._statusBarItem && this._statusBarItem.dispose();
} this._disposable && this._disposable.dispose();
}
private _onConfigurationChanged() {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!; private _onConfigurationChanged() {
const cfg = workspace.getConfiguration().get<IConfig>(ExtensionKey)!;
let changed = false;
let changed = false;
if (!Objects.areEquivalent(cfg.blame.line, this._config && this._config.blame.line) ||
!Objects.areEquivalent(cfg.annotations.line.trailing, this._config && this._config.annotations.line.trailing) || if (!Objects.areEquivalent(cfg.blame.line, this._config && this._config.blame.line) ||
!Objects.areEquivalent(cfg.annotations.line.hover, this._config && this._config.annotations.line.hover) || !Objects.areEquivalent(cfg.annotations.line.trailing, this._config && this._config.annotations.line.trailing) ||
!Objects.areEquivalent(cfg.theme.annotations.line.trailing, this._config && this._config.theme.annotations.line.trailing)) { !Objects.areEquivalent(cfg.annotations.line.hover, this._config && this._config.annotations.line.hover) ||
changed = true; !Objects.areEquivalent(cfg.theme.annotations.line.trailing, this._config && this._config.theme.annotations.line.trailing)) {
this._clearAnnotations(this._editor); changed = true;
} this._clearAnnotations(this._editor);
}
if (!Objects.areEquivalent(cfg.statusBar, this._config && this._config.statusBar)) {
changed = true; if (!Objects.areEquivalent(cfg.statusBar, this._config && this._config.statusBar)) {
if (cfg.statusBar.enabled) { changed = true;
const alignment = cfg.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left; if (cfg.statusBar.enabled) {
if (this._statusBarItem !== undefined && this._statusBarItem.alignment !== alignment) { const alignment = cfg.statusBar.alignment !== 'left' ? StatusBarAlignment.Right : StatusBarAlignment.Left;
this._statusBarItem.dispose(); if (this._statusBarItem !== undefined && this._statusBarItem.alignment !== alignment) {
this._statusBarItem = undefined; this._statusBarItem.dispose();
} this._statusBarItem = undefined;
}
this._statusBarItem = this._statusBarItem || window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
this._statusBarItem.command = cfg.statusBar.command; this._statusBarItem = this._statusBarItem || window.createStatusBarItem(alignment, alignment === StatusBarAlignment.Right ? 1000 : 0);
} this._statusBarItem.command = cfg.statusBar.command;
else if (!cfg.statusBar.enabled && this._statusBarItem) { }
this._statusBarItem.dispose(); else if (!cfg.statusBar.enabled && this._statusBarItem) {
this._statusBarItem = undefined; this._statusBarItem.dispose();
} this._statusBarItem = undefined;
} }
}
this._config = cfg;
this._config = cfg;
if (!changed) return;
if (!changed) return;
const trackCurrentLine = cfg.statusBar.enabled || cfg.blame.line.enabled;
if (trackCurrentLine && !this._activeEditorLineDisposable) { const trackCurrentLine = cfg.statusBar.enabled || cfg.blame.line.enabled;
const subscriptions: Disposable[] = []; if (trackCurrentLine && !this._activeEditorLineDisposable) {
const subscriptions: Disposable[] = [];
subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this)); subscriptions.push(window.onDidChangeActiveTextEditor(this._onActiveTextEditorChanged, this));
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this)); subscriptions.push(window.onDidChangeTextEditorSelection(this._onTextEditorSelectionChanged, this));
subscriptions.push(this.gitContextTracker.onDidBlameabilityChange(this._onBlameabilityChanged, this));
this._activeEditorLineDisposable = Disposable.from(...subscriptions);
} this._activeEditorLineDisposable = Disposable.from(...subscriptions);
else if (!trackCurrentLine && this._activeEditorLineDisposable) { }
this._activeEditorLineDisposable.dispose(); else if (!trackCurrentLine && this._activeEditorLineDisposable) {
this._activeEditorLineDisposable = undefined; this._activeEditorLineDisposable.dispose();
} this._activeEditorLineDisposable = undefined;
}
this._onActiveTextEditorChanged(window.activeTextEditor);
} this._onActiveTextEditorChanged(window.activeTextEditor);
}
private isEditorBlameable(editor: TextEditor | undefined): boolean {
if (editor === undefined || editor.document === undefined) return false; private isEditorBlameable(editor: TextEditor | undefined): boolean {
if (editor === undefined || editor.document === undefined) return false;
if (!this.git.isTrackable(editor.document.uri)) return false;
if (editor.document.isUntitled && editor.document.uri.scheme === DocumentSchemes.File) return false; if (!this.git.isTrackable(editor.document.uri)) return false;
if (editor.document.isUntitled && editor.document.uri.scheme === DocumentSchemes.File) return false;
return this.git.isEditorBlameable(editor);
} return this.git.isEditorBlameable(editor);
}
private async _onActiveTextEditorChanged(editor: TextEditor | undefined) {
this._currentLine = -1; private async _onActiveTextEditorChanged(editor: TextEditor | undefined) {
this._clearAnnotations(this._editor); this._currentLine = -1;
this._clearAnnotations(this._editor);
if (editor === undefined || !this.isEditorBlameable(editor)) {
this.clear(editor); if (editor === undefined || !this.isEditorBlameable(editor)) {
this._editor = undefined; this.clear(editor);
this._editor = undefined;
return;
} return;
}
this._blameable = editor !== undefined && editor.document !== undefined && !editor.document.isDirty;
this._editor = editor; this._blameable = editor !== undefined && editor.document !== undefined && !editor.document.isDirty;
this._uri = await GitUri.fromUri(editor.document.uri, this.git); this._editor = editor;
this._uri = await GitUri.fromUri(editor.document.uri, this.git);
const maxLines = this._config.advanced.caching.maxLines;
// If caching is on and the file is small enough -- kick off a blame for the whole file const maxLines = this._config.advanced.caching.maxLines;
if (this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines)) { // If caching is on and the file is small enough -- kick off a blame for the whole file
this.git.getBlameForFile(this._uri); if (this._config.advanced.caching.enabled && (maxLines <= 0 || editor.document.lineCount <= maxLines)) {
} this.git.getBlameForFile(this._uri);
}
this._updateBlameDebounced(editor.selection.active.line, editor);
} this._updateBlameDebounced(editor.selection.active.line, editor);
}
private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
this._blameable = e.blameable; private _onBlameabilityChanged(e: BlameabilityChangeEvent) {
if (!e.blameable || !this._editor) { this._blameable = e.blameable;
this.clear(e.editor); if (!e.blameable || !this._editor) {
return; this.clear(e.editor);
} return;
}
// Make sure this is for the editor we are tracking
if (!TextEditorComparer.equals(this._editor, e.editor)) return; // Make sure this is for the editor we are tracking
if (!TextEditorComparer.equals(this._editor, e.editor)) return;
this._updateBlameDebounced(this._editor.selection.active.line, this._editor);
} this._updateBlameDebounced(this._editor.selection.active.line, this._editor);
}
private _onAnnotationsToggled() {
this._onActiveTextEditorChanged(window.activeTextEditor); private _onAnnotationsToggled() {
} this._onActiveTextEditorChanged(window.activeTextEditor);
}
private _onGitCacheChanged() {
this._onActiveTextEditorChanged(window.activeTextEditor); private _onGitCacheChanged() {
} this._onActiveTextEditorChanged(window.activeTextEditor);
}
private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
// Make sure this is for the editor we are tracking private async _onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return; // Make sure this is for the editor we are tracking
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return;
const line = e.selections[0].active.line;
if (line === this._currentLine) return; const line = e.selections[0].active.line;
if (line === this._currentLine) return;
this._currentLine = line;
this._currentLine = line;
if (!this._uri && e.textEditor !== undefined) { if (!this._uri && e.textEditor !== undefined) {
this._uri = await GitUri.fromUri(e.textEditor.document.uri, this.git); this._uri = await GitUri.fromUri(e.textEditor.document.uri, this.git);
} }
this._clearAnnotations(e.textEditor); this._clearAnnotations(e.textEditor);
this._updateBlameDebounced(line, e.textEditor); this._updateBlameDebounced(line, e.textEditor);
} }
private async _updateBlame(line: number, editor: TextEditor) { private async _updateBlame(line: number, editor: TextEditor) {
line = line - this._uri.offset; line = line - this._uri.offset;
let commit: GitCommit | undefined = undefined; let commit: GitCommit | undefined = undefined;
let commitLine: IGitCommitLine | undefined = undefined; let commitLine: IGitCommitLine | undefined = undefined;
// Since blame information isn't valid when there are unsaved changes -- don't show any status // Since blame information isn't valid when there are unsaved changes -- don't show any status
if (this._blameable && line >= 0) { if (this._blameable && line >= 0) {
const blameLine = await this.git.getBlameForLine(this._uri, line); const blameLine = await this.git.getBlameForLine(this._uri, line);
commitLine = blameLine === undefined ? undefined : blameLine.line; commitLine = blameLine === undefined ? undefined : blameLine.line;
commit = blameLine === undefined ? undefined : blameLine.commit; commit = blameLine === undefined ? undefined : blameLine.commit;
} }
if (commit !== undefined && commitLine !== undefined) { if (commit !== undefined && commitLine !== undefined) {
this.show(commit, commitLine, editor); this.show(commit, commitLine, editor);
} }
else { else {
this.clear(editor); this.clear(editor);
} }
} }
async clear(editor: TextEditor | undefined) { async clear(editor: TextEditor | undefined) {
this._clearAnnotations(editor, true); this._clearAnnotations(editor, true);
this._statusBarItem && this._statusBarItem.hide(); this._statusBarItem && this._statusBarItem.hide();
} }
private async _clearAnnotations(editor: TextEditor | undefined, force: boolean = false) { private async _clearAnnotations(editor: TextEditor | undefined, force: boolean = false) {
if (editor === undefined || (!this._isAnnotating && !force)) return; if (editor === undefined || (!this._isAnnotating && !force)) return;
@@ -208,233 +209,233 @@ export class CurrentLineController extends Disposable {
this._isAnnotating = false; this._isAnnotating = false;
if (!force) return; if (!force) return;
// I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay // I have no idea why the decorators sometimes don't get removed, but if they don't try again with a tiny delay
await Functions.wait(1); await Functions.wait(1);
editor.setDecorations(annotationDecoration, []); editor.setDecorations(annotationDecoration, []);
} }
async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) { async show(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
// I have no idea why I need this protection -- but it happens // I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return; if (editor.document === undefined) return;
this._updateStatusBar(commit); this._updateStatusBar(commit);
await this._updateAnnotations(commit, blameLine, editor); await this._updateAnnotations(commit, blameLine, editor);
} }
async showAnnotations(editor: TextEditor, type: LineAnnotationType) { async showAnnotations(editor: TextEditor, type: LineAnnotationType) {
if (editor === undefined) return; if (editor === undefined) return;
const cfg = this._config.blame.line; const cfg = this._config.blame.line;
if (!cfg.enabled || cfg.annotationType !== type) { if (!cfg.enabled || cfg.annotationType !== type) {
cfg.enabled = true; cfg.enabled = true;
cfg.annotationType = type; cfg.annotationType = type;
await this._clearAnnotations(editor); await this._clearAnnotations(editor);
await this._updateBlame(editor.selection.active.line, editor); await this._updateBlame(editor.selection.active.line, editor);
} }
} }
async toggleAnnotations(editor: TextEditor, type: LineAnnotationType) { async toggleAnnotations(editor: TextEditor, type: LineAnnotationType) {
if (editor === undefined) return; if (editor === undefined) return;
const cfg = this._config.blame.line; const cfg = this._config.blame.line;
cfg.enabled = !cfg.enabled; cfg.enabled = !cfg.enabled;
cfg.annotationType = type; cfg.annotationType = type;
await this._clearAnnotations(editor); await this._clearAnnotations(editor);
await this._updateBlame(editor.selection.active.line, editor); await this._updateBlame(editor.selection.active.line, editor);
} }
private async _updateAnnotations(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) { private async _updateAnnotations(commit: GitCommit, blameLine: IGitCommitLine, editor: TextEditor) {
const cfg = this._config.blame.line; const cfg = this._config.blame.line;
if (!cfg.enabled) return; if (!cfg.enabled) return;
const line = blameLine.line + this._uri.offset; const line = blameLine.line + this._uri.offset;
const decorationOptions: DecorationOptions[] = []; const decorationOptions: DecorationOptions[] = [];
let showChanges = false; let showChanges = false;
let showChangesStartIndex = 0; let showChangesStartIndex = 0;
let showChangesInStartingWhitespace = false; let showChangesInStartingWhitespace = false;
let showDetails = false; let showDetails = false;
let showDetailsStartIndex = 0; let showDetailsStartIndex = 0;
let showDetailsInStartingWhitespace = false; let showDetailsInStartingWhitespace = false;
switch (cfg.annotationType) { switch (cfg.annotationType) {
case LineAnnotationType.Trailing: { case LineAnnotationType.Trailing: {
const cfgAnnotations = this._config.annotations.line.trailing; const cfgAnnotations = this._config.annotations.line.trailing;
showChanges = cfgAnnotations.hover.changes; showChanges = cfgAnnotations.hover.changes;
showDetails = cfgAnnotations.hover.details; showDetails = cfgAnnotations.hover.details;
if (cfgAnnotations.hover.wholeLine) { if (cfgAnnotations.hover.wholeLine) {
showChangesStartIndex = 0; showChangesStartIndex = 0;
showChangesInStartingWhitespace = false; showChangesInStartingWhitespace = false;
showDetailsStartIndex = 0; showDetailsStartIndex = 0;
showDetailsInStartingWhitespace = false; showDetailsInStartingWhitespace = false;
} }
else { else {
showChangesStartIndex = endOfLineIndex; showChangesStartIndex = endOfLineIndex;
showChangesInStartingWhitespace = true; showChangesInStartingWhitespace = true;
showDetailsStartIndex = endOfLineIndex; showDetailsStartIndex = endOfLineIndex;
showDetailsInStartingWhitespace = true; showDetailsInStartingWhitespace = true;
} }
const decoration = Annotations.trailing(commit, cfgAnnotations.format, cfgAnnotations.dateFormat, this._config.theme); const decoration = Annotations.trailing(commit, cfgAnnotations.format, cfgAnnotations.dateFormat, this._config.theme);
decoration.range = editor.document.validateRange(new Range(line, endOfLineIndex, line, endOfLineIndex)); decoration.range = editor.document.validateRange(new Range(line, endOfLineIndex, line, endOfLineIndex));
decorationOptions.push(decoration); decorationOptions.push(decoration);
break; break;
} }
case LineAnnotationType.Hover: { case LineAnnotationType.Hover: {
const cfgAnnotations = this._config.annotations.line.hover; const cfgAnnotations = this._config.annotations.line.hover;
showChanges = cfgAnnotations.changes; showChanges = cfgAnnotations.changes;
showChangesStartIndex = 0; showChangesStartIndex = 0;
showChangesInStartingWhitespace = false; showChangesInStartingWhitespace = false;
showDetails = cfgAnnotations.details; showDetails = cfgAnnotations.details;
showDetailsStartIndex = 0; showDetailsStartIndex = 0;
showDetailsInStartingWhitespace = false; showDetailsInStartingWhitespace = false;
break; break;
} }
} }
if (showDetails || showChanges) { if (showDetails || showChanges) {
const annotationType = this.annotationController.getAnnotationType(editor); const annotationType = this.annotationController.getAnnotationType(editor);
const firstNonWhitespace = editor.document.lineAt(line).firstNonWhitespaceCharacterIndex; const firstNonWhitespace = editor.document.lineAt(line).firstNonWhitespaceCharacterIndex;
switch (annotationType) { switch (annotationType) {
case FileAnnotationType.Gutter: { case FileAnnotationType.Gutter: {
const cfgHover = this._config.annotations.file.gutter.hover; const cfgHover = this._config.annotations.file.gutter.hover;
if (cfgHover.details) { if (cfgHover.details) {
showDetailsInStartingWhitespace = false; showDetailsInStartingWhitespace = false;
if (cfgHover.wholeLine) { if (cfgHover.wholeLine) {
// Avoid double annotations if we are showing the whole-file hover blame annotations // Avoid double annotations if we are showing the whole-file hover blame annotations
showDetails = false; showDetails = false;
} }
else { else {
if (showDetailsStartIndex === 0) { if (showDetailsStartIndex === 0) {
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace; showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
} }
if (showChangesStartIndex === 0) { if (showChangesStartIndex === 0) {
showChangesInStartingWhitespace = true; showChangesInStartingWhitespace = true;
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace; showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
} }
} }
} }
break; break;
} }
case FileAnnotationType.Hover: { case FileAnnotationType.Hover: {
const cfgHover = this._config.annotations.file.hover; const cfgHover = this._config.annotations.file.hover;
showDetailsInStartingWhitespace = false; showDetailsInStartingWhitespace = false;
if (cfgHover.wholeLine) { if (cfgHover.wholeLine) {
// Avoid double annotations if we are showing the whole-file hover blame annotations // Avoid double annotations if we are showing the whole-file hover blame annotations
showDetails = false; showDetails = false;
showChangesStartIndex = 0; showChangesStartIndex = 0;
} }
else { else {
if (showDetailsStartIndex === 0) { if (showDetailsStartIndex === 0) {
showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace; showDetailsStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
} }
if (showChangesStartIndex === 0) { if (showChangesStartIndex === 0) {
showChangesInStartingWhitespace = true; showChangesInStartingWhitespace = true;
showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace; showChangesStartIndex = firstNonWhitespace === 0 ? 1 : firstNonWhitespace;
} }
} }
break; break;
} }
} }
if (showDetails) { if (showDetails) {
// Get the full commit message -- since blame only returns the summary // Get the full commit message -- since blame only returns the summary
let logCommit: GitCommit | undefined = undefined; let logCommit: GitCommit | undefined = undefined;
if (!commit.isUncommitted) { if (!commit.isUncommitted) {
logCommit = await this.git.getLogCommit(this._uri.repoPath, this._uri.fsPath, commit.sha); logCommit = await this.git.getLogCommit(this._uri.repoPath, this._uri.fsPath, commit.sha);
} }
// I have no idea why I need this protection -- but it happens // I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return; if (editor.document === undefined) return;
const decoration = Annotations.detailsHover(logCommit || commit); const decoration = Annotations.detailsHover(logCommit || commit);
decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex)); decoration.range = editor.document.validateRange(new Range(line, showDetailsStartIndex, line, endOfLineIndex));
decorationOptions.push(decoration); decorationOptions.push(decoration);
if (showDetailsInStartingWhitespace && showDetailsStartIndex !== 0) { if (showDetailsInStartingWhitespace && showDetailsStartIndex !== 0) {
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace)); decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
} }
} }
if (showChanges) { if (showChanges) {
const decoration = await Annotations.changesHover(commit, line, this._uri, this.git); const decoration = await Annotations.changesHover(commit, line, this._uri, this.git);
// I have no idea why I need this protection -- but it happens // I have no idea why I need this protection -- but it happens
if (editor.document === undefined) return; if (editor.document === undefined) return;
decoration.range = editor.document.validateRange(new Range(line, showChangesStartIndex, line, endOfLineIndex)); decoration.range = editor.document.validateRange(new Range(line, showChangesStartIndex, line, endOfLineIndex));
decorationOptions.push(decoration); decorationOptions.push(decoration);
if (showChangesInStartingWhitespace && showChangesStartIndex !== 0) { if (showChangesInStartingWhitespace && showChangesStartIndex !== 0) {
decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace)); decorationOptions.push(Annotations.withRange(decoration, 0, firstNonWhitespace));
} }
} }
} }
if (decorationOptions.length) { if (decorationOptions.length) {
editor.setDecorations(annotationDecoration, decorationOptions); editor.setDecorations(annotationDecoration, decorationOptions);
this._isAnnotating = true; this._isAnnotating = true;
} }
} }
private _updateStatusBar(commit: GitCommit) { private _updateStatusBar(commit: GitCommit) {
const cfg = this._config.statusBar; const cfg = this._config.statusBar;
if (!cfg.enabled || this._statusBarItem === undefined) return; if (!cfg.enabled || this._statusBarItem === undefined) return;
this._statusBarItem.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, cfg.dateFormat)}`; this._statusBarItem.text = `$(git-commit) ${CommitFormatter.fromTemplate(cfg.format, commit, cfg.dateFormat)}`;
switch (cfg.command) { switch (cfg.command) {
case StatusBarCommand.BlameAnnotate: case StatusBarCommand.BlameAnnotate:
this._statusBarItem.tooltip = 'Toggle Blame Annotations'; this._statusBarItem.tooltip = 'Toggle Blame Annotations';
break; break;
case StatusBarCommand.ShowBlameHistory: case StatusBarCommand.ShowBlameHistory:
this._statusBarItem.tooltip = 'Open Blame History Explorer'; this._statusBarItem.tooltip = 'Open Blame History Explorer';
break; break;
case StatusBarCommand.ShowFileHistory: case StatusBarCommand.ShowFileHistory:
this._statusBarItem.tooltip = 'Open File History Explorer'; this._statusBarItem.tooltip = 'Open File History Explorer';
break; break;
case StatusBarCommand.DiffWithPrevious: case StatusBarCommand.DiffWithPrevious:
this._statusBarItem.command = Commands.DiffLineWithPrevious; this._statusBarItem.command = Commands.DiffLineWithPrevious;
this._statusBarItem.tooltip = 'Compare Line Commit with Previous'; this._statusBarItem.tooltip = 'Compare Line Commit with Previous';
break; break;
case StatusBarCommand.DiffWithWorking: case StatusBarCommand.DiffWithWorking:
this._statusBarItem.command = Commands.DiffLineWithWorking; this._statusBarItem.command = Commands.DiffLineWithWorking;
this._statusBarItem.tooltip = 'Compare Line Commit with Working Tree'; this._statusBarItem.tooltip = 'Compare Line Commit with Working Tree';
break; break;
case StatusBarCommand.ToggleCodeLens: case StatusBarCommand.ToggleCodeLens:
this._statusBarItem.tooltip = 'Toggle Git CodeLens'; this._statusBarItem.tooltip = 'Toggle Git CodeLens';
break; break;
case StatusBarCommand.ShowQuickCommitDetails: case StatusBarCommand.ShowQuickCommitDetails:
this._statusBarItem.tooltip = 'Show Commit Details'; this._statusBarItem.tooltip = 'Show Commit Details';
break; break;
case StatusBarCommand.ShowQuickCommitFileDetails: case StatusBarCommand.ShowQuickCommitFileDetails:
this._statusBarItem.tooltip = 'Show Line Commit Details'; this._statusBarItem.tooltip = 'Show Line Commit Details';
break; break;
case StatusBarCommand.ShowQuickFileHistory: case StatusBarCommand.ShowQuickFileHistory:
this._statusBarItem.tooltip = 'Show File History'; this._statusBarItem.tooltip = 'Show File History';
break; break;
case StatusBarCommand.ShowQuickCurrentBranchHistory: case StatusBarCommand.ShowQuickCurrentBranchHistory:
this._statusBarItem.tooltip = 'Show Branch History'; this._statusBarItem.tooltip = 'Show Branch History';
break; break;
} }
this._statusBarItem.show(); this._statusBarItem.show();
} }
} }