Path: blob/main/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.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 { Emitter, Event } from '../../../../../base/common/event.js';6import { toDisposable } from '../../../../../base/common/lifecycle.js';7import { StandardTokenType } from '../../../encodedTokenAttributes.js';8import { ILanguageIdCodec } from '../../../languages.js';9import { IModelContentChangedEvent } from '../../../textModelEvents.js';10import { BackgroundTokenizationState } from '../../../tokenizationTextModelPart.js';11import { LineTokens } from '../../../tokens/lineTokens.js';12import { TextModel } from '../../textModel.js';13import { AbstractSyntaxTokenBackend } from '../abstractSyntaxTokenBackend.js';14import { autorun, derived, IObservable, ObservablePromise } from '../../../../../base/common/observable.js';15import { TreeSitterTree } from './treeSitterTree.js';16import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';17import { TreeSitterTokenizationImpl } from './treeSitterTokenizationImpl.js';18import { ITreeSitterLibraryService } from '../../../services/treeSitter/treeSitterLibraryService.js';19import { LineRange } from '../../../core/ranges/lineRange.js';2021export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend {22protected _backgroundTokenizationState: BackgroundTokenizationState = BackgroundTokenizationState.InProgress;23protected readonly _onDidChangeBackgroundTokenizationState: Emitter<void> = this._register(new Emitter<void>());24public readonly onDidChangeBackgroundTokenizationState: Event<void> = this._onDidChangeBackgroundTokenizationState.event;2526private readonly _tree: IObservable<TreeSitterTree | undefined>;27private readonly _tokenizationImpl: IObservable<TreeSitterTokenizationImpl | undefined>;2829constructor(30private readonly _languageIdObs: IObservable<string>,31languageIdCodec: ILanguageIdCodec,32textModel: TextModel,33visibleLineRanges: IObservable<readonly LineRange[]>,34@ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService,35@IInstantiationService private readonly _instantiationService: IInstantiationService36) {37super(languageIdCodec, textModel);383940const parserClassPromise = new ObservablePromise(this._treeSitterLibraryService.getParserClass());414243const parserClassObs = derived(this, reader => {44const parser = parserClassPromise.promiseResult?.read(reader)?.getDataOrThrow();45return parser;46});474849this._tree = derived(this, reader => {50const parserClass = parserClassObs.read(reader);51if (!parserClass) {52return undefined;53}5455const currentLanguage = this._languageIdObs.read(reader);56const treeSitterLang = this._treeSitterLibraryService.getLanguage(currentLanguage, reader);57if (!treeSitterLang) {58return undefined;59}6061const parser = new parserClass();62reader.store.add(toDisposable(() => {63parser.delete();64}));65parser.setLanguage(treeSitterLang);6667const queries = this._treeSitterLibraryService.getInjectionQueries(currentLanguage, reader);68if (queries === undefined) {69return undefined;70}7172return reader.store.add(this._instantiationService.createInstance(TreeSitterTree, currentLanguage, undefined, parser, parserClass, /*queries, */this._textModel));73});747576this._tokenizationImpl = derived(this, reader => {77const treeModel = this._tree.read(reader);78if (!treeModel) {79return undefined;80}8182const queries = this._treeSitterLibraryService.getHighlightingQueries(treeModel.languageId, reader);83if (!queries) {84return undefined;85}8687return reader.store.add(this._instantiationService.createInstance(TreeSitterTokenizationImpl, treeModel, queries, this._languageIdCodec, visibleLineRanges));88});8990this._register(autorun(reader => {91const tokModel = this._tokenizationImpl.read(reader);92if (!tokModel) {93return;94}95reader.store.add(tokModel.onDidChangeTokens((e) => {96this._onDidChangeTokens.fire(e.changes);97}));98reader.store.add(tokModel.onDidChangeBackgroundTokenization(e => {99this._backgroundTokenizationState = BackgroundTokenizationState.Completed;100this._onDidChangeBackgroundTokenizationState.fire();101}));102}));103}104105get tree(): IObservable<TreeSitterTree | undefined> {106return this._tree;107}108109get tokenizationImpl(): IObservable<TreeSitterTokenizationImpl | undefined> {110return this._tokenizationImpl;111}112113public getLineTokens(lineNumber: number): LineTokens {114const model = this._tokenizationImpl.get();115if (!model) {116const content = this._textModel.getLineContent(lineNumber);117return LineTokens.createEmpty(content, this._languageIdCodec);118}119return model.getLineTokens(lineNumber);120}121122public todo_resetTokenization(fireTokenChangeEvent: boolean = true): void {123if (fireTokenChangeEvent) {124this._onDidChangeTokens.fire({125semanticTokensApplied: false,126ranges: [127{128fromLineNumber: 1,129toLineNumber: this._textModel.getLineCount(),130},131],132});133}134}135136public override handleDidChangeAttached(): void {137// TODO @alexr00 implement for background tokenization138}139140public override handleDidChangeContent(e: IModelContentChangedEvent): void {141if (e.isFlush) {142// Don't fire the event, as the view might not have got the text change event yet143this.todo_resetTokenization(false);144} else {145const model = this._tokenizationImpl.get();146model?.handleContentChanged(e);147}148149const treeModel = this._tree.get();150treeModel?.handleContentChange(e);151}152153public override forceTokenization(lineNumber: number): void {154const model = this._tokenizationImpl.get();155if (!model) {156return;157}158if (!model.hasAccurateTokensForLine(lineNumber)) {159model.tokenizeEncoded(lineNumber);160}161}162163public override hasAccurateTokensForLine(lineNumber: number): boolean {164const model = this._tokenizationImpl.get();165if (!model) {166return false;167}168return model.hasAccurateTokensForLine(lineNumber);169}170171public override isCheapToTokenize(lineNumber: number): boolean {172// TODO @alexr00 determine what makes it cheap to tokenize?173return true;174}175176public override getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType {177// TODO @alexr00 implement once we have custom parsing and don't just feed in the whole text model value178return StandardTokenType.Other;179}180181public override tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null {182const model = this._tokenizationImpl.get();183if (!model) {184return null;185}186return model.tokenizeLinesAt(lineNumber, lines);187}188189public override get hasTokens(): boolean {190const model = this._tokenizationImpl.get();191if (!model) {192return false;193}194return model.hasTokens();195}196}197198199