extHostDocuments.ts 21.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

7
import {toErrorMessage, onUnexpectedError} from 'vs/base/common/errors';
E
Erich Gamma 已提交
8 9 10
import {IEmitterEvent} from 'vs/base/common/eventEmitter';
import {IModelService} from 'vs/editor/common/services/modelService';
import * as EditorCommon from 'vs/editor/common/editorCommon';
11 12
import {IPrefixSumIndexOfResult} from 'vs/editor/common/viewModel/prefixSumComputer';
import {MirrorModel2} from 'vs/editor/common/model/mirrorModel2';
E
Erich Gamma 已提交
13 14 15 16
import {Remotable, IThreadService} from 'vs/platform/thread/common/thread';
import Event, {Emitter} from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
17
import {Range, Position, Disposable} from 'vs/workbench/api/node/extHostTypes';
E
Erich Gamma 已提交
18
import {IEventService} from 'vs/platform/event/common/event';
19
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
B
Benjamin Pasero 已提交
20
import {EventType as FileEventType, LocalFileChangeEvent, ITextFileService} from 'vs/workbench/parts/files/common/files';
21
import * as TypeConverters from './extHostTypeConverters';
E
Erich Gamma 已提交
22 23 24 25
import {TPromise} from 'vs/base/common/winjs.base';
import * as vscode from 'vscode';
import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers';
import {IFileService} from 'vs/platform/files/common/files';
26
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
27 28
import {asWinJsPromise} from 'vs/base/common/async';
import {IModeService} from 'vs/editor/common/services/modeService';
29
import * as weak from 'weak';
E
Erich Gamma 已提交
30 31 32 33 34 35 36 37 38

export interface IModelAddedData {
	url: URI;
	versionId: number;
	value: EditorCommon.IRawText;
	modeId: string;
	isDirty: boolean;
}

B
Benjamin Pasero 已提交
39
const _modeId2WordDefinition: {
J
Johannes Rieken 已提交
40
	[modeId: string]: RegExp;
E
Erich Gamma 已提交
41 42
} = Object.create(null);

J
Johannes Rieken 已提交
43
export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void {
E
Erich Gamma 已提交
44 45 46
	_modeId2WordDefinition[modeId] = wordDefinition;
}

J
Johannes Rieken 已提交
47
export function getWordDefinitionFor(modeId: string): RegExp {
E
Erich Gamma 已提交
48 49 50
	return _modeId2WordDefinition[modeId];
}

51 52
@Remotable.PluginHostContext('ExtHostModelService')
export class ExtHostModelService {
E
Erich Gamma 已提交
53

54 55
	private _onDidAddDocumentEventEmitter: Emitter<vscode.TextDocument>;
	public onDidAddDocument: Event<vscode.TextDocument>;
E
Erich Gamma 已提交
56

57 58
	private _onDidRemoveDocumentEventEmitter: Emitter<vscode.TextDocument>;
	public onDidRemoveDocument: Event<vscode.TextDocument>;
E
Erich Gamma 已提交
59 60 61 62

	private _onDidChangeDocumentEventEmitter: Emitter<vscode.TextDocumentChangeEvent>;
	public onDidChangeDocument: Event<vscode.TextDocumentChangeEvent>;

63 64
	private _onDidSaveDocumentEventEmitter: Emitter<vscode.TextDocument>;
	public onDidSaveDocument: Event<vscode.TextDocument>;
E
Erich Gamma 已提交
65

66 67
	private _documentData: { [modelUri: string]: ExtHostDocumentData; };
	private _documentLoader: { [modelUri: string]: TPromise<ExtHostDocumentData> };
68
	private _documentContentProviders: { [scheme: string]: vscode.TextDocumentContentProvider };
E
Erich Gamma 已提交
69 70 71 72 73 74

	private _proxy: MainThreadDocuments;

