Skip to content

Commit 44a8080

Browse files
committed
Fixes microsoft#61825: Refactor mode creation
1 parent 34c9201 commit 44a8080

33 files changed

Lines changed: 232 additions & 176 deletions

File tree

src/vs/editor/common/services/languagesRegistry.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { onUnexpectedError } from 'vs/base/common/errors';
7+
import { Emitter, Event } from 'vs/base/common/event';
8+
import { Disposable } from 'vs/base/common/lifecycle';
79
import * as mime from 'vs/base/common/mime';
810
import * as strings from 'vs/base/common/strings';
911
import { URI } from 'vs/base/common/uri';
@@ -26,7 +28,10 @@ export interface IResolvedLanguage {
2628
configurationFiles: URI[];
2729
}
2830

29-
export class LanguagesRegistry {
31+
export class LanguagesRegistry extends Disposable {
32+
33+
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
34+
public readonly onDidChange: Event<void> = this._onDidChange.event;
3035

3136
private _nextLanguageId: number;
3237
private _languages: { [id: string]: IResolvedLanguage; };
@@ -39,6 +44,7 @@ export class LanguagesRegistry {
3944
private _warnOnOverwrite: boolean;
4045

4146
constructor(useModesRegistry = true, warnOnOverwrite = false) {
47+
super();
4248
this._nextLanguageId = 1;
4349
this._languages = {};
4450
this._mimeTypesMap = {};
@@ -49,7 +55,7 @@ export class LanguagesRegistry {
4955

5056
if (useModesRegistry) {
5157
this._registerLanguages(ModesRegistry.getLanguages());
52-
ModesRegistry.onDidAddLanguages((m) => this._registerLanguages(m));
58+
this._register(ModesRegistry.onDidAddLanguages((m) => this._registerLanguages(m)));
5359
}
5460
}
5561

@@ -80,6 +86,8 @@ export class LanguagesRegistry {
8086
});
8187

8288
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerOverrideIdentifiers(ModesRegistry.getLanguages().map(language => language.id));
89+
90+
this._onDidChange.fire();
8391
}
8492

8593
private _registerLanguage(lang: ILanguageExtensionPoint): void {

src/vs/editor/common/services/modeService.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Event } from 'vs/base/common/event';
7+
import { IDisposable } from 'vs/base/common/lifecycle';
78
import { URI } from 'vs/base/common/uri';
89
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
910
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -21,6 +22,11 @@ export interface ILanguageExtensionPoint {
2122
configuration?: URI;
2223
}
2324

25+
export interface ILanguageSelection extends IDisposable {
26+
readonly languageIdentifier: LanguageIdentifier;
27+
readonly onDidChange: Event<LanguageIdentifier>;
28+
}
29+
2430
export interface IModeService {
2531
_serviceBrand: any;
2632

@@ -41,8 +47,9 @@ export interface IModeService {
4147
getConfigurationFiles(modeId: string): URI[];
4248

4349
// --- instantiation
44-
getMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): IMode | null;
45-
getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): Promise<IMode>;
46-
getOrCreateModeByLanguageName(languageName: string): Promise<IMode>;
47-
getOrCreateModeByFilepathOrFirstLine(filepath: string, firstLine?: string): Promise<IMode>;
50+
create(commaSeparatedMimetypesOrCommaSeparatedIds: string): ILanguageSelection;
51+
createByLanguageName(languageName: string): ILanguageSelection;
52+
createByFilepathOrFirstLine(filepath: string, firstLine?: string): ILanguageSelection;
53+
54+
triggerMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): void;
4855
}

