Path: blob/main/src/vs/editor/common/model/tokens/tokenizerSyntaxTokenBackend.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { onUnexpectedError } from '../../../../base/common/errors.js';6import { Emitter, Event } from '../../../../base/common/event.js';7import { MutableDisposable, DisposableMap } from '../../../../base/common/lifecycle.js';8import { countEOL } from '../../core/misc/eolCounter.js';9import { Position } from '../../core/position.js';10import { LineRange } from '../../core/ranges/lineRange.js';11import { StandardTokenType } from '../../encodedTokenAttributes.js';12import { IBackgroundTokenizer, IState, ILanguageIdCodec, TokenizationRegistry, ITokenizationSupport, IBackgroundTokenizationStore } from '../../languages.js';13import { IAttachedView } from '../../model.js';14import { IModelContentChangedEvent } from '../../textModelEvents.js';15import { BackgroundTokenizationState } from '../../tokenizationTextModelPart.js';16import { ContiguousMultilineTokens } from '../../tokens/contiguousMultilineTokens.js';17import { ContiguousMultilineTokensBuilder } from '../../tokens/contiguousMultilineTokensBuilder.js';18import { ContiguousTokensStore } from '../../tokens/contiguousTokensStore.js';19import { LineTokens } from '../../tokens/lineTokens.js';20import { TextModel } from '../textModel.js';21import { TokenizerWithStateStoreAndTextModel, DefaultBackgroundTokenizer, TrackingTokenizationStateStore } from '../textModelTokens.js';22import { AbstractSyntaxTokenBackend, AttachedViewHandler, AttachedViews } from './abstractSyntaxTokenBackend.js';2324/** For TextMate */25export class TokenizerSyntaxTokenBackend extends AbstractSyntaxTokenBackend {26private _tokenizer: TokenizerWithStateStoreAndTextModel | null = null;27protected _backgroundTokenizationState: BackgroundTokenizationState = BackgroundTokenizationState.InProgress;28protected readonly _onDidChangeBackgroundTokenizationState: Emitter<void> = this._register(new Emitter<void>());29public readonly onDidChangeBackgroundTokenizationState: Event<void> = this._onDidChangeBackgroundTokenizationState.event;3031private _defaultBackgroundTokenizer: DefaultBackgroundTokenizer | null = null;32private readonly _backgroundTokenizer = this._register(new MutableDisposable<IBackgroundTokenizer>());3334private readonly _tokens = new ContiguousTokensStore(this._languageIdCodec);35private _debugBackgroundTokens: ContiguousTokensStore | undefined;36private _debugBackgroundStates: TrackingTokenizationStateStore<IState> | undefined;3738private readonly _debugBackgroundTokenizer = this._register(new MutableDisposable<IBackgroundTokenizer>());3940private readonly _attachedViewStates = this._register(new DisposableMap<IAttachedView, AttachedViewHandler>());4142constructor(43languageIdCodec: ILanguageIdCodec,44textModel: TextModel,45private readonly getLanguageId: () => string,46attachedViews: AttachedViews,47) {48super(languageIdCodec, textModel);4950this._register(TokenizationRegistry.onDidChange((e) => {51const languageId = this.getLanguageId();52if (e.changedLanguages.indexOf(languageId) === -1) {53return;54}55this.todo_resetTokenization();56}));5758this.todo_resetTokenization();5960this._register(attachedViews.onDidChangeVisibleRanges(({ view, state }) => {61if (state) {62let existing = this._attachedViewStates.get(view);63if (!existing) {64existing = new AttachedViewHandler(() => this.refreshRanges(existing!.lineRanges));65this._attachedViewStates.set(view, existing);66}67existing.handleStateChange(state);68} else {69this._attachedViewStates.deleteAndDispose(view);70}71}));72}7374public todo_resetTokenization(fireTokenChangeEvent: boolean = true): void {75this._tokens.flush();76this._debugBackgroundTokens?.flush();77if (this._debugBackgroundStates) {78this._debugBackgroundStates = new TrackingTokenizationStateStore(this._textModel.getLineCount());79}80if (fireTokenChangeEvent) {81this._onDidChangeTokens.fire({82semanticTokensApplied: false,83ranges: [84{85fromLineNumber: 1,86toLineNumber: this._textModel.getLineCount(),87},88],89});90}9192const initializeTokenization = (): [ITokenizationSupport, IState] | [null, null] => {93if (this._textModel.isTooLargeForTokenization()) {94return [null, null];95}96const tokenizationSupport = TokenizationRegistry.get(this.getLanguageId());97if (!tokenizationSupport) {98return [null, null];99}100let initialState: IState;101try {102initialState = tokenizationSupport.getInitialState();103} catch (e) {104onUnexpectedError(e);105return [null, null];106}107return [tokenizationSupport, initialState];108};109110const [tokenizationSupport, initialState] = initializeTokenization();111if (tokenizationSupport && initialState) {112this._tokenizer = new TokenizerWithStateStoreAndTextModel(this._textModel.getLineCount(), tokenizationSupport, this._textModel, this._languageIdCodec);113} else {114this._tokenizer = null;115}116117this._backgroundTokenizer.clear();118119this._defaultBackgroundTokenizer = null;120if (this._tokenizer) {121const b: IBackgroundTokenizationStore = {122setTokens: (tokens) => {123this.setTokens(tokens);124},125backgroundTokenizationFinished: () => {126if (this._backgroundTokenizationState === BackgroundTokenizationState.Completed) {127// We already did a full tokenization and don't go back to progressing.128return;129}130const newState = BackgroundTokenizationState.Completed;131this._backgroundTokenizationState = newState;132this._onDidChangeBackgroundTokenizationState.fire();133},134setEndState: (lineNumber, state) => {135if (!this._tokenizer) { return; }136const firstInvalidEndStateLineNumber = this._tokenizer.store.getFirstInvalidEndStateLineNumber();137// Don't accept states for definitely valid states, the renderer is ahead of the worker!138if (firstInvalidEndStateLineNumber !== null && lineNumber >= firstInvalidEndStateLineNumber) {139this._tokenizer?.store.setEndState(lineNumber, state);140}141},142};143144if (tokenizationSupport && tokenizationSupport.createBackgroundTokenizer && !tokenizationSupport.backgroundTokenizerShouldOnlyVerifyTokens) {145this._backgroundTokenizer.value = tokenizationSupport.createBackgroundTokenizer(this._textModel, b);146}147if (!this._backgroundTokenizer.value && !this._textModel.isTooLargeForTokenization()) {148this._backgroundTokenizer.value = this._defaultBackgroundTokenizer =149new DefaultBackgroundTokenizer(this._tokenizer, b);150this._defaultBackgroundTokenizer.handleChanges();151}152153if (tokenizationSupport?.backgroundTokenizerShouldOnlyVerifyTokens && tokenizationSupport.createBackgroundTokenizer) {154this._debugBackgroundTokens = new ContiguousTokensStore(this._languageIdCodec);155this._debugBackgroundStates = new TrackingTokenizationStateStore(this._textModel.getLineCount());156this._debugBackgroundTokenizer.clear();157this._debugBackgroundTokenizer.value = tokenizationSupport.createBackgroundTokenizer(this._textModel, {158setTokens: (tokens) => {159this._debugBackgroundTokens?.setMultilineTokens(tokens, this._textModel);160},161backgroundTokenizationFinished() {162// NO OP163},164setEndState: (lineNumber, state) => {165this._debugBackgroundStates?.setEndState(lineNumber, state);166},167});168} else {169this._debugBackgroundTokens = undefined;170this._debugBackgroundStates = undefined;171this._debugBackgroundTokenizer.value = undefined;172}173}174175this.refreshAllVisibleLineTokens();176}177178public handleDidChangeAttached() {179this._defaultBackgroundTokenizer?.handleChanges();180}181182public handleDidChangeContent(e: IModelContentChangedEvent): void {183if (e.isFlush) {184// Don't fire the event, as the view might not have got the text change event yet185this.todo_resetTokenization(false);186} else if (!e.isEolChange) { // We don't have to do anything on an EOL change187for (const c of e.changes) {188const [eolCount, firstLineLength] = countEOL(c.text);189190this._tokens.acceptEdit(c.range, eolCount, firstLineLength);191this._debugBackgroundTokens?.acceptEdit(c.range, eolCount, firstLineLength);192}193this._debugBackgroundStates?.acceptChanges(e.changes);194195if (this._tokenizer) {196this._tokenizer.store.acceptChanges(e.changes);197}198this._defaultBackgroundTokenizer?.handleChanges();199}200}201202private setTokens(tokens: ContiguousMultilineTokens[]): { changes: { fromLineNumber: number; toLineNumber: number }[] } {203const { changes } = this._tokens.setMultilineTokens(tokens, this._textModel);204205if (changes.length > 0) {206this._onDidChangeTokens.fire({ semanticTokensApplied: false, ranges: changes, });207}208209return { changes: changes };210}211212private refreshAllVisibleLineTokens(): void {213const ranges = LineRange.joinMany([...this._attachedViewStates].map(([_, s]) => s.lineRanges));214this.refreshRanges(ranges);215}216217private refreshRanges(ranges: readonly LineRange[]): void {218for (const range of ranges) {219this.refreshRange(range.startLineNumber, range.endLineNumberExclusive - 1);220}221}222223private refreshRange(startLineNumber: number, endLineNumber: number): void {224if (!this._tokenizer) {225return;226}227228startLineNumber = Math.max(1, Math.min(this._textModel.getLineCount(), startLineNumber));229endLineNumber = Math.min(this._textModel.getLineCount(), endLineNumber);230231const builder = new ContiguousMultilineTokensBuilder();232const { heuristicTokens } = this._tokenizer.tokenizeHeuristically(builder, startLineNumber, endLineNumber);233const changedTokens = this.setTokens(builder.finalize());234235if (heuristicTokens) {236// We overrode tokens with heuristically computed ones.237// Because old states might get reused (thus stopping invalidation),238// we have to explicitly request the tokens for the changed ranges again.239for (const c of changedTokens.changes) {240this._backgroundTokenizer.value?.requestTokens(c.fromLineNumber, c.toLineNumber + 1);241}242}243244this._defaultBackgroundTokenizer?.checkFinished();245}246247public forceTokenization(lineNumber: number): void {248const builder = new ContiguousMultilineTokensBuilder();249this._tokenizer?.updateTokensUntilLine(builder, lineNumber);250this.setTokens(builder.finalize());251this._defaultBackgroundTokenizer?.checkFinished();252}253254public hasAccurateTokensForLine(lineNumber: number): boolean {255if (!this._tokenizer) {256return true;257}258return this._tokenizer.hasAccurateTokensForLine(lineNumber);259}260261public isCheapToTokenize(lineNumber: number): boolean {262if (!this._tokenizer) {263return true;264}265return this._tokenizer.isCheapToTokenize(lineNumber);266}267268public getLineTokens(lineNumber: number): LineTokens {269const lineText = this._textModel.getLineContent(lineNumber);270const result = this._tokens.getTokens(271this._textModel.getLanguageId(),272lineNumber - 1,273lineText274);275if (this._debugBackgroundTokens && this._debugBackgroundStates && this._tokenizer) {276if (this._debugBackgroundStates.getFirstInvalidEndStateLineNumberOrMax() > lineNumber && this._tokenizer.store.getFirstInvalidEndStateLineNumberOrMax() > lineNumber) {277const backgroundResult = this._debugBackgroundTokens.getTokens(278this._textModel.getLanguageId(),279lineNumber - 1,280lineText281);282if (!result.equals(backgroundResult) && this._debugBackgroundTokenizer.value?.reportMismatchingTokens) {283this._debugBackgroundTokenizer.value.reportMismatchingTokens(lineNumber);284}285}286}287return result;288}289290public getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType {291if (!this._tokenizer) {292return StandardTokenType.Other;293}294295const position = this._textModel.validatePosition(new Position(lineNumber, column));296this.forceTokenization(position.lineNumber);297return this._tokenizer.getTokenTypeIfInsertingCharacter(position, character);298}299300301public tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null {302if (!this._tokenizer) {303return null;304}305this.forceTokenization(lineNumber);306return this._tokenizer.tokenizeLinesAt(lineNumber, lines);307}308309public get hasTokens(): boolean {310return this._tokens.hasTokens;311}312}313314315