Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ILanguageSelection directly to the text model #175344

Merged
merged 1 commit into from Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/vs/editor/common/model.ts
Expand Up @@ -22,6 +22,7 @@ import { IGuidesTextModelPart } from 'vs/editor/common/textModelGuides';
import { ITokenizationTextModelPart } from 'vs/editor/common/tokenizationTextModelPart';
import { ThemeColor } from 'vs/base/common/themables';
import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
import { ILanguageSelection } from 'vs/editor/common/languages/language';

/**
* Vertical Lane in the overview ruler of the editor.
Expand Down Expand Up @@ -871,7 +872,15 @@ export interface ITextModel {
* @param source The source of the call that set the language.
* @internal
*/
setMode(languageId: string, source?: string): void;
setLanguage(languageId: string, source?: string): void;

/**
* Set the current language mode associated with the model.
* @param languageSelection The new language selection.
* @param source The source of the call that set the language.
* @internal
*/
setLanguage(languageSelection: ILanguageSelection, source?: string): void;

/**
* Returns the real (inner-most) language mode at a given position.
Expand Down
24 changes: 20 additions & 4 deletions src/vs/editor/common/model/textModel.ts
Expand Up @@ -9,7 +9,7 @@ import { Color } from 'vs/base/common/color';
import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { combinedDisposable, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { combinedDisposable, Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { listenStream } from 'vs/base/common/stream';
import * as strings from 'vs/base/common/strings';
import { Constants } from 'vs/base/common/uint';
Expand All @@ -24,7 +24,7 @@ import { TextChange } from 'vs/editor/common/core/textChange';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/core/textModelDefaults';
import { IWordAtPosition } from 'vs/editor/common/core/wordHelper';
import { FormattingOptions } from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import * as model from 'vs/editor/common/model';
import { BracketPairsTextModelPart } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl';
Expand Down Expand Up @@ -243,6 +243,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
private _buffer: model.ITextBuffer;
private _bufferDisposable: IDisposable;
private _options: model.TextModelResolvedOptions;
private _languageSelectionListener = this._register(new MutableDisposable<IDisposable>());

private _isDisposed: boolean;
private __isDisposing: boolean;
Expand Down Expand Up @@ -287,7 +288,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati

constructor(
source: string | model.ITextBufferFactory,
languageId: string,
languageIdOrSelection: string | ILanguageSelection,
creationOptions: model.ITextModelCreationOptions,
associatedResource: URI | null = null,
@IUndoRedoService private readonly _undoRedoService: IUndoRedoService,
Expand All @@ -313,6 +314,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati

this._options = TextModel.resolveOptions(this._buffer, creationOptions);

const languageId = (typeof languageIdOrSelection === 'string' ? languageIdOrSelection : languageIdOrSelection.languageId);
if (typeof languageIdOrSelection !== 'string') {
this._languageSelectionListener.value = languageIdOrSelection.onDidChange(() => this._setLanguage(languageIdOrSelection.languageId));
}

this._bracketPairs = this._register(new BracketPairsTextModelPart(this, this._languageConfigurationService));
this._guidesTextModelPart = this._register(new GuidesTextModelPart(this, this._languageConfigurationService));
this._decorationProvider = this._register(new ColorizedBracketPairsDecorationProvider(this));
Expand Down Expand Up @@ -1907,7 +1913,17 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
return this.tokenization.getLanguageId();
}

public setMode(languageId: string, source?: string): void {
public setLanguage(languageIdOrSelection: string | ILanguageSelection, source?: string): void {
if (typeof languageIdOrSelection === 'string') {
this._languageSelectionListener.clear();
this._setLanguage(languageIdOrSelection, source);
} else {
this._languageSelectionListener.value = languageIdOrSelection.onDidChange(() => this._setLanguage(languageIdOrSelection.languageId, source));
this._setLanguage(languageIdOrSelection.languageId, source);
}
}

private _setLanguage(languageId: string, source?: string): void {
this.tokenization.setLanguageId(languageId, source);
this._languageService.requestRichLanguageFeatures(languageId);
}
Expand Down
2 changes: 0 additions & 2 deletions src/vs/editor/common/services/model.ts
Expand Up @@ -21,8 +21,6 @@ export interface IModelService {

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

setMode(model: ITextModel, languageSelection: ILanguageSelection, source?: string): void;

destroyModel(resource: URI): void;

getModels(): ITextModel[];
Expand Down
49 changes: 7 additions & 42 deletions src/vs/editor/common/services/modelService.ts
Expand Up @@ -40,46 +40,22 @@ function computeModelSha1(model: ITextModel): string {
return shaComputer.digest();
}


class ModelData implements IDisposable {
public readonly model: TextModel;

private _languageSelection: ILanguageSelection | null;
private _languageSelectionListener: IDisposable | null;

private readonly _modelEventListeners = new DisposableStore();

constructor(
model: TextModel,
public readonly model: TextModel,
onWillDispose: (model: ITextModel) => void,
onDidChangeLanguage: (model: ITextModel, e: IModelLanguageChangedEvent) => void
) {
this.model = model;

this._languageSelection = null;
this._languageSelectionListener = null;

this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model)));
this._modelEventListeners.add(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e)));
}

private _disposeLanguageSelection(): void {
if (this._languageSelectionListener) {
this._languageSelectionListener.dispose();
this._languageSelectionListener = null;
}
}

public dispose(): void {
this._modelEventListeners.dispose();
this._disposeLanguageSelection();
}

public setLanguage(languageSelection: ILanguageSelection, source?: string): void {
this._disposeLanguageSelection();
this._languageSelection = languageSelection;
this._languageSelectionListener = this._languageSelection.onDidChange(() => this.model.setMode(languageSelection.languageId, source));
this.model.setMode(languageSelection.languageId, source);
}
}

Expand Down Expand Up @@ -242,7 +218,8 @@ export class ModelService extends Disposable implements IModelService {
return true;
}

public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions {
public getCreationOptions(languageIdOrSelection: string | ILanguageSelection, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions {
const language = (typeof languageIdOrSelection === 'string' ? languageIdOrSelection : languageIdOrSelection.languageId);
let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
if (!creationOptions) {
const editor = this._configurationService.getValue<IRawEditorConfig>('editor', { overrideIdentifier: language, resource });
Expand Down Expand Up @@ -345,12 +322,12 @@ export class ModelService extends Disposable implements IModelService {
}
}

private _createModelData(value: string | ITextBufferFactory, languageId: string, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
private _createModelData(value: string | ITextBufferFactory, languageIdOrSelection: string | ILanguageSelection, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
// create & save the model
const options = this.getCreationOptions(languageId, resource, isForSimpleWidget);
const options = this.getCreationOptions(languageIdOrSelection, resource, isForSimpleWidget);
const model: TextModel = new TextModel(
value,
languageId,
languageIdOrSelection,
options,
resource,
this._undoRedoService,
Expand Down Expand Up @@ -478,8 +455,7 @@ export class ModelService extends Disposable implements IModelService {
let modelData: ModelData;

if (languageSelection) {
modelData = this._createModelData(value, languageSelection.languageId, resource, isForSimpleWidget);
this.setMode(modelData.model, languageSelection);
modelData = this._createModelData(value, languageSelection, resource, isForSimpleWidget);
} else {
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_ID, resource, isForSimpleWidget);
}
Expand All @@ -489,17 +465,6 @@ export class ModelService extends Disposable implements IModelService {
return modelData.model;
}

public setMode(model: ITextModel, languageSelection: ILanguageSelection, source?: string): void {
if (!languageSelection) {
return;
}
const modelData = this._models[MODEL_ID(model.uri)];
if (!modelData) {
return;
}
modelData.setLanguage(languageSelection, source);
}

public destroyModel(resource: URI): void {
// We need to support that not all models get disposed through this service (i.e. model.dispose() should work!)
const modelData = this._models[MODEL_ID(resource)];
Expand Down
3 changes: 1 addition & 2 deletions src/vs/editor/standalone/browser/standaloneEditor.ts
Expand Up @@ -240,9 +240,8 @@ export function createModel(value: string, language?: string, uri?: URI): ITextM
*/
export function setModelLanguage(model: ITextModel, mimeTypeOrLanguageId: string): void {
const languageService = StandaloneServices.get(ILanguageService);
const modelService = StandaloneServices.get(IModelService);
const languageId = languageService.getLanguageIdByMimeType(mimeTypeOrLanguageId) || mimeTypeOrLanguageId || PLAINTEXT_LANGUAGE_ID;
modelService.setMode(model, languageService.createById(languageId));
model.setLanguage(languageService.createById(languageId));
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/test/browser/widget/codeEditorWidget.test.ts
Expand Up @@ -41,7 +41,7 @@ suite('CodeEditorWidget', () => {
invoked = true;
}));