src/vs/editor/common/services/modeServiceImpl.ts

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,41 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { onUnexpectedError } from 'vs/base/common/errors';
76
import { Emitter, Event } from 'vs/base/common/event';
7+
import { Disposable } from 'vs/base/common/lifecycle';
88
import { URI } from 'vs/base/common/uri';
99
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
1010
import { FrankensteinMode } from 'vs/editor/common/modes/abstractMode';
1111
import { NULL_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/nullMode';
1212
import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry';
13-
import { IModeService } from 'vs/editor/common/services/modeService';
13+
import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService';
14+
15+
class LanguageSelection extends Disposable implements ILanguageSelection {
16+
17+
public languageIdentifier: LanguageIdentifier;
18+
19+
private readonly _selector: () => LanguageIdentifier;
20+
21+
private readonly _onDidChange: Emitter<LanguageIdentifier> = this._register(new Emitter<LanguageIdentifier>());
22+
public readonly onDidChange: Event<LanguageIdentifier> = this._onDidChange.event;
23+
24+
constructor(languagesRegistry: LanguagesRegistry, selector: () => LanguageIdentifier) {
25+
super();
26+
this._selector = selector;
27+
this.languageIdentifier = this._selector();
28+
this._register(languagesRegistry.onDidChange(() => this._evaluate()));
29+
}
30+
31+
private _evaluate(): void {
32+
let languageIdentifier = this._selector();
33+
if (languageIdentifier.id === this.languageIdentifier.id) {
34+
// no change
35+
return;
36+
}
37+
this.languageIdentifier = languageIdentifier;
38+
this._onDidChange.fire(this.languageIdentifier);
39+
}
40+
}
1441

1542
export class ModeServiceImpl implements IModeService {
1643
public _serviceBrand: any;
@@ -93,44 +120,44 @@ export class ModeServiceImpl implements IModeService {
93120

94121
// --- instantiation
95122

96-
public getMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): IMode | null {
97-
const modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds);
98-
99-
let isPlainText = false;
100-
for (let i = 0; i < modeIds.length; i++) {
101-
if (this._instantiatedModes.hasOwnProperty(modeIds[i])) {
102-
return this._instantiatedModes[modeIds[i]];
103-
}
104-
isPlainText = isPlainText || (modeIds[i] === 'plaintext');
105-
}
106-
107-
if (isPlainText) {
108-
// Try to do it synchronously
109-
let r: IMode | null = null;
110-
this.getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds).then((mode) => {
111-
r = mode;
112-
}, onUnexpectedError);
113-
return r;
114-
}
115-
return null;
116-
}
117-
118-
public getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): Promise<IMode> {
119-
return this._onReady().then(() => {
123+
public create(commaSeparatedMimetypesOrCommaSeparatedIds: string): ILanguageSelection {
124+
return new LanguageSelection(this._registry, () => {
120125
const modeId = this.getModeId(commaSeparatedMimetypesOrCommaSeparatedIds);
121-
// Fall back to plain text if no mode was found
122-
return this._getOrCreateMode(modeId || 'plaintext');
126+
return this._createModeAndGetLanguageIdentifier(modeId);
123127
});
124128
}
125129

126-
public getOrCreateModeByLanguageName(languageName: string): Promise<IMode> {
127-
return this._onReady().then(() => {
130+
public createByLanguageName(languageName: string): ILanguageSelection {
131+
return new LanguageSelection(this._registry, () => {
128132
const modeId = this._getModeIdByLanguageName(languageName);
129-
// Fall back to plain text if no mode was found
130-
return this._getOrCreateMode(modeId || 'plaintext');
133+
return this._createModeAndGetLanguageIdentifier(modeId);
134+
});
135+
}
136+
137+
public createByFilepathOrFirstLine(filepath: string, firstLine?: string): ILanguageSelection {
138+
return new LanguageSelection(this._registry, () => {
139+
const modeId = this.getModeIdByFilepathOrFirstLine(filepath, firstLine);
140+
return this._createModeAndGetLanguageIdentifier(modeId);
131141
});
132142
}
133143

144+
private _createModeAndGetLanguageIdentifier(modeId: string | null): LanguageIdentifier {
145+
// Fall back to plain text if no mode was found
146+
const languageIdentifier = this.getLanguageIdentifier(modeId || 'plaintext') || NULL_LANGUAGE_IDENTIFIER;
147+
this._getOrCreateMode(languageIdentifier.language);
148+
return languageIdentifier;
149+
}
150+
151+
public triggerMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): void {
152+
const modeId = this.getModeId(commaSeparatedMimetypesOrCommaSeparatedIds);
153+
// Fall back to plain text if no mode was found
154+
this._getOrCreateMode(modeId || 'plaintext');
155+
}
156+
157+
public waitForLanguageRegistration(): Promise<void> {
158+
return this._onReady().then(() => { });
159+
}
160+
134161
private _getModeIdByLanguageName(languageName: string): string | null {
135162
const modeIds = this._registry.getModeIdsFromLanguageName(languageName);
136163

@@ -141,14 +168,6 @@ export class ModeServiceImpl implements IModeService {
141168
return null;
142169
}
143170

144-
public getOrCreateModeByFilepathOrFirstLine(filepath: string, firstLine?: string): Promise<IMode> {
145-
return this._onReady().then(() => {
146-
const modeId = this.getModeIdByFilepathOrFirstLine(filepath, firstLine);
147-
// Fall back to plain text if no mode was found
148-
return this._getOrCreateMode(modeId || 'plaintext');
149-
});
150-
}
151-
152171
private _getOrCreateMode(modeId: string): IMode {
153172
if (!this._instantiatedModes.hasOwnProperty(modeId)) {
154173
let languageIdentifier = this.getLanguageIdentifier(modeId) || NULL_LANGUAGE_IDENTIFIER;

src/vs/editor/common/services/modelService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@
66
import { Event } from 'vs/base/common/event';
77
import { URI } from 'vs/base/common/uri';
88
import { ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
9-
import { IMode } from 'vs/editor/common/modes';
9+
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
1010
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1111

1212
export const IModelService = createDecorator<IModelService>('modelService');
1313

1414
export interface IModelService {
1515
_serviceBrand: any;
1616

17-
createModel(value: string | ITextBufferFactory, modeOrPromise: Promise<IMode> | IMode, resource: URI | undefined, isForSimpleWidget?: boolean): ITextModel;
17+
createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource: URI, isForSimpleWidget?: boolean): ITextModel;
1818

1919
updateModel(model: ITextModel, value: string | ITextBufferFactory): void;
2020

21-
setMode(model: ITextModel, modeOrPromise: Promise<IMode> | IMode): void;
21+
setMode(model: ITextModel, languageSelection: ILanguageSelection): void;
2222

2323
destroyModel(resource: URI): void;
2424

src/vs/editor/common/services/modelServiceImpl.ts

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import * as nls from 'vs/nls';
77
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
8-
import { isThenable } from 'vs/base/common/async';
98
import { Emitter, Event } from 'vs/base/common/event';
109
import { MarkdownString } from 'vs/base/common/htmlContent';
1110
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -20,8 +19,9 @@ import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSi
2019
import { ClassName } from 'vs/editor/common/model/intervalTree';
2120
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
2221
import { IModelLanguageChangedEvent } from 'vs/editor/common/model/textModelEvents';
23-
import { IMode, LanguageIdentifier } from 'vs/editor/common/modes';
22+
import { LanguageIdentifier } from 'vs/editor/common/modes';
2423
import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry';
24+
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
2525
import { IModelService } from 'vs/editor/common/services/modelService';
2626
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
2727
import { overviewRulerError, overviewRulerInfo, overviewRulerWarning } from 'vs/editor/common/view/editorColorRegistry';
@@ -34,7 +34,10 @@ function MODEL_ID(resource: URI): string {
3434
}
3535

3636
class ModelData implements IDisposable {
37-
model: ITextModel;
37+
public readonly model: ITextModel;
38+
39+
private _languageSelection: ILanguageSelection | null;
40+
private _languageSelectionListener: IDisposable | null;
3841

3942
private _markerDecorations: string[];
4043
private _modelEventListeners: IDisposable[];
@@ -46,21 +49,43 @@ class ModelData implements IDisposable {
4649
) {
4750
this.model = model;
4851

52+
this._languageSelection = null;
53+
this._languageSelectionListener = null;
54+
4955
this._markerDecorations = [];
5056

5157
this._modelEventListeners = [];
5258
this._modelEventListeners.push(model.onWillDispose(() => onWillDispose(model)));
5359
this._modelEventListeners.push(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e)));
5460
}
5561

62+
private _disposeLanguageSelection(): void {
63+
if (this._languageSelectionListener) {
64+
this._languageSelectionListener.dispose();
65+
this._languageSelectionListener = null;
66+
}
67+
if (this._languageSelection) {
68+
this._languageSelection.dispose();
69+
this._languageSelection = null;
70+
}
71+
}
72+
5673
public dispose(): void {
5774
this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, []);
5875
this._modelEventListeners = dispose(this._modelEventListeners);
76+
this._disposeLanguageSelection();
5977
}
6078

6179
public acceptMarkerDecorations(newDecorations: IModelDeltaDecoration[]): void {
6280
this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, newDecorations);
6381
}
82+
83+
public setLanguage(languageSelection: ILanguageSelection): void {
84+
this._disposeLanguageSelection();
85+
this._languageSelection = languageSelection;
86+
this._languageSelectionListener = this._languageSelection.onDidChange(() => this.model.setMode(languageSelection.languageIdentifier));
87+
this.model.setMode(languageSelection.languageIdentifier);
88+
}
6489
}
6590

6691
class ModelMarkerHandler {
@@ -508,14 +533,14 @@ export class ModelServiceImpl extends Disposable implements IModelService {
508533
return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
509534
}
510535

511-
public createModel(value: string | ITextBufferFactory, modeOrPromise: Promise<IMode> | IMode, resource: URI, isForSimpleWidget: boolean = false): ITextModel {
536+
public createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource: URI, isForSimpleWidget: boolean = false): ITextModel {
512537
let modelData: ModelData;
513538

514-
if (!modeOrPromise || isThenable(modeOrPromise)) {
515-
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource, isForSimpleWidget);
516-
this.setMode(modelData.model, modeOrPromise);
539+
if (languageSelection) {
540+
modelData = this._createModelData(value, languageSelection.languageIdentifier, resource, isForSimpleWidget);
541+
this.setMode(modelData.model, languageSelection);
517542
} else {
518-
modelData = this._createModelData(value, modeOrPromise.getLanguageIdentifier(), resource, isForSimpleWidget);
543+
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource, isForSimpleWidget);
519544
}
520545

521546
// handle markers (marker service => model)
@@ -528,19 +553,15 @@ export class ModelServiceImpl extends Disposable implements IModelService {
528553
return modelData.model;
529554
}
530555

531-
public setMode(model: ITextModel, modeOrPromise: Promise<IMode> | IMode): void {
532-
if (!modeOrPromise) {
556+
public setMode(model: ITextModel, languageSelection: ILanguageSelection): void {
557+
if (!languageSelection) {
533558
return;
534559
}
535-
if (isThenable(modeOrPromise)) {
536-
modeOrPromise.then((mode) => {
537-
if (!model.isDisposed()) {
538-
model.setMode(mode.getLanguageIdentifier());
539-
}
540-
});
541-
} else {
542-
model.setMode(modeOrPromise.getLanguageIdentifier());
560+
let modelData = this._models[MODEL_ID(model.uri)];
561+
if (!modelData) {
562+
return;
543563
}
564+
modelData.setLanguage(languageSelection);
544565
}
545566

546567
public destroyModel(resource: URI): void {

src/vs/editor/contrib/markdown/markdownRenderer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export class MarkdownRenderer {
4848
}
4949
}
5050

51-
return this._modeService.getOrCreateMode(modeId || '').then(_ => {
51+
this._modeService.triggerMode(modeId || '');
52+
return Promise.resolve(true).then(_ => {
5253
const promise = TokenizationRegistry.getPromise(modeId || '');
5354
if (promise) {
5455
return promise.then(support => tokenizeToString(value, support));

src/vs/editor/contrib/smartSelect/test/tokenSelectionSupport.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Range } from 'vs/editor/common/core/range';
88
import { Position } from 'vs/editor/common/core/position';
99
import { LanguageIdentifier } from 'vs/editor/common/modes';
1010
import { TokenSelectionSupport } from 'vs/editor/contrib/smartSelect/tokenSelectionSupport';
11-
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
11+
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
1212
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
1313
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
1414
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
@@ -56,7 +56,7 @@ suite('TokenSelectionSupport', () => {
5656

5757
function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): void {
5858
let uri = URI.file('test.js');
59-
modelService.createModel(text.join('\n'), mode, uri);
59+
modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
6060

6161
let actual = tokenSelectionSupport.getRangesToPositionSync(uri, new Position(lineNumber, column));
6262

src/vs/editor/standalone/browser/colorizer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class Colorizer {
6060
}
6161

6262
// Send out the event to create the mode
63-
modeService.getOrCreateMode(language);
63+
modeService.triggerMode(language);
6464

6565
let tokenizationSupport = TokenizationRegistry.get(language);
6666
if (tokenizationSupport) {

0 commit comments

Comments
 (0)