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

A
Alex Dima 已提交
6 7 8 9
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as network from 'vs/base/common/network';
import * as platform from 'vs/base/common/platform';
10
import { URI } from 'vs/base/common/uri';
A
Alex Dima 已提交
11 12
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { EditOperation } from 'vs/editor/common/core/editOperation';
J
Johannes Rieken 已提交
13
import { Range } from 'vs/editor/common/core/range';
14
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
15
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
A
Alex Dima 已提交
16
import { IModelLanguageChangedEvent } from 'vs/editor/common/model/textModelEvents';
A
Alex Dima 已提交
17
import { LanguageIdentifier } from 'vs/editor/common/modes';
A
Alex Dima 已提交
18
import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry';
A
Alex Dima 已提交
19
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
A
Alex Dima 已提交
20
import { IModelService } from 'vs/editor/common/services/modelService';
S
Sandeep Somavarapu 已提交
21
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
A
Alex Dima 已提交
22
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
23
import { IMarkerService } from 'vs/platform/markers/common/markers';
E
Erich Gamma 已提交
24

B
Benjamin Pasero 已提交
25
function MODEL_ID(resource: URI): string {
A
Alex Dima 已提交
26 27 28 29
	return resource.toString();
}

class ModelData implements IDisposable {
A
Alex Dima 已提交
30 31 32 33
	public readonly model: ITextModel;

	private _languageSelection: ILanguageSelection | null;
	private _languageSelectionListener: IDisposable | null;
E
Erich Gamma 已提交
34

35
	private _modelEventListeners: IDisposable[];
E
Erich Gamma 已提交
36

37
	constructor(
A
Alex Dima 已提交
38 39 40
		model: ITextModel,
		onWillDispose: (model: ITextModel) => void,
		onDidChangeLanguage: (model: ITextModel, e: IModelLanguageChangedEvent) => void
41
	) {
E
Erich Gamma 已提交
42
		this.model = model;
A
Alex Dima 已提交
43

A
Alex Dima 已提交
44 45 46
		this._languageSelection = null;
		this._languageSelectionListener = null;

47 48 49
		this._modelEventListeners = [];
		this._modelEventListeners.push(model.onWillDispose(() => onWillDispose(model)));
		this._modelEventListeners.push(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e)));
E
Erich Gamma 已提交
50 51
	}

A
Alex Dima 已提交
52 53 54 55 56 57 58 59 60 61 62
	private _disposeLanguageSelection(): void {
		if (this._languageSelectionListener) {
			this._languageSelectionListener.dispose();
			this._languageSelectionListener = null;
		}
		if (this._languageSelection) {
			this._languageSelection.dispose();
			this._languageSelection = null;
		}
	}

E
Erich Gamma 已提交
63
	public dispose(): void {
64
		this._modelEventListeners = dispose(this._modelEventListeners);
A
Alex Dima 已提交
65
		this._disposeLanguageSelection();
A
Alex Dima 已提交
66
	}
E
Erich Gamma 已提交
67

A
Alex Dima 已提交
68 69 70 71 72 73
	public setLanguage(languageSelection: ILanguageSelection): void {
		this._disposeLanguageSelection();
		this._languageSelection = languageSelection;
		this._languageSelectionListener = this._languageSelection.onDidChange(() => this.model.setMode(languageSelection.languageIdentifier));
		this.model.setMode(languageSelection.languageIdentifier);
	}
A
Alex Dima 已提交
74
}
E
Erich Gamma 已提交
75

S
Sandeep Somavarapu 已提交
76 77 78 79 80 81 82 83 84
interface IRawEditorConfig {
	tabSize?: any;
	insertSpaces?: any;
	detectIndentation?: any;
	trimAutoWhitespace?: any;
	creationOptions?: any;
	largeFileOptimizations?: any;
}

85
interface IRawConfig {
S
Sandeep Somavarapu 已提交
86 87
	eol?: any;
	editor?: IRawEditorConfig;
88 89
}

90
const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? DefaultEndOfLine.LF : DefaultEndOfLine.CRLF;
91