	constructor(@IThreadService threadService: IThreadService) {
		this._proxy = threadService.getRemotable(MainThreadDocuments);

75
		this._onDidAddDocumentEventEmitter = new Emitter<vscode.TextDocument>();
E
Erich Gamma 已提交
76 77
		this.onDidAddDocument = this._onDidAddDocumentEventEmitter.event;

78
		this._onDidRemoveDocumentEventEmitter = new Emitter<vscode.TextDocument>();
E
Erich Gamma 已提交
79 80 81 82 83
		this.onDidRemoveDocument = this._onDidRemoveDocumentEventEmitter.event;

		this._onDidChangeDocumentEventEmitter = new Emitter<vscode.TextDocumentChangeEvent>();
		this.onDidChangeDocument = this._onDidChangeDocumentEventEmitter.event;

84
		this._onDidSaveDocumentEventEmitter = new Emitter<vscode.TextDocument>();
E
Erich Gamma 已提交
85 86
		this.onDidSaveDocument = this._onDidSaveDocumentEventEmitter.event;

87 88
		this._documentData = Object.create(null);
		this._documentLoader = Object.create(null);
89
		this._documentContentProviders = Object.create(null);
E
Erich Gamma 已提交
90 91
	}

92 93
	public getAllDocumentData(): ExtHostDocumentData[] {
		const result: ExtHostDocumentData[] = [];
94
		for (let key in this._documentData) {
95
			result.push(this._documentData[key]);
E
Erich Gamma 已提交
96
		}
97
		return result;
E
Erich Gamma 已提交
98 99
	}

100
	public getDocumentData(resource: vscode.Uri): ExtHostDocumentData {
E
Erich Gamma 已提交
101
		if (!resource) {
102
			return;
E
Erich Gamma 已提交
103
		}
104 105
		const data = this._documentData[resource.toString()];
		if (data) {
106
			return data;
107
		}
E
Erich Gamma 已提交
108 109
	}

110
	public ensureDocumentData(uri: URI): TPromise<ExtHostDocumentData> {
E
Erich Gamma 已提交
111

112
		let cached = this._documentData[uri.toString()];
E
Erich Gamma 已提交
113
		if (cached) {
114
			return TPromise.as(cached);
E
Erich Gamma 已提交
115
		}
116

117 118 119 120 121 122 123 124 125 126
		let promise = this._documentLoader[uri.toString()];
		if (!promise) {
			promise = this._proxy._tryOpenDocument(uri).then(() => {
				delete this._documentLoader[uri.toString()];
				return this._documentData[uri.toString()];
			}, err => {
				delete this._documentLoader[uri.toString()];
				return TPromise.wrapError(err);
			});
			this._documentLoader[uri.toString()] = promise;
127 128
		}

129
		return promise;
E
Erich Gamma 已提交
130 131
	}

132
	public registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider): vscode.Disposable {
133 134 135 136
		if (scheme === 'file' || scheme === 'untitled' || this._documentContentProviders[scheme]) {
			throw new Error(`scheme '${scheme}' already registered`);
		}
		this._documentContentProviders[scheme] = provider;
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

		let subscription: IDisposable;
		if (typeof provider.onDidChange === 'function') {
			subscription = provider.onDidChange(uri => {
				if (this._documentData[uri.toString()]) {
					this.$provideTextDocumentContent(<URI>uri).then(value => {
						return this._proxy.$onVirtualDocumentChange(<URI>uri, value);
					}, onUnexpectedError);
				}
			});
		}
		return new Disposable(() => {
			delete this._documentContentProviders[scheme];
			if (subscription) {
				subscription.dispose();
			}
		});
154 155
	}

156
	$provideTextDocumentContent(uri: URI): TPromise<string> {
157 158 159 160
		const provider = this._documentContentProviders[uri.scheme];
		if (!provider) {
			return TPromise.wrapError<string>(`unsupported uri-scheme: ${uri.scheme}`);
		}
161 162 163 164 165 166
		return asWinJsPromise(token => provider.provideTextDocumentContent(uri, token)).then(value => {
			if (typeof value !== 'string') {
				return TPromise.wrapError('received illegal value from text document provider');
			}
			return value;
		});
J
Johannes Rieken 已提交
167 168
	}

169 170 171 172
	$isDocumentReferenced(uri: URI): TPromise<boolean> {
		return TPromise.as(this.getDocumentData(uri).isDocumentReferenced);
	}

J
Johannes Rieken 已提交
173
	public _acceptModelAdd(initData: IModelAddedData): void {
174 175 176
		let data = new ExtHostDocumentData(this._proxy, initData.url, initData.value.lines, initData.value.EOL, initData.modeId, initData.versionId, initData.isDirty);
		let key = data.document.uri.toString();
		if (this._documentData[key]) {
E
Erich Gamma 已提交
177 178
			throw new Error('Document `' + key + '` already exists.');
		}
179 180
		this._documentData[key] = data;
		this._onDidAddDocumentEventEmitter.fire(data.document);
E
Erich Gamma 已提交
181 182
	}

