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

Fixes #101956

上级 a6eccc39
......@@ -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);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册