未验证 提交 80257234 编写于 作者: A Alex Dima

Fixes #101956

上级 a6eccc39
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { toErrorMessage } from 'vs/base/common/errorMessage'; 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 { Schemas } from 'vs/base/common/network';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
...@@ -19,6 +19,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ ...@@ -19,6 +19,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { toLocalResource, extUri, IExtUri } from 'vs/base/common/resources'; import { toLocalResource, extUri, IExtUri } from 'vs/base/common/resources';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { Emitter } from 'vs/base/common/event';
export class BoundModelReferenceCollection { export class BoundModelReferenceCollection {
...@@ -73,7 +74,36 @@ 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 _modelService: IModelService;
private readonly _textModelResolverService: ITextModelService; private readonly _textModelResolverService: ITextModelService;
...@@ -82,8 +112,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { ...@@ -82,8 +112,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
private readonly _environmentService: IWorkbenchEnvironmentService; private readonly _environmentService: IWorkbenchEnvironmentService;
private readonly _uriIdentityService: IUriIdentityService; private readonly _uriIdentityService: IUriIdentityService;
private readonly _toDispose = new DisposableStore(); private _modelTrackers: { [modelUrl: string]: ModelTracker; };
private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
private readonly _proxy: ExtHostDocumentsShape; private readonly _proxy: ExtHostDocumentsShape;
private readonly _modelIsSynced = new Set<string>(); private readonly _modelIsSynced = new Set<string>();
private readonly _modelReferenceCollection: BoundModelReferenceCollection; private readonly _modelReferenceCollection: BoundModelReferenceCollection;
...@@ -99,6 +128,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { ...@@ -99,6 +128,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
@IUriIdentityService uriIdentityService: IUriIdentityService, @IUriIdentityService uriIdentityService: IUriIdentityService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService
) { ) {
super();
this._modelService = modelService; this._modelService = modelService;
this._textModelResolverService = textModelResolverService; this._textModelResolverService = textModelResolverService;
this._textFileService = textFileService; this._textFileService = textFileService;
...@@ -106,26 +136,26 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { ...@@ -106,26 +136,26 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
this._environmentService = environmentService; this._environmentService = environmentService;
this._uriIdentityService = uriIdentityService; 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._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments);
this._toDispose.add(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this))); this._register(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this)));
this._toDispose.add(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this))); this._register(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this)));
this._toDispose.add(modelService.onModelModeChanged(this._onModelModeChanged, 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)) { if (this._shouldHandleFileEvent(e.model.resource)) {
this._proxy.$acceptModelSaved(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)) { if (this._shouldHandleFileEvent(m.resource)) {
this._proxy.$acceptDirtyStateChanged(m.resource, m.isDirty()); 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) { if (e.operation === FileOperation.MOVE || e.operation === FileOperation.DELETE) {
for (const { source } of e.files) { for (const { source } of e.files) {
if (source) { if (source) {
...@@ -135,15 +165,23 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { ...@@ -135,15 +165,23 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
} }
})); }));
this._modelToDisposeMap = Object.create(null); this._modelTrackers = Object.create(null);
} }
public dispose(): void { public dispose(): void {
Object.keys(this._modelToDisposeMap).forEach((modelUrl) => { Object.keys(this._modelTrackers).forEach((modelUrl) => {
this._modelToDisposeMap[modelUrl].dispose(); this._modelTrackers[modelUrl].dispose();
}); });
this._modelToDisposeMap = Object.create(null); this._modelTrackers = Object.create(null);
this._toDispose.dispose(); 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 { private _shouldHandleFileEvent(resource: URI): boolean {
...@@ -159,9 +197,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { ...@@ -159,9 +197,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
} }
const modelUrl = model.uri; const modelUrl = model.uri;
this._modelIsSynced.add(modelUrl.toString()); this._modelIsSynced.add(modelUrl.toString());
this._modelToDisposeMap[modelUrl.toString()] = model.onDidChangeContent((e) => { this._modelTrackers[modelUrl.toString()] = new ModelTracker(model, this._onIsCaughtUpWithContentChanges, this._proxy, this._textFileService);
this._proxy.$acceptModelChanged(modelUrl, e, this._textFileService.isDirty(modelUrl));
});
} }
private _onModelModeChanged(event: { model: ITextModel; oldModeId: string; }): void { private _onModelModeChanged(event: { model: ITextModel; oldModeId: string; }): void {
...@@ -179,8 +215,8 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { ...@@ -179,8 +215,8 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
return; return;
} }
this._modelIsSynced.delete(strModelUrl); this._modelIsSynced.delete(strModelUrl);
this._modelToDisposeMap[strModelUrl].dispose(); this._modelTrackers[strModelUrl].dispose();
delete this._modelToDisposeMap[strModelUrl]; delete this._modelTrackers[strModelUrl];
} }
// --- from extension host process // --- from extension host process
......
...@@ -306,6 +306,7 @@ export class MainThreadDocumentsAndEditors { ...@@ -306,6 +306,7 @@ export class MainThreadDocumentsAndEditors {
private readonly _toDispose = new DisposableStore(); private readonly _toDispose = new DisposableStore();
private readonly _proxy: ExtHostDocumentsAndEditorsShape; private readonly _proxy: ExtHostDocumentsAndEditorsShape;
private readonly _mainThreadDocuments: MainThreadDocuments;
private readonly _textEditors = new Map<string, MainThreadTextEditor>(); private readonly _textEditors = new Map<string, MainThreadTextEditor>();
private readonly _onTextEditorAdd = new Emitter<MainThreadTextEditor[]>(); private readonly _onTextEditorAdd = new Emitter<MainThreadTextEditor[]>();
...@@ -336,8 +337,8 @@ export class MainThreadDocumentsAndEditors { ...@@ -336,8 +337,8 @@ export class MainThreadDocumentsAndEditors {
) { ) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors);
const mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService)); this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService));
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments); extHostContext.set(MainContext.MainThreadDocuments, this._mainThreadDocuments);
const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService)); const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService));
extHostContext.set(MainContext.MainThreadTextEditors, mainThreadTextEditors); extHostContext.set(MainContext.MainThreadTextEditors, mainThreadTextEditors);
...@@ -367,7 +368,7 @@ export class MainThreadDocumentsAndEditors { ...@@ -367,7 +368,7 @@ export class MainThreadDocumentsAndEditors {
// added editors // added editors
for (const apiEditor of delta.addedEditors) { for (const apiEditor of delta.addedEditors) {
const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.editor.getModel(), 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); this._textEditors.set(apiEditor.id, mainThreadEditor);
addedEditors.push(mainThreadEditor); addedEditors.push(mainThreadEditor);
......
...@@ -20,6 +20,7 @@ import { equals } from 'vs/base/common/arrays'; ...@@ -20,6 +20,7 @@ import { equals } from 'vs/base/common/arrays';
import { CodeEditorStateFlag, EditorState } from 'vs/editor/browser/core/editorState'; import { CodeEditorStateFlag, EditorState } from 'vs/editor/browser/core/editorState';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
import { MainThreadDocuments } from 'vs/workbench/api/browser/mainThreadDocuments';
export interface IFocusTracker { export interface IFocusTracker {
onGainedFocus(): void; onGainedFocus(): void;
...@@ -160,7 +161,8 @@ export class MainThreadTextEditorProperties { ...@@ -160,7 +161,8 @@ export class MainThreadTextEditorProperties {
export class MainThreadTextEditor { export class MainThreadTextEditor {
private readonly _id: string; private readonly _id: string;
private _model: ITextModel; private readonly _model: ITextModel;
private readonly _mainThreadDocuments: MainThreadDocuments;
private readonly _modelService: IModelService; private readonly _modelService: IModelService;
private readonly _clipboardService: IClipboardService; private readonly _clipboardService: IClipboardService;
private readonly _modelListeners = new DisposableStore(); private readonly _modelListeners = new DisposableStore();
...@@ -176,6 +178,7 @@ export class MainThreadTextEditor { ...@@ -176,6 +178,7 @@ export class MainThreadTextEditor {
model: ITextModel, model: ITextModel,
codeEditor: ICodeEditor, codeEditor: ICodeEditor,
focusTracker: IFocusTracker, focusTracker: IFocusTracker,
mainThreadDocuments: MainThreadDocuments,
modelService: IModelService, modelService: IModelService,
clipboardService: IClipboardService, clipboardService: IClipboardService,
) { ) {
...@@ -184,6 +187,7 @@ export class MainThreadTextEditor { ...@@ -184,6 +187,7 @@ export class MainThreadTextEditor {
this._codeEditor = null; this._codeEditor = null;
this._properties = null; this._properties = null;
this._focusTracker = focusTracker; this._focusTracker = focusTracker;
this._mainThreadDocuments = mainThreadDocuments;
this._modelService = modelService; this._modelService = modelService;
this._clipboardService = clipboardService; this._clipboardService = clipboardService;
...@@ -198,7 +202,6 @@ export class MainThreadTextEditor { ...@@ -198,7 +202,6 @@ export class MainThreadTextEditor {
} }
public dispose(): void { public dispose(): void {
this._model = null!;
this._modelListeners.dispose(); this._modelListeners.dispose();
this._codeEditor = null; this._codeEditor = null;
this._codeEditorListeners.dispose(); this._codeEditorListeners.dispose();
...@@ -257,21 +260,46 @@ export class MainThreadTextEditor { ...@@ -257,21 +260,46 @@ export class MainThreadTextEditor {
this._focusTracker.onLostFocus(); 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) => { this._codeEditorListeners.add(this._codeEditor.onDidChangeCursorSelection((e) => {
// selection // selection
this._updatePropertiesNow(e.source); updateProperties(e.source);
})); }));
this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration(() => { this._codeEditorListeners.add(this._codeEditor.onDidChangeConfiguration(() => {
// options // options
this._updatePropertiesNow(null); updateProperties(null);
})); }));
this._codeEditorListeners.add(this._codeEditor.onDidLayoutChange(() => { this._codeEditorListeners.add(this._codeEditor.onDidLayoutChange(() => {
// visibleRanges // visibleRanges
this._updatePropertiesNow(null); updateProperties(null);
})); }));
this._codeEditorListeners.add(this._codeEditor.onDidScrollChange(() => { this._codeEditorListeners.add(this._codeEditor.onDidScrollChange(() => {
// visibleRanges // visibleRanges
this._updatePropertiesNow(null); updateProperties(null);
})); }));
this._updatePropertiesNow(null); this._updatePropertiesNow(null);
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册