modelServiceImpl.ts 31.8 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
import { Emitter, Event } from 'vs/base/common/event';
7
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
8
import * as platform from 'vs/base/common/platform';
A
wip  
Alexandru Dima 已提交
9
import * as errors from 'vs/base/common/errors';
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';
16
import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
17
import { LanguageIdentifier, SemanticTokensProviderRegistry, SemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits } 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';
A
wip  
Alexandru Dima 已提交
23 24
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
A
Alexandru Dima 已提交
25
import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
26
import { IThemeService } from 'vs/platform/theme/common/themeService';
E
Erich Gamma 已提交
27

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

class ModelData implements IDisposable {
A
Alex Dima 已提交
33 34 35 36
	public readonly model: ITextModel;

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

38
	private readonly _modelEventListeners = new DisposableStore();
E
Erich Gamma 已提交
39

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

A
Alex Dima 已提交
47 48 49
		this._languageSelection = null;
		this._languageSelectionListener = null;

50 51
		this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model)));
		this._modelEventListeners.add(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e)));
E
Erich Gamma 已提交
52 53
	}

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

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

A
Alex Dima 已提交
70 71 72 73 74 75
	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 已提交
76
}
E
Erich Gamma 已提交
77

S
Sandeep Somavarapu 已提交
78 79
interface IRawEditorConfig {
	tabSize?: any;
D
David Lechner 已提交
80
	indentSize?: any;
S
Sandeep Somavarapu 已提交
81 82 83 84 85 86 87
	insertSpaces?: any;
	detectIndentation?: any;
	trimAutoWhitespace?: any;
	creationOptions?: any;
	largeFileOptimizations?: any;
}

88
interface IRawConfig {
S
Sandeep Somavarapu 已提交
89 90
	eol?: any;
	editor?: IRawEditorConfig;
91 92
}

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

A
Alex Dima 已提交
95
export class ModelServiceImpl extends Disposable implements IModelService {
96
	public _serviceBrand: undefined;
E
Erich Gamma 已提交
97

98 99 100
	private readonly _configurationService: IConfigurationService;
	private readonly _configurationServiceSubscription: IDisposable;
	private readonly _resourcePropertiesService: ITextResourcePropertiesService;
E
Erich Gamma 已提交
101

A
Alex Dima 已提交
102 103 104 105 106 107
	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;

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

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

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

120
	constructor(
121
		@IConfigurationService configurationService: IConfigurationService,
122 123
		@ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService,
		@IThemeService themeService: IThemeService
124
	) {
A
Alex Dima 已提交
125
		super();
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
		this._updateModelOptions();
A
wip  
Alexandru Dima 已提交
133

134
		this._register(new SemanticColoringFeature(this, themeService));
135
	}
J
Joao Moreno 已提交
136

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

D
David Lechner 已提交
149
		let indentSize = tabSize;
A
Alex Dima 已提交
150
		if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') {
D
David Lechner 已提交
151 152 153 154 155 156 157 158 159
			let parsedIndentSize = parseInt(config.editor.indentSize, 10);
			if (!isNaN(parsedIndentSize)) {
				indentSize = parsedIndentSize;
			}
			if (indentSize < 1) {
				indentSize = 1;
			}
		}

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

165
		let newDefaultEOL = DEFAULT_EOL;
S
Sandeep Somavarapu 已提交
166
		const eol = config.eol;
167
		if (eol === '\r\n') {
168
			newDefaultEOL = DefaultEndOfLine.CRLF;
169
		} else if (eol === '\n') {
170
			newDefaultEOL = DefaultEndOfLine.LF;
171
		}
172

173
		let trimAutoWhitespace = EDITOR_MODEL_DEFAULTS.trimAutoWhitespace;
174 175 176
		if (config.editor && typeof config.editor.trimAutoWhitespace !== 'undefined') {
			trimAutoWhitespace = (config.editor.trimAutoWhitespace === 'false' ? false : Boolean(config.editor.trimAutoWhitespace));
		}
177

178
		let detectIndentation = EDITOR_MODEL_DEFAULTS.detectIndentation;
