From 2e95c110f42ef5e7f366dbc41672ba89f26c48f2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 Jan 2016 12:50:40 +0100 Subject: [PATCH] use weak references to track TextDocument that were given out to consumers --- package.json | 1 + src/typings/weak.d.ts | 29 +++++++ .../workbench/api/common/extHost.api.impl.ts | 20 +++-- .../workbench/api/common/extHostDocuments.ts | 81 ++++++++++--------- src/vs/workbench/api/common/extHostEditors.ts | 16 ++-- .../api/common/extHostLanguageFeatures.ts | 26 +++--- 6 files changed, 106 insertions(+), 67 deletions(-) create mode 100644 src/typings/weak.d.ts diff --git a/package.json b/package.json index 2b83553bb64..374cdd1c353 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "vscode-debugprotocol": "^1.2.1", "vscode-textmate": "^1.0.9", "native-keymap": "^0.1.2", + "weak": "^1.0.1", "winreg": "0.0.12", "yauzl": "^2.3.1" }, diff --git a/src/typings/weak.d.ts b/src/typings/weak.d.ts new file mode 100644 index 00000000000..62c528a4ec2 --- /dev/null +++ b/src/typings/weak.d.ts @@ -0,0 +1,29 @@ + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare namespace weak { + interface WeakRef { + // tagging + } +} + +interface WeakFunction { + (obj: T, callback?: () => any): T & weak.WeakRef; + (obj: any, callback?: () => any): any & weak.WeakRef; + + get(ref: weak.WeakRef): any; + get(ref: weak.WeakRef): T; + + isDead(ref: weak.WeakRef): boolean; + isNearDeath(ref: weak.WeakRef): boolean; + isWeakRef(obj: any): boolean; +} + +declare const weak: WeakFunction; + +declare module 'weak' { + export = weak; +} \ No newline at end of file diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a1b5619ec80..66f0dc2375d 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -244,16 +244,24 @@ export class ExtHostAPIImplementation { return pluginHostFileSystemEvent.createFileSystemWatcher(pattern, ignoreCreate, ignoreChange, ignoreDelete); }, get textDocuments() { - return pluginHostDocuments.getDocuments(); + return pluginHostDocuments.getAllDocumentData().map(data => data.document); }, set textDocuments(value) { throw errors.readonly(); }, - // createTextDocument(text: string, fileName?: string, language?: string): Thenable { - // return pluginHostDocuments.createDocument(text, fileName, language); - // }, - openTextDocument(uriOrFileName:vscode.Uri | string) { - return pluginHostDocuments.openDocument(uriOrFileName); + openTextDocument(uriOrFileName: vscode.Uri | string) { + let uri: URI; + if (typeof uriOrFileName === 'string') { + uri = URI.file(uriOrFileName); + } else if (uriOrFileName instanceof URI) { + uri = uriOrFileName; + } else { + throw new Error('illegal argument - uriOrFileName'); + } + return pluginHostDocuments.ensureDocumentData(uri).then(() => { + const data = pluginHostDocuments.getDocumentData(uri); + return data && data.document; + }); }, registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) { return pluginHostDocuments.registerTextDocumentContentProvider(scheme, provider); diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 264eb7c4ffa..d1a42892418 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -30,6 +30,7 @@ import {IEditorInput, IResourceInput} from 'vs/platform/editor/common/editor'; import {IMode} from 'vs/editor/common/modes'; import {IModeService} from 'vs/editor/common/services/modeService'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import * as weak from 'weak'; export interface IModelAddedData { url: URI; @@ -92,38 +93,29 @@ export class ExtHostModelService { this._documentContentProviders = Object.create(null); } - public getDocuments(): vscode.TextDocument[] { - let r: vscode.TextDocument[] = []; + public getAllDocumentData(): ExtHostDocumentData[] { + const result: ExtHostDocumentData[] = []; for (let key in this._documentData) { - r.push(this._documentData[key].document); + result.push(this._documentData[key]); } - return r; + return result; } - public getDocument(resource: vscode.Uri): vscode.TextDocument { + public getDocumentData(resource: vscode.Uri): ExtHostDocumentData { if (!resource) { - return null; + return; } const data = this._documentData[resource.toString()]; if (data) { - return data.document; + return data; } } - public openDocument(uriOrFileName: vscode.Uri | string): TPromise { - - let uri: URI; - if (typeof uriOrFileName === 'string') { - uri = URI.file(uriOrFileName); - } else if (uriOrFileName instanceof URI) { - uri = uriOrFileName; - } else { - throw new Error('illegal argument - uriOrFileName'); - } + public ensureDocumentData(uri: URI): TPromise { let cached = this._documentData[uri.toString()]; if (cached) { - return TPromise.as(cached.document); + return TPromise.as(cached); } let promise = this._documentLoader[uri.toString()]; @@ -138,7 +130,7 @@ export class ExtHostModelService { this._documentLoader[uri.toString()] = promise; } - return promise.then(data => data.document); + return promise; } public registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider): vscode.Disposable { @@ -231,7 +223,7 @@ export class ExtHostDocumentData extends MirrorModel2 { private _languageId: string; private _isDirty: boolean; private _textLines: vscode.TextLine[]; - private _document: vscode.TextDocument; + private _documentRef: weak.WeakRef & vscode.TextDocument; constructor(proxy: MainThreadDocuments, uri: URI, lines: string[], eol: string, languageId: string, versionId: number, isDirty: boolean) { @@ -250,27 +242,36 @@ export class ExtHostDocumentData extends MirrorModel2 { } get document(): vscode.TextDocument { - if (!this._document) { - const document = this; - this._document = { - get uri() { return document._uri }, - get fileName() { return document._uri.fsPath }, - get isUntitled() { return document._uri.scheme !== 'file' }, - get languageId() { return document._languageId }, - get version() { return document._versionId }, - get isDirty() { return document._isDirty }, - save() { return document._proxy._trySaveDocument(document._uri) }, - getText(range?) { return range ? document._getTextInRange(range) : document.getText() }, - get lineCount() { return document._lines.length }, - lineAt(lineOrPos) { return document.lineAt(lineOrPos) }, - offsetAt(pos) { return document.offsetAt(pos) }, - positionAt(offset) { return document.positionAt(offset) }, - validateRange(ran) { return document.validateRange(ran) }, - validatePosition(pos) { return document.validatePosition(pos) }, - getWordRangeAtPosition(pos){ return document.getWordRangeAtPosition(pos)} - } + // dereferences or creates the actual document for this + // document data. keeps a weak reference only such that + // we later when a document isn't needed anymore + + if (!this.isDocumentReferenced) { + const data = this; + const doc = { + get uri() { return data._uri }, + get fileName() { return data._uri.fsPath }, + get isUntitled() { return data._uri.scheme !== 'file' }, + get languageId() { return data._languageId }, + get version() { return data._versionId }, + get isDirty() { return data._isDirty }, + save() { return data._proxy._trySaveDocument(data._uri) }, + getText(range?) { return range ? data._getTextInRange(range) : data.getText() }, + get lineCount() { return data._lines.length }, + lineAt(lineOrPos) { return data.lineAt(lineOrPos) }, + offsetAt(pos) { return data.offsetAt(pos) }, + positionAt(offset) { return data.positionAt(offset) }, + validateRange(ran) { return data.validateRange(ran) }, + validatePosition(pos) { return data.validatePosition(pos) }, + getWordRangeAtPosition(pos) { return data.getWordRangeAtPosition(pos) } + }; + this._documentRef = weak(doc); } - return this._document; + return weak.get(this._documentRef); + } + + get isDocumentReferenced(): boolean { + return this._documentRef && !weak.isDead(this._documentRef); } _acceptLanguageId(newLanguageId:string): void { diff --git a/src/vs/workbench/api/common/extHostEditors.ts b/src/vs/workbench/api/common/extHostEditors.ts index ad802d257ac..e8f9d561c20 100644 --- a/src/vs/workbench/api/common/extHostEditors.ts +++ b/src/vs/workbench/api/common/extHostEditors.ts @@ -9,7 +9,7 @@ import Event, {Emitter} from 'vs/base/common/event'; import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; import {TPromise} from 'vs/base/common/winjs.base'; import {Remotable, IThreadService} from 'vs/platform/thread/common/thread'; -import {ExtHostModelService} from 'vs/workbench/api/common/extHostDocuments'; +import {ExtHostModelService, ExtHostDocumentData} from 'vs/workbench/api/common/extHostDocuments'; import {Selection, Range, Position, EditorOptions} from './extHostTypes'; import {ISingleEditOperation, ISelection, IRange, IInternalIndentationOptions, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IRangeWithMessage} from 'vs/editor/common/editorCommon'; import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; @@ -94,7 +94,7 @@ export class ExtHostEditors { // --- called from main thread _acceptTextEditorAdd(data:ITextEditorAddData): void { - let document = this._modelService.getDocument(data.document); + let document = this._modelService.getDocumentData(data.document); let newEditor = new ExtHostTextEditor(this._proxy, data.id, document, data.selections.map(TypeConverters.toSelection), data.options); this._editors[data.id] = newEditor; } @@ -268,20 +268,20 @@ class ExtHostTextEditor implements vscode.TextEditor { private _proxy: MainThreadEditors; private _id: string; - private _document: vscode.TextDocument; + private _documentData: ExtHostDocumentData; private _selections: Selection[]; private _options: TextEditorOptions; - constructor(proxy: MainThreadEditors, id: string, document: TextDocument, selections: Selection[], options: EditorOptions) { + constructor(proxy: MainThreadEditors, id: string, document: ExtHostDocumentData, selections: Selection[], options: EditorOptions) { this._proxy = proxy; this._id = id; - this._document = document; + this._documentData = document; this._selections = selections; this._options = options; } dispose() { - this._document = null; + this._documentData = null; } @deprecated('TextEditor.show') show(column: vscode.ViewColumn) { @@ -295,7 +295,7 @@ class ExtHostTextEditor implements vscode.TextEditor { // ---- the document get document(): vscode.TextDocument { - return this._document; + return this._documentData.document; } set document(value) { @@ -379,7 +379,7 @@ class ExtHostTextEditor implements vscode.TextEditor { // ---- editing edit(callback:(edit:TextEditorEdit)=>void): Thenable { - let edit = new TextEditorEdit(this._document); + let edit = new TextEditorEdit(this._documentData.document); callback(edit); return this._applyEdit(edit); } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 2e1785849e7..53db64f333f 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -46,7 +46,7 @@ class OutlineAdapter implements IOutlineSupport { } getOutline(resource: URI): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; return asWinJsPromise(token => this._provider.provideDocumentSymbols(doc, token)).then(value => { if (Array.isArray(value)) { return value.map(TypeConverters.SymbolInformation.toOutlineEntry); @@ -76,7 +76,7 @@ class CodeLensAdapter implements modes.ICodeLensSupport { } findCodeLensSymbols(resource: URI): TPromise { - const doc = this._documents.getDocument(resource); + const doc = this._documents.getDocumentData(resource).document; const version = doc.version; const key = resource.toString(); @@ -180,7 +180,7 @@ class DeclarationAdapter implements modes.IDeclarationSupport { } findDeclaration(resource: URI, position: IPosition): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; let pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(value => { if (Array.isArray(value)) { @@ -214,7 +214,7 @@ class ExtraInfoAdapter implements modes.IExtraInfoSupport { computeInfo(resource: URI, position: IPosition): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; let pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideHover(doc, pos, token)).then(value => { @@ -245,7 +245,7 @@ class OccurrencesAdapter implements modes.IOccurrencesSupport { findOccurrences(resource: URI, position: IPosition): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; let pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideDocumentHighlights(doc, pos, token)).then(value => { @@ -278,7 +278,7 @@ class ReferenceAdapter implements modes.IReferenceSupport { } findReferences(resource: URI, position: IPosition, includeDeclaration: boolean): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; let pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideReferences(doc, pos, { includeDeclaration }, token)).then(value => { @@ -312,7 +312,7 @@ class QuickFixAdapter implements modes.IQuickFixSupport { getQuickFixes(resource: URI, range: IRange, markers?: IMarker[]): TPromise { - const doc = this._documents.getDocument(resource); + const doc = this._documents.getDocumentData(resource).document; const ran = TypeConverters.toRange(range); const diagnostics = markers.map(marker => { const diag = new Diagnostic(TypeConverters.toRange(marker), marker.message); @@ -355,7 +355,7 @@ class DocumentFormattingAdapter implements modes.IFormattingSupport { formatDocument(resource: URI, options: modes.IFormattingOptions): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; return asWinJsPromise(token => this._provider.provideDocumentFormattingEdits(doc, options, token)).then(value => { if (Array.isArray(value)) { @@ -377,7 +377,7 @@ class RangeFormattingAdapter implements modes.IFormattingSupport { formatRange(resource: URI, range: IRange, options: modes.IFormattingOptions): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; let ran = TypeConverters.toRange(range); return asWinJsPromise(token => this._provider.provideDocumentRangeFormattingEdits(doc, ran, options, token)).then(value => { @@ -402,7 +402,7 @@ class OnTypeFormattingAdapter implements modes.IFormattingSupport { formatAfterKeystroke(resource: URI, position: IPosition, ch: string, options: modes.IFormattingOptions): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; let pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideOnTypeFormattingEdits(doc, pos, ch, options, token)).then(value => { @@ -442,7 +442,7 @@ class RenameAdapter implements modes.IRenameSupport { rename(resource: URI, position: IPosition, newName: string): TPromise { - let doc = this._documents.getDocument(resource); + let doc = this._documents.getDocumentData(resource).document; let pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideRenameEdits(doc, pos, newName, token)).then(value => { @@ -497,7 +497,7 @@ class SuggestAdapter implements modes.ISuggestSupport { suggest(resource: URI, position: IPosition): TPromise { - const doc = this._documents.getDocument(resource); + const doc = this._documents.getDocumentData(resource).document; const pos = TypeConverters.toPosition(position); const ran = doc.getWordRangeAtPosition(pos); @@ -596,7 +596,7 @@ class ParameterHintsAdapter implements modes.IParameterHintsSupport { getParameterHints(resource: URI, position: IPosition, triggerCharacter?: string): TPromise { - const doc = this._documents.getDocument(resource); + const doc = this._documents.getDocumentData(resource).document; const pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideSignatureHelp(doc, pos, token)).then(value => {