viewModel.model.setMode('testMode');
viewModel.model.setLanguage('testMode');

assert.deepStrictEqual(invoked, true);

Expand All @@ -55,7 +55,7 @@ suite('CodeEditorWidget', () => {
const languageService = instantiationService.get(ILanguageService);
const disposables = new DisposableStore();
disposables.add(languageService.registerLanguage({ id: 'testMode' }));
viewModel.model.setMode('testMode');
viewModel.model.setLanguage('testMode');

let invoked = false;
disposables.add(editor.onDidChangeModelLanguageConfiguration((e) => {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/test/common/model/textModelWithTokens.test.ts
Expand Up @@ -586,12 +586,12 @@ suite('TextModelWithTokens regression tests', () => {
assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]);
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]);

model.setMode(LANG_ID1);
model.setLanguage(LANG_ID1);

assertViewLineTokens(model, 1, true, [createViewLineToken(12, 11)]);
assertViewLineTokens(model, 2, true, [createViewLineToken(9, 12)]);

model.setMode(LANG_ID2);
model.setLanguage(LANG_ID2);

assertViewLineTokens(model, 1, false, [createViewLineToken(12, 1)]);
assertViewLineTokens(model, 2, false, [createViewLineToken(9, 1)]);
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadLanguages.ts
Expand Up @@ -52,7 +52,7 @@ export class MainThreadLanguages implements MainThreadLanguagesShape {
const uri = URI.revive(resource);
const ref = await this._resolverService.createModelReference(uri);
try {
this._modelService.setMode(ref.object.textEditorModel, this._languageService.createById(languageId));
ref.object.textEditorModel.setLanguage(this._languageService.createById(languageId));
} finally {
ref.dispose();
}
Expand Down
Expand Up @@ -207,7 +207,7 @@ export class TextResourceEditor extends AbstractTextResourceEditor {
// High confidence, set language id at TextEditorModel level to block future auto-detection
this.input.model.setLanguageId(candidateLanguage.id);
} else {
this.modelService.setMode(textModel, this.languageService.createById(candidateLanguage.id));
textModel.setLanguage(this.languageService.createById(candidateLanguage.id));
}

const opts = this.modelService.getCreationOptions(textModel.getLanguageId(), textModel.uri, textModel.isForSimpleWidget);
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/common/editor/textEditorModel.ts
Expand Up @@ -95,7 +95,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
return;
}

this.modelService.setMode(this.textEditorModel, this.languageService.createById(languageId), source);
this.textEditorModel.setLanguage(this.languageService.createById(languageId), source);
}

protected installModelListeners(model: ITextModel): void {
Expand Down Expand Up @@ -211,7 +211,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel

// language (only if specific and changed)
if (preferredLanguageId && preferredLanguageId !== PLAINTEXT_LANGUAGE_ID && this.textEditorModel.getLanguageId() !== preferredLanguageId) {
this.modelService.setMode(this.textEditorModel, this.languageService.createById(preferredLanguageId));
this.textEditorModel.setLanguage(this.languageService.createById(preferredLanguageId));
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/debug/browser/breakpointWidget.ts
Expand Up @@ -169,7 +169,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
if (this.editor.hasModel()) {
// Use plaintext language for log messages, otherwise respect underlying editor language #125619
const languageId = this.context === Context.LOG_MESSAGE ? PLAINTEXT_LANGUAGE_ID : this.editor.getModel().getLanguageId();
this.input.getModel().setMode(languageId);
this.input.getModel().setLanguage(languageId);
}
}

Expand Down Expand Up @@ -229,7 +229,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
CONTEXT_IN_BREAKPOINT_WIDGET.bindTo(scopedContextKeyService).set(true);
const model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:${this.editor.getId()}:breakpointinput`), true);
if (this.editor.hasModel()) {
model.setMode(this.editor.getModel().getLanguageId());
model.setLanguage(this.editor.getModel().getLanguageId());
}
this.input.setModel(model);
this.setInputMode();
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/debug/browser/repl.ts
Expand Up @@ -342,7 +342,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget {
this.modelChangeListener.dispose();
this.modelChangeListener = activeEditorControl.onDidChangeModelLanguage(() => this.setMode());
if (this.model && activeEditorControl.hasModel()) {
this.model.setMode(activeEditorControl.getModel().getLanguageId());
this.model.setLanguage(activeEditorControl.getModel().getLanguageId());
}
}
}
Expand Down
Expand Up @@ -39,7 +39,7 @@ suite('Emmet', () => {
assert.fail('Editor model not found');
}

model.setMode(mode);
model.setLanguage(mode);
const langOutput = EmmetEditorAction.getLanguage(editor, new MockGrammarContributions(scopeName));
if (!langOutput) {
assert.fail('langOutput not found');
Expand Down
Expand Up @@ -554,7 +554,7 @@ export class InteractiveEditor extends EditorPane {
if (selectedOrSuggested) {
const language = selectedOrSuggested.supportedLanguages[0];
const newMode = language ? this.#languageService.createById(language).languageId : PLAINTEXT_LANGUAGE_ID;
textModel.setMode(newMode);
textModel.setLanguage(newMode);

NOTEBOOK_KERNEL.bindTo(this.#contextKeyService).set(selectedOrSuggested.id);
}
Expand Down
Expand Up @@ -139,7 +139,7 @@ registerAction2(class extends Action2 {
if (editorUri) {
const lang = await languageDetectionService.detectLanguage(editorUri);
if (lang) {
editor.getModel()?.setMode(lang, LanguageDetectionLanguageEventSource);
editor.getModel()?.setLanguage(lang, LanguageDetectionLanguageEventSource);
} else {
notificationService.warn(localize('noDetection', "Unable to detect editor language"));
}
Expand Down
Expand Up @@ -10,7 +10,6 @@ import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { localize } from 'vs/nls';
import { IResourceUndoRedoElement, IUndoRedoService, UndoRedoElementType, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
Expand Down Expand Up @@ -59,7 +58,6 @@ export class MergeEditorModel extends EditorModel {
private readonly diffComputer: IMergeDiffComputer,
private readonly options: { resetResult: boolean },
public readonly telemetry: MergeEditorTelemetry,
@IModelService private readonly modelService: IModelService,
@ILanguageService private readonly languageService: ILanguageService,
@IUndoRedoService private readonly undoRedoService: IUndoRedoService,
) {
Expand Down Expand Up @@ -558,10 +556,10 @@ export class MergeEditorModel extends EditorModel {

public setLanguageId(languageId: string, source?: string): void {
const language = this.languageService.createById(languageId);
this.modelService.setMode(this.base, language, source);
this.modelService.setMode(this.input1.textModel, language, source);
this.modelService.setMode(this.input2.textModel, language, source);
this.modelService.setMode(this.resultTextModel, language, source);
this.base.setLanguage(language, source);
this.input1.textModel.setLanguage(language, source);
this.input2.textModel.setLanguage(language, source);
this.resultTextModel.setLanguage(language, source);
}

public getInitialResultValue(): string {
Expand Down
Expand Up @@ -506,7 +506,7 @@ class CellInfoContentProvider {
}

model.setValue(newResult.content);
model.setMode(newResult.mode.languageId);
model.setLanguage(newResult.mode.languageId);
});

const once = model.onWillDispose(() => {
Expand Down
Expand Up @@ -93,7 +93,7 @@ export class NotebookCellTextModel extends Disposable implements ICell {

if (this._textModel) {
const languageId = this._languageService.createById(newLanguageId);
this._textModel.setMode(languageId.languageId);
this._textModel.setLanguage(languageId.languageId);
}

if (this._language === newLanguage) {
Expand Down
Expand Up @@ -95,7 +95,7 @@ class PerfModelContentProvider implements ITextModelContentProvider {
this._model = this._modelService.getModel(resource) || this._modelService.createModel('Loading...', langId, resource);

this._modelDisposables.push(langId.onDidChange(e => {
this._model?.setMode(e);
this._model?.setLanguage(e);
}));
this._modelDisposables.push(this._extensionService.onDidChangeExtensionsStatus(this._updateModel, this));

Expand Down