179 180 181
		if (config.editor && typeof config.editor.detectIndentation !== 'undefined') {
			detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation));
		}
182

183 184 185
		let largeFileOptimizations = EDITOR_MODEL_DEFAULTS.largeFileOptimizations;
		if (config.editor && typeof config.editor.largeFileOptimizations !== 'undefined') {
			largeFileOptimizations = (config.editor.largeFileOptimizations === 'false' ? false : Boolean(config.editor.largeFileOptimizations));
186 187
		}

188
		return {
189
			isForSimpleWidget: isForSimpleWidget,
190
			tabSize: tabSize,
D
David Lechner 已提交
191
			indentSize: indentSize,
192 193 194
			insertSpaces: insertSpaces,
			detectIndentation: detectIndentation,
			defaultEOL: newDefaultEOL,
195
			trimAutoWhitespace: trimAutoWhitespace,
196
			largeFileOptimizations: largeFileOptimizations
197
		};
E
Erich Gamma 已提交
198 199
	}

A
Alex Dima 已提交
200
	public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions {
201
		let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
202
		if (!creationOptions) {
S
Sandeep Somavarapu 已提交
203
			const editor = this._configurationService.getValue<IRawEditorConfig>('editor', { overrideIdentifier: language, resource });
S
Sandeep Somavarapu 已提交
204
			const eol = this._resourcePropertiesService.getEOL(resource, language);
S
Sandeep Somavarapu 已提交
205
			creationOptions = ModelServiceImpl._readModelOptions({ editor, eol }, isForSimpleWidget);
206
			this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions;
207 208
		}
		return creationOptions;
209 210
	}

211
	private _updateModelOptions(): void {
212 213
		let oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource;
		this._modelCreationOptionsByLanguageAndResource = Object.create(null);
214

215
		// Update options on all models
A
Alex Dima 已提交
216 217 218 219
		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];
220
			const language = modelData.model.getLanguageIdentifier().language;
221 222
			const uri = modelData.model.uri;
			const oldOptions = oldOptionsByLanguageAndResource[language + uri];
223
			const newOptions = this.getCreationOptions(language, uri, modelData.model.isForSimpleWidget);
224 225 226
			ModelServiceImpl._setModelOptionsForModel(modelData.model, newOptions, oldOptions);
		}
	}
227

A
Alex Dima 已提交
228
	private static _setModelOptionsForModel(model: ITextModel, newOptions: ITextModelCreationOptions, currentOptions: ITextModelCreationOptions): void {
229 230 231 232
		if (currentOptions && currentOptions.defaultEOL !== newOptions.defaultEOL && model.getLineCount() === 1) {
			model.setEOL(newOptions.defaultEOL === DefaultEndOfLine.LF ? EndOfLineSequence.LF : EndOfLineSequence.CRLF);
		}

233 234 235 236
		if (currentOptions
			&& (currentOptions.detectIndentation === newOptions.detectIndentation)
			&& (currentOptions.insertSpaces === newOptions.insertSpaces)
			&& (currentOptions.tabSize === newOptions.tabSize)
A
Alex Dima 已提交
237
			&& (currentOptions.indentSize === newOptions.indentSize)
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
			&& (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,
A
Alex Dima 已提交
253
				indentSize: newOptions.indentSize,
254 255
				trimAutoWhitespace: newOptions.trimAutoWhitespace
			});
256
		}
257 258
	}

E
Erich Gamma 已提交
259
	public dispose(): void {
260
		this._configurationServiceSubscription.dispose();
A
Alex Dima 已提交
261
		super.dispose();
E
Erich Gamma 已提交
262 263 264 265
	}

	// --- begin IModelService

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

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

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

A
Alex Dima 已提交
284
		return modelData;
E
Erich Gamma 已提交
285 286
	}

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

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

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

307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
	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;
	}

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

335 336 337 338
		if (modelLineCount === textBufferLineCount && commonPrefix === modelLineCount) {
			// equality case
			return [];
		}
339

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

342 343 344 345 346 347 348 349 350 351
		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));
352 353
		}

354
		return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
355 356
	}

