mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 20:13:32 +01:00
Fixes #101956
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { IDisposable, IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IReference, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
@@ -19,6 +19,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { toLocalResource, extUri, IExtUri } from 'vs/base/common/resources';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class BoundModelReferenceCollection {
|
||||
|
||||
@@ -73,7 +74,36 @@ export class BoundModelReferenceCollection {
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
class ModelTracker extends Disposable {
|
||||
|
||||
private _knownVersionId: number;
|
||||
|
||||
constructor(
|
||||
private readonly _model: ITextModel,
|
||||
private readonly _onIsCaughtUpWithContentChanges: Emitter<URI>,
|
||||
private readonly _proxy: ExtHostDocumentsShape,
|
||||
private readonly _textFileService: ITextFileService,
|
||||
) {
|
||||
super();
|
||||
this._knownVersionId = this._model.getVersionId();
|
||||
this._register(this._model.onDidChangeContent((e) => {
|
||||
this._knownVersionId = e.versionId;
|
||||
this._proxy.$acceptModelChanged(this._model.uri, e, this._textFileService.isDirty(this._model.uri));
|
||||
if (this.isCaughtUpWithContentChanges()) {
|
||||
this._onIsCaughtUpWithContentChanges.fire(this._model.uri);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public isCaughtUpWithContentChanges(): boolean {
|
||||
return (this._model.getVersionId() === this._knownVersionId);
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadDocuments extends Disposable implements MainThreadDocumentsShape {
|
||||
|
||||
private _onIsCaughtUpWithContentChanges = this._register(new Emitter<URI>());
|
||||
public readonly onIsCaughtUpWithContentChanges = this._onIsCaughtUpWithContentChanges.event;
|
||||
|
||||
private readonly _modelService: IModelService;
|
||||
private readonly _textModelResolverService: ITextModelService;
|
||||
@@ -82,8 +112,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
private readonly _environmentService: IWorkbenchEnvironmentService;
|
||||
private readonly _uriIdentityService: IUriIdentityService;
|
||||
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
|
||||
private _modelTrackers: { [modelUrl: string]: ModelTracker; };
|
||||
private readonly _proxy: ExtHostDocumentsShape;
|
||||
private readonly _modelIsSynced = new Set<string>();
|
||||
private readonly _modelReferenceCollection: BoundModelReferenceCollection;
|
||||
@@ -99,6 +128,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
@IUriIdentityService uriIdentityService: IUriIdentityService,
|
||||
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
|
||||
) {
|
||||
super();
|
||||
this._modelService = modelService;
|
||||
this._textModelResolverService = textModelResolverService;
|
||||
this._textFileService = textFileService;
|
||||
@@ -106,26 +136,26 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
this._environmentService = environmentService;
|
||||
this._uriIdentityService = uriIdentityService;
|
||||
|
||||
this._modelReferenceCollection = this._toDispose.add(new BoundModelReferenceCollection(uriIdentityService.extUri));
|
||||
this._modelReferenceCollection = this._register(new BoundModelReferenceCollection(uriIdentityService.extUri));
|
||||
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments);
|
||||
|
||||
this._toDispose.add(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this)));
|
||||
this._toDispose.add(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this)));
|
||||
this._toDispose.add(modelService.onModelModeChanged(this._onModelModeChanged, this));
|
||||
this._register(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this)));
|
||||
this._register(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this)));
|
||||
this._register(modelService.onModelModeChanged(this._onModelModeChanged, this));
|
||||
|
||||
this._toDispose.add(textFileService.files.onDidSave(e => {
|
||||
this._register(textFileService.files.onDidSave(e => {
|
||||
if (this._shouldHandleFileEvent(e.model.resource)) {
|
||||
this._proxy.$acceptModelSaved(e.model.resource);
|
||||
}
|
||||
}));
|
||||
this._toDispose.add(textFileService.files.onDidChangeDirty(m => {
|
||||
this._register(textFileService.files.onDidChangeDirty(m => {
|
||||
if (this._shouldHandleFileEvent(m.resource)) {
|
||||
this._proxy.$acceptDirtyStateChanged(m.resource, m.isDirty());
|
||||
}
|
||||
}));
|
||||
|
||||
this._toDispose.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => {
|
||||
this._register(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => {
|
||||
if (e.operation === FileOperation.MOVE || e.operation === FileOperation.DELETE) {
|
||||
for (const { source } of e.files) {
|
||||
if (source) {
|
||||
@@ -135,15 +165,23 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
}
|
||||
}));
|
||||
|
||||
this._modelToDisposeMap = Object.create(null);
|
||||
this._modelTrackers = Object.create(null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
Object.keys(this._modelToDisposeMap).forEach((modelUrl) => {
|
||||
this._modelToDisposeMap[modelUrl].dispose();
|
||||
Object.keys(this._modelTrackers).forEach((modelUrl) => {
|
||||
this._modelTrackers[modelUrl].dispose();
|
||||
});
|
||||
this._modelToDisposeMap = Object.create(null);
|
||||
this._toDispose.dispose();
|
||||
this._modelTrackers = Object.create(null);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public isCaughtUpWithContentChanges(resource: URI): boolean {
|
||||
const modelUrl = resource.toString();
|
||||
if (this._modelTrackers[modelUrl]) {
|
||||
return this._modelTrackers[modelUrl].isCaughtUpWithContentChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _shouldHandleFileEvent(resource: URI): boolean {
|
||||
@@ -159,9 +197,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
}
|
||||
const modelUrl = model.uri;
|
||||
this._modelIsSynced.add(modelUrl.toString());
|
||||
this._modelToDisposeMap[modelUrl.toString()] = model.onDidChangeContent((e) => {
|
||||
this._proxy.$acceptModelChanged(modelUrl, e, this._textFileService.isDirty(modelUrl));
|
||||
});
|
||||
this._modelTrackers[modelUrl.toString()] = new ModelTracker(model, this._onIsCaughtUpWithContentChanges, this._proxy, this._textFileService);
|
||||
}
|
||||
|
||||
private _onModelModeChanged(event: { model: ITextModel; oldModeId: string; }): void {
|
||||
@@ -179,8 +215,8 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
return;
|
||||
}
|
||||
this._modelIsSynced.delete(strModelUrl);
|
||||
this._modelToDisposeMap[strModelUrl].dispose();
|
||||
delete this._modelToDisposeMap[strModelUrl];
|
||||
this._modelTrackers[strModelUrl].dispose();
|
||||
delete this._modelTrackers[strModelUrl];
|
||||
}
|
||||
|
||||
// --- from extension host process
|
||||
|
||||
@@ -306,6 +306,7 @@ export class MainThreadDocumentsAndEditors {
|
||||
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _proxy: ExtHostDocumentsAndEditorsShape;
|
||||
private readonly _mainThreadDocuments: MainThreadDocuments;
|
||||
private readonly _textEditors = new Map<string, MainThreadTextEditor>();
|
||||
|
||||
private readonly _onTextEditorAdd = new Emitter<MainThreadTextEditor[]>();
|
||||
@@ -336,8 +337,8 @@ export class MainThreadDocumentsAndEditors {
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors);
|
||||
|
||||
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService));
|
||||
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
|
||||
this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService));
|
||||
extHostContext.set(MainContext.MainThreadDocuments, this._mainThreadDocuments);
|
||||
|
||||
const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService));
|
||||
extHostContext.set(MainContext.MainThreadTextEditors, mainThreadTextEditors);
|
||||
@@ -367,7 +368,7 @@ export class MainThreadDocumentsAndEditors {
|
||||
// added editors
|
||||
for (const apiEditor of delta.addedEditors) {
|
||||
const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.editor.getModel(),
|
||||
apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this._modelService, this._clipboardService);
|
||||
apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this._mainThreadDocuments, this._modelService, this._clipboardService);
|
||||
|
||||
this._textEditors.set(apiEditor.id, mainThreadEditor);
|
||||
addedEditors.push(mainThreadEditor);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { equals } from 'vs/base/common/arrays';
|
||||
import { CodeEditorStateFlag, EditorState } from 'vs/editor/browser/core/editorState';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
|
||||
import { MainThreadDocuments } from 'vs/workbench/api/browser/mainThreadDocuments';
|
||||
|
||||
export interface IFocusTracker {
|
||||
onGainedFocus(): void;
|
||||
@@ -160,7 +161,8 @@ export class MainThreadTextEditorProperties {
|
||||
export class MainThreadTextEditor {
|
||||
|
||||
private readonly _id: string;
|
||||
private _model: ITextModel;
|
||||
private readonly _model: ITextModel;
|
||||
private readonly _mainThreadDocuments: MainThreadDocuments;
|
||||
private readonly _modelService: IModelService;
|
||||
private readonly _clipboardService: IClipboardService;
|
||||
private readonly _modelListeners = new DisposableStore();
|
||||
@@ -176,6 +178,7 @@ export class MainThreadTextEditor {
|
||||
model: ITextModel,
|
||||
codeEditor: ICodeEditor,
|
||||
focusTracker: IFocusTracker,
|
||||
mainThreadDocuments: MainThreadDocuments,
|
||||
modelService: IModelService,
|
||||
clipboardService: IClipboardService,
|
||||
) {
|
||||
@@ -184,6 +187,7 @@ export class MainThreadTextEditor {
|
||||
this._codeEditor = null;
|
||||
this._properties = null;
|
||||
this._focusTracker = focusTracker;
|
||||
this._mainThreadDocuments = mainThreadDocuments;
|
||||
this._modelService = modelService;
|
||||
this._clipboardService = clipboardService;
|
||||
|
||||
@@ -198,7 +202,6 @@ export class MainThreadTextEditor {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._model = null!;
|
||||
this._modelListeners.dispose();
|
||||
this._codeEditor = null;
|
||||
this._codeEditorListeners.dispose();
|
||||
@@ -257,21 +260,46 @@ export class MainThreadTextEditor {
|
||||
this._focusTracker.onLostFocus();
|
||||
}));
|
||||
|
||||
let nextSelectionChangeSource: string | null = null;
|
||||
this._codeEditorListeners.add(this._mainThreadDocuments.onIsCaughtUpWithContentChanges((uri) => {
|
||||
if (uri.toString() === this._model.uri.toString()) {
|
||||
const selectionChangeSource = nextSelectionChangeSource;
|
||||
nextSelectionChangeSource = null;
|
||||
this._updatePropertiesNow(selectionChangeSource);
|
||||
}
|
||||
}));
|
||||
|
||||
const updateProperties = (selectionChangeSource: string | null) => {
|
||||
// Some editor events get delivered faster than model content changes. This is
|
||||
// problematic, as this leads to editor properties reaching the extension host
|
||||
// too soon, before the model content change that was the root cause.
|
||||
//
|
||||
// If this case is identified, then let's update editor properties on the next model
|
||||
// content change instead.
|
||||
if (this._mainThreadDocuments.isCaughtUpWithContentChanges(this._model.uri)) {
|
||||
nextSelectionChangeSource = null;
|
||||
this._updatePropertiesNow(selectionChangeSource);
|
||||
} else {
|
||||
// update editor properties on the next model content change
|
||||
nextSelectionChangeSource = selectionChangeSource;
|
||||
}
|
||||
};
|
||||
|
||||
this._codeEditorListeners.add(this._codeEditor.onDidChangeCursorSelection((e) => {
|
||||
// selection
|
||||
this._updatePropertiesNow(e.source);
|
||||
updateProperties(e.source);
|
||||
}));
|
||||
this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration(() => {
|
||||
// options
|
||||
this._updatePropertiesNow(null);
|
||||
updateProperties(null);
|
||||
}));
|
||||
this._codeEditorListeners.add(this._codeEditor.onDidLayoutChange(() => {
|
||||
// visibleRanges
|
||||
this._updatePropertiesNow(null);
|
||||
updateProperties(null);
|
||||
}));
|
||||
this._codeEditorListeners.add(this._codeEditor.onDidScrollChange(() => {
|
||||
// visibleRanges
|
||||
this._updatePropertiesNow(null);
|
||||
updateProperties(null);
|
||||
}));
|
||||
this._updatePropertiesNow(null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user