J
Johannes Rieken 已提交
183
	public _acceptModelModeChanged(url: URI, oldModeId: string, newModeId: string): void {
184
		let data = this._documentData[url.toString()];
E
Erich Gamma 已提交
185 186 187

		// Treat a mode change as a remove + add

188 189 190
		this._onDidRemoveDocumentEventEmitter.fire(data.document);
		data._acceptLanguageId(newModeId);
		this._onDidAddDocumentEventEmitter.fire(data.document);
E
Erich Gamma 已提交
191 192 193
	}

	public _acceptModelSaved(url: URI): void {
194 195 196
		let data = this._documentData[url.toString()];
		data._acceptIsDirty(false);
		this._onDidSaveDocumentEventEmitter.fire(data.document);
E
Erich Gamma 已提交
197 198 199
	}

	public _acceptModelDirty(url: URI): void {
200
		let document = this._documentData[url.toString()];
201
		document._acceptIsDirty(true);
E
Erich Gamma 已提交
202 203 204
	}

	public _acceptModelReverted(url: URI): void {
205
		let document = this._documentData[url.toString()];
206
		document._acceptIsDirty(false);
E
Erich Gamma 已提交
207 208 209 210
	}

	public _acceptModelRemoved(url: URI): void {
		let key = url.toString();
211
		if (!this._documentData[key]) {
E
Erich Gamma 已提交
212 213
			throw new Error('Document `' + key + '` does not exist.');
		}
214 215 216 217
		let data = this._documentData[key];
		delete this._documentData[key];
		this._onDidRemoveDocumentEventEmitter.fire(data.document);
		data.dispose();
E
Erich Gamma 已提交
218 219 220
	}

	public _acceptModelChanged(url: URI, events: EditorCommon.IModelContentChangedEvent2[]): void {
221 222
		let data = this._documentData[url.toString()];
		data.onEvents(events);
E
Erich Gamma 已提交
223
		this._onDidChangeDocumentEventEmitter.fire({
224
			document: data.document,
E
Erich Gamma 已提交
225 226 227 228 229 230 231 232 233 234 235
			contentChanges: events.map((e) => {
				return {
					range: TypeConverters.toRange(e.range),
					rangeLength: e.rangeLength,
					text: e.text
				};
			})
		});
	}
}

236
export class ExtHostDocumentData extends MirrorModel2 {
237 238 239 240 241

	private _proxy: MainThreadDocuments;
	private _languageId: string;
	private _isDirty: boolean;
	private _textLines: vscode.TextLine[];
242
	private _documentRef: weak.WeakRef & vscode.TextDocument;
243 244 245 246 247 248

	constructor(proxy: MainThreadDocuments, uri: URI, lines: string[], eol: string,
		languageId: string, versionId: number, isDirty: boolean) {

		super(uri, lines, eol, versionId);
		this._proxy = proxy;
E
Erich Gamma 已提交
249 250
		this._languageId = languageId;
		this._isDirty = isDirty;
251
		this._textLines = [];
E
Erich Gamma 已提交
252 253 254 255
	}

	dispose(): void {
		this._textLines.length = 0;
256
		this._isDirty = false;
257
		super.dispose();
E
Erich Gamma 已提交
258 259
	}

260
	get document(): vscode.TextDocument {
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
		// 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);
285
		}
286 287 288 289 290
		return weak.get(this._documentRef);
	}

	get isDocumentReferenced(): boolean {
		return this._documentRef && !weak.isDead(this._documentRef);
291 292
	}

J
Johannes Rieken 已提交
293
	_acceptLanguageId(newLanguageId: string): void {
294 295 296
		this._languageId = newLanguageId;
	}

J
Johannes Rieken 已提交
297
	_acceptIsDirty(isDirty: boolean): void {
298
		this._isDirty = isDirty;
E
Erich Gamma 已提交
299 300
	}