A
Alex Dima 已提交
357
	public createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource?: URI, isForSimpleWidget: boolean = false): ITextModel {
358 359
		let modelData: ModelData;

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

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

A
Alex Dima 已提交
369
		return modelData.model;
E
Erich Gamma 已提交
370 371
	}

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

J
Johannes Rieken 已提交
383
	public destroyModel(resource: URI): void {
A
Alex Dima 已提交
384 385 386 387
		// 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 已提交
388
		}
A
Alex Dima 已提交
389
		modelData.model.dispose();
E
Erich Gamma 已提交
390 391
	}

A
Alex Dima 已提交
392 393
	public getModels(): ITextModel[] {
		let ret: ITextModel[] = [];
A
Alex Dima 已提交
394 395 396 397 398

		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 已提交
399
		}
A
Alex Dima 已提交
400

E
Erich Gamma 已提交
401 402 403
		return ret;
	}

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

	// --- end IModelService

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

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

422 423 424
		// clean up cache
		delete this._modelCreationOptionsByLanguageAndResource[model.getLanguageIdentifier().language + model.uri];

A
Alex Dima 已提交
425 426 427
		this._onModelRemoved.fire(model);
	}

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

export interface ILineSequence {
	getLineContent(lineNumber: number): string;
}
A
wip  
Alexandru Dima 已提交
441

A
wip  
Alexandru Dima 已提交
442
class SemanticColoringFeature extends Disposable {
A
wip  
Alexandru Dima 已提交
443
	private _watchers: Record<string, ModelSemanticColoring>;
444
	private _semanticStyling: SemanticStyling;
A
wip  
Alexandru Dima 已提交
445

446
	constructor(modelService: IModelService, themeService: IThemeService) {
A
wip  
Alexandru Dima 已提交
447 448
		super();
		this._watchers = Object.create(null);
449
		this._semanticStyling = this._register(new SemanticStyling(themeService));
A
wip  
Alexandru Dima 已提交
450
		this._register(modelService.onModelAdded((model) => {
451
			this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling);
A
wip  
Alexandru Dima 已提交
452 453 454 455 456 457 458 459
		}));
		this._register(modelService.onModelRemoved((model) => {
			this._watchers[model.uri.toString()].dispose();
			delete this._watchers[model.uri.toString()];
		}));
	}
}

460
class SemanticStyling extends Disposable {
461

462
	private _caches: WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>;
463 464 465 466

	constructor(
		private readonly _themeService: IThemeService
	) {
467
		super();
468
		this._caches = new WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>();
A
Alexandru Dima 已提交
469 470 471
		if (this._themeService) {
			// workaround for tests which use undefined... :/
			this._register(this._themeService.onThemeChange(() => {
472
				this._caches = new WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>();
A
Alexandru Dima 已提交
473 474
			}));
		}
475 476
	}

477
	public get(provider: SemanticTokensProvider): SemanticColoringProviderStyling {
478 479 480 481 482 483 484 485 486 487 488
		if (!this._caches.has(provider)) {
			this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService));
		}
		return this._caches.get(provider)!;
	}
}

const enum Constants {
	NO_STYLING = 0b01111111111111111111111111111111
}

489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
class HashTableEntry {
	public readonly tokenTypeIndex: number;
	public readonly tokenModifierSet: number;
	public readonly metadata: number;
	public next: HashTableEntry | null;

	constructor(tokenTypeIndex: number, tokenModifierSet: number, metadata: number) {
		this.tokenTypeIndex = tokenTypeIndex;
		this.tokenModifierSet = tokenModifierSet;
		this.metadata = metadata;
		this.next = null;
	}
}

class HashTable {

