Skip to content

Commit

Permalink
SCM - switch to using ReferenceCollection in the IdirtyDiffModelServi…
Browse files Browse the repository at this point in the history
…ce (#235579)

Initial implementation
  • Loading branch information
lszomoru authored Dec 9, 2024
1 parent c61b174 commit f10bb21
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 143 deletions.
46 changes: 28 additions & 18 deletions src/vs/workbench/api/browser/mainThreadEditors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
this._proxy.$acceptEditorPropertiesChanged(id, data);
}));

const diffInformationObs = this._getTextEditorDiffInformation(textEditor);
const diffInformationObs = this._getTextEditorDiffInformation(textEditor, toDispose);
toDispose.push(autorun(reader => {
const diffInformation = diffInformationObs.read(reader);
this._proxy.$acceptEditorDiffInformation(id, diffInformation);
Expand Down Expand Up @@ -131,17 +131,17 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
return result;
}

private _getTextEditorDiffInformation(textEditor: MainThreadTextEditor): IObservable<ITextEditorDiffInformation[] | undefined> {
private _getTextEditorDiffInformation(textEditor: MainThreadTextEditor, toDispose: IDisposable[]): IObservable<ITextEditorDiffInformation[] | undefined> {
const codeEditor = textEditor.getCodeEditor();
if (!codeEditor) {
return constObservable(undefined);
}

// Check if the TextModel belongs to a DiffEditor
const diffEditors = this._codeEditorService.listDiffEditors();
const [diffEditor] = diffEditors.filter(d =>
d.getOriginalEditor().getId() === codeEditor.getId() ||
d.getModifiedEditor().getId() === codeEditor.getId());
const [diffEditor] = this._codeEditorService.listDiffEditors()
.filter(d =>
d.getOriginalEditor().getId() === codeEditor.getId() ||
d.getModifiedEditor().getId() === codeEditor.getId());

const editorModelObs = diffEditor
? observableFromEvent(this, diffEditor.onDidChangeModel, () => diffEditor.getModel())
Expand All @@ -159,13 +159,14 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {

// TextEditor
if (isITextModel(editorModel)) {
const dirtyDiffModel = this._dirtyDiffModelService.getDirtyDiffModel(editorModelUri);
if (!dirtyDiffModel) {
const dirtyDiffModelRef = this._dirtyDiffModelService.createDirtyDiffModelReference(editorModelUri);
if (!dirtyDiffModelRef) {
return constObservable(undefined);
}

return observableFromEvent(this, dirtyDiffModel.onDidChange, () => {
return dirtyDiffModel.getQuickDiffResults()
toDispose.push(dirtyDiffModelRef);
return observableFromEvent(this, dirtyDiffModelRef.object.onDidChange, () => {
return dirtyDiffModelRef.object.getQuickDiffResults()
.map(result => ({
original: result.original,
modified: result.modified,
Expand All @@ -178,13 +179,14 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
// we can provide multiple "original resources" to diff with the modified
// resource.
const diffAlgorithm = this._configurationService.getValue<DiffAlgorithmName>('diffEditor.diffAlgorithm');
const dirtyDiffModel = this._dirtyDiffModelService.getDiffModel(editorModelUri, diffAlgorithm);
if (!dirtyDiffModel) {
const dirtyDiffModelRef = this._dirtyDiffModelService.createDiffModelReference(editorModelUri, diffAlgorithm);
if (!dirtyDiffModelRef) {
return constObservable(undefined);
}

return observableFromEvent(Event.any(dirtyDiffModel.onDidChange, diffEditor.onDidUpdateDiff), () => {
const dirtyDiffInformation = dirtyDiffModel.getQuickDiffResults()
toDispose.push(dirtyDiffModelRef);
return observableFromEvent(Event.any(dirtyDiffModelRef.object.onDidChange, diffEditor.onDidUpdateDiff), () => {
const dirtyDiffInformation = dirtyDiffModelRef.object.getQuickDiffResults()
.map(result => ({
original: result.original,
modified: result.modified,
Expand Down Expand Up @@ -389,11 +391,19 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
return Promise.resolve([]);
}

const dirtyDiffModel = this._dirtyDiffModelService.getDirtyDiffModel(codeEditor.getModel().uri);
const scmQuickDiff = dirtyDiffModel?.quickDiffs.find(quickDiff => quickDiff.isSCM);
const scmQuickDiffChanges = dirtyDiffModel?.changes.filter(change => change.label === scmQuickDiff?.label);
const dirtyDiffModelRef = this._dirtyDiffModelService.createDirtyDiffModelReference(codeEditor.getModel().uri);
if (!dirtyDiffModelRef) {
return Promise.resolve([]);
}

return Promise.resolve(scmQuickDiffChanges?.map(change => change.change) ?? []);
try {
const scmQuickDiff = dirtyDiffModelRef.object.quickDiffs.find(quickDiff => quickDiff.isSCM);
const scmQuickDiffChanges = dirtyDiffModelRef.object.changes.filter(change => change.label === scmQuickDiff?.label);

return Promise.resolve(scmQuickDiffChanges.map(change => change.change) ?? []);
} finally {
dirtyDiffModelRef.dispose();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ suite('MainThreadDocumentsAndEditors', () => {
new TestPathService(),
new TestConfigurationService(),
new class extends mock<IDirtyDiffModelService>() {
override getDirtyDiffModel() {
override createDiffModelReference() {
return undefined;
}
override getDiffModel() {
override createDirtyDiffModelReference() {
return undefined;
}
}
Expand Down
120 changes: 41 additions & 79 deletions src/vs/workbench/contrib/scm/browser/dirtyDiffModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
import { ResourceMap } from '../../../../base/common/map.js';
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { EncodingMode, IResolvedTextFileEditorModel, isTextFileEditorModel, ITextFileEditorModel, ITextFileService } from '../../../services/textfile/common/textfiles.js';
import { Disposable, DisposableMap, DisposableStore } from '../../../../base/common/lifecycle.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { autorun, observableFromEvent } from '../../../../base/common/observable.js';
import { isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js';
import { Disposable, DisposableMap, DisposableStore, IReference, ReferenceCollection } from '../../../../base/common/lifecycle.js';
import { DiffAlgorithmName, IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js';
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
import { URI } from '../../../../base/common/uri.js';
Expand Down Expand Up @@ -38,108 +35,73 @@ export interface IDirtyDiffModelService {
_serviceBrand: undefined;

/**
* Returns `undefined` if the editor model is not resolved
* @param uri
* Returns `undefined` if the editor model is not resolved.
* Model refrence has to be disposed once not needed anymore.
* @param resource
* @param algorithm
*/
getDirtyDiffModel(uri: URI): DirtyDiffModel | undefined;
createDiffModelReference(resource: URI, algorithm: DiffAlgorithmName): IReference<DirtyDiffModel> | undefined;

/**
* Returns `undefined` if the editor model is not resolved
* @param uri
* @param algorithm
* Returns `undefined` if the editor model is not resolved.
* Model refrence has to be disposed once not needed anymore.
* @param resource
*/
getDiffModel(uri: URI, algorithm: DiffAlgorithmName): DirtyDiffModel | undefined;
createDirtyDiffModelReference(resource: URI): IReference<DirtyDiffModel> | undefined;
}

export class DirtyDiffModelService extends Disposable implements IDirtyDiffModelService {
_serviceBrand: undefined;
class DirtyDiffModelReferenceCollection extends ReferenceCollection<DirtyDiffModel> {
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) {
super();
}

private readonly _dirtyDiffModels = new ResourceMap<DirtyDiffModel>();
private readonly _diffModels = new ResourceMap<Map<DiffAlgorithmName, DirtyDiffModel>>();
protected override createReferencedObject(_key: string, textFileModel: IResolvedTextFileEditorModel, algorithm: DiffAlgorithmName | undefined): DirtyDiffModel {
return this._instantiationService.createInstance(DirtyDiffModel, textFileModel, algorithm);
}

private _visibleTextEditorControls = observableFromEvent(
this.editorService.onDidVisibleEditorsChange,
() => this.editorService.visibleTextEditorControls);
protected override destroyReferencedObject(_key: string, object: DirtyDiffModel): void {
object.dispose();
}
}

export class DirtyDiffModelService implements IDirtyDiffModelService {
_serviceBrand: undefined;

private readonly _references: DirtyDiffModelReferenceCollection;

constructor(
@IEditorService private readonly editorService: IEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ITextFileService private readonly textFileService: ITextFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
) {
super();

this._register(autorun(reader => {
const visibleTextEditorControls = this._visibleTextEditorControls.read(reader);

// Dispose dirty diff models for text editors that are not visible
for (const [uri, dirtyDiffModel] of this._dirtyDiffModels) {
const textEditorControl = visibleTextEditorControls
.find(editor => isCodeEditor(editor) &&
this.uriIdentityService.extUri.isEqual(editor.getModel()?.uri, uri));

if (textEditorControl) {
continue;
}

dirtyDiffModel.dispose();
this._dirtyDiffModels.delete(uri);
}

// Dispose diff models for diff editors that are not visible
for (const [uri, dirtyDiffModel] of this._diffModels) {
const diffEditorControl = visibleTextEditorControls
.find(editor => isDiffEditor(editor) &&
this.uriIdentityService.extUri.isEqual(editor.getModel()?.modified.uri, uri));

if (diffEditorControl) {
continue;
}

for (const algorithm of dirtyDiffModel.keys()) {
dirtyDiffModel.get(algorithm)?.dispose();
dirtyDiffModel.delete(algorithm);
}
this._diffModels.delete(uri);
}
}));
this._references = this.instantiationService.createInstance(DirtyDiffModelReferenceCollection);
}

getDirtyDiffModel(uri: URI): DirtyDiffModel | undefined {
let model = this._dirtyDiffModels.get(uri);
if (model) {
return model;
}

const textFileModel = this.textFileService.files.get(uri);
createDiffModelReference(resource: URI, algorithm: DiffAlgorithmName): IReference<DirtyDiffModel> | undefined {
const textFileModel = this.textFileService.files.get(resource);
if (!textFileModel?.isResolved()) {
return undefined;
}

model = this.instantiationService.createInstance(DirtyDiffModel, textFileModel, undefined);
this._dirtyDiffModels.set(uri, model);

return model;
return this._createModelReference(resource, textFileModel, algorithm);
}

getDiffModel(uri: URI, algorithm: DiffAlgorithmName): DirtyDiffModel | undefined {
let model = this._diffModels.get(uri)?.get(algorithm);
if (model) {
return model;
}

const textFileModel = this.textFileService.files.get(uri);
createDirtyDiffModelReference(resource: URI): IReference<DirtyDiffModel> | undefined {
const textFileModel = this.textFileService.files.get(resource);
if (!textFileModel?.isResolved()) {
return undefined;
}

model = this.instantiationService.createInstance(DirtyDiffModel, textFileModel, algorithm);
if (!this._diffModels.has(uri)) {
this._diffModels.set(uri, new Map());
}
this._diffModels.get(uri)!.set(algorithm, model);
return this._createModelReference(resource, textFileModel, undefined);
}

private _createModelReference(resource: URI, textFileModel: IResolvedTextFileEditorModel, algorithm: DiffAlgorithmName | undefined): IReference<DirtyDiffModel> {
resource = algorithm === undefined
? this.uriIdentityService.asCanonicalUri(resource)
: this.uriIdentityService.asCanonicalUri(resource)
.with({ query: `algorithm=${algorithm}` });

return model;
return this._references.acquire(resource.toString(), textFileModel, algorithm);
}
}

Expand Down
Loading

0 comments on commit f10bb21

Please sign in to comment.