-
Notifications
You must be signed in to change notification settings - Fork 31.3k
/
Copy pathtreeSitterCodeEditors.ts
84 lines (72 loc) · 3.4 KB
/
treeSitterCodeEditors.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../../base/common/event.js';
import { Disposable, DisposableMap, DisposableStore } from '../../../../base/common/lifecycle.js';
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';
import { Range } from '../../../../editor/common/core/range.js';
export interface IViewPortChangeEvent {
model: ITextModel;
ranges: Range[];
}
export class TreeSitterCodeEditors extends Disposable {
private readonly _editors = this._register(new DisposableMap<ICodeEditor>());
private readonly _onDidChangeViewport = this._register(new Emitter<IViewPortChangeEvent>());
public readonly onDidChangeViewport = this._onDidChangeViewport.event;
constructor(private readonly _languageId: string,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService) {
super();
this._register(this._codeEditorService.onCodeEditorAdd(this._onCodeEditorAdd, this));
this._codeEditorService.listCodeEditors().forEach(this._onCodeEditorAdd, this);
this._register(this._codeEditorService.onCodeEditorRemove(this._onCodeEditorRemove, this));
}
private _onCodeEditorRemove(editor: ICodeEditor): void {
this._editors.deleteAndDispose(editor);
}
private async _onCodeEditorAdd(editor: ICodeEditor): Promise<void> {
let model = editor.getModel() ?? undefined;
if (!model) {
const disposableStore: DisposableStore = this._register(new DisposableStore());
await Event.toPromise(Event.once(editor.onDidChangeModel), disposableStore);
model = editor.getModel() ?? undefined;
}
if (!model) {
return;
}
let language = model.getLanguageId();
if (language === PLAINTEXT_LANGUAGE_ID) {
const disposableStore = this._register(new DisposableStore());
await Event.toPromise(Event.once(model.onDidChangeLanguage), disposableStore);
language = model.getLanguageId();
}
if (language !== this._languageId) {
return;
}
this._editors.set(editor, editor.onDidScrollChange(() => this._onViewportChange(editor), this));
this._onViewportChange(editor);
}
private async _onViewportChange(editor: ICodeEditor): Promise<void> {
const ranges = this._nonIntersectingViewPortRanges(editor);
this._onDidChangeViewport.fire({ model: editor.getModel()!, ranges });
}
private _nonIntersectingViewPortRanges(editor: ICodeEditor) {
const viewportRanges = editor.getVisibleRangesPlusViewportAboveBelow();
const nonIntersectingRanges: Range[] = [];
for (const range of viewportRanges) {
if (nonIntersectingRanges.length !== 0) {
const prev = nonIntersectingRanges[nonIntersectingRanges.length - 1];
if (Range.areOnlyIntersecting(prev, range)) {
const newRange = prev.plusRange(range);
nonIntersectingRanges[nonIntersectingRanges.length - 1] = newRange;
continue;
}
}
nonIntersectingRanges.push(range);
}
return nonIntersectingRanges;
}
}