301
	private _getTextInRange(_range: vscode.Range): string {
E
Erich Gamma 已提交
302 303 304 305 306 307 308 309 310 311
		let range = this.validateRange(_range);

		if (range.isEmpty) {
			return '';
		}

		if (range.isSingleLine) {
			return this._lines[range.start.line].substring(range.start.character, range.end.character);
		}

B
Benjamin Pasero 已提交
312
		let lineEnding = this._eol,
E
Erich Gamma 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
			startLineIndex = range.start.line,
			endLineIndex = range.end.line,
			resultLines: string[] = [];

		resultLines.push(this._lines[startLineIndex].substring(range.start.character));
		for (var i = startLineIndex + 1; i < endLineIndex; i++) {
			resultLines.push(this._lines[i]);
		}
		resultLines.push(this._lines[endLineIndex].substring(0, range.end.character));

		return resultLines.join(lineEnding);
	}

	lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine {

		let line: number;
		if (lineOrPosition instanceof Position) {
			line = lineOrPosition.line;
		} else if (typeof lineOrPosition === 'number') {
			line = lineOrPosition;
		}

		if (line < 0 || line >= this._lines.length) {
			throw new Error('Illegal value ' + line + ' for `line`');
		}

		let result = this._textLines[line];
		if (!result || result.lineNumber !== line || result.text !== this._lines[line]) {

			const text = this._lines[line];
			const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)[1].length;
			const range = new Range(line, 0, line, text.length);
			const rangeIncludingLineBreak = new Range(line, 0, line + 1, 0);

			result = Object.freeze({
				lineNumber: line,
				range,
				rangeIncludingLineBreak,
				text,
				firstNonWhitespaceCharacterIndex,
				isEmptyOrWhitespace: firstNonWhitespaceCharacterIndex === text.length
			});

			this._textLines[line] = result;
		}

		return result;
	}

362
	offsetAt(position: vscode.Position): number {
E
Erich Gamma 已提交
363 364 365 366 367
		position = this.validatePosition(position);
		this._ensureLineStarts();
		return this._lineStarts.getAccumulatedValue(position.line - 1) + position.character;
	}

368
	positionAt(offset: number): vscode.Position {
E
Erich Gamma 已提交
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
		offset = Math.floor(offset);
		offset = Math.max(0, offset);

		this._ensureLineStarts();
		let out: IPrefixSumIndexOfResult = { index: 0, remainder: 0 };
		this._lineStarts.getIndexOf(offset, out);

		let lineLength = this._lines[out.index].length;

		// Ensure we return a valid position
		return new Position(out.index, Math.min(out.remainder, lineLength));
	}

	// ---- range math

J
Johannes Rieken 已提交
384
	validateRange(range: vscode.Range): vscode.Range {
E
Erich Gamma 已提交
385 386 387 388 389 390 391 392 393 394
		if (!(range instanceof Range)) {
			throw new Error('Invalid argument');
		}

		let start = this.validatePosition(range.start);
		let end = this.validatePosition(range.end);

		if (start === range.start && end === range.end) {
			return range;
		}
395
		return new Range(start.line, start.character, end.line, end.character);
E
Erich Gamma 已提交
396 397
	}

J
Johannes Rieken 已提交
398
	validatePosition(position: vscode.Position): vscode.Position {
E
Erich Gamma 已提交
399
		if (!(position instanceof Position)) {
B
Benjamin Pasero 已提交
400
			throw new Error('Invalid argument');
E
Erich Gamma 已提交
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
		}

		let {line, character} = position;
		let hasChanged = false;

		if (line < 0) {
			line = 0;
			hasChanged = true;
		}

		if (line >= this._lines.length) {
			line = this._lines.length - 1;
			hasChanged = true;
		}

		if (character < 0) {
			character = 0;
			hasChanged = true;
		}

		let maxCharacter = this._lines[line].length;
		if (character > maxCharacter) {
			character = maxCharacter;
			hasChanged = true;
		}

		if (!hasChanged) {
			return position;
		}
		return new Position(line, character);
	}

433
	getWordRangeAtPosition(_position: vscode.Position): vscode.Range {
E
Erich Gamma 已提交
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
		let position = this.validatePosition(_position);

		let wordAtText = WordHelper._getWordAtText(
			position.character + 1,
			WordHelper.ensureValidWordDefinition(getWordDefinitionFor(this._languageId)),
			this._lines[position.line],
			0
		);

		if (wordAtText) {
			return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1);
		}
	}
}

@Remotable.MainContext('MainThreadDocuments')
export class MainThreadDocuments {
J
Johannes Rieken 已提交
451 452
	private _modelService: IModelService;
	private _modeService: IModeService;
E
Erich Gamma 已提交
453
	private _textFileService: ITextFileService;
454
	private _editorService: IWorkbenchEditorService;
E
Erich Gamma 已提交
455 456 457
	private _fileService: IFileService;
	private _untitledEditorService: IUntitledEditorService;
	private _toDispose: IDisposable[];
J
Johannes Rieken 已提交
458
	private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
459
	private _proxy: ExtHostModelService;
J
Johannes Rieken 已提交
460
	private _modelIsSynced: { [modelId: string]: boolean; };
E
Erich Gamma 已提交
461 462 463

