Browse Source

Closes #112 - Adds blame support for unsaved files

main
Eric Amodio 7 years ago
parent
commit
8212659460
28 changed files with 349 additions and 134 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +1
    -1
      package.json
  3. +19
    -15
      src/annotations/annotationController.ts
  4. +21
    -7
      src/annotations/annotationProvider.ts
  5. +0
    -1
      src/annotations/annotations.ts
  6. +21
    -7
      src/annotations/blameAnnotationProvider.ts
  7. +1
    -1
      src/annotations/gutterBlameAnnotationProvider.ts
  8. +1
    -1
      src/annotations/heatmapBlameAnnotationProvider.ts
  9. +1
    -1
      src/annotations/hoverBlameAnnotationProvider.ts
  10. +7
    -5
      src/annotations/recentChangesAnnotationProvider.ts
  11. +21
    -13
      src/codeLensController.ts
  12. +3
    -3
      src/commands/copyMessageToClipboard.ts
  13. +3
    -3
      src/commands/copyShaToClipboard.ts
  14. +3
    -3
      src/commands/diffLineWithPrevious.ts
  15. +3
    -3
      src/commands/diffLineWithWorking.ts
  16. +3
    -3
      src/commands/openCommitInRemote.ts
  17. +1
    -1
      src/commands/showFileBlame.ts
  18. +1
    -1
      src/commands/showLineBlame.ts
  19. +2
    -2
      src/commands/toggleFileBlame.ts
  20. +2
    -2
      src/commands/toggleFileHeatmap.ts
  21. +2
    -2
      src/commands/toggleFileRecentChanges.ts
  22. +1
    -1
      src/commands/toggleLineBlame.ts
  23. +2
    -0
      src/constants.ts
  24. +23
    -9
      src/currentLineController.ts
  25. +173
    -40
      src/git/gitContextTracker.ts
  26. +6
    -2
      src/gitCodeLensProvider.ts
  27. +5
    -5
      src/gitService.ts
  28. +22
    -2
      src/system/function.ts

+ 1
- 0
CHANGELOG.md View File