A
Alex Dima 已提交
92
export class ModelServiceImpl extends Disposable implements IModelService {
93
	public _serviceBrand: any;
E
Erich Gamma 已提交
94

A
Alex Dima 已提交
95
	private _markerService: IMarkerService | null;
E
Erich Gamma 已提交
96
	private _markerServiceSubscription: IDisposable;
97 98
	private _configurationService: IConfigurationService;
	private _configurationServiceSubscription: IDisposable;
S
Sandeep Somavarapu 已提交
99
	private _resourcePropertiesService: ITextResourcePropertiesService;
E
Erich Gamma 已提交
100

A
Alex Dima 已提交
101 102 103 104 105 106
	private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
	public readonly onModelAdded: Event<ITextModel> = this._onModelAdded.event;

	private readonly _onModelRemoved: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
	public readonly onModelRemoved: Event<ITextModel> = this._onModelRemoved.event;

107
	private readonly _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }> = this._register(new Emitter<{ model: ITextModel; oldModeId: string; }>());
A
Alex Dima 已提交
108
	public readonly onModelModeChanged: Event<{ model: ITextModel; oldModeId: string; }> = this._onModelModeChanged.event;
109

110
	private _modelCreationOptionsByLanguageAndResource: {
111
		[languageAndResource: string]: ITextModelCreationOptions;
112
	};
A
Alex Dima 已提交
113 114 115 116

	/**
	 * All the models known in the system.
	 */
B
Benjamin Pasero 已提交
117
	private _models: { [modelId: string]: ModelData; };
E
Erich Gamma 已提交
118

119
	constructor(
A
Alex Dima 已提交
120
		@IMarkerService markerService: IMarkerService | null,
121
		@IConfigurationService configurationService: IConfigurationService,
122
		@ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService
123
	) {
A
Alex Dima 已提交
124
		super();
E
Erich Gamma 已提交
125
		this._markerService = markerService;
126
		this._configurationService = configurationService;
S
Sandeep Somavarapu 已提交
127
		this._resourcePropertiesService = resourcePropertiesService;
B
Benjamin Pasero 已提交
128
		this._models = {};
129
		this._modelCreationOptionsByLanguageAndResource = Object.create(null);
B
Benjamin Pasero 已提交
130

131
		this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions());
132 133
		this._updateModelOptions();
	}
J
Joao Moreno 已提交
134

135
	private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
136
		let tabSize = EDITOR_MODEL_DEFAULTS.tabSize;
137 138 139 140
		if (config.editor && typeof config.editor.tabSize !== 'undefined') {
			let parsedTabSize = parseInt(config.editor.tabSize, 10);
			if (!isNaN(parsedTabSize)) {
				tabSize = parsedTabSize;
141
			}
A
Alex Dima 已提交
142 143 144
			if (tabSize < 1) {
				tabSize = 1;
			}
145
		}
146

147
		let insertSpaces = EDITOR_MODEL_DEFAULTS.insertSpaces;
148 149 150
		if (config.editor && typeof config.editor.insertSpaces !== 'undefined') {
			insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces));
		}
151

152
		let newDefaultEOL = DEFAULT_EOL;
S
Sandeep Somavarapu 已提交
153
		const eol = config.eol;
154
		if (eol === '\r\n') {
155
			newDefaultEOL = DefaultEndOfLine.CRLF;
156
		} else if (eol === '\n') {
157
			newDefaultEOL = DefaultEndOfLine.LF;
158
		}
159

160
		let trimAutoWhitespace = EDITOR_MODEL_DEFAULTS.trimAutoWhitespace;
161 162 163
		if (config.editor && typeof config.editor.trimAutoWhitespace !== 'undefined') {
			trimAutoWhitespace = (config.editor.trimAutoWhitespace === 'false' ? false : Boolean(config.editor.trimAutoWhitespace));
		}
164

165
		let detectIndentation = EDITOR_MODEL_DEFAULTS.detectIndentation;
166 167 168
		if (config.editor && typeof config.editor.detectIndentation !== 'undefined') {
			detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation));
		}
169

170 171 172
		let largeFileOptimizations = EDITOR_MODEL_DEFAULTS.largeFileOptimizations;
		if (config.editor && typeof config.editor.largeFileOptimizations !== 'undefined') {
			largeFileOptimizations = (config.editor.largeFileOptimizations === 'false' ? false : Boolean(config.editor.largeFileOptimizations));
173 174
		}

