|
@ -64,7 +64,7 @@ export class DocumentTracker implements Disposable { |
|
|
|
|
|
|
|
|
private _dirtyIdleTriggerDelay!: number; |
|
|
private _dirtyIdleTriggerDelay!: number; |
|
|
private readonly _disposable: Disposable | undefined; |
|
|
private readonly _disposable: Disposable | undefined; |
|
|
private readonly _documentMap = new Map<TextDocument | string, TrackedDocument<T>>(); |
|
|
|
|
|
|
|
|
private readonly _documentMap = new Map<TextDocument | string, Promise<TrackedDocument<T>>>(); |
|
|
|
|
|
|
|
|
constructor() { |
|
|
constructor() { |
|
|
this._disposable = Disposable.from( |
|
|
this._disposable = Disposable.from( |
|
@ -76,20 +76,20 @@ export class DocumentTracker implements Disposable { |
|
|
workspace.onDidSaveTextDocument(this.onTextDocumentSaved, this), |
|
|
workspace.onDidSaveTextDocument(this.onTextDocumentSaved, this), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
this.onConfigurationChanged(configuration.initializingChangeEvent); |
|
|
|
|
|
|
|
|
void this.onConfigurationChanged(configuration.initializingChangeEvent); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
dispose() { |
|
|
dispose() { |
|
|
this._disposable?.dispose(); |
|
|
this._disposable?.dispose(); |
|
|
|
|
|
|
|
|
this.clear(); |
|
|
|
|
|
|
|
|
void this.clear(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
initialize() { |
|
|
initialize() { |
|
|
this.onActiveTextEditorChanged(window.activeTextEditor); |
|
|
|
|
|
|
|
|
void this.onActiveTextEditorChanged(window.activeTextEditor); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private onConfigurationChanged(e: ConfigurationChangeEvent) { |
|
|
|
|
|
|
|
|
private async onConfigurationChanged(e: ConfigurationChangeEvent) { |
|
|
// Only rest the cached state if we aren't initializing
|
|
|
// Only rest the cached state if we aren't initializing
|
|
|
if ( |
|
|
if ( |
|
|
!configuration.initializing(e) && |
|
|
!configuration.initializing(e) && |
|
@ -97,7 +97,7 @@ export class DocumentTracker implements Disposable { |
|
|
configuration.changed(e, 'advanced', 'caching', 'enabled')) |
|
|
configuration.changed(e, 'advanced', 'caching', 'enabled')) |
|
|
) { |
|
|
) { |
|
|
for (const d of this._documentMap.values()) { |
|
|
for (const d of this._documentMap.values()) { |
|
|
d.reset('config'); |
|
|
|
|
|
|
|
|
(await d).reset('config'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -108,15 +108,15 @@ export class DocumentTracker implements Disposable { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private _timer: NodeJS.Timer | undefined; |
|
|
private _timer: NodeJS.Timer | undefined; |
|
|
private onActiveTextEditorChanged(editor: TextEditor | undefined) { |
|
|
|
|
|
if (editor !== undefined && !isTextEditor(editor)) return; |
|
|
|
|
|
|
|
|
private async onActiveTextEditorChanged(editor: TextEditor | undefined) { |
|
|
|
|
|
if (editor != null && !isTextEditor(editor)) return; |
|
|
|
|
|
|
|
|
if (this._timer !== undefined) { |
|
|
|
|
|
|
|
|
if (this._timer != null) { |
|
|
clearTimeout(this._timer); |
|
|
clearTimeout(this._timer); |
|
|
this._timer = undefined; |
|
|
this._timer = undefined; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (editor === undefined) { |
|
|
|
|
|
|
|
|
if (editor == null) { |
|
|
this._timer = setTimeout(() => { |
|
|
this._timer = setTimeout(() => { |
|
|
this._timer = undefined; |
|
|
this._timer = undefined; |
|
|
|
|
|
|
|
@ -127,34 +127,30 @@ export class DocumentTracker implements Disposable { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const doc = this._documentMap.get(editor.document); |
|
|
const doc = this._documentMap.get(editor.document); |
|
|
if (doc !== undefined) { |
|
|
|
|
|
doc.activate(); |
|
|
|
|
|
|
|
|
if (doc != null) { |
|
|
|
|
|
(await doc).activate(); |
|
|
|
|
|
|
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// No need to activate this, as it is implicit in initialization if currently active
|
|
|
// No need to activate this, as it is implicit in initialization if currently active
|
|
|
this.addCore(editor.document); |
|
|
|
|
|
|
|
|
void this.addCore(editor.document); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private onTextDocumentChanged(e: TextDocumentChangeEvent) { |
|
|
|
|
|
|
|
|
private async onTextDocumentChanged(e: TextDocumentChangeEvent) { |
|
|
const { scheme } = e.document.uri; |
|
|
const { scheme } = e.document.uri; |
|
|
if (scheme !== DocumentSchemes.File && scheme !== DocumentSchemes.Git && scheme !== DocumentSchemes.Vsls) { |
|
|
if (scheme !== DocumentSchemes.File && scheme !== DocumentSchemes.Git && scheme !== DocumentSchemes.Vsls) { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let doc = this._documentMap.get(e.document); |
|
|
|
|
|
if (doc === undefined) { |
|
|
|
|
|
doc = this.addCore(e.document); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const doc = await (this._documentMap.get(e.document) ?? this.addCore(e.document)); |
|
|
doc.reset('document'); |
|
|
doc.reset('document'); |
|
|
|
|
|
|
|
|
const dirty = e.document.isDirty; |
|
|
const dirty = e.document.isDirty; |
|
|
const editor = window.activeTextEditor; |
|
|
const editor = window.activeTextEditor; |
|
|
|
|
|
|
|
|
// If we have an idle tracker, either reset or cancel it
|
|
|
// If we have an idle tracker, either reset or cancel it
|
|
|
if (this._dirtyIdleTriggeredDebounced !== undefined) { |
|
|
|
|
|
|
|
|
if (this._dirtyIdleTriggeredDebounced != null) { |
|
|
if (dirty) { |
|
|
if (dirty) { |
|
|
this._dirtyIdleTriggeredDebounced({ editor: editor!, document: doc }); |
|
|
this._dirtyIdleTriggeredDebounced({ editor: editor!, document: doc }); |
|
|
} else { |
|
|
} else { |
|
@ -163,7 +159,7 @@ export class DocumentTracker implements Disposable { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Only fire change events for the active document
|
|
|
// Only fire change events for the active document
|
|
|
if (editor !== undefined && editor.document === e.document) { |
|
|
|
|
|
|
|
|
if (editor?.document === e.document) { |
|
|
this._onDidChangeContent.fire({ editor: editor, document: doc, contentChanges: e.contentChanges }); |
|
|
this._onDidChangeContent.fire({ editor: editor, document: doc, contentChanges: e.contentChanges }); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -173,24 +169,25 @@ export class DocumentTracker implements Disposable { |
|
|
doc.dirty = dirty; |
|
|
doc.dirty = dirty; |
|
|
|
|
|
|
|
|
// Only fire state change events for the active document
|
|
|
// Only fire state change events for the active document
|
|
|
if (editor === undefined || editor.document !== e.document) return; |
|
|
|
|
|
|
|
|
if (editor == null || editor.document !== e.document) return; |
|
|
|
|
|
|
|
|
this.fireDocumentDirtyStateChanged({ editor: editor, document: doc, dirty: doc.dirty }); |
|
|
this.fireDocumentDirtyStateChanged({ editor: editor, document: doc, dirty: doc.dirty }); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private onTextDocumentClosed(document: TextDocument) { |
|
|
|
|
|
|
|
|
private async onTextDocumentClosed(document: TextDocument) { |
|
|
const doc = this._documentMap.get(document); |
|
|
const doc = this._documentMap.get(document); |
|
|
if (doc === undefined) return; |
|
|
|
|
|
|
|
|
if (doc == null) return; |
|
|
|
|
|
|
|
|
doc.dispose(); |
|
|
|
|
|
this._documentMap.delete(document); |
|
|
this._documentMap.delete(document); |
|
|
this._documentMap.delete(doc.key); |
|
|
|
|
|
|
|
|
this._documentMap.delete(GitUri.toKey(document.uri)); |
|
|
|
|
|
|
|
|
|
|
|
(await doc).dispose(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private onTextDocumentSaved(document: TextDocument) { |
|
|
|
|
|
|
|
|
private async onTextDocumentSaved(document: TextDocument) { |
|
|
const doc = this._documentMap.get(document); |
|
|
const doc = this._documentMap.get(document); |
|
|
if (doc !== undefined) { |
|
|
|
|
|
void doc.update({ forceBlameChange: true }); |
|
|
|
|
|
|
|
|
if (doc != null) { |
|
|
|
|
|
void (await doc).update({ forceBlameChange: true }); |
|
|
|
|
|
|
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
@ -213,31 +210,30 @@ export class DocumentTracker implements Disposable { |
|
|
add(document: TextDocument): Promise<TrackedDocument<T>>; |
|
|
add(document: TextDocument): Promise<TrackedDocument<T>>; |
|
|
add(uri: Uri): Promise<TrackedDocument<T>>; |
|
|
add(uri: Uri): Promise<TrackedDocument<T>>; |
|
|
add(documentOrId: TextDocument | Uri): Promise<TrackedDocument<T>> { |
|
|
add(documentOrId: TextDocument | Uri): Promise<TrackedDocument<T>> { |
|
|
return this._add(documentOrId); |
|
|
|
|
|
|
|
|
const doc = this._add(documentOrId); |
|
|
|
|
|
return doc; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
clear() { |
|
|
|
|
|
|
|
|
async clear() { |
|
|
for (const d of this._documentMap.values()) { |
|
|
for (const d of this._documentMap.values()) { |
|
|
d.dispose(); |
|
|
|
|
|
|
|
|
(await d).dispose(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this._documentMap.clear(); |
|
|
this._documentMap.clear(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
get(fileName: string): Promise<TrackedDocument<T> | undefined>; |
|
|
|
|
|
get(document: TextDocument): Promise<TrackedDocument<T> | undefined>; |
|
|
|
|
|
get(uri: Uri): Promise<TrackedDocument<T> | undefined>; |
|
|
|
|
|
get(documentOrId: string | TextDocument | Uri): Promise<TrackedDocument<T> | undefined> { |
|
|
|
|
|
return this._get(documentOrId); |
|
|
|
|
|
|
|
|
get(fileName: string): Promise<TrackedDocument<T>> | undefined; |
|
|
|
|
|
get(document: TextDocument): Promise<TrackedDocument<T>> | undefined; |
|
|
|
|
|
get(uri: Uri): Promise<TrackedDocument<T>> | undefined; |
|
|
|
|
|
get(documentOrId: string | TextDocument | Uri): Promise<TrackedDocument<T>> | undefined { |
|
|
|
|
|
const doc = this._get(documentOrId); |
|
|
|
|
|
return doc; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async getOrAdd(document: TextDocument): Promise<TrackedDocument<T>>; |
|
|
async getOrAdd(document: TextDocument): Promise<TrackedDocument<T>>; |
|
|
async getOrAdd(uri: Uri): Promise<TrackedDocument<T>>; |
|
|
async getOrAdd(uri: Uri): Promise<TrackedDocument<T>>; |
|
|
async getOrAdd(documentOrId: TextDocument | Uri): Promise<TrackedDocument<T>> { |
|
|
async getOrAdd(documentOrId: TextDocument | Uri): Promise<TrackedDocument<T>> { |
|
|
let doc = await this._get(documentOrId); |
|
|
|
|
|
if (doc === undefined) { |
|
|
|
|
|
doc = await this._add(documentOrId); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const doc = this._get(documentOrId) ?? this._add(documentOrId); |
|
|
return doc; |
|
|
return doc; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -269,7 +265,7 @@ export class DocumentTracker implements Disposable { |
|
|
document = new MissingRevisionTextDocument(documentOrId); |
|
|
document = new MissingRevisionTextDocument(documentOrId); |
|
|
|
|
|
|
|
|
// const [fileName, repoPath] = await Container.git.findWorkingFileName(documentOrId, undefined, ref);
|
|
|
// const [fileName, repoPath] = await Container.git.findWorkingFileName(documentOrId, undefined, ref);
|
|
|
// if (fileName === undefined) throw new Error(`Failed to add tracking for document: ${documentOrId}`);
|
|
|
|
|
|
|
|
|
// if (fileName == null) throw new Error(`Failed to add tracking for document: ${documentOrId}`);
|
|
|
|
|
|
|
|
|
// documentOrId = await workspace.openTextDocument(path.resolve(repoPath!, fileName));
|
|
|
// documentOrId = await workspace.openTextDocument(path.resolve(repoPath!, fileName));
|
|
|
} else { |
|
|
} else { |
|
@ -283,12 +279,10 @@ export class DocumentTracker implements Disposable { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const doc = this.addCore(document); |
|
|
const doc = this.addCore(document); |
|
|
await doc.ensureInitialized(); |
|
|
|
|
|
|
|
|
|
|
|
return doc; |
|
|
return doc; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private async _get(documentOrId: string | TextDocument | Uri) { |
|
|
|
|
|
|
|
|
private _get(documentOrId: string | TextDocument | Uri) { |
|
|
if (GitUri.is(documentOrId)) { |
|
|
if (GitUri.is(documentOrId)) { |
|
|
documentOrId = GitUri.toKey(documentOrId.documentUri({ useVersionedPath: true })); |
|
|
documentOrId = GitUri.toKey(documentOrId.documentUri({ useVersionedPath: true })); |
|
|
} else if (typeof documentOrId === 'string' || documentOrId instanceof Uri) { |
|
|
} else if (typeof documentOrId === 'string' || documentOrId instanceof Uri) { |
|
@ -296,19 +290,17 @@ export class DocumentTracker implements Disposable { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const doc = this._documentMap.get(documentOrId); |
|
|
const doc = this._documentMap.get(documentOrId); |
|
|
if (doc === undefined) return undefined; |
|
|
|
|
|
|
|
|
|
|
|
await doc.ensureInitialized(); |
|
|
|
|
|
return doc; |
|
|
return doc; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private addCore(document: TextDocument): TrackedDocument<T> { |
|
|
|
|
|
|
|
|
private async addCore(document: TextDocument): Promise<TrackedDocument<T>> { |
|
|
const key = GitUri.toKey(document.uri); |
|
|
const key = GitUri.toKey(document.uri); |
|
|
|
|
|
|
|
|
// Always start out false, so we will fire the event if needed
|
|
|
// Always start out false, so we will fire the event if needed
|
|
|
const doc = new TrackedDocument<T>(document, key, false, { |
|
|
|
|
|
|
|
|
const doc = TrackedDocument.create<T>(document, key, false, { |
|
|
onDidBlameStateChange: (e: DocumentBlameStateChangeEvent<T>) => this._onDidChangeBlameState.fire(e), |
|
|
onDidBlameStateChange: (e: DocumentBlameStateChangeEvent<T>) => this._onDidChangeBlameState.fire(e), |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this._documentMap.set(document, doc); |
|
|
this._documentMap.set(document, doc); |
|
|
this._documentMap.set(key, doc); |
|
|
this._documentMap.set(key, doc); |
|
|
|
|
|
|
|
@ -323,29 +315,18 @@ export class DocumentTracker implements Disposable { |
|
|
| undefined; |
|
|
| undefined; |
|
|
private fireDocumentDirtyStateChanged(e: DocumentDirtyStateChangeEvent<T>) { |
|
|
private fireDocumentDirtyStateChanged(e: DocumentDirtyStateChangeEvent<T>) { |
|
|
if (e.dirty) { |
|
|
if (e.dirty) { |
|
|
setImmediate(async () => { |
|
|
|
|
|
if (this._dirtyStateChangedDebounced !== undefined) { |
|
|
|
|
|
this._dirtyStateChangedDebounced.cancel(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setImmediate(() => { |
|
|
|
|
|
this._dirtyStateChangedDebounced?.cancel(); |
|
|
if (window.activeTextEditor !== e.editor) return; |
|
|
if (window.activeTextEditor !== e.editor) return; |
|
|
|
|
|
|
|
|
await e.document.ensureInitialized(); |
|
|
|
|
|
this._onDidChangeDirtyState.fire(e); |
|
|
this._onDidChangeDirtyState.fire(e); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (this._dirtyIdleTriggerDelay > 0) { |
|
|
if (this._dirtyIdleTriggerDelay > 0) { |
|
|
if (this._dirtyIdleTriggeredDebounced === undefined) { |
|
|
|
|
|
|
|
|
if (this._dirtyIdleTriggeredDebounced == null) { |
|
|
this._dirtyIdleTriggeredDebounced = Functions.debounce( |
|
|
this._dirtyIdleTriggeredDebounced = Functions.debounce( |
|
|
async (e: DocumentDirtyIdleTriggerEvent<T>) => { |
|
|
|
|
|
if ( |
|
|
|
|
|
this._dirtyIdleTriggeredDebounced !== undefined && |
|
|
|
|
|
this._dirtyIdleTriggeredDebounced.pending!() |
|
|
|
|
|
) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await e.document.ensureInitialized(); |
|
|
|
|
|
|
|
|
(e: DocumentDirtyIdleTriggerEvent<T>) => { |
|
|
|
|
|
if (this._dirtyIdleTriggeredDebounced?.pending!()) return; |
|
|
|
|
|
|
|
|
e.document.isDirtyIdle = true; |
|
|
e.document.isDirtyIdle = true; |
|
|
this._onDidTriggerDirtyIdle.fire(e); |
|
|
this._onDidTriggerDirtyIdle.fire(e); |
|
@ -361,11 +342,10 @@ export class DocumentTracker implements Disposable { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (this._dirtyStateChangedDebounced === undefined) { |
|
|
|
|
|
this._dirtyStateChangedDebounced = Functions.debounce(async (e: DocumentDirtyStateChangeEvent<T>) => { |
|
|
|
|
|
|
|
|
if (this._dirtyStateChangedDebounced == null) { |
|
|
|
|
|
this._dirtyStateChangedDebounced = Functions.debounce((e: DocumentDirtyStateChangeEvent<T>) => { |
|
|
if (window.activeTextEditor !== e.editor) return; |
|
|
if (window.activeTextEditor !== e.editor) return; |
|
|
|
|
|
|
|
|
await e.document.ensureInitialized(); |
|
|
|
|
|
this._onDidChangeDirtyState.fire(e); |
|
|
this._onDidChangeDirtyState.fire(e); |
|
|
}, 250); |
|
|
}, 250); |
|
|
} |
|
|
} |
|
|