Skip to content

Commit d1c2f52

Browse files
committed
Closes #3284 reworks document tracking
1 parent cdb0e0a commit d1c2f52

17 files changed

+532
-362
lines changed

package.json

Lines changed: 74 additions & 74 deletions
Large diffs are not rendered by default.

src/annotations/annotationProvider.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import type { TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEve
22
import { Disposable, window } from 'vscode';
33
import type { FileAnnotationType } from '../config';
44
import type { Container } from '../container';
5-
import { setContext } from '../system/context';
65
import { Logger } from '../system/logger';
6+
import type { Deferred } from '../system/promise';
7+
import { defer } from '../system/promise';
78
import type { TrackedGitDocument } from '../trackers/trackedDocument';
89
import type { Decoration } from './annotations';
910

@@ -23,14 +24,18 @@ export function getEditorCorrelationKey(editor: TextEditor | undefined): TextEdi
2324
return `${editor?.document.uri.toString()}|${editor?.viewColumn}`;
2425
}
2526

27+
export type DidChangeStatusCallback = (e: { editor?: TextEditor; status?: AnnotationStatus }) => void;
28+
2629
export abstract class AnnotationProviderBase<TContext extends AnnotationContext = AnnotationContext>
2730
implements Disposable
2831
{
2932
private decorations: Decoration[] | undefined;
3033
protected disposable: Disposable;
34+
private _computing: Deferred<void> | undefined;
3135

3236
constructor(
3337
protected readonly container: Container,
38+
protected readonly onDidChangeStatus: DidChangeStatusCallback,
3439
public readonly annotationType: FileAnnotationType,
3540
editor: TextEditor,
3641
protected readonly trackedDocument: TrackedGitDocument,
@@ -43,7 +48,7 @@ export abstract class AnnotationProviderBase<TContext extends AnnotationContext
4348
}
4449

4550
dispose() {
46-
this.clear();
51+
void this.clear();
4752

4853
this.disposable.dispose();
4954
}
@@ -75,17 +80,11 @@ export abstract class AnnotationProviderBase<TContext extends AnnotationContext
7580
return this._status;
7681
}
7782

