extHostDocuments.ts 22.0 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
import {MirrorModel2} from 'vs/editor/common/model/mirrorModel2';
E
Erich Gamma 已提交
12 13 14
import {Remotable, IThreadService} from 'vs/platform/thread/common/thread';
import Event, {Emitter} from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
J
Joao Moreno 已提交
15
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
16
import {Range, Position, Disposable} from 'vs/workbench/api/node/extHostTypes';
E
Erich Gamma 已提交
17
import {IEventService} from 'vs/platform/event/common/event';
18
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
B
Benjamin Pasero 已提交
19
import {EventType as FileEventType, LocalFileChangeEvent, ITextFileService} from 'vs/workbench/parts/files/common/files';
20
import * as TypeConverters from './extHostTypeConverters';
E
Erich Gamma 已提交
21 22 23 24
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';
25
import {IModeService} from 'vs/editor/common/services/modeService';
26
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
27
import {ResourceEditorInput} from 'vs/workbench/common/editor/resourceEditorInput';
28
import {asWinJsPromise} from 'vs/base/common/async';
E
Erich Gamma 已提交
29 30 31 32 33 34 35 36 37

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

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

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

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

A
Alex Dima 已提交
50
@Remotable.ExtHostContext('ExtHostModelService')
51
export class ExtHostModelService {
E
Erich Gamma 已提交
52

53 54
	private static _handlePool: number = 0;

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

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

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

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

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

	private _proxy: MainThreadDocuments;

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

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

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

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

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

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

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

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

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

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

118 119 120 121 122 123 124 125 126 127
		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;
128 129
		}

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

133
	public registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider): vscode.Disposable {
134
		if (scheme === 'file' || scheme === 'untitled') {
135 136
			throw new Error(`scheme '${scheme}' already registered`);
		}
137 138 139 140 141

		const handle = ExtHostModelService._handlePool++;

		this._documentContentProviders[handle] = provider;
		this._proxy.$registerTextContentProvider(handle, scheme);
142 143 144 145 146

		let subscription: IDisposable;
		if (typeof provider.onDidChange === 'function') {
			subscription = provider.onDidChange(uri => {
				if (this._documentData[uri.toString()]) {
147
					this.$provideTextDocumentContent(handle, <URI>uri).then(value => {
148 149 150 151 152 153
						return this._proxy.$onVirtualDocumentChange(<URI>uri, value);
					}, onUnexpectedError);
				}
			});
		}
		return new Disposable(() => {
154 155 156
			if (delete this._documentContentProviders[handle]) {
				this._proxy.$unregisterTextContentProvider(handle);
			}
157 158
			if (subscription) {
				subscription.dispose();
159
				subscription = undefined;
160 161
			}
		});
162 163
	}

164 165
	$provideTextDocumentContent(handle: number, uri: URI): TPromise<string> {
		const provider = this._documentContentProviders[handle];
166 167 168
		if (!provider) {
			return TPromise.wrapError<string>(`unsupported uri-scheme: ${uri.scheme}`);
		}
169
		return asWinJsPromise(token => provider.provideTextDocumentContent(uri, token));
J
Johannes Rieken 已提交
170 171
	}

J
Johannes Rieken 已提交
172
	public _acceptModelAdd(initData: IModelAddedData): void {
173 174 175
		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 已提交
176 177
			throw new Error('Document `' + key + '` already exists.');
		}
178 179
		this._documentData[key] = data;
		this._onDidAddDocumentEventEmitter.fire(data.document);
E
Erich Gamma 已提交
180 181
	}