	constructor(
		@IThreadService threadService: IThreadService,
464 465
		@IModelService modelService: IModelService,
		@IModeService modeService: IModeService,
J
Johannes Rieken 已提交
466
		@IEventService eventService: IEventService,
E
Erich Gamma 已提交
467
		@ITextFileService textFileService: ITextFileService,
468
		@IWorkbenchEditorService editorService: IWorkbenchEditorService,
E
Erich Gamma 已提交
469 470 471
		@IFileService fileService: IFileService,
		@IUntitledEditorService untitledEditorService: IUntitledEditorService
	) {
J
Johannes Rieken 已提交
472 473
		this._modelService = modelService;
		this._modeService = modeService;
E
Erich Gamma 已提交
474 475 476 477
		this._textFileService = textFileService;
		this._editorService = editorService;
		this._fileService = fileService;
		this._untitledEditorService = untitledEditorService;
478
		this._proxy = threadService.getRemotable(ExtHostModelService);
479
		this._modelIsSynced = {};
E
Erich Gamma 已提交
480 481

		this._toDispose = [];
482 483 484
		modelService.onModelAdded(this._onModelAdded, this, this._toDispose);
		modelService.onModelRemoved(this._onModelRemoved, this, this._toDispose);
		modelService.onModelModeChanged(this._onModelModeChanged, this, this._toDispose);
E
Erich Gamma 已提交
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507

		this._toDispose.push(eventService.addListener2(FileEventType.FILE_SAVED, (e: LocalFileChangeEvent) => {
			this._proxy._acceptModelSaved(e.getAfter().resource);
		}));
		this._toDispose.push(eventService.addListener2(FileEventType.FILE_REVERTED, (e: LocalFileChangeEvent) => {
			this._proxy._acceptModelReverted(e.getAfter().resource);
		}));
		this._toDispose.push(eventService.addListener2(FileEventType.FILE_DIRTY, (e: LocalFileChangeEvent) => {
			this._proxy._acceptModelDirty(e.getAfter().resource);
		}));

		this._modelToDisposeMap = Object.create(null);
	}

	public dispose(): void {
		Object.keys(this._modelToDisposeMap).forEach((modelUrl) => {
			this._modelToDisposeMap[modelUrl].dispose();
		});
		this._modelToDisposeMap = Object.create(null);
		this._toDispose = disposeAll(this._toDispose);
	}

	private _onModelAdded(model: EditorCommon.IModel): void {
508 509 510 511 512
		// Same filter as in mainThreadEditors
		if (model.isTooLargeForHavingARichMode()) {
			// don't synchronize too large models
			return null;
		}
E
Erich Gamma 已提交
513
		let modelUrl = model.getAssociatedResource();
514
		this._modelIsSynced[modelUrl.toString()] = true;
E
Erich Gamma 已提交
515 516 517 518 519 520 521 522 523 524
		this._modelToDisposeMap[modelUrl.toString()] = model.addBulkListener2((events) => this._onModelEvents(modelUrl, events));
		this._proxy._acceptModelAdd({
			url: model.getAssociatedResource(),
			versionId: model.getVersionId(),
			value: model.toRawText(),
			modeId: model.getMode().getId(),
			isDirty: this._textFileService.isDirty(modelUrl)
		});
	}

J
Johannes Rieken 已提交
525
	private _onModelModeChanged(event: { model: EditorCommon.IModel; oldModeId: string; }): void {
526
		let {model, oldModeId} = event;
527 528 529 530
		let modelUrl = model.getAssociatedResource();
		if (!this._modelIsSynced[modelUrl.toString()]) {
			return;
		}
E
Erich Gamma 已提交
531 532 533 534 535
		this._proxy._acceptModelModeChanged(model.getAssociatedResource(), oldModeId, model.getMode().getId());
	}

	private _onModelRemoved(model: EditorCommon.IModel): void {
		let modelUrl = model.getAssociatedResource();
536 537 538 539
		if (!this._modelIsSynced[modelUrl.toString()]) {
			return;
		}
		delete this._modelIsSynced[modelUrl.toString()];
E
Erich Gamma 已提交
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
		this._modelToDisposeMap[modelUrl.toString()].dispose();
		delete this._modelToDisposeMap[modelUrl.toString()];
		this._proxy._acceptModelRemoved(modelUrl);
	}

