Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/model/tokens/treeSitter/treeSitterSyntaxTokenBackend.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event } from '../../../../../base/common/event.js';
7
import { toDisposable } from '../../../../../base/common/lifecycle.js';
8
import { StandardTokenType } from '../../../encodedTokenAttributes.js';
9
import { ILanguageIdCodec } from '../../../languages.js';
10
import { IModelContentChangedEvent } from '../../../textModelEvents.js';
11
import { BackgroundTokenizationState } from '../../../tokenizationTextModelPart.js';
12
import { LineTokens } from '../../../tokens/lineTokens.js';
13
import { TextModel } from '../../textModel.js';
14
import { AbstractSyntaxTokenBackend } from '../abstractSyntaxTokenBackend.js';
15
import { autorun, derived, IObservable, ObservablePromise } from '../../../../../base/common/observable.js';
16
import { TreeSitterTree } from './treeSitterTree.js';
17
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
18
import { TreeSitterTokenizationImpl } from './treeSitterTokenizationImpl.js';
19
import { ITreeSitterLibraryService } from '../../../services/treeSitter/treeSitterLibraryService.js';
20
import { LineRange } from '../../../core/ranges/lineRange.js';
21
22
export class TreeSitterSyntaxTokenBackend extends AbstractSyntaxTokenBackend {
23
protected _backgroundTokenizationState: BackgroundTokenizationState = BackgroundTokenizationState.InProgress;
24
protected readonly _onDidChangeBackgroundTokenizationState: Emitter<void> = this._register(new Emitter<void>());
25
public readonly onDidChangeBackgroundTokenizationState: Event<void> = this._onDidChangeBackgroundTokenizationState.event;
26
27
private readonly _tree: IObservable<TreeSitterTree | undefined>;
28
private readonly _tokenizationImpl: IObservable<TreeSitterTokenizationImpl | undefined>;
29
30
constructor(
31
private readonly _languageIdObs: IObservable<string>,
32
languageIdCodec: ILanguageIdCodec,
33
textModel: TextModel,
34
visibleLineRanges: IObservable<readonly LineRange[]>,
35
@ITreeSitterLibraryService private readonly _treeSitterLibraryService: ITreeSitterLibraryService,
36
@IInstantiationService private readonly _instantiationService: IInstantiationService
37
) {
38
super(languageIdCodec, textModel);
39
40
41
const parserClassPromise = new ObservablePromise(this._treeSitterLibraryService.getParserClass());
42
43
44
const parserClassObs = derived(this, reader => {
45
const parser = parserClassPromise.promiseResult?.read(reader)?.getDataOrThrow();
46
return parser;
47
});
48
49
50
this._tree = derived(this, reader => {
51
const parserClass = parserClassObs.read(reader);
52
if (!parserClass) {
53
return undefined;
54
}
55
56
const currentLanguage = this._languageIdObs.read(reader);
57
const treeSitterLang = this._treeSitterLibraryService.getLanguage(currentLanguage, reader);
58
if (!treeSitterLang) {
59
return undefined;
60
}
61
62
const parser = new parserClass();
63
reader.store.add(toDisposable(() => {
64
parser.delete();
65
}));
66
parser.setLanguage(treeSitterLang);
67
68
const queries = this._treeSitterLibraryService.getInjectionQueries(currentLanguage, reader);
69
if (queries === undefined) {
70
return undefined;
71
}
72
73
return reader.store.add(this._instantiationService.createInstance(TreeSitterTree, currentLanguage, undefined, parser, parserClass, /*queries, */this._textModel));
74
});
75
76
77
this._tokenizationImpl = derived(this, reader => {
78
const treeModel = this._tree.read(reader);
79
if (!treeModel) {
80
return undefined;
81
}
82
83
const queries = this._treeSitterLibraryService.getHighlightingQueries(treeModel.languageId, reader);
84
if (!queries) {
85
return undefined;
86
}
87
88
return reader.store.add(this._instantiationService.createInstance(TreeSitterTokenizationImpl, treeModel, queries, this._languageIdCodec, visibleLineRanges));
89
});
90
91
this._register(autorun(reader => {
92
const tokModel = this._tokenizationImpl.read(reader);
93
if (!tokModel) {
94
return;
95
}
96
reader.store.add(tokModel.onDidChangeTokens((e) => {
97
this._onDidChangeTokens.fire(e.changes);
98
}));
99
reader.store.add(tokModel.onDidChangeBackgroundTokenization(e => {
100
this._backgroundTokenizationState = BackgroundTokenizationState.Completed;
101
this._onDidChangeBackgroundTokenizationState.fire();
102
}));
103
}));
104
}
105
106
get tree(): IObservable<TreeSitterTree | undefined> {
107
return this._tree;
108
}
109
110
get tokenizationImpl(): IObservable<TreeSitterTokenizationImpl | undefined> {
111
return this._tokenizationImpl;
112
}
113
114
public getLineTokens(lineNumber: number): LineTokens {
115
const model = this._tokenizationImpl.get();
116
if (!model) {
117
const content = this._textModel.getLineContent(lineNumber);
118
return LineTokens.createEmpty(content, this._languageIdCodec);
119
}
120
return model.getLineTokens(lineNumber);
121
}
122
123
public todo_resetTokenization(fireTokenChangeEvent: boolean = true): void {
124
if (fireTokenChangeEvent) {
125
this._onDidChangeTokens.fire({
126
semanticTokensApplied: false,
127
ranges: [
128
{
129
fromLineNumber: 1,
130
toLineNumber: this._textModel.getLineCount(),
131
},
132
],
133
});
134
}
135
}
136
137
public override handleDidChangeAttached(): void {
138
// TODO @alexr00 implement for background tokenization
139
}
140
141
public override handleDidChangeContent(e: IModelContentChangedEvent): void {
142
if (e.isFlush) {
143
// Don't fire the event, as the view might not have got the text change event yet
144
this.todo_resetTokenization(false);
145
} else {
146
const model = this._tokenizationImpl.get();
147
model?.handleContentChanged(e);
148
}
149
150
const treeModel = this._tree.get();
151
treeModel?.handleContentChange(e);
152
}
153
154
public override forceTokenization(lineNumber: number): void {
155
const model = this._tokenizationImpl.get();
156
if (!model) {
157
return;
158
}
159
if (!model.hasAccurateTokensForLine(lineNumber)) {
160
model.tokenizeEncoded(lineNumber);
161
}
162
}
163
164
public override hasAccurateTokensForLine(lineNumber: number): boolean {
165
const model = this._tokenizationImpl.get();
166
if (!model) {
167
return false;
168
}
169
return model.hasAccurateTokensForLine(lineNumber);
170
}
171
172
public override isCheapToTokenize(lineNumber: number): boolean {
173
// TODO @alexr00 determine what makes it cheap to tokenize?
174
return true;
175
}
176
177
public override getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType {
178
// TODO @alexr00 implement once we have custom parsing and don't just feed in the whole text model value
179
return StandardTokenType.Other;
180
}
181
182
public override tokenizeLinesAt(lineNumber: number, lines: string[]): LineTokens[] | null {
183
const model = this._tokenizationImpl.get();
184
if (!model) {
185
return null;
186
}
187
return model.tokenizeLinesAt(lineNumber, lines);
188
}
189
190
public override get hasTokens(): boolean {
191
const model = this._tokenizationImpl.get();
192
if (!model) {
193
return false;
194
}
195
return model.hasTokens();
196
}
197
}
198
199