175
		return {
176
			isForSimpleWidget: isForSimpleWidget,
177 178 179 180
			tabSize: tabSize,
			insertSpaces: insertSpaces,
			detectIndentation: detectIndentation,
			defaultEOL: newDefaultEOL,
181
			trimAutoWhitespace: trimAutoWhitespace,
182
			largeFileOptimizations: largeFileOptimizations
183
		};
E
Erich Gamma 已提交
184 185
	}

A
Alex Dima 已提交
186
	public getCreationOptions(language: string, resource: URI | null | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions {
187
		let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
188
		if (!creationOptions) {
S
Sandeep Somavarapu 已提交
189
			const editor = this._configurationService.getValue<IRawEditorConfig>('editor', { overrideIdentifier: language, resource });
S
Sandeep Somavarapu 已提交
190
			const eol = this._resourcePropertiesService.getEOL(resource, language);
S
Sandeep Somavarapu 已提交
191
			creationOptions = ModelServiceImpl._readModelOptions({ editor, eol }, isForSimpleWidget);
192
			this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions;
193 194
		}
		return creationOptions;
195 196
	}

197
	private _updateModelOptions(): void {
198 199
		let oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource;
		this._modelCreationOptionsByLanguageAndResource = Object.create(null);
200

201
		// Update options on all models
A
Alex Dima 已提交
202 203 204 205
		let keys = Object.keys(this._models);
		for (let i = 0, len = keys.length; i < len; i++) {
			let modelId = keys[i];
			let modelData = this._models[modelId];
206
			const language = modelData.model.getLanguageIdentifier().language;
207 208
			const uri = modelData.model.uri;
			const oldOptions = oldOptionsByLanguageAndResource[language + uri];
209
			const newOptions = this.getCreationOptions(language, uri, modelData.model.isForSimpleWidget);
210 211 212
			ModelServiceImpl._setModelOptionsForModel(modelData.model, newOptions, oldOptions);
		}
	}
213

A
Alex Dima 已提交
214
	private static _setModelOptionsForModel(model: ITextModel, newOptions: ITextModelCreationOptions, currentOptions: ITextModelCreationOptions): void {
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
		if (currentOptions
			&& (currentOptions.detectIndentation === newOptions.detectIndentation)
			&& (currentOptions.insertSpaces === newOptions.insertSpaces)
			&& (currentOptions.tabSize === newOptions.tabSize)
			&& (currentOptions.trimAutoWhitespace === newOptions.trimAutoWhitespace)
		) {
			// Same indent opts, no need to touch the model
			return;
		}

		if (newOptions.detectIndentation) {
			model.detectIndentation(newOptions.insertSpaces, newOptions.tabSize);
			model.updateOptions({
				trimAutoWhitespace: newOptions.trimAutoWhitespace
			});
		} else {
			model.updateOptions({
				insertSpaces: newOptions.insertSpaces,
				tabSize: newOptions.tabSize,
				trimAutoWhitespace: newOptions.trimAutoWhitespace
			});
236
		}
237 238
	}

E
Erich Gamma 已提交
239
	public dispose(): void {
B
Benjamin Pasero 已提交
240
		if (this._markerServiceSubscription) {
E
Erich Gamma 已提交
241 242
			this._markerServiceSubscription.dispose();
		}
243
		this._configurationServiceSubscription.dispose();
A
Alex Dima 已提交
244
		super.dispose();
E
Erich Gamma 已提交
245 246
	}

A
Alex Dima 已提交
247
	private _cleanUp(model: ITextModel): void {
S
Sandeep Somavarapu 已提交
248 249
		// clean up markers for internal, transient models
		if (model.uri.scheme === network.Schemas.inMemory
B
Benjamin Pasero 已提交
250 251 252
			|| model.uri.scheme === network.Schemas.internal
			|| model.uri.scheme === network.Schemas.vscode) {
			if (this._markerService) {
A
Alex Dima 已提交
253
				this._markerService.read({ resource: model.uri }).map(marker => marker.owner).forEach(owner => this._markerService!.remove(owner, [model.uri]));
B
Benjamin Pasero 已提交
254
			}
S
Sandeep Somavarapu 已提交
255
		}
256 257 258

		// clean up cache
		delete this._modelCreationOptionsByLanguageAndResource[model.getLanguageIdentifier().language + model.uri];
S
Sandeep Somavarapu 已提交
259 260
	}

E
Erich Gamma 已提交
261 262
	// --- begin IModelService

A
Alex Dima 已提交
263
	private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | null | undefined, isForSimpleWidget: boolean): ModelData {
A
Alex Dima 已提交
264
		// create & save the model
265
		const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
266 267
		const model: TextModel = new TextModel(value, options, languageIdentifier, resource);
		const modelId = MODEL_ID(model.uri);
E
Erich Gamma 已提交
268 269 270

		if (this._models[modelId]) {
			// There already exists a model with this id => this is a programmer error
271
			throw new Error('ModelService: Cannot add model because it already exists!');
E
Erich Gamma 已提交
272 273
		}

274
		const modelData = new ModelData(
275 276 277 278
			model,
			(model) => this._onWillDispose(model),
			(model, e) => this._onDidChangeLanguage(model, e)
		);
A
Alex Dima 已提交
279
		this._models[modelId] = modelData;
E
Erich Gamma 已提交
280

A
Alex Dima 已提交
281
		return modelData;
E
Erich Gamma 已提交
282 283
	}

