Skip to content

Commit

Permalink
allow extensions to work with closed documents, #15723
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed Mar 22, 2017
1 parent 3755bb8 commit d8904bc
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 42 deletions.
64 changes: 39 additions & 25 deletions src/vs/workbench/api/node/extHostDocumentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<string, RegExp>();
export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
Expand All @@ -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 {
Expand All @@ -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<boolean> {
if (this._isDisposed) {
return TPromise.wrapError<boolean>('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 '';
Expand All @@ -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) {
Expand Down Expand Up @@ -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);

Expand All @@ -174,21 +188,21 @@ 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;
}
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');
}
Expand Down Expand Up @@ -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);
}
Expand Down
57 changes: 41 additions & 16 deletions src/vs/workbench/test/node/api/extHostDocumentData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ 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', () => {

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);
}

Expand All @@ -47,17 +49,40 @@ suite('ExtHostDocumentData', () => {
assert.throws(() => (<any>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');
Expand All @@ -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);
});
Expand Down Expand Up @@ -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);
});
});
Expand All @@ -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));
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/test/node/api/mainThreadDocuments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {

Expand Down

0 comments on commit d8904bc

Please sign in to comment.