	private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143];

	private _elementsCount: number;
	private _currentLengthIndex: number;
	private _currentLength: number;
	private _growCount: number;
	private _elements: (HashTableEntry | null)[];

	constructor() {
		this._elementsCount = 0;
		this._currentLengthIndex = 0;
		this._currentLength = HashTable._SIZES[this._currentLengthIndex];
		this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
		this._elements = [];
		HashTable._nullOutEntries(this._elements, this._currentLength);
	}

	private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void {
		for (let i = 0; i < length; i++) {
			entries[i] = null;
		}
	}

	private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number): number {
		return ((((tokenTypeIndex << 5) - tokenTypeIndex) + tokenModifierSet) | 0) % this._currentLength;  // tokenTypeIndex * 31 + tokenModifierSet, keep as int32
	}

	public get(tokenTypeIndex: number, tokenModifierSet: number): HashTableEntry | null {
		const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet);

		let p = this._elements[hash];
		while (p) {
			if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet) {
				return p;
			}
			p = p.next;
		}

		return null;
	}

	public add(tokenTypeIndex: number, tokenModifierSet: number, metadata: number): void {
		this._elementsCount++;
		if (this._growCount !== 0 && this._elementsCount >= this._growCount) {
			// expand!
			const oldElements = this._elements;

			this._currentLengthIndex++;
			this._currentLength = HashTable._SIZES[this._currentLengthIndex];
			this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
			this._elements = [];
			HashTable._nullOutEntries(this._elements, this._currentLength);

			for (const first of oldElements) {
				let p = first;
				while (p) {
					const oldNext = p.next;
					p.next = null;
					this._add(p);
					p = oldNext;
				}
			}
		}
		this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, metadata));
	}

	private _add(element: HashTableEntry): void {
		const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet);
		element.next = this._elements[hash];
		this._elements[hash] = element;
	}
}

578 579
class SemanticColoringProviderStyling {

580 581
	private readonly _hashTable: HashTable;

582
	constructor(
583
		private readonly _legend: SemanticTokensLegend,
584 585
		private readonly _themeService: IThemeService
	) {
586
		this._hashTable = new HashTable();
587 588 589
	}

	public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number {
590 591 592 593 594
		const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet);
		if (entry) {
			return entry.metadata;
		}

595 596 597 598 599 600 601 602 603
		const tokenType = this._legend.tokenTypes[tokenTypeIndex];
		const tokenModifiers: string[] = [];
		for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
			if (tokenModifierSet & 1) {
				tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]);
			}
			tokenModifierSet = tokenModifierSet >> 1;
		}

604
		let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers);
605
		if (typeof metadata === 'undefined') {
606
			metadata = Constants.NO_STYLING;
607
		}
608 609

		this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata);
610 611 612 613
		return metadata;
	}
}

614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
const enum SemanticColoringConstants {
	/**
	 * Let's aim at having 8KB buffers if possible...
	 * So that would be 8192 / (5 * 4) = 409.6 tokens per area
	 */
	DesiredTokensPerArea = 400,

	/**
	 * Try to keep the total number of areas under 1024 if possible,
	 * simply compensate by having more tokens per area...
	 */
	DesiredMaxAreas = 1024,
}

class SemanticTokensResponse {
	constructor(
		private readonly _provider: SemanticTokensProvider,
		public readonly resultId: string | undefined,
		public readonly data: Uint32Array
	) { }

	public dispose(): void {
		this._provider.releaseSemanticTokens(this.resultId);
	}
}

A
wip  
Alexandru Dima 已提交
640 641
class ModelSemanticColoring extends Disposable {

642
	private _isDisposed: boolean;
A
wip  
Alexandru Dima 已提交
643
	private readonly _model: ITextModel;
644
	private readonly _semanticStyling: SemanticStyling;
A
wip  
Alexandru Dima 已提交
645
	private readonly _fetchSemanticTokens: RunOnceScheduler;
646
	private _currentResponse: SemanticTokensResponse | null;
A
wip  
Alexandru Dima 已提交
647 648
	private _currentRequestCancellationTokenSource: CancellationTokenSource | null;

649
	constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) {
A
wip  
Alexandru Dima 已提交
650
		super();
A
wip  
Alexandru Dima 已提交
651

652
		this._isDisposed = false;
A
wip  
Alexandru Dima 已提交
653
		this._model = model;
654
		this._semanticStyling = stylingProvider;
A
wip  
Alexandru Dima 已提交
655 656 657 658 659
		this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 500));
		this._currentResponse = null;
		this._currentRequestCancellationTokenSource = null;

		this._register(this._model.onDidChangeContent(e => this._fetchSemanticTokens.schedule()));