	private _onModelEvents(modelUrl: URI, events: IEmitterEvent[]): void {
		let changedEvents: EditorCommon.IModelContentChangedEvent2[] = [];
		for (let i = 0, len = events.length; i < len; i++) {
			let e = events[i];
			switch (e.getType()) {
				case EditorCommon.EventType.ModelContentChanged2:
					changedEvents.push(<EditorCommon.IModelContentChangedEvent2>e.getData());
					break;
			}
		}
		if (changedEvents.length > 0) {
			this._proxy._acceptModelChanged(modelUrl, changedEvents);
		}
	}

	// --- from plugin host process

562
	_trySaveDocument(uri: URI): TPromise<boolean> {
E
Erich Gamma 已提交
563 564 565 566 567 568 569 570 571
		return this._textFileService.save(uri);
	}

	_tryOpenDocument(uri: URI): TPromise<any> {

		if (!uri.scheme || !uri.fsPath) {
			return TPromise.wrapError('Uri must have scheme and path. One or both are missing in: ' + uri.toString());
		}

J
Johannes Rieken 已提交
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
		let promise: TPromise<boolean>;
		switch (uri.scheme) {
			case 'file':
				promise = this._handleFileScheme(uri);
				break;
			case 'untitled':
				promise = this._handleUnititledScheme(uri);
				break;
			default:
				promise = this._handleAnyScheme(uri);
				break;
		}

		return promise.then(success => {
			if (!success) {
				return TPromise.wrapError('cannot open ' + uri.toString());
			}
E
Erich Gamma 已提交
589 590 591 592
		}, err => {
			return TPromise.wrapError('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err));
		});
	}
593

J
Johannes Rieken 已提交
594 595 596 597
	private _handleFileScheme(uri: URI): TPromise<boolean> {
		return this._editorService.resolveEditorModel({ resource: uri }).then(model => {
			return !!model;
		});
598 599
	}

J
Johannes Rieken 已提交
600 601 602 603 604 605 606 607 608 609 610 611
	private _handleUnititledScheme(uri: URI): TPromise<boolean> {
		let asFileUri = URI.file(uri.fsPath);
		return this._fileService.resolveFile(asFileUri).then(stats => {
			// don't create a new file ontop of an existing file
			return TPromise.wrapError<boolean>('file already exists on disk');
		}, err => {
			let input = this._untitledEditorService.createOrGet(asFileUri); // using file-uri makes it show in 'Working Files' section
			return input.resolve(true).then(model => {
				if (input.getResource().toString() !== uri.toString()) {
					throw new Error(`expected URI ${uri.toString() } BUT GOT ${input.getResource().toString() }`);
				}
				return this._proxy._acceptModelDirty(uri); // mark as dirty
612
			}).then(() => {
J
Johannes Rieken 已提交
613
				return true;
614 615 616 617
			});
		});
	}

618 619
	// --- virtual document logic

J
Johannes Rieken 已提交
620
	private _handleAnyScheme(uri: URI): TPromise<boolean> {
621

J
Johannes Rieken 已提交
622 623 624
		if (this._modelService.getModel(uri)) {
			return TPromise.as(true);
		}
625

626
		return this._proxy.$provideTextDocumentContent(uri).then(value => {
627 628

			// create document from string
J
Johannes Rieken 已提交
629 630
			const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
			const mode = this._modeService.getOrCreateModeByFilenameOrFirstLine(uri.fsPath, firstLineText);
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
			const model = this._modelService.createModel(value, mode, uri);

			// if neither the extension host nor an editor reference this
			// document anymore we destroy the model to reclaim memory
			const handle = setInterval(() => {
				this._editorService.inputToType({ resource: uri }).then(input => {
					if (!this._editorService.isVisible(input, true)) {
						return this._proxy.$isDocumentReferenced(uri).then(referenced => {
							if (!referenced) {
								clearInterval(handle);
								this._modelService.destroyModel(uri);
							}
						});
					}
				}, onUnexpectedError);
			}, 30 * 1000);

			return model;
649

J
Johannes Rieken 已提交
650
		}).then(() => {
651
			return true;
J
Johannes Rieken 已提交
652
		});
653
	}
654 655 656 657 658 659 660 661

	$onVirtualDocumentChange(uri: URI, value: string): TPromise<any> {
		const model = this._modelService.getModel(uri);
		if (model) {
			model.setValue(value);
			return;
		}
	}
J
Johannes Rieken 已提交
662
}