284
	public updateModel(model: ITextModel, value: string | ITextBufferFactory): void {
285
		const options = this.getCreationOptions(model.getLanguageIdentifier().language, model.uri, model.isForSimpleWidget);
286
		const textBuffer = createTextBuffer(value, options.defaultEOL);
287 288

		// Return early if the text is already set in that form
289
		if (model.equalsTextBuffer(textBuffer)) {
290 291
			return;
		}
292 293

		// Otherwise find a diff between the values and update model
294
		model.pushStackElement();
A
Alex Dima 已提交
295
		model.pushEOL(textBuffer.getEOL() === '\r\n' ? EndOfLineSequence.CRLF : EndOfLineSequence.LF);
296
		model.pushEditOperations(
297
			[],
298
			ModelServiceImpl._computeEdits(model, textBuffer),
299
			(inverseEditOperations: IIdentifiedSingleEditOperation[]) => []
300
		);
301
		model.pushStackElement();
302 303
	}

304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
	private static _commonPrefix(a: ILineSequence, aLen: number, aDelta: number, b: ILineSequence, bLen: number, bDelta: number): number {
		const maxResult = Math.min(aLen, bLen);

		let result = 0;
		for (let i = 0; i < maxResult && a.getLineContent(aDelta + i) === b.getLineContent(bDelta + i); i++) {
			result++;
		}
		return result;
	}

	private static _commonSuffix(a: ILineSequence, aLen: number, aDelta: number, b: ILineSequence, bLen: number, bDelta: number): number {
		const maxResult = Math.min(aLen, bLen);

		let result = 0;
		for (let i = 0; i < maxResult && a.getLineContent(aDelta + aLen - i) === b.getLineContent(bDelta + bLen - i); i++) {
			result++;
		}
		return result;
	}

324 325 326
	/**
	 * Compute edits to bring `model` to the state of `textSource`.
	 */
327
	public static _computeEdits(model: ITextModel, textBuffer: ITextBuffer): IIdentifiedSingleEditOperation[] {
328
		const modelLineCount = model.getLineCount();
329 330
		const textBufferLineCount = textBuffer.getLineCount();
		const commonPrefix = this._commonPrefix(model, modelLineCount, 1, textBuffer, textBufferLineCount, 1);
331

332 333 334 335
		if (modelLineCount === textBufferLineCount && commonPrefix === modelLineCount) {
			// equality case
			return [];
		}
336

337
		const commonSuffix = this._commonSuffix(model, modelLineCount - commonPrefix, commonPrefix, textBuffer, textBufferLineCount - commonPrefix, commonPrefix);
338

339 340 341 342 343 344 345 346 347 348
		let oldRange: Range, newRange: Range;
		if (commonSuffix > 0) {
			oldRange = new Range(commonPrefix + 1, 1, modelLineCount - commonSuffix + 1, 1);
			newRange = new Range(commonPrefix + 1, 1, textBufferLineCount - commonSuffix + 1, 1);
		} else if (commonPrefix > 0) {
			oldRange = new Range(commonPrefix, model.getLineMaxColumn(commonPrefix), modelLineCount, model.getLineMaxColumn(modelLineCount));
			newRange = new Range(commonPrefix, 1 + textBuffer.getLineLength(commonPrefix), textBufferLineCount, 1 + textBuffer.getLineLength(textBufferLineCount));
		} else {
			oldRange = new Range(1, 1, modelLineCount, model.getLineMaxColumn(modelLineCount));
			newRange = new Range(1, 1, textBufferLineCount, 1 + textBuffer.getLineLength(textBufferLineCount));
349 350
		}

351
		return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
352 353
	}

A
Alex Dima 已提交
354
	public createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource: URI | null | undefined, isForSimpleWidget: boolean = false): ITextModel {
355 356
		let modelData: ModelData;

A
Alex Dima 已提交
357 358 359
		if (languageSelection) {
			modelData = this._createModelData(value, languageSelection.languageIdentifier, resource, isForSimpleWidget);
			this.setMode(modelData.model, languageSelection);
360
		} else {
A
Alex Dima 已提交
361
			modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource, isForSimpleWidget);
362
		}
E
Erich Gamma 已提交
363

A
Alex Dima 已提交
364
		this._onModelAdded.fire(modelData.model);
E
Erich Gamma 已提交
365

A
Alex Dima 已提交
366
		return modelData.model;
E
Erich Gamma 已提交
367 368
	}

A
Alex Dima 已提交
369 370
	public setMode(model: ITextModel, languageSelection: ILanguageSelection): void {
		if (!languageSelection) {
371 372
			return;
		}
A
Alex Dima 已提交
373 374 375
		let modelData = this._models[MODEL_ID(model.uri)];
		if (!modelData) {
			return;
376
		}
A
Alex Dima 已提交
377
		modelData.setLanguage(languageSelection);
378 379
	}

J
Johannes Rieken 已提交
380
	public destroyModel(resource: URI): void {
A
Alex Dima 已提交
381 382 383 384
		// We need to support that not all models get disposed through this service (i.e. model.dispose() should work!)
		let modelData = this._models[MODEL_ID(resource)];
		if (!modelData) {
			return;
E
Erich Gamma 已提交
385
		}
A
Alex Dima 已提交
386
		modelData.model.dispose();
E
Erich Gamma 已提交
387 388
	}

A
Alex Dima 已提交
389 390
	public getModels(): ITextModel[] {
		let ret: ITextModel[] = [];
A
Alex Dima 已提交
391 392 393 394 395

		let keys = Object.keys(this._models);
		for (let i = 0, len = keys.length; i < len; i++) {
			let modelId = keys[i];
			ret.push(this._models[modelId].model);
E
Erich Gamma 已提交
396
		}
A
Alex Dima 已提交
397

E
Erich Gamma 已提交
398 399 400
		return ret;
	}

A
Alex Dima 已提交
401
	public getModel(resource: URI): ITextModel | null {
A
Alex Dima 已提交
402 403 404 405
		let modelId = MODEL_ID(resource);
		let modelData = this._models[modelId];
		if (!modelData) {
			return null;
E
Erich Gamma 已提交
406
		}
A
Alex Dima 已提交
407
		return modelData.model;
E
Erich Gamma 已提交
408 409 410 411
	}

	// --- end IModelService

A
Alex Dima 已提交
412
	private _onWillDispose(model: ITextModel): void {
413
		let modelId = MODEL_ID(model.uri);
A
Alex Dima 已提交
414 415 416 417 418
		let modelData = this._models[modelId];

		delete this._models[modelId];
		modelData.dispose();

419
		this._cleanUp(model);
A
Alex Dima 已提交
420 421 422
		this._onModelRemoved.fire(model);
	}

A
Alex Dima 已提交
423
	private _onDidChangeLanguage(model: ITextModel, e: IModelLanguageChangedEvent): void {
424 425
		const oldModeId = e.oldLanguage;
		const newModeId = model.getLanguageIdentifier().language;
426 427
		const oldOptions = this.getCreationOptions(oldModeId, model.uri, model.isForSimpleWidget);
		const newOptions = this.getCreationOptions(newModeId, model.uri, model.isForSimpleWidget);
428 429
		ModelServiceImpl._setModelOptionsForModel(model, newOptions, oldOptions);
		this._onModelModeChanged.fire({ model, oldModeId });
E
Erich Gamma 已提交
430 431
	}
}
432 433 434 435

export interface ILineSequence {
	getLineContent(lineNumber: number): string;
}