660
		this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule()));
A
Alexandru Dima 已提交
661 662 663 664
		if (themeService) {
			// workaround for tests which use undefined... :/
			this._register(themeService.onThemeChange(_ => {
				// clear out existing tokens
665
				this._setSemanticTokens(null, null, null, []);
A
Alexandru Dima 已提交
666 667 668
				this._fetchSemanticTokens.schedule();
			}));
		}
669
		this._fetchSemanticTokens.schedule(0);
A
wip  
Alexandru Dima 已提交
670 671 672
	}

	public dispose(): void {
673
		this._isDisposed = true;
A
wip  
Alexandru Dima 已提交
674 675 676 677 678 679 680 681 682
		if (this._currentResponse) {
			this._currentResponse.dispose();
			this._currentResponse = null;
		}
		if (this._currentRequestCancellationTokenSource) {
			this._currentRequestCancellationTokenSource.cancel();
			this._currentRequestCancellationTokenSource = null;
		}
		super.dispose();
A
wip  
Alexandru Dima 已提交
683 684
	}

A
wip  
Alexandru Dima 已提交
685 686 687 688 689 690 691 692 693 694
	private _fetchSemanticTokensNow(): void {
		if (this._currentRequestCancellationTokenSource) {
			// there is already a request running, let it finish...
			return;
		}
		const provider = this._getSemanticColoringProvider();
		if (!provider) {
			return;
		}
		this._currentRequestCancellationTokenSource = new CancellationTokenSource();
695 696 697 698 699 700

		const pendingChanges: IModelContentChangedEvent[] = [];
		const contentChangeListener = this._model.onDidChangeContent((e) => {
			pendingChanges.push(e);
		});

701
		const styling = this._semanticStyling.get(provider);
702 703 704

		const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null;
		const request = Promise.resolve(provider.provideSemanticTokens(this._model, lastResultId, null, this._currentRequestCancellationTokenSource.token));
A
wip  
Alexandru Dima 已提交
705 706 707

		request.then((res) => {
			this._currentRequestCancellationTokenSource = null;
708
			contentChangeListener.dispose();
709
			this._setSemanticTokens(provider, res || null, styling, pendingChanges);
A
wip  
Alexandru Dima 已提交
710 711
		}, (err) => {
			errors.onUnexpectedError(err);
712
			this._currentRequestCancellationTokenSource = null;
713
			contentChangeListener.dispose();
714
			this._setSemanticTokens(provider, null, styling, pendingChanges);
A
wip  
Alexandru Dima 已提交
715 716 717
		});
	}

718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
	private static _isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens {
		return v && !!((<SemanticTokens>v).data);
	}

	private static _isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits {
		return v && Array.isArray((<SemanticTokensEdits>v).edits);
	}

	private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void {
		for (let i = 0; i < length; i++) {
			dest[destOffset + i] = src[srcOffset + i];
		}
	}

	private _setSemanticTokens(provider: SemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void {
		const currentResponse = this._currentResponse;
A
wip  
Alexandru Dima 已提交
734 735 736 737
		if (this._currentResponse) {
			this._currentResponse.dispose();
			this._currentResponse = null;
		}
738 739
		if (this._isDisposed) {
			// disposed!
740 741
			if (provider && tokens) {
				provider.releaseSemanticTokens(tokens.resultId);
742 743 744
			}
			return;
		}
745
		if (!provider || !tokens || !styling) {
746 747 748
			this._model.setSemanticTokens(null);
			return;
		}
749

750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
		if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) {
			if (!currentResponse) {
				// not possible!
				this._model.setSemanticTokens(null);
				return;
			}
			if (tokens.edits.length === 0) {
				// nothing to do!
				tokens = {
					resultId: tokens.resultId,
					data: currentResponse.data
				};
			} else {
				let deltaLength = 0;
				for (const edit of tokens.edits) {
					deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount;
				}

				const srcData = currentResponse.data;
				const destData = new Uint32Array(srcData.length + deltaLength);

				let srcLastStart = srcData.length;
				let destLastStart = destData.length;
				for (let i = tokens.edits.length - 1; i >= 0; i--) {
					const edit = tokens.edits[i];

					const copyCount = srcLastStart - (edit.start + edit.deleteCount);
					if (copyCount > 0) {
						ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount);
						destLastStart -= copyCount;
					}

					if (edit.data) {
						ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length);
						destLastStart -= edit.data.length;
					}

					srcLastStart = edit.start;
788
				}
789 790 791 792 793 794 795 796 797

				if (srcLastStart > 0) {
					ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart);
				}

				tokens = {
					resultId: tokens.resultId,
					data: destData
				};