182 183
	public _acceptModelModeChanged(strURL: string, oldModeId: string, newModeId: string): void {
		let data = this._documentData[strURL];
E
Erich Gamma 已提交
184 185 186

		// Treat a mode change as a remove + add

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

192 193
	public _acceptModelSaved(strURL: string): void {
		let data = this._documentData[strURL];
194 195
		data._acceptIsDirty(false);
		this._onDidSaveDocumentEventEmitter.fire(data.document);
E
Erich Gamma 已提交
196 197
	}

198 199
	public _acceptModelDirty(strURL: string): void {
		let document = this._documentData[strURL];
200
		document._acceptIsDirty(true);
E
Erich Gamma 已提交
201 202
	}

203 204
	public _acceptModelReverted(strURL: string): void {
		let document = this._documentData[strURL];
205
		document._acceptIsDirty(false);
E
Erich Gamma 已提交
206 207
	}

208 209 210
	public _acceptModelRemoved(strURL: string): void {
		if (!this._documentData[strURL]) {
			throw new Error('Document `' + strURL + '` does not exist.');
E
Erich Gamma 已提交
211
		}
212 213
		let data = this._documentData[strURL];
		delete this._documentData[strURL];
214 215
		this._onDidRemoveDocumentEventEmitter.fire(data.document);
		data.dispose();
E
Erich Gamma 已提交
216 217
	}

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

234
export class ExtHostDocumentData extends MirrorModel2 {
235 236 237 238 239

	private _proxy: MainThreadDocuments;
	private _languageId: string;
	private _isDirty: boolean;
	private _textLines: vscode.TextLine[];
J
Johannes Rieken 已提交
240
	private _document: vscode.TextDocument;
241 242 243 244 245 246

	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 已提交
247 248
		this._languageId = languageId;
		this._isDirty = isDirty;
249
		this._textLines = [];
E
Erich Gamma 已提交
250 251 252 253
	}

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

258
	get document(): vscode.TextDocument {
J
Johannes Rieken 已提交
259
		if (!this._document) {
260
			const data = this;
J
Johannes Rieken 已提交
261
			this._document = {
J
Johannes Rieken 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
				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); }
277
			};
278
		}
J
Johannes Rieken 已提交
279
		return this._document;
280 281
	}

J
Johannes Rieken 已提交
282
	_acceptLanguageId(newLanguageId: string): void {
283 284 285
		this._languageId = newLanguageId;
	}

J
Johannes Rieken 已提交
286
	_acceptIsDirty(isDirty: boolean): void {
287
		this._isDirty = isDirty;
E
Erich Gamma 已提交
288 289
	}

290
	private _getTextInRange(_range: vscode.Range): string {
E
Erich Gamma 已提交
291 292 293 294 295 296 297 298 299 300
		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 已提交
301
		let lineEnding = this._eol,
E
Erich Gamma 已提交
302 303 304 305 306 307 308 309 310 311 312 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
			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;
	}

351
	offsetAt(position: vscode.Position): number {
E
Erich Gamma 已提交
352 353 354 355 356
		position = this.validatePosition(position);
		this._ensureLineStarts();
		return this._lineStarts.getAccumulatedValue(position.line - 1) + position.character;
	}

357
	positionAt(offset: number): vscode.Position {
E
Erich Gamma 已提交
358 359 360 361
		offset = Math.floor(offset);
		offset = Math.max(0, offset);

		this._ensureLineStarts();
A
Alex Dima 已提交
362
		let out = this._lineStarts.getIndexOf(offset);
E
Erich Gamma 已提交
363 364 365 366 367 368 369 370 371

		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 已提交
372
	validateRange(range: vscode.Range): vscode.Range {
E
Erich Gamma 已提交
373 374 375 376 377 378 379 380 381 382
		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;
		}
383
		return new Range(start.line, start.character, end.line, end.character);
E
Erich Gamma 已提交
384 385
	}

