diff --git a/src/vs/workbench/api/node/extHostDocumentData.ts b/src/vs/workbench/api/node/extHostDocumentData.ts index 29735c3ac8f85..ee8d0bc2d8d01 100644 --- a/src/vs/workbench/api/node/extHostDocumentData.ts +++ b/src/vs/workbench/api/node/extHostDocumentData.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import { ok } from 'vs/base/common/assert'; import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; import { MirrorModel2 } from 'vs/editor/common/model/mirrorModel2'; import URI from 'vs/base/common/uri'; @@ -12,6 +13,7 @@ import * as vscode from 'vscode'; import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { MainThreadDocumentsShape } from './extHost.protocol'; import { ITextSource } from 'vs/editor/common/model/textSource'; +import { TPromise } from 'vs/base/common/winjs.base'; const _modeId2WordDefinition = new Map(); export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { @@ -26,23 +28,26 @@ export class ExtHostDocumentData extends MirrorModel2 { private _proxy: MainThreadDocumentsShape; private _languageId: string; private _isDirty: boolean; - private _textLines: vscode.TextLine[]; private _document: vscode.TextDocument; + private _textLines: vscode.TextLine[] = []; + private _isDisposed: boolean = false; constructor(proxy: MainThreadDocumentsShape, uri: URI, lines: string[], eol: string, - languageId: string, versionId: number, isDirty: boolean) { - + languageId: string, versionId: number, isDirty: boolean + ) { super(uri, lines, eol, versionId); this._proxy = proxy; this._languageId = languageId; this._isDirty = isDirty; - this._textLines = []; } dispose(): void { - this._textLines.length = 0; + // we don't really dispose documents but let + // extensions still read from them. some + // operations, live saving, will now error tho + ok(!this._isDisposed); + this._isDisposed = true; this._isDirty = false; - super.dispose(); } equalLines({ lines }: ITextSource): boolean { @@ -68,30 +73,39 @@ export class ExtHostDocumentData extends MirrorModel2 { get languageId() { return data._languageId; }, get version() { return data._versionId; }, get isDirty() { return data._isDirty; }, - save() { return data._proxy.$trySaveDocument(data._uri); }, + save() { return data._save(); }, 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, regexp?) { return data.getWordRangeAtPosition(pos, regexp); } + 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, regexp?) { return data._getWordRangeAtPosition(pos, regexp); } }; } - return this._document; + return Object.freeze(this._document); } _acceptLanguageId(newLanguageId: string): void { + ok(!this._isDisposed); this._languageId = newLanguageId; } _acceptIsDirty(isDirty: boolean): void { + ok(!this._isDisposed); this._isDirty = isDirty; } + private _save(): TPromise { + if (this._isDisposed) { + return TPromise.wrapError('Document has been closed'); + } + return this._proxy.$trySaveDocument(this._uri); + } + private _getTextInRange(_range: vscode.Range): string { - let range = this.validateRange(_range); + let range = this._validateRange(_range); if (range.isEmpty) { return ''; @@ -115,7 +129,7 @@ export class ExtHostDocumentData extends MirrorModel2 { return resultLines.join(lineEnding); } - lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine { + private _lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine { let line: number; if (lineOrPosition instanceof Position) { @@ -153,13 +167,13 @@ export class ExtHostDocumentData extends MirrorModel2 { return result; } - offsetAt(position: vscode.Position): number { - position = this.validatePosition(position); + private _offsetAt(position: vscode.Position): number { + position = this._validatePosition(position); this._ensureLineStarts(); return this._lineStarts.getAccumulatedValue(position.line - 1) + position.character; } - positionAt(offset: number): vscode.Position { + private _positionAt(offset: number): vscode.Position { offset = Math.floor(offset); offset = Math.max(0, offset); @@ -174,13 +188,13 @@ export class ExtHostDocumentData extends MirrorModel2 { // ---- range math - validateRange(range: vscode.Range): vscode.Range { + private _validateRange(range: vscode.Range): vscode.Range { if (!(range instanceof Range)) { throw new Error('Invalid argument'); } - let start = this.validatePosition(range.start); - let end = this.validatePosition(range.end); + let start = this._validatePosition(range.start); + let end = this._validatePosition(range.end); if (start === range.start && end === range.end) { return range; @@ -188,7 +202,7 @@ export class ExtHostDocumentData extends MirrorModel2 { return new Range(start.line, start.character, end.line, end.character); } - validatePosition(position: vscode.Position): vscode.Position { + private _validatePosition(position: vscode.Position): vscode.Position { if (!(position instanceof Position)) { throw new Error('Invalid argument'); } @@ -224,8 +238,8 @@ export class ExtHostDocumentData extends MirrorModel2 { return new Position(line, character); } - getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range { - let position = this.validatePosition(_position); + private _getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range { + let position = this._validatePosition(_position); if (!regexp || regExpLeadsToEndlessLoop(regexp)) { regexp = getWordDefinitionFor(this._languageId); } diff --git a/src/vs/workbench/test/node/api/extHostDocumentData.test.ts b/src/vs/workbench/test/node/api/extHostDocumentData.test.ts index 4ddad3fdace5a..6a011190def5d 100644 --- a/src/vs/workbench/test/node/api/extHostDocumentData.test.ts +++ b/src/vs/workbench/test/node/api/extHostDocumentData.test.ts @@ -11,6 +11,8 @@ import { ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocumentData'; import { Position } from 'vs/workbench/api/node/extHostTypes'; import { Range as CodeEditorRange } from 'vs/editor/common/core/range'; import * as EditorCommon from 'vs/editor/common/editorCommon'; +import { MainThreadDocumentsShape } from 'vs/workbench/api/node/extHost.protocol'; +import { TPromise } from 'vs/base/common/winjs.base'; suite('ExtHostDocumentData', () => { @@ -18,14 +20,14 @@ suite('ExtHostDocumentData', () => { let data: ExtHostDocumentData; function assertPositionAt(offset: number, line: number, character: number) { - let position = data.positionAt(offset); + let position = data.document.positionAt(offset); assert.equal(position.line, line); assert.equal(position.character, character); } function assertOffsetAt(line: number, character: number, offset: number) { let pos = new Position(line, character); - let actual = data.offsetAt(pos); + let actual = data.document.offsetAt(pos); assert.equal(actual, offset); } @@ -47,17 +49,40 @@ suite('ExtHostDocumentData', () => { assert.throws(() => (data).document.lineCount = 9); }); + test('save', function () { + let saved: URI; + let data = new ExtHostDocumentData(new class extends MainThreadDocumentsShape { + $trySaveDocument(uri) { + assert.ok(!saved); + saved = uri; + return TPromise.as(true); + } + }, URI.parse('foo:bar'), [], '\n', 'text', 1, true); + + return data.document.save().then(() => { + assert.equal(saved.toString(), 'foo:bar'); + + data.dispose(); + + return data.document.save().then(() => { + assert.ok(false, 'expected failure'); + }, err => { + assert.ok(err); + }); + }); + }); + test('lines', function () { assert.equal(data.document.lineCount, 4); - assert.throws(() => data.lineAt(-1)); - assert.throws(() => data.lineAt(data.document.lineCount)); - assert.throws(() => data.lineAt(Number.MAX_VALUE)); - assert.throws(() => data.lineAt(Number.MIN_VALUE)); - assert.throws(() => data.lineAt(0.8)); + assert.throws(() => data.document.lineAt(-1)); + assert.throws(() => data.document.lineAt(data.document.lineCount)); + assert.throws(() => data.document.lineAt(Number.MAX_VALUE)); + assert.throws(() => data.document.lineAt(Number.MIN_VALUE)); + assert.throws(() => data.document.lineAt(0.8)); - let line = data.lineAt(0); + let line = data.document.lineAt(0); assert.equal(line.lineNumber, 0); assert.equal(line.text.length, 16); assert.equal(line.text, 'This is line one'); @@ -79,7 +104,7 @@ suite('ExtHostDocumentData', () => { assert.equal(line.firstNonWhitespaceCharacterIndex, 0); // fetch line again - line = data.lineAt(0); + line = data.document.lineAt(0); assert.equal(line.text, '\t This is line one'); assert.equal(line.firstNonWhitespaceCharacterIndex, 2); }); @@ -208,32 +233,32 @@ suite('ExtHostDocumentData', () => { 'aaaa bbbb+cccc abc' ], '\n', 'text', 1, false); - let range = data.getWordRangeAtPosition(new Position(0, 2)); + let range = data.document.getWordRangeAtPosition(new Position(0, 2)); assert.equal(range.start.line, 0); assert.equal(range.start.character, 0); assert.equal(range.end.line, 0); assert.equal(range.end.character, 4); // ignore bad regular expresson /.*/ - range = data.getWordRangeAtPosition(new Position(0, 2), /.*/); + range = data.document.getWordRangeAtPosition(new Position(0, 2), /.*/); assert.equal(range.start.line, 0); assert.equal(range.start.character, 0); assert.equal(range.end.line, 0); assert.equal(range.end.character, 4); - range = data.getWordRangeAtPosition(new Position(0, 5), /[a-z+]+/); + range = data.document.getWordRangeAtPosition(new Position(0, 5), /[a-z+]+/); assert.equal(range.start.line, 0); assert.equal(range.start.character, 5); assert.equal(range.end.line, 0); assert.equal(range.end.character, 14); - range = data.getWordRangeAtPosition(new Position(0, 17), /[a-z+]+/); + range = data.document.getWordRangeAtPosition(new Position(0, 17), /[a-z+]+/); assert.equal(range.start.line, 0); assert.equal(range.start.character, 15); assert.equal(range.end.line, 0); assert.equal(range.end.character, 18); - range = data.getWordRangeAtPosition(new Position(0, 11), /yy/); + range = data.document.getWordRangeAtPosition(new Position(0, 11), /yy/); assert.equal(range, undefined); }); }); @@ -258,12 +283,12 @@ suite('ExtHostDocumentData updates line mapping', () => { let position = new Position(line, character + (previousIsCarriageReturn ? -1 : 0)); if (direction === AssertDocumentLineMappingDirection.OffsetToPosition) { - let actualPosition = doc.positionAt(offset); + let actualPosition = doc.document.positionAt(offset); assert.equal(positionToStr(actualPosition), positionToStr(position), 'positionAt mismatch for offset ' + offset); } else { // The position coordinate system cannot express the position between \r and \n let expectedOffset = offset + (previousIsCarriageReturn ? -1 : 0); - let actualOffset = doc.offsetAt(position); + let actualOffset = doc.document.offsetAt(position); assert.equal(actualOffset, expectedOffset, 'offsetAt mismatch for position ' + positionToStr(position)); } diff --git a/src/vs/workbench/test/node/api/mainThreadDocuments.test.ts b/src/vs/workbench/test/node/api/mainThreadDocuments.test.ts index a7c3a5f8933c3..334b12318bb92 100644 --- a/src/vs/workbench/test/node/api/mainThreadDocuments.test.ts +++ b/src/vs/workbench/test/node/api/mainThreadDocuments.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import { BoundModelReferenceCollection } from 'vs/workbench/api/node/mainThreadDocuments'; import { Model } from 'vs/editor/common/model/model'; -import { TPromise } from "vs/base/common/winjs.base"; +import { TPromise } from 'vs/base/common/winjs.base'; suite('BoundModelReferenceCollection', () => {