@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
### Added
- Adds experimental support for providing blame annotations, code lens, etc on files with unsaved changes (enabled via `"gitlens.insiders": true`) -- closes [#112](https://github.com/eamodio/vscode-gitlens/issues/112)
- Adds `gitlens.defaultDateStyle` setting to specify how dates will be displayed by default -- closes [#89](https://github.com/eamodio/vscode-gitlens/issues/89)
### Fixed

+ 1
- 1
package.json View File

@ -1697,7 +1697,7 @@
},
{
"command": "gitlens.toggleFileRecentChanges",
"when": "gitlens:activeIsTracked"
"when": "gitlens:activeIsBlameable"
},
{
"command": "gitlens.toggleLineBlame",

+ 19
- 15
src/annotations/annotationController.ts View File

@ -1,11 +1,11 @@
'use strict';
import { Functions, Iterables } from '../system';
import { ConfigurationChangeEvent, DecorationRangeBehavior, DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, Progress, ProgressLocation, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, ThemeColor, window, workspace } from 'vscode';
import { ConfigurationChangeEvent, DecorationRangeBehavior, DecorationRenderOptions, Disposable, Event, EventEmitter, ExtensionContext, OverviewRulerLane, Progress, ProgressLocation, TextDocument, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, ThemeColor, window, workspace } from 'vscode';
import { AnnotationProviderBase, TextEditorCorrelationKey } from './annotationProvider';
import { TextDocumentComparer } from '../comparers';
import { configuration, IConfig, LineHighlightLocations } from '../configuration';
import { CommandContext, isTextEditor, setCommandContext } from '../constants';
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri } from '../gitService';
import { BlameabilityChangeEvent, GitContextTracker, GitService, GitUri, LineDirtyStateChangeEvent } from '../gitService';
import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider';
import { HeatmapBlameAnnotationProvider } from './heatmapBlameAnnotationProvider';
import { HoverBlameAnnotationProvider } from './hoverBlameAnnotationProvider';
@ -168,11 +168,11 @@ export class AnnotationController extends Disposable {
if (provider === undefined) continue;
if (provider.annotationType === FileAnnotationType.RecentChanges) {
provider.reset(Decorations.recentChangesAnnotation, Decorations.recentChangesHighlight);
provider.reset({ decoration: Decorations.recentChangesAnnotation, highlightDecoration: Decorations.recentChangesHighlight });
}
else {
if (provider.annotationType === cfg.blame.file.annotationType) {
provider.reset(Decorations.blameAnnotation, Decorations.blameHighlight);
provider.reset({ decoration: Decorations.blameAnnotation, highlightDecoration: Decorations.blameHighlight });
}
else {
this.showAnnotations(provider.editor, cfg.blame.file.annotationType);
@ -189,10 +189,14 @@ export class AnnotationController extends Disposable {
const provider = this.getProvider(editor);
if (provider === undefined) {
this.gitContextTracker.setLineTracking(editor, false);
setCommandContext(CommandContext.AnnotationStatus, undefined);
this.detachKeyboardHook();
}
else {
this.gitContextTracker.setLineTracking(editor, true);
setCommandContext(CommandContext.AnnotationStatus, AnnotationStatus.Computed);
this.attachKeyboardHook();
}
@ -204,13 +208,13 @@ export class AnnotationController extends Disposable {
this.clear(e.editor, AnnotationClearReason.BlameabilityChanged);
}
private onTextDocumentChanged(e: TextDocumentChangeEvent) {
if (!e.document.isDirty || !this.git.isTrackable(e.document.uri)) return;
private onLineDirtyStateChanged(e: LineDirtyStateChangeEvent) {
if (e.editor === undefined || !this.git.isTrackable(e.editor.document.uri)) return;
for (const [key, p] of this._annotationProviders) {
if (!TextDocumentComparer.equals(p.document, e.document)) continue;
for (const p of this._annotationProviders.values()) {
if (!TextDocumentComparer.equals(p.document, e.editor.document)) continue;
this.clearCore(key, AnnotationClearReason.DocumentClosed);
p.reset();
}
}
@ -365,19 +369,19 @@ export class AnnotationController extends Disposable {
let provider: AnnotationProviderBase | undefined = undefined;
switch (type) {
case FileAnnotationType.Gutter:
provider = new GutterBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, Decorations.blameHighlight, this.git, gitUri);
provider = new GutterBlameAnnotationProvider(this.context, editor, this.gitContextTracker, Decorations.blameAnnotation, Decorations.blameHighlight, this.git, gitUri);
break;
case FileAnnotationType.Heatmap:
provider = new HeatmapBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, undefined, this.git, gitUri);
provider = new HeatmapBlameAnnotationProvider(this.context, editor, this.gitContextTracker, Decorations.blameAnnotation, undefined, this.git, gitUri);
break;
case FileAnnotationType.Hover:
provider = new HoverBlameAnnotationProvider(this.context, editor, Decorations.blameAnnotation, Decorations.blameHighlight, this.git, gitUri);
provider = new HoverBlameAnnotationProvider(this.context, editor, this.gitContextTracker, Decorations.blameAnnotation, Decorations.blameHighlight, this.git, gitUri);
break;
case FileAnnotationType.RecentChanges:
provider = new RecentChangesAnnotationProvider(this.context, editor, undefined, Decorations.recentChangesHighlight!, this.git, gitUri);
provider = new RecentChangesAnnotationProvider(this.context, editor, this.gitContextTracker, undefined, Decorations.recentChangesHighlight!, this.git, gitUri);
break;
}
if (provider === undefined || !(await provider.validate())) return false;
@ -393,9 +397,9 @@ export class AnnotationController extends Disposable {
window.onDidChangeActiveTextEditor(Functions.debounce(this.onActiveTextEditorChanged, 50), this),
window.onDidChangeTextEditorViewColumn(this.onTextEditorViewColumnChanged, this),
window.onDidChangeVisibleTextEditors(this.onVisibleTextEditorsChanged, this),
workspace.onDidChangeTextDocument(Functions.debounce(this.onTextDocumentChanged, 50), this),
workspace.onDidCloseTextDocument(this.onTextDocumentClosed, this),
this.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this)
this.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this),
this.gitContextTracker.onDidChangeLineDirtyState(this.onLineDirtyStateChanged, this)
);
}

+ 21
- 7
src/annotations/annotationProvider.ts View File

@ -3,6 +3,7 @@ import { DecorationOptions, Disposable, ExtensionContext, TextDocument, TextEdit
import { FileAnnotationType } from '../annotations/annotationController';
import { TextDocumentComparer } from '../comparers';
import { configuration, IConfig } from '../configuration';
import { GitContextTracker } from '../gitService';
export type TextEditorCorrelationKey = string;
@ -23,6 +24,7 @@ export abstract class AnnotationProviderBase extends Disposable {
constructor(
context: ExtensionContext,
public editor: TextEditor,
protected readonly gitContextTracker: GitContextTracker,
protected decoration: TextEditorDecorationType | undefined,
protected highlightDecoration: TextEditorDecorationType | undefined
) {
@ -61,6 +63,8 @@ export abstract class AnnotationProviderBase extends Disposable {
}
async clear() {
this.gitContextTracker.setLineTracking(this.editor, false);
if (this.editor !== undefined) {
try {
if (this.highlightDecoration !== undefined) {
@ -71,21 +75,25 @@ export abstract class AnnotationProviderBase extends Disposable {
this.editor.setDecorations(this.decoration, []);
}
}
catch (ex) { }
catch { }
}
}
async reset(decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined) {
await this.clear();
async reset(changes?: { decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined }) {
if (changes !== undefined) {
await this.clear();
this._config = configuration.get<IConfig>();
this.decoration = decoration;
this.highlightDecoration = highlightDecoration;
this.decoration = changes.decoration;
this.highlightDecoration = changes.highlightDecoration;
}
this._config = configuration.get<IConfig>();
await this.provideAnnotation(this.editor === undefined ? undefined : this.editor.selection.active.line);
}
restore(editor: TextEditor, force: boolean = false) {
this.gitContextTracker.setLineTracking(this.editor, true);
// If the editor isn't disposed then we don't need to do anything
// Explicitly check for `false`
if (!force && (this.editor as any)._disposed === false) return;
@ -99,7 +107,13 @@ export abstract class AnnotationProviderBase extends Disposable {
}
}
abstract async provideAnnotation(shaOrLine?: string | number): Promise<boolean>;
provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
this.gitContextTracker.setLineTracking(this.editor, true);
return this.onProvideAnnotation(shaOrLine);
}
abstract async onProvideAnnotation(shaOrLine?: string | number): Promise<boolean>;
abstract async selection(shaOrLine?: string | number): Promise<void>;
abstract async validate(): Promise<boolean>;
}

+ 0
- 1
src/annotations/annotations.ts View File

@ -15,7 +15,6 @@ interface IRenderOptions extends DecorationInstanceRenderOptions, ThemableDecora
uncommittedColor?: string | ThemeColor;
}
export const endOfLineIndex = 1000000;
const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___';

+ 21
- 7
src/annotations/blameAnnotationProvider.ts View File

@ -3,8 +3,9 @@ import { Arrays, Iterables } from '../system';
import { CancellationToken, Disposable, ExtensionContext, Hover, HoverProvider, languages, Position, Range, TextDocument, TextEditor, TextEditorDecorationType } from 'vscode';
import { FileAnnotationType } from './annotationController';
import { AnnotationProviderBase } from './annotationProvider';
import { Annotations, endOfLineIndex } from './annotations';
import { GitBlame, GitCommit, GitService, GitUri } from '../gitService';
import { Annotations } from './annotations';
import { RangeEndOfLineIndex } from '../constants';
import { GitBlame, GitCommit, GitContextTracker, GitService, GitUri } from '../gitService';
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase {
@ -14,14 +15,17 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
constructor(
context: ExtensionContext,
editor: TextEditor,
gitContextTracker: GitContextTracker,
decoration: TextEditorDecorationType | undefined,
highlightDecoration: TextEditorDecorationType | undefined,
protected readonly git: GitService,
protected readonly uri: GitUri
) {
super(context, editor, decoration, highlightDecoration);
super(context, editor, gitContextTracker, decoration, highlightDecoration);
this._blame = this.git.getBlameForFile(this.uri);
this._blame = editor.document.isDirty
? this.git.getBlameForFileContents(this.uri, editor.document.getText())
: this.git.getBlameForFile(this.uri);
}
async clear() {
@ -29,6 +33,16 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
super.clear();
}
async reset(changes?: { decoration: TextEditorDecorationType | undefined, highlightDecoration: TextEditorDecorationType | undefined }) {
if (this.editor !== undefined) {
this._blame = this.editor.document.isDirty
? this.git.getBlameForFileContents(this.uri, this.editor.document.getText())
: this.git.getBlameForFile(this.uri);
}
super.reset(changes);
}
async selection(shaOrLine?: string | number, blame?: GitBlame) {
if (!this.highlightDecoration) return;
@ -57,7 +71,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
}
const highlightDecorationRanges = Arrays.filterMap(blame.lines,
l => l.sha === sha ? this.editor.document.validateRange(new Range(l.line, 0, l.line, 1000000)) : undefined);
l => l.sha === sha ? this.editor.document.validateRange(new Range(l.line, 0, l.line, RangeEndOfLineIndex)) : undefined);
this.editor.setDecorations(this.highlightDecoration, highlightDecorationRanges);
}
@ -104,7 +118,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
}
const message = Annotations.getHoverMessage(logCommit || commit, this._config.defaultDateFormat, await this.git.hasRemote(commit.repoPath), this._config.blame.file.annotationType);
return new Hover(message, document.validateRange(new Range(position.line, 0, position.line, endOfLineIndex)));
return new Hover(message, document.validateRange(new Range(position.line, 0, position.line, RangeEndOfLineIndex)));
}
async provideChangesHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
@ -112,7 +126,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
if (commit === undefined) return undefined;
const hover = await Annotations.changesHover(commit, position.line, await GitUri.fromUri(document.uri, this.git), this.git);
return new Hover(hover.hoverMessage!, document.validateRange(new Range(position.line, 0, position.line, endOfLineIndex)));
return new Hover(hover.hoverMessage!, document.validateRange(new Range(position.line, 0, position.line, RangeEndOfLineIndex)));
}
private async getCommitForHover(position: Position): Promise<GitCommit | undefined> {

+ 1
- 1
src/annotations/gutterBlameAnnotationProvider.ts View File

@ -10,7 +10,7 @@ import { Logger } from '../logger';
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
async provideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
async onProvideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
this.annotationType = FileAnnotationType.Gutter;
const blame = await this.getBlame();

+ 1
- 1
src/annotations/heatmapBlameAnnotationProvider.ts View File

@ -8,7 +8,7 @@ import { Logger } from '../logger';
export class HeatmapBlameAnnotationProvider extends BlameAnnotationProviderBase {
async provideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
async onProvideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
this.annotationType = FileAnnotationType.Heatmap;
const blame = await this.getBlame();

+ 1
- 1
src/annotations/hoverBlameAnnotationProvider.ts View File

@ -8,7 +8,7 @@ import { Logger } from '../logger';
export class HoverBlameAnnotationProvider extends BlameAnnotationProviderBase {
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
async onProvideAnnotation(shaOrLine?: string | number): Promise<boolean> {
this.annotationType = FileAnnotationType.Hover;
const cfg = this._config.annotations.file.hover;

+ 7
- 5
src/annotations/recentChangesAnnotationProvider.ts View File

@ -1,9 +1,10 @@
'use strict';
import { DecorationOptions, ExtensionContext, MarkdownString, Position, Range, TextEditor, TextEditorDecorationType } from 'vscode';
import { Annotations, endOfLineIndex } from './annotations';
import { FileAnnotationType } from './annotationController';
import { AnnotationProviderBase } from './annotationProvider';
import { GitService, GitUri } from '../gitService';
import { Annotations } from './annotations';
import { RangeEndOfLineIndex } from '../constants';
import { GitContextTracker, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
@ -11,15 +12,16 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
constructor(
context: ExtensionContext,
editor: TextEditor,
gitContextTracker: GitContextTracker,
decoration: TextEditorDecorationType | undefined,
highlightDecoration: TextEditorDecorationType | undefined,
private readonly git: GitService,
private readonly uri: GitUri
) {
super(context, editor, decoration, highlightDecoration);
super(context, editor, gitContextTracker, decoration, highlightDecoration);
}
async provideAnnotation(shaOrLine?: string | number): Promise<boolean> {
async onProvideAnnotation(shaOrLine?: string | number): Promise<boolean> {
this.annotationType = FileAnnotationType.RecentChanges;
const commit = await this.git.getLogCommit(this.uri.repoPath, this.uri.fsPath, { previous: true });
@ -44,7 +46,7 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
if (line.state === 'unchanged') continue;
const range = this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, endOfLineIndex)));
const range = this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, RangeEndOfLineIndex)));
if (cfg.hover.details) {
this._decorations.push({

+ 21
- 13
src/codeLensController.ts View File

@ -21,8 +21,7 @@ export class CodeLensController extends Disposable {
super(() => this.dispose());
this._disposable = Disposable.from(
configuration.onDidChange(this.onConfigurationChanged, this),
this.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this)
configuration.onDidChange(this.onConfigurationChanged, this)
);
this.onConfigurationChanged(configuration.initializingChangeEvent);
}
@ -50,7 +49,10 @@ export class CodeLensController extends Disposable {
}
else {
this._provider = new GitCodeLensProvider(this.context, this.git);
this._providerDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, this._provider);
this._providerDisposable = Disposable.from(
languages.registerCodeLensProvider(GitCodeLensProvider.selector, this._provider),
this.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this)
);
}
}
else {
@ -67,26 +69,32 @@ export class CodeLensController extends Disposable {
}
private onBlameabilityChanged(e: BlameabilityChangeEvent) {
if (this._provider === undefined) return;
// Only reset if we have saved, since the code lens won't naturally be re-rendered
if (this._provider === undefined || !e.blameable || e.reason !== BlameabilityChangeReason.DocumentChanged) return;
// Don't reset if this was an editor change, because code lens will naturally be re-rendered
if (e.blameable && e.reason !== BlameabilityChangeReason.EditorChanged) {
Logger.log('Blameability changed; resetting CodeLens provider');
this._provider.reset();
}
Logger.log('Blameability changed; resetting CodeLens provider');
this._provider.reset();
}
toggleCodeLens(editor: TextEditor) {
if (!this._canToggle) return;
Logger.log(`toggleCodeLens()`);
if (this._providerDisposable !== undefined) {
this._providerDisposable.dispose();
this._providerDisposable = undefined;
if (this._provider !== undefined) {
if (this._providerDisposable !== undefined) {
this._providerDisposable.dispose();
this._providerDisposable = undefined;
}
this._provider = undefined;
return;
}
this._providerDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, new GitCodeLensProvider(this.context, this.git));
this._provider = new GitCodeLensProvider(this.context, this.git);
this._providerDisposable = Disposable.from(
languages.registerCodeLensProvider(GitCodeLensProvider.selector, this._provider),
this.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this)
);
}
}

+ 3
- 3
src/commands/copyMessageToClipboard.ts View File

@ -52,13 +52,13 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
if (args.message === undefined) {
if (args.sha === undefined) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const blameline = (editor && editor.selection.active.line) || 0;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
const blame = editor && editor.document && editor.document.isDirty
? await this.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.git.getBlameForLine(gitUri, blameline);
if (!blame) return undefined;
if (blame.commit.isUncommitted) return undefined;

+ 3
- 3
src/commands/copyShaToClipboard.ts View File

@ -50,13 +50,13 @@ export class CopyShaToClipboardCommand extends ActiveEditorCommand {
const gitUri = await GitUri.fromUri(uri, this.git);
if (args.sha === undefined) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const blameline = (editor && editor.selection.active.line) || 0;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
const blame = editor && editor.document && editor.document.isDirty
? await this.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return undefined;
args.sha = blame.commit.sha;

+ 3
- 3
src/commands/diffLineWithPrevious.ts View File

@ -33,13 +33,13 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
}
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const blameline = args.line;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
const blame = editor && editor.document && editor.document.isDirty
? await this.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = blame.commit;

+ 3
- 3
src/commands/diffLineWithWorking.ts View File

@ -33,13 +33,13 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
}
if (args.commit === undefined || GitService.isUncommitted(args.commit.sha)) {
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const blameline = args.line;
if (blameline < 0) return undefined;
try {
const blame = await this.git.getBlameForLine(gitUri, blameline);
const blame = editor && editor.document && editor.document.isDirty
? await this.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = blame.commit;

+ 3
- 3
src/commands/openCommitInRemote.ts View File

@ -40,8 +40,6 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
async execute(editor?: TextEditor, uri?: Uri, args: OpenCommitInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
if (uri === undefined) return undefined;
if (editor !== undefined && editor.document !== undefined && editor.document.isDirty) return undefined;
const gitUri = await GitUri.fromUri(uri, this.git);
if (!gitUri.repoPath) return undefined;
@ -50,7 +48,9 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
const blameline = editor === undefined ? 0 : editor.selection.active.line;
if (blameline < 0) return undefined;
const blame = await this.git.getBlameForLine(gitUri, blameline);
const blame = editor && editor.document && editor.document.isDirty
? await this.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider');
let commit = blame.commit;

+ 1
- 1
src/commands/showFileBlame.ts View File

@ -19,7 +19,7 @@ export class ShowFileBlameCommand extends EditorCommand {
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowFileBlameCommandArgs = {}): Promise<any> {
if (editor === undefined || editor.document.isDirty) return undefined;
if (editor === undefined) return undefined;
try {
if (args.type === undefined) {

+ 1
- 1
src/commands/showLineBlame.ts View File

@ -18,7 +18,7 @@ export class ShowLineBlameCommand extends EditorCommand {
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ShowLineBlameCommandArgs = {}): Promise<any> {
if (editor === undefined || editor.document.isDirty) return undefined;
if (editor === undefined) return undefined;
try {
if (args.type === undefined) {

+ 2
- 2
src/commands/toggleFileBlame.ts View File

@ -20,12 +20,12 @@ export class ToggleFileBlameCommand extends EditorCommand {
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleFileBlameCommandArgs = {}): Promise<any> {
if (editor === undefined || editor.document.isDirty) return undefined;
if (editor === undefined) return undefined;
// Handle the case where we are focused on a non-editor editor (output, debug console)
if (uri !== undefined && !UriComparer.equals(uri, editor.document.uri)) {
const e = window.visibleTextEditors.find(e => UriComparer.equals(uri, e.document.uri));
if (e !== undefined && !e.document.isDirty) {
if (e !== undefined) {
editor = e;
}
}

+ 2
- 2
src/commands/toggleFileHeatmap.ts View File

@ -14,12 +14,12 @@ export class ToggleFileHeatmapCommand extends EditorCommand {
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise<any> {
if (editor === undefined || editor.document.isDirty) return undefined;
if (editor === undefined) return undefined;
// Handle the case where we are focused on a non-editor editor (output, debug console)
if (uri !== undefined && !UriComparer.equals(uri, editor.document.uri)) {
const e = window.visibleTextEditors.find(e => UriComparer.equals(uri, e.document.uri));
if (e !== undefined && !e.document.isDirty) {
if (e !== undefined) {
editor = e;
}
}

+ 2
- 2
src/commands/toggleFileRecentChanges.ts View File

@ -14,12 +14,12 @@ export class ToggleFileRecentChangesCommand extends EditorCommand {
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri): Promise<any> {
if (editor === undefined || editor.document.isDirty) return undefined;
if (editor === undefined) return undefined;
// Handle the case where we are focused on a non-editor editor (output, debug console)
if (uri !== undefined && !UriComparer.equals(uri, editor.document.uri)) {
const e = window.visibleTextEditors.find(e => UriComparer.equals(uri, e.document.uri));
if (e !== undefined && !e.document.isDirty) {
if (e !== undefined) {
editor = e;
}
}

+ 1
- 1
src/commands/toggleLineBlame.ts View File

@ -18,7 +18,7 @@ export class ToggleLineBlameCommand extends EditorCommand {
}
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, args: ToggleLineBlameCommandArgs = {}): Promise<any> {
if (editor === undefined || editor.document.isDirty) return undefined;
if (editor === undefined) return undefined;
try {
if (args.type === undefined) {

+ 2
- 0
src/constants.ts View File

@ -9,6 +9,8 @@ export const QualifiedExtensionId = `eamodio.${ExtensionId}`;
export const ApplicationInsightsKey = 'a9c302f8-6483-4d01-b92c-c159c799c679';
export const RangeEndOfLineIndex = 100000000;
export enum BuiltInCommands {
CloseActiveEditor = 'workbench.action.closeActiveEditor',
CloseAllEditors = 'workbench.action.closeAllEditors',

+ 23
- 9
src/currentLineController.ts View File

@ -2,12 +2,12 @@
import { Functions, IDeferred } from './system';
import { CancellationToken, ConfigurationChangeEvent, debug, DecorationRangeBehavior, DecorationRenderOptions, Disposable, ExtensionContext, Hover, HoverProvider, languages, Position, Range, StatusBarAlignment, StatusBarItem, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, window } from 'vscode';
import { AnnotationController, FileAnnotationType } from './annotations/annotationController';
import { Annotations, endOfLineIndex } from './annotations/annotations';
import { Annotations } from './annotations/annotations';
import { Commands } from './commands';
import { TextEditorComparer } from './comparers';
import { configuration, IConfig, StatusBarCommand } from './configuration';
import { DocumentSchemes, isTextEditor } from './constants';
import { BlameabilityChangeEvent, CommitFormatter, GitCommit, GitCommitLine, GitContextTracker, GitLogCommit, GitService, GitUri, ICommitFormatOptions } from './gitService';
import { DocumentSchemes, isTextEditor, RangeEndOfLineIndex } from './constants';
import { BlameabilityChangeEvent, CommitFormatter, DirtyStateChangeEvent, GitCommit, GitCommitLine, GitContextTracker, GitLogCommit, GitService, GitUri, ICommitFormatOptions } from './gitService';
// import { Logger } from './logger';
const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
@ -28,7 +28,7 @@ export class CurrentLineController extends Disposable {
private _blameable: boolean;
private _blameLineAnnotationState: { enabled: boolean, annotationType: LineAnnotationType, reason: 'user' | 'debugging' } | undefined;
private _config: IConfig;
private _currentLine: { line: number, commit?: GitCommit, logCommit?: GitLogCommit } = { line: -1 };
private _currentLine: { line: number, commit?: GitCommit, logCommit?: GitLogCommit, dirty?: boolean } = { line: -1 };
private _debugSessionEndDisposable: Disposable | undefined;
private _disposable: Disposable;
private _editor: TextEditor | undefined;
@ -116,7 +116,8 @@ export class CurrentLineController extends Disposable {
this._trackCurrentLineDisposable = this._trackCurrentLineDisposable || Disposable.from(
window.onDidChangeActiveTextEditor(Functions.debounce(this.onActiveTextEditorChanged, 50), this),
window.onDidChangeTextEditorSelection(this.onTextEditorSelectionChanged, this),
this.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this)
this.gitContextTracker.onDidChangeBlameability(this.onBlameabilityChanged, this),
this.gitContextTracker.onDidChangeDirtyState(this.onDirtyStateChanged, this)
);
}
else if (this._trackCurrentLineDisposable !== undefined) {
@ -178,6 +179,15 @@ export class CurrentLineController extends Disposable {
this.refresh(window.activeTextEditor);
}
private async onDirtyStateChanged(e: DirtyStateChangeEvent) {
if (e.dirty) {
this.clear(this._editor);
}
else {
this.refresh(this._editor);
}
}
private async onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent): Promise<void> {
// Make sure this is for the editor we are tracking
if (!this._blameable || !TextEditorComparer.equals(this._editor, e.textEditor)) return;
@ -219,7 +229,9 @@ export class CurrentLineController extends Disposable {
let commitLine: GitCommitLine | undefined = undefined;
// Since blame information isn't valid when there are unsaved changes -- don't show any status
if (this._blameable && line >= 0) {
const blameLine = await this.git.getBlameForLine(this._uri, line);
const blameLine = editor.document.isDirty
? await this.git.getBlameForLineContents(this._uri, line, editor.document.getText())
: await this.git.getBlameForLine(this._uri, line);
// Make sure we are still blameable after the await
if (this._blameable) {
@ -239,6 +251,8 @@ export class CurrentLineController extends Disposable {
}
async clear(editor: TextEditor | undefined) {
this._updateBlameDebounced.cancel();
this.unregisterHoverProviders();
this.clearAnnotations(editor, true);
this._statusBarItem && this._statusBarItem.hide();
@ -366,7 +380,7 @@ export class CurrentLineController extends Disposable {
const cfg = this._config.annotations.line.trailing;
const decoration = Annotations.trailing(commit, cfg.format, cfg.dateFormat === null ? this._config.defaultDateFormat : cfg.dateFormat);
decoration.range = editor.document.validateRange(new Range(line, endOfLineIndex, line, endOfLineIndex));
decoration.range = editor.document.validateRange(new Range(line, RangeEndOfLineIndex, line, RangeEndOfLineIndex));
editor.setDecorations(annotationDecoration, [decoration]);
this._isAnnotating = true;
@ -414,7 +428,7 @@ export class CurrentLineController extends Disposable {
const wholeLine = state.annotationType === LineAnnotationType.Hover || (state.annotationType === LineAnnotationType.Trailing && this._config.annotations.line.trailing.hover.wholeLine) ||
fileAnnotations === FileAnnotationType.Hover || (fileAnnotations === FileAnnotationType.Gutter && this._config.annotations.file.gutter.hover.wholeLine);
const range = document.validateRange(new Range(position.line, wholeLine ? 0 : endOfLineIndex, position.line, endOfLineIndex));
const range = document.validateRange(new Range(position.line, wholeLine ? 0 : RangeEndOfLineIndex, position.line, RangeEndOfLineIndex));
if (!wholeLine && range.start.character !== position.character) return undefined;
// Get the full commit message -- since blame only returns the summary
@ -452,7 +466,7 @@ export class CurrentLineController extends Disposable {
const wholeLine = state.annotationType === LineAnnotationType.Hover || (state.annotationType === LineAnnotationType.Trailing && this._config.annotations.line.trailing.hover.wholeLine) ||
fileAnnotations === FileAnnotationType.Hover || (fileAnnotations === FileAnnotationType.Gutter && this._config.annotations.file.gutter.hover.wholeLine);
const range = document.validateRange(new Range(position.line, wholeLine ? 0 : endOfLineIndex, position.line, endOfLineIndex));
const range = document.validateRange(new Range(position.line, wholeLine ? 0 : RangeEndOfLineIndex, position.line, RangeEndOfLineIndex));
if (!wholeLine && range.start.character !== position.character) return undefined;
const hover = await Annotations.changesHover(commit, position.line, this._uri, this.git);

+ 173
- 40
src/git/gitContextTracker.ts View File

@ -1,9 +1,9 @@
'use strict';
import { Functions, IDeferred } from '../system';
import { ConfigurationChangeEvent, Disposable, Event, EventEmitter, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
import { ConfigurationChangeEvent, Disposable, Event, EventEmitter, Range, TextDocumentChangeEvent, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode';
import { TextDocumentComparer } from '../comparers';
import { configuration } from '../configuration';
import { CommandContext, isTextEditor, setCommandContext } from '../constants';
import { CommandContext, isTextEditor, RangeEndOfLineIndex, setCommandContext } from '../constants';
import { GitChangeEvent, GitChangeReason, GitService, GitUri, Repository, RepositoryChangeEvent } from '../gitService';
import { Logger } from '../logger';
@ -15,11 +15,24 @@ export enum BlameabilityChangeReason {
}
export interface BlameabilityChangeEvent {
blameable: boolean;
editor: TextEditor | undefined;
blameable: boolean;
dirty: boolean;
reason: BlameabilityChangeReason;
}
export interface DirtyStateChangeEvent {
editor: TextEditor | undefined;
dirty: boolean;
}
export interface LineDirtyStateChangeEvent extends DirtyStateChangeEvent {
line: number;
lineDirty: boolean;
}
interface Context {
editor?: TextEditor;
repo?: Repository;
@ -31,6 +44,8 @@ interface Context {
interface ContextState {
blameable?: boolean;
dirty: boolean;
line?: number;
lineDirty?: boolean;
revision?: boolean;
tracked?: boolean;
}
@ -42,17 +57,35 @@ export class GitContextTracker extends Disposable {
return this._onDidChangeBlameability.event;
}
private _onDidChangeDirtyState = new EventEmitter<DirtyStateChangeEvent>();
get onDidChangeDirtyState(): Event<DirtyStateChangeEvent> {
return this._onDidChangeDirtyState.event;
}
private _onDidChangeLineDirtyState = new EventEmitter<LineDirtyStateChangeEvent>();
get onDidChangeLineDirtyState(): Event<LineDirtyStateChangeEvent> {
return this._onDidChangeLineDirtyState.event;
}
private readonly _context: Context = { state: { dirty: false } };
private readonly _disposable: Disposable;
private _listenersDisposable: Disposable | undefined;
private _onDirtyStateChangedDebounced: ((dirty: boolean) => void) & IDeferred;
private _fireDirtyStateChangedDebounced: (() => void) & IDeferred;
private _checkLineDirtyStateChangedDebounced: (() => void) & IDeferred;
private _fireLineDirtyStateChangedDebounced: (() => void) & IDeferred;
private _insiders = false;
constructor(
private readonly git: GitService
) {
super(() => this.dispose());
this._onDirtyStateChangedDebounced = Functions.debounce(this.onDirtyStateChanged, 250);
this._fireDirtyStateChangedDebounced = Functions.debounce(this.fireDirtyStateChanged, 1000);
this._checkLineDirtyStateChangedDebounced = Functions.debounce(this.checkLineDirtyStateChanged, 1000);
this._fireLineDirtyStateChangedDebounced = Functions.debounce(this.fireLineDirtyStateChanged, 1000);
this._disposable = Disposable.from(
workspace.onDidChangeConfiguration(this.onConfigurationChanged, this)
@ -65,29 +98,54 @@ export class GitContextTracker extends Disposable {
this._disposable && this._disposable.dispose();
}
private onConfigurationChanged(e: ConfigurationChangeEvent) {
if (!configuration.initializing(e) && !e.affectsConfiguration('git.enabled', null!)) return;
private _lineTrackingEnabled: boolean = false;
setLineTracking(editor: TextEditor | undefined, enabled: boolean) {
if (this._context.editor !== editor) return;
const enabled = workspace.getConfiguration('git', null!).get<boolean>('enabled', true);
if (this._listenersDisposable !== undefined) {
this._listenersDisposable.dispose();
this._listenersDisposable = undefined;
// If we are changing line tracking, reset the current line info, so we will refresh
if (this._lineTrackingEnabled !== enabled) {
this._context.state.line = undefined;
this._context.state.lineDirty = undefined;
}
this._lineTrackingEnabled = enabled;
}
setCommandContext(CommandContext.Enabled, enabled);
private onConfigurationChanged(e: ConfigurationChangeEvent) {
const initializing = configuration.initializing(e);
if (enabled) {
this._listenersDisposable = Disposable.from(
window.onDidChangeActiveTextEditor(Functions.debounce(this.onActiveTextEditorChanged, 50), this),
workspace.onDidChangeTextDocument(this.onTextDocumentChanged, this),
this.git.onDidBlameFail(this.onBlameFailed, this),
this.git.onDidChange(this.onGitChanged, this)
);
const section = configuration.name('insiders').value;
if (initializing || configuration.changed(e, section)) {
this._insiders = configuration.get<boolean>(section);
this.updateContext(BlameabilityChangeReason.EditorChanged, window.activeTextEditor, true);
if (!initializing) {
this.updateContext(BlameabilityChangeReason.EditorChanged, window.activeTextEditor, true);
}
}
else {
this.updateContext(BlameabilityChangeReason.EditorChanged, window.activeTextEditor, false);
if (initializing || e.affectsConfiguration('git.enabled', null!)) {
const enabled = workspace.getConfiguration('git', null!).get<boolean>('enabled', true);
if (this._listenersDisposable !== undefined) {
this._listenersDisposable.dispose();
this._listenersDisposable = undefined;
}
setCommandContext(CommandContext.Enabled, enabled);
if (enabled) {
this._listenersDisposable = Disposable.from(
window.onDidChangeActiveTextEditor(Functions.debounce(this.onActiveTextEditorChanged, 50), this),
workspace.onDidChangeTextDocument(this.onTextDocumentChanged, this),
window.onDidChangeTextEditorSelection(this.onTextEditorSelectionChanged, this),
this.git.onDidBlameFail(this.onBlameFailed, this),
this.git.onDidChange(this.onGitChanged, this)
);
this.updateContext(BlameabilityChangeReason.EditorChanged, window.activeTextEditor, true);
}
else {
this.updateContext(BlameabilityChangeReason.EditorChanged, window.activeTextEditor, false);
}
}
}
@ -97,6 +155,10 @@ export class GitContextTracker extends Disposable {
// Logger.log('GitContextTracker.onActiveTextEditorChanged', editor && editor.document.uri.fsPath);
// Reset the current line info, so we will refresh
this._context.state.line = undefined;
this._context.state.lineDirty = undefined;
this.updateContext(BlameabilityChangeReason.EditorChanged, editor, true);
}
@ -106,11 +168,6 @@ export class GitContextTracker extends Disposable {
this.updateBlameability(BlameabilityChangeReason.BlameFailed, false);
}
private onDirtyStateChanged(dirty: boolean) {
this._context.state.dirty = dirty;
this.updateBlameability(BlameabilityChangeReason.DocumentChanged);
}
private onGitChanged(e: GitChangeEvent) {
if (e.reason !== GitChangeReason.Repositories) return;
@ -126,28 +183,95 @@ export class GitContextTracker extends Disposable {
if (this._context.editor === undefined || !TextDocumentComparer.equals(this._context.editor.document, e.document)) return;
const dirty = e.document.isDirty;
const line = (this._context.editor && this._context.editor.selection.active.line) || -1;
let changed = false;
if (this._context.state.dirty !== dirty || this._context.state.line !== line) {
changed = true;
this._context.state.dirty = dirty;
if (this._context.state.line !== line) {
this._context.state.lineDirty = undefined;
}
this._context.state.line = line;
if (dirty) {
this._fireDirtyStateChangedDebounced.cancel();
setImmediate(() => this.fireDirtyStateChanged());
}
else {
this._fireDirtyStateChangedDebounced();
}
}
// If we haven't changed state, kick out
if (dirty === this._context.state.dirty) {
this._onDirtyStateChangedDebounced.cancel();
if (!this._lineTrackingEnabled || !this._insiders) return;
// If the file dirty state hasn't changed, check if the line has
if (!changed) {
this._checkLineDirtyStateChangedDebounced();
return;
}
// Logger.log('GitContextTracker.onTextDocumentChanged', `Dirty(${dirty}) state changed`);
this._context.state.lineDirty = dirty;
if (dirty) {
this._onDirtyStateChangedDebounced.cancel();
this.onDirtyStateChanged(dirty);
this._fireLineDirtyStateChangedDebounced.cancel();
setImmediate(() => this.fireLineDirtyStateChanged());
}
else {
this._fireLineDirtyStateChangedDebounced();
}
}
return;
private async checkLineDirtyStateChanged() {
const line = this._context.state.line;
if (this._context.editor === undefined || line === undefined || line < 0) return;
// Since we only care about this one line, just pass empty lines to align the contents for blaming (and also skip using the cache)
const contents = `${' \n'.repeat(line)}${this._context.editor.document.getText(new Range(line, 0, line, RangeEndOfLineIndex))}\n`;
const blameLine = await this.git.getBlameForLineContents(this._context.uri!, line, contents, { skipCache: true });
const lineDirty = blameLine !== undefined && blameLine.commit.isUncommitted;
if (this._context.state.lineDirty !== lineDirty) {
this._context.state.lineDirty = lineDirty;
this._fireLineDirtyStateChangedDebounced.cancel();
setImmediate(() => this.fireLineDirtyStateChanged());
}
}
this._onDirtyStateChangedDebounced(dirty);
private fireDirtyStateChanged() {
if (this._insiders) {
this._onDidChangeDirtyState.fire({
editor: this._context.editor,
dirty: this._context.state.dirty
} as DirtyStateChangeEvent);
}
else {
this.updateBlameability(BlameabilityChangeReason.DocumentChanged);
}
}
private fireLineDirtyStateChanged() {
this._onDidChangeLineDirtyState.fire({
editor: this._context.editor,
dirty: this._context.state.dirty,
line: this._context.state.line,
lineDirty: this._context.state.lineDirty
} as LineDirtyStateChangeEvent);
}
private onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent) {
if (this._context.state.line === e.selections[0].active.line) return;
this._context.state.line = undefined;
this._context.state.lineDirty = false;
}
private async updateContext(reason: BlameabilityChangeReason, editor: TextEditor | undefined, force: boolean = false) {
try {
let dirty = false;
let revision = false;
let tracked = false;
if (force || this._context.editor !== editor) {
@ -167,13 +291,12 @@ export class GitContextTracker extends Disposable {
this._context.repoDisposable = repo.onDidChange(this.onRepoChanged, this);
}
this._context.state.dirty = editor.document.isDirty;
dirty = editor.document.isDirty;
revision = !!this._context.uri.sha;
tracked = await this.git.isTracked(this._context.uri);
}
else {
this._context.uri = undefined;
this._context.state.dirty = false;
this._context.state.blameable = false;
}
}
@ -193,6 +316,13 @@ export class GitContextTracker extends Disposable {
setCommandContext(CommandContext.ActiveFileIsTracked, tracked);
}
if (this._context.state.dirty !== dirty) {
this._context.state.dirty = dirty;
if (this._insiders) {
this._fireDirtyStateChangedDebounced();
}
}
this.updateBlameability(reason, undefined, force);
this.updateRemotes();
}
@ -204,7 +334,9 @@ export class GitContextTracker extends Disposable {
private updateBlameability(reason: BlameabilityChangeReason, blameable?: boolean, force: boolean = false) {
try {
if (blameable === undefined) {
blameable = this._context.state.tracked && !this._context.state.dirty;
blameable = this._insiders
? this._context.state.tracked
: this._context.state.tracked && !this._context.state.dirty;
}
if (!force && this._context.state.blameable === blameable) return;
@ -213,10 +345,11 @@ export class GitContextTracker extends Disposable {
setCommandContext(CommandContext.ActiveIsBlameable, blameable);
this._onDidChangeBlameability.fire({
editor: this._context.editor,
blameable: blameable!,
editor: this._context && this._context.editor,
dirty: this._context.state.dirty,
reason: reason
});
} as BlameabilityChangeEvent);
}
catch (ex) {
Logger.error(ex, 'GitContextTracker.updateBlameability');

+ 6
- 2
src/gitCodeLensProvider.ts View File

@ -69,7 +69,9 @@ export class GitCodeLensProvider implements CodeLensProvider {
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
if (!await this.git.isTracked(document.uri.fsPath)) return [];
const dirty = document.isDirty;
const dirty = configuration.get<boolean>(configuration.name('insiders').value)
? false
: document.isDirty;
const cfg = configuration.get<ICodeLensConfig>(configuration.name('codeLens').value, document.uri);
this._debug = cfg.debug;
@ -103,7 +105,9 @@ export class GitCodeLensProvider implements CodeLensProvider {
}
else {
[blame, symbols] = await Promise.all([
this.git.getBlameForFile(gitUri),
document.isDirty
? this.git.getBlameForFileContents(gitUri, document.getText())
: this.git.getBlameForFile(gitUri),
commands.executeCommand(BuiltInCommands.ExecuteDocumentSymbolProvider, document.uri) as Promise<SymbolInformation[]>
]);
}

+ 5
- 5
src/gitService.ts View File

@ -271,7 +271,7 @@ export class GitService extends Disposable {
if (!initializing) {
// Defer the event trigger enough to let everything unwind
setTimeout(() => this.fireChange(GitChangeReason.Repositories), 1);
setImmediate(() => this.fireChange(GitChangeReason.Repositories));
}
}
@ -687,10 +687,10 @@ export class GitService extends Disposable {
}
}
async getBlameForLineContents(uri: GitUri, line: number, contents: string): Promise<GitBlameLine | undefined> {
async getBlameForLineContents(uri: GitUri, line: number, contents: string, options: { skipCache?: boolean } = {}): Promise<GitBlameLine | undefined> {
Logger.log(`getBlameForLineContents('${uri.repoPath}', '${uri.fsPath}', ${line})`);
if (this.UseCaching) {
if (!options.skipCache && this.UseCaching) {
const blame = await this.getBlameForFileContents(uri, contents);
if (blame === undefined) return undefined;
@ -1230,11 +1230,11 @@ export class GitService extends Disposable {
this._repositoryTree.set(rp, repo);
// Send a notification that the repositories changed
setTimeout(async () => {
setImmediate(async () => {
await setCommandContext(CommandContext.HasRepository, this._repositoryTree.any());
this.fireChange(GitChangeReason.Repositories);
}, 0);
});
}
return rp;

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

@ -13,8 +13,28 @@ interface IPropOfValue {
}
export namespace Functions {
export function debounce<T extends Function>(fn: T, wait?: number, options?: { leading?: boolean, maxWait?: number, trailing?: boolean }): T & IDeferred {
return _debounce(fn, wait, options);
export function debounce<T extends Function>(fn: T, wait?: number, options?: { leading?: boolean, maxWait?: number, track?: boolean, trailing?: boolean }): T & IDeferred & { pending?: () => boolean } {
const { track, ...opts } = { track: false, ...(options || {}) } as { leading?: boolean, maxWait?: number, track?: boolean, trailing?: boolean };
if (track !== true) return _debounce(fn, wait, opts);
let pending = false;
const debounced = _debounce(function() {
pending = false;
return fn.apply(null, arguments);
} as any as T, wait, options) as T & IDeferred;
const tracked = function() {
pending = true;
return debounced.apply(null, arguments);
} as any as T & IDeferred & { pending(): boolean};
tracked.pending = function() { return pending; };
tracked.cancel = function() { return debounced.cancel.apply(debounced, arguments); };
tracked.flush = function(...args: any[]) { return debounced.flush.apply(debounced, arguments); };
return tracked;
}
export function once<T extends Function>(fn: T): T {

Loading…
Cancel
Save