J
Johannes Rieken 已提交
386
	validatePosition(position: vscode.Position): vscode.Position {
E
Erich Gamma 已提交
387
		if (!(position instanceof Position)) {
B
Benjamin Pasero 已提交
388
			throw new Error('Invalid argument');
E
Erich Gamma 已提交
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
		}

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

421
	getWordRangeAtPosition(_position: vscode.Position): vscode.Range {
E
Erich Gamma 已提交
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
		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 已提交
439
	private _modelService: IModelService;
440
	private _modeService: IModeService;
E
Erich Gamma 已提交
441
	private _textFileService: ITextFileService;
442
	private _editorService: IWorkbenchEditorService;
E
Erich Gamma 已提交
443 444 445
	private _fileService: IFileService;
	private _untitledEditorService: IUntitledEditorService;
	private _toDispose: IDisposable[];
J
Johannes Rieken 已提交
446
	private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
447
	private _proxy: ExtHostModelService;
J
Johannes Rieken 已提交
448
	private _modelIsSynced: { [modelId: string]: boolean; };
449 450
	private _resourceContentProvider: { [handle: number]: IDisposable };
	private _virtualDocumentSet: { [resource: string]: boolean };
E
Erich Gamma 已提交
451 452 453

	constructor(
		@IThreadService threadService: IThreadService,
454
		@IModelService modelService: IModelService,
455
		@IModeService modeService: IModeService,
J
Johannes Rieken 已提交
456
		@IEventService eventService: IEventService,
E
Erich Gamma 已提交
457
		@ITextFileService textFileService: ITextFileService,
458
		@IWorkbenchEditorService editorService: IWorkbenchEditorService,
E
Erich Gamma 已提交
459 460 461
		@IFileService fileService: IFileService,
		@IUntitledEditorService untitledEditorService: IUntitledEditorService
	) {
J
Johannes Rieken 已提交
462
		this._modelService = modelService;
463
		this._modeService = modeService;
E
Erich Gamma 已提交
464 465 466 467
		this._textFileService = textFileService;
		this._editorService = editorService;
		this._fileService = fileService;
		this._untitledEditorService = untitledEditorService;
468
		this._proxy = threadService.getRemotable(ExtHostModelService);
469
		this._modelIsSynced = {};
E
Erich Gamma 已提交
470 471

		this._toDispose = [];
472 473 474
		modelService.onModelAdded(this._onModelAdded, this, this._toDispose);
		modelService.onModelRemoved(this._onModelRemoved, this, this._toDispose);
		modelService.onModelModeChanged(this._onModelModeChanged, this, this._toDispose);
E
Erich Gamma 已提交
475 476

		this._toDispose.push(eventService.addListener2(FileEventType.FILE_SAVED, (e: LocalFileChangeEvent) => {
477 478 479
			if (this._shouldHandleFileEvent(e)) {
				this._proxy._acceptModelSaved(e.getAfter().resource.toString());
			}
E
Erich Gamma 已提交
480 481
		}));
		this._toDispose.push(eventService.addListener2(FileEventType.FILE_REVERTED, (e: LocalFileChangeEvent) => {
482 483 484
			if (this._shouldHandleFileEvent(e)) {
				this._proxy._acceptModelReverted(e.getAfter().resource.toString());
			}
E
Erich Gamma 已提交
485 486
		}));
		this._toDispose.push(eventService.addListener2(FileEventType.FILE_DIRTY, (e: LocalFileChangeEvent) => {
487 488 489
			if (this._shouldHandleFileEvent(e)) {
				this._proxy._acceptModelDirty(e.getAfter().resource.toString());
			}
E
Erich Gamma 已提交
490 491
		}));

J
Johannes Rieken 已提交
492
		const handle = setInterval(() => this._runDocumentCleanup(), 1000 * 60 * 3);
J
Johannes Rieken 已提交
493
		this._toDispose.push({ dispose() { clearInterval(handle); } });
494

E
Erich Gamma 已提交
495
		this._modelToDisposeMap = Object.create(null);
496
		this._resourceContentProvider = Object.create(null);
497
		this._virtualDocumentSet = Object.create(null);
E
Erich Gamma 已提交
498 499 500 501 502 503 504
	}

	public dispose(): void {
		Object.keys(this._modelToDisposeMap).forEach((modelUrl) => {
			this._modelToDisposeMap[modelUrl].dispose();
		});
		this._modelToDisposeMap = Object.create(null);
J
Joao Moreno 已提交
505
		this._toDispose = dispose(this._toDispose);
E
Erich Gamma 已提交
506 507
	}

508 509 510
	private _shouldHandleFileEvent(e: LocalFileChangeEvent): boolean {
		const after = e.getAfter();
		const model = this._modelService.getModel(after.resource);
J
ops  
Johannes Rieken 已提交
511
		return model && !model.isTooLargeForHavingARichMode();
512 513
	}

E
Erich Gamma 已提交
514
	private _onModelAdded(model: EditorCommon.IModel): void {
515 516 517 518 519
		// Same filter as in mainThreadEditors
		if (model.isTooLargeForHavingARichMode()) {
			// don't synchronize too large models
			return null;
		}
E
Erich Gamma 已提交
520
		let modelUrl = model.getAssociatedResource();
521
		this._modelIsSynced[modelUrl.toString()] = true;
E
Erich Gamma 已提交
522 523 524 525 526 527 528 529 530 531
		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 已提交
532
	private _onModelModeChanged(event: { model: EditorCommon.IModel; oldModeId: string; }): void {
533
		let {model, oldModeId} = event;
534 535 536 537
		let modelUrl = model.getAssociatedResource();
		if (!this._modelIsSynced[modelUrl.toString()]) {
			return;
		}
538
		this._proxy._acceptModelModeChanged(model.getAssociatedResource().toString(), oldModeId, model.getMode().getId());
E
Erich Gamma 已提交
539 540 541 542
	}

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

	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) {
563
			this._proxy._acceptModelChanged(modelUrl.toString(), changedEvents);
E
Erich Gamma 已提交
564 565 566
		}
	}

A
Alex Dima 已提交
567
	// --- from extension host process
E
Erich Gamma 已提交
568

569
	_trySaveDocument(uri: URI): TPromise<boolean> {
E
Erich Gamma 已提交
570 571 572 573 574
		return this._textFileService.save(uri);
	}

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

575 576
		if (!uri.scheme || !(uri.fsPath || uri.authority)) {
			return TPromise.wrapError(`Invalid uri. Scheme and authority or path must be set.`);
E
Erich Gamma 已提交
577 578
		}

J
Johannes Rieken 已提交
579 580 581 582 583
		let promise: TPromise<boolean>;
		switch (uri.scheme) {
			case 'untitled':
				promise = this._handleUnititledScheme(uri);
				break;
584
			case 'file':
J
Johannes Rieken 已提交
585
			default:
586
				promise = this._handleAsResourceInput(uri);
J
Johannes Rieken 已提交
587 588 589 590 591 592 593
				break;
		}

		return promise.then(success => {
			if (!success) {
				return TPromise.wrapError('cannot open ' + uri.toString());
			}
E
Erich Gamma 已提交
594 595 596 597
		}, err => {
			return TPromise.wrapError('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err));
		});
	}
598

599
	private _handleAsResourceInput(uri: URI): TPromise<boolean> {
J
Johannes Rieken 已提交
600 601 602
		return this._editorService.resolveEditorModel({ resource: uri }).then(model => {
			return !!model;
		});
603 604
	}

J
Johannes Rieken 已提交
605 606 607 608 609 610 611 612 613 614 615
	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() }`);
				}
616
				return this._proxy._acceptModelDirty(uri.toString()); // mark as dirty
617
			}).then(() => {
J
Johannes Rieken 已提交
618
				return true;
619 620 621 622
			});
		});
	}

623 624
	// --- virtual document logic

625 626
	$registerTextContentProvider(handle:number, scheme: string): void {
		this._resourceContentProvider[handle] = ResourceEditorInput.registerResourceContentProvider(scheme, {
627
			provideTextContent: (uri: URI): TPromise<EditorCommon.IModel> => {
628 629 630 631 632 633 634
				return this._proxy.$provideTextDocumentContent(handle, uri).then(value => {
					if (value) {
						this._virtualDocumentSet[uri.toString()] = true;
						const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
						const mode = this._modeService.getOrCreateModeByFilenameOrFirstLine(uri.fsPath, firstLineText);
						return this._modelService.createModel(value, mode, uri);
					}
635
				});
636 637 638
			}
		});
	}
639

640 641
	$unregisterTextContentProvider(handle: number): void {
		const registration = this._resourceContentProvider[handle];
642 643
		if (registration) {
			registration.dispose();
644
			delete this._resourceContentProvider[handle];
J
Johannes Rieken 已提交
645
		}
646
	}
647

648 649 650 651 652 653
	$onVirtualDocumentChange(uri: URI, value: string): void {
		const model = this._modelService.getModel(uri);
		if (model) {
			model.setValue(value);
		}
	}
654

655
	private _runDocumentCleanup(): void {
656

657 658 659 660
		const toBeDisposed: URI[] = [];

		TPromise.join(Object.keys(this._virtualDocumentSet).map(key => {
			let resource = URI.parse(key);
J
Johannes Rieken 已提交
661 662 663
			return this._editorService.inputToType({ resource }).then(input => {
				if (!this._editorService.isVisible(input, true)) {
					toBeDisposed.push(resource);
664 665
				}
			});
666 667 668 669 670
		})).then(() => {
			for (let resource of toBeDisposed) {
				this._modelService.destroyModel(resource);
				delete this._virtualDocumentSet[resource.toString()];
			}
671
		}, onUnexpectedError);
672
	}
673
}