A
wip  
Alexandru Dima 已提交
798
			}
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
		}

		if (ModelSemanticColoring._isSemanticTokens(tokens)) {

			this._currentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data);

			const srcData = tokens.data;
			const tokenCount = (tokens.data.length / 5) | 0;
			const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea);

			const result: MultilineTokens2[] = [];

			let tokenIndex = 0;
			let lastLineNumber = 1;
			let lastStartCharacter = 0;
			while (tokenIndex < tokenCount) {
				const tokenStartIndex = tokenIndex;
				let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount);

				// Keep tokens on the same line in the same area...
				if (tokenEndIndex < tokenCount) {

					let smallTokenEndIndex = tokenEndIndex;
					while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) {
						smallTokenEndIndex--;
					}

					if (smallTokenEndIndex - 1 === tokenStartIndex) {
						// there are so many tokens on this line that our area would be empty, we must now go right
						let bigTokenEndIndex = tokenEndIndex;
						while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) {
							bigTokenEndIndex++;
						}
						tokenEndIndex = bigTokenEndIndex;
					} else {
						tokenEndIndex = smallTokenEndIndex;
					}
				}

				let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4);
				let destOffset = 0;
				let areaLine = 0;
				while (tokenIndex < tokenEndIndex) {
					const srcOffset = 5 * tokenIndex;
					const deltaLine = srcData[srcOffset];
					const deltaCharacter = srcData[srcOffset + 1];
					const lineNumber = lastLineNumber + deltaLine;
					const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter);
					const length = srcData[srcOffset + 2];
					const tokenTypeIndex = srcData[srcOffset + 3];
					const tokenModifierSet = srcData[srcOffset + 4];
					const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet);

					if (metadata !== Constants.NO_STYLING) {
						if (areaLine === 0) {
							areaLine = lineNumber;
						}
						destData[destOffset] = lineNumber - areaLine;
						destData[destOffset + 1] = startCharacter;
						destData[destOffset + 2] = startCharacter + length;
						destData[destOffset + 3] = metadata;
						destOffset += 4;
					}

					lastLineNumber = lineNumber;
					lastStartCharacter = startCharacter;
					tokenIndex++;
				}

				if (destOffset !== destData.length) {
					destData = destData.subarray(0, destOffset);
				}
A
Alexandru Dima 已提交
871

872 873
				const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData));
				result.push(tokens);
874
			}
875 876 877 878 879 880 881 882 883 884 885 886

			// Adjust incoming semantic tokens
			if (pendingChanges.length > 0) {
				// More changes occurred while the request was running
				// We need to:
				// 1. Adjust incoming semantic tokens
				// 2. Request them again
				for (const change of pendingChanges) {
					for (const area of result) {
						for (const singleChange of change.changes) {
							area.applyEdit(singleChange.range, singleChange.text);
						}
887 888
					}
				}
889 890

				this._fetchSemanticTokens.schedule();
891
			}
892

893 894
			this._model.setSemanticTokens(result);
			return;
895
		}
896

897
		this._model.setSemanticTokens(null);
A
wip  
Alexandru Dima 已提交
898 899
	}

900 901
	private _getSemanticColoringProvider(): SemanticTokensProvider | null {
		const result = SemanticTokensProviderRegistry.ordered(this._model);
A
wip  
Alexandru Dima 已提交
902 903
		return (result.length > 0 ? result[0] : null);
	}
A
wip  
Alexandru Dima 已提交
904
}