78-
get statusContextValue(): `${AnnotationStatus}+${FileAnnotationType}` | undefined {
79-
return this.status != null ? `${this.status}+${this.annotationType}` : undefined;
80-
}
81-
82-
private async setStatus(value: AnnotationStatus | undefined, editor: TextEditor | undefined): Promise<void> {
83+
private setStatus(value: AnnotationStatus | undefined, editor: TextEditor | undefined): void {
8384
if (this.status === value) return;
8485

8586
this._status = value;
86-
if (editor != null && editor === window.activeTextEditor) {
87-
await setContext('gitlens:annotationStatus', this.statusContextValue);
88-
}
87+
this.onDidChangeStatus({ editor: editor, status: value });
8988
}
9089

9190
private onTextEditorSelectionChanged(e: TextEditorSelectionChangeEvent) {
@@ -98,11 +97,15 @@ export abstract class AnnotationProviderBase<TContext extends AnnotationContext
9897
return true;
9998
}
10099

101-
clear() {
100+
async clear() {
101+
if (this._computing?.pending) {
102+
await this._computing.promise;
103+
}
104+
102105
const decorations = this.decorations;
103106
this.decorations = undefined;
104107
this.annotationContext = undefined;
105-
void this.setStatus(undefined, this.editor);
108+
this.setStatus(undefined, this.editor);
106109

107110
if (this.editor == null) return;
108111

@@ -122,23 +125,27 @@ export abstract class AnnotationProviderBase<TContext extends AnnotationContext
122125
previousChange?(): void;
123126

124127
async provideAnnotation(context?: TContext, state?: AnnotationState): Promise<boolean> {
125-
void this.setStatus('computing', this.editor);
128+
this._computing = defer<void>();
129+
this.setStatus('computing', this.editor);
126130

127131
try {
128132
this.annotationContext = context;
129133

130134
if (await this.onProvideAnnotation(context, state)) {
131-
void this.setStatus('computed', this.editor);
135+
this.setStatus('computed', this.editor);
132136
await this.selection?.(
133137
state?.restoring ? { line: this.editor.selection.active.line } : context?.selection,
134138
);
139+
140+
this._computing.fulfill();
135141
return true;
136142
}
137143
} catch (ex) {
138144
Logger.error(ex);
139145
}
140146

141-
void this.setStatus(undefined, this.editor);
147+
this.setStatus(undefined, this.editor);
148+
this._computing.fulfill();
142149
return false;
143150
}
144151

@@ -175,15 +182,15 @@ export abstract class AnnotationProviderBase<TContext extends AnnotationContext
175182
return;
176183
}
177184

178-
void this.setStatus('computing', this.editor);
185+
this.setStatus('computing', this.editor);
179186

180187
if (this.decorations?.length) {
181188
for (const d of this.decorations) {
182189
this.editor.setDecorations(d.decorationType, d.rangesOrOptions);
183190
}
184191
}
185192

186-
void this.setStatus('computed', this.editor);
193+
this.setStatus('computed', this.editor);
187194
}
188195

189196
selection?(selection?: TContext['selection']): Promise<void>;
@@ -193,7 +200,7 @@ export abstract class AnnotationProviderBase<TContext extends AnnotationContext
193200
if (this.decorations?.length) {
194201
// If we have no new decorations, just completely clear the old ones
195202
if (!decorations?.length) {
196-
this.clear();
203+
void this.clear();
197204

198205
return;
199206
}

src/annotations/blameAnnotationProvider.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { changesMessage, detailsMessage } from '../hovers/hovers';
99
import { configuration } from '../system/configuration';
1010
import { log } from '../system/decorators/log';
1111
import type { TrackedGitDocument } from '../trackers/trackedDocument';
12+
import type { DidChangeStatusCallback } from './annotationProvider';
1213
import { AnnotationProviderBase } from './annotationProvider';
1314
import type { ComputedHeatmap } from './annotations';
1415
import { getHeatmapColors } from './annotations';
@@ -21,11 +22,12 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
2122

2223
constructor(
2324
container: Container,
25+
onDidChangeStatus: DidChangeStatusCallback,
2426
annotationType: FileAnnotationType,
2527
editor: TextEditor,
2628
trackedDocument: TrackedGitDocument,
2729
) {
28-
super(container, annotationType, editor, trackedDocument);
30+
super(container, onDidChangeStatus, annotationType, editor, trackedDocument);
2931

3032
this.blame = container.git.getBlame(this.trackedDocument.uri, editor.document);
3133

@@ -39,7 +41,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
3941
this.hoverProviderDisposable.dispose();
4042
this.hoverProviderDisposable = undefined;
4143
}
42-
super.clear();
44+
return super.clear();
4345
}
4446

4547
override async validate(): Promise<boolean> {

src/annotations/fileAnnotationController.ts

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ import type {
3939
DocumentDirtyIdleTriggerEvent,
4040
DocumentDirtyStateChangeEvent,
4141
} from '../trackers/documentTracker';
42-
import type { AnnotationContext, AnnotationProviderBase, TextEditorCorrelationKey } from './annotationProvider';
42+
import type {
43+
AnnotationContext,
44+
AnnotationProviderBase,
45+
AnnotationStatus,
46+
TextEditorCorrelationKey,
47+
} from './annotationProvider';
4348
import { getEditorCorrelationKey } from './annotationProvider';
4449
import type { ChangesAnnotationContext } from './gutterChangesAnnotationProvider';
4550

@@ -177,10 +182,8 @@ export class FileAnnotationController implements Disposable {
177182

178183
const provider = this.getProvider(editor);
179184
if (provider == null) {
180-
void setContext('gitlens:annotationStatus', undefined);
181185
void this.detachKeyboardHook();
182186
} else {
183-
void setContext('gitlens:annotationStatus', provider.statusContextValue);
184187
void this.attachKeyboardHook();
185188
}
186189
}
@@ -201,8 +204,11 @@ export class FileAnnotationController implements Disposable {
201204
void this.clearCore(getEditorCorrelationKey(editor));
202205
}
203206

204-
private onDirtyIdleTriggered(e: DocumentDirtyIdleTriggerEvent) {
205-
if (!e.document.isBlameable || !configuration.get('fileAnnotations.preserveWhileEditing')) return;
207+
private async onDirtyIdleTriggered(e: DocumentDirtyIdleTriggerEvent) {
208+
if (!configuration.get('fileAnnotations.preserveWhileEditing')) return;
209+
210+
const status = await e.document.getStatus();
211+
if (!status.blameable) return;
206212

207213
const editor = window.activeTextEditor;
208214
if (editor == null) return;
@@ -292,7 +298,8 @@ export class FileAnnotationController implements Disposable {
292298
if (provider == null) return undefined;
293299

294300
const trackedDocument = await this.container.documentTracker.get(editor!.document);
295-
if (!trackedDocument?.isBlameable) return undefined;
301+
const status = await trackedDocument?.getStatus();
302+
if (!status?.blameable) return undefined;
296303

297304
return provider.annotationType;
298305
}
@@ -320,6 +327,52 @@ export class FileAnnotationController implements Disposable {
320327
debouncedRestore(editor);
321328
}
322329

330+
private readonly _annotatedUris = new Set<string>();
331+
private readonly _computingUris = new Set<string>();
332+
333+
async onProviderEditorStatusChanged(editor: TextEditor | undefined, status: AnnotationStatus | undefined) {
334+
if (editor == null) return;
335+
336+
let windowStatus;
337+
338+
if (this.isInWindowToggle()) {
339+
windowStatus = status;
340+
this._annotatedUris.clear();
341+
this._computingUris.clear();
342+
} else {
343+
windowStatus = undefined;
344+
const uri = editor.document.uri.toString();
345+
346+
switch (status) {
347+
case 'computing':
348+
this._annotatedUris.add(uri);
349+
this._computingUris.add(uri);
350+
break;
351+
case 'computed':
352+
this._annotatedUris.add(uri);
353+
this._computingUris.delete(uri);
354+
break;
355+
default:
356+
this._annotatedUris.delete(uri);
357+
this._computingUris.delete(uri);
358+
break;
359+
}
360+
361+
const provider = this.getProvider(editor);
362+
if (provider == null) {
363+
this._annotatedUris.delete(uri);
364+
} else {
365+
this._annotatedUris.add(uri);
366+
}
367+
}
368+
369+
await Promise.allSettled([
370+
setContext('gitlens:window:annotated', windowStatus),
371+
setContext('gitlens:tabs:annotated:computing', [...this._computingUris]),
372+
setContext('gitlens:tabs:annotated', [...this._annotatedUris]),
373+
]);
374+
}
375+
323376
async show(editor: TextEditor | undefined, type: FileAnnotationType, context?: AnnotationContext): Promise<boolean>;
324377
async show(editor: TextEditor | undefined, type: 'changes', context?: ChangesAnnotationContext): Promise<boolean>;
325378
@log<FileAnnotationController['show']>({
@@ -362,7 +415,8 @@ export class FileAnnotationController implements Disposable {
362415
this._editor = editor;
363416

364417
const trackedDocument = await this.container.documentTracker.getOrAdd(editor.document);
365-
if (!trackedDocument.isBlameable) return false;
418+
const status = await trackedDocument?.getStatus();
419+
if (!status?.blameable) return false;
366420

367421
const currentProvider = this.getProvider(editor);
368422
if (currentProvider?.annotationType === type) {
@@ -373,14 +427,12 @@ export class FileAnnotationController implements Disposable {
373427
const provider = await window.withProgress(
374428
{ location: ProgressLocation.Window },
375429
async (progress: Progress<{ message: string }>) => {
376-
await setContext('gitlens:annotationStatus', 'computing');
430+
void this.onProviderEditorStatusChanged(editor, 'computing');
377431

378432
const computingAnnotations = this.showAnnotationsCore(currentProvider, editor, type, context, progress);
379-
const provider = await computingAnnotations;
433+
void (await computingAnnotations);
380434

381-
if (editor === this._editor) {
382-
await setContext('gitlens:annotationStatus', provider?.statusContextValue);
383-
}
435+
void this.onProviderEditorStatusChanged(editor, 'computed');
384436

385437
return computingAnnotations;
386438
},
@@ -415,7 +467,8 @@ export class FileAnnotationController implements Disposable {
415467
): Promise<boolean> {
416468
if (editor != null && this._toggleModes.get(type) === 'file') {
417469
const trackedDocument = await this.container.documentTracker.getOrAdd(editor.document);
418-
if ((type === 'changes' && !trackedDocument.isTracked) || !trackedDocument.isBlameable) {
470+
const status = await trackedDocument?.getStatus();
471+
if ((type === 'changes' && !status?.tracked) || !status?.blameable) {
419472
return false;
420473
}
421474
}
@@ -484,7 +537,10 @@ export class FileAnnotationController implements Disposable {
484537
provider.dispose();
485538

486539
if (!this._annotationProviders.size || key === getEditorCorrelationKey(this._editor)) {
487-
await setContext('gitlens:annotationStatus', undefined);
540+
if (this._editor != null) {
541+
void this.onProviderEditorStatusChanged(this._editor, undefined);
542+
}
543+
488544
await this.detachKeyboardHook();
489545
}
490546

@@ -542,21 +598,36 @@ export class FileAnnotationController implements Disposable {
542598
const { GutterBlameAnnotationProvider } = await import(
543599
/* webpackChunkName: "annotations" */ './gutterBlameAnnotationProvider'
544600
);
545-
provider = new GutterBlameAnnotationProvider(this.container, editor, trackedDocument);
601+
provider = new GutterBlameAnnotationProvider(
602+
this.container,
603+
e => this.onProviderEditorStatusChanged(e.editor, e.status),
604+
editor,
605+
trackedDocument,
606+
);
546607
break;
547608
}
548609
case 'changes': {
549610
const { GutterChangesAnnotationProvider } = await import(
550611
/* webpackChunkName: "annotations" */ './gutterChangesAnnotationProvider'
551612
);
552-
provider = new GutterChangesAnnotationProvider(this.container, editor, trackedDocument);
613+
provider = new GutterChangesAnnotationProvider(
614+
this.container,
615+
e => this.onProviderEditorStatusChanged(e.editor, e.status),
616+
editor,
617+
trackedDocument,
618+
);
553619
break;
554620
}
555621
case 'heatmap': {
556622
const { GutterHeatmapBlameAnnotationProvider } = await import(
557623
/* webpackChunkName: "annotations" */ './gutterHeatmapBlameAnnotationProvider'
558624
);
559-
provider = new GutterHeatmapBlameAnnotationProvider(this.container, editor, trackedDocument);
625+
provider = new GutterHeatmapBlameAnnotationProvider(
626+
this.container,
627+
e => this.onProviderEditorStatusChanged(e.editor, e.status),
628+
editor,
629+
trackedDocument,
630+
);
560631
break;
561632
}
562633
}

src/annotations/gutterBlameAnnotationProvider.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { maybeStopWatch } from '../system/stopwatch';
1515
import type { TokenOptions } from '../system/string';
1616
import { getTokensFromTemplate, getWidth } from '../system/string';
1717
import type { TrackedGitDocument } from '../trackers/trackedDocument';
18-
import type { AnnotationContext, AnnotationState } from './annotationProvider';
18+
import type { AnnotationContext, AnnotationState, DidChangeStatusCallback } from './annotationProvider';
1919
import { applyHeatmap, getGutterDecoration, getGutterRenderOptions } from './annotations';
2020
import { BlameAnnotationProviderBase } from './blameAnnotationProvider';
2121
import { Decorations } from './fileAnnotationController';
@@ -30,12 +30,17 @@ export interface BlameFontOptions {
3030
}
3131

3232
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
33-
constructor(container: Container, editor: TextEditor, trackedDocument: TrackedGitDocument) {
34-
super(container, 'blame', editor, trackedDocument);
33+
constructor(
34+
container: Container,
35+
onDidChangeStatus: DidChangeStatusCallback,
36+
editor: TextEditor,
37+
trackedDocument: TrackedGitDocument,
38+
) {
39+
super(container, onDidChangeStatus, 'blame', editor, trackedDocument);
3540
}
3641

37-
override clear() {
38-
super.clear();
42+
override async clear() {
43+
await super.clear();
3944

4045
if (Decorations.gutterBlameHighlight != null) {
4146
try {

0 commit comments

Comments
 (0)