Skip to content

Commit 2685e2a

Browse files
raffaeuaiday-mar
andauthored
Refactoring editor sticky scroll (microsoft#248131)
* refactor: enhance documentation for StickyLineCandidateProvider methods microsoft#248082 * Removed unusued statement causing hygiene build to fail microsoft#244845 * polish --------- Co-authored-by: Aiday Marlen Kyzy <[email protected]> Co-authored-by: Aiday Marlen Kyzy <[email protected]>
1 parent ff63adf commit 2685e2a

File tree

1 file changed

+82
-46
lines changed

1 file changed

+82
-46
lines changed

src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { ILanguageFeaturesService } from '../../../common/services/languageFeatu
99
import { CancellationToken, CancellationTokenSource, } from '../../../../base/common/cancellation.js';
1010
import { EditorOption } from '../../../common/config/editorOptions.js';
1111
import { RunOnceScheduler } from '../../../../base/common/async.js';
12-
import { Range } from '../../../common/core/range.js';
1312
import { binarySearch } from '../../../../base/common/arrays.js';
1413
import { Event, Emitter } from '../../../../base/common/event.js';
1514
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
@@ -27,17 +26,33 @@ export class StickyLineCandidate {
2726
}
2827

2928
export interface IStickyLineCandidateProvider {
30-
29+
/**
30+
* Dispose resources used by the provider.
31+
*/
3132
dispose(): void;
33+
34+
/**
35+
* Get the version ID of the sticky model.
36+
*/
3237
getVersionId(): number | undefined;
38+
39+
/**
40+
* Update the sticky line candidates.
41+
*/
3342
update(): Promise<void>;
43+
44+
/**
45+
* Get sticky line candidates intersecting a given range.
46+
*/
3447
getCandidateStickyLinesIntersecting(range: StickyRange): StickyLineCandidate[];
35-
onDidChangeStickyScroll: Event<void>;
3648

49+
/**
50+
* Event triggered when sticky scroll changes.
51+
*/
52+
onDidChangeStickyScroll: Event<void>;
3753
}
3854

3955
export class StickyLineCandidateProvider extends Disposable implements IStickyLineCandidateProvider {
40-
4156
static readonly ID = 'store.contrib.stickyScrollController';
4257

4358
private readonly _onDidChangeStickyScroll = this._register(new Emitter<void>());
@@ -69,19 +84,19 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi
6984
this.readConfiguration();
7085
}
7186

87+
/**
88+
* Read and apply the sticky scroll configuration.
89+
*/
7290
private readConfiguration() {
7391
this._sessionStore.clear();
7492
const options = this._editor.getOption(EditorOption.stickyScroll);
7593
if (!options.enabled) {
7694
return;
7795
}
7896
this._sessionStore.add(this._editor.onDidChangeModel(() => {
79-
// We should not show an old model for a different file, it will always be wrong.
80-
// So we clear the model here immediately and then trigger an update.
8197
this._model = null;
8298
this.updateStickyModelProvider();
8399
this._onDidChangeStickyScroll.fire();
84-
85100
this.update();
86101
}));
87102
this._sessionStore.add(this._editor.onDidChangeHiddenAreas(() => this.update()));
@@ -95,54 +110,69 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi
95110
this.update();
96111
}
97112

113+
/**
114+
* Get the version ID of the sticky model.
115+
*/
98116
public getVersionId(): number | undefined {
99117
return this._model?.version;
100118
}
101119

120+
/**
121+
* Update the sticky model provider.
122+
*/
102123
private updateStickyModelProvider() {
103124
this._stickyModelProvider?.dispose();
104125
this._stickyModelProvider = null;
105-
const editor = this._editor;
106-
if (editor.hasModel()) {
126+
if (this._editor.hasModel()) {
107127
this._stickyModelProvider = new StickyModelProvider(
108-
editor,
128+
this._editor,
109129
() => this._updateSoon.schedule(),
110130
this._languageConfigurationService,
111131
this._languageFeaturesService
112132
);
113133
}
114134
}
115135

136+
/**
137+
* Update the sticky line candidates.
138+
*/
116139
public async update(): Promise<void> {
117140
this._cts?.dispose(true);
118141
this._cts = new CancellationTokenSource();
119142
await this.updateStickyModel(this._cts.token);
120143
this._onDidChangeStickyScroll.fire();
121144
}
122145

146+
/**
147+
* Update the sticky model based on the current editor state.
148+
*/
123149
private async updateStickyModel(token: CancellationToken): Promise<void> {
124150
if (!this._editor.hasModel() || !this._stickyModelProvider || this._editor.getModel().isTooLargeForTokenization()) {
125151
this._model = null;
126152
return;
127153
}
128154
const model = await this._stickyModelProvider.update(token);
129-
if (token.isCancellationRequested) {
130-
// the computation was canceled, so do not overwrite the model
131-
return;
155+
if (!token.isCancellationRequested) {
156+
this._model = model;
132157
}
133-
this._model = model;
134158
}
135159

136-
private updateIndex(index: number) {
137-
if (index === -1) {
138-
index = 0;
139-
} else if (index < 0) {
140-
index = -index - 2;
160+
/**
161+
* Get sticky line candidates intersecting a given range.
162+
*/
163+
public getCandidateStickyLinesIntersecting(range: StickyRange): StickyLineCandidate[] {
164+
if (!this._model?.element) {
165+
return [];
141166
}
142-
return index;
167+
const stickyLineCandidates: StickyLineCandidate[] = [];
168+
this.getCandidateStickyLinesIntersectingFromStickyModel(range, this._model.element, stickyLineCandidates, 0, 0, -1);
169+
return this.filterHiddenRanges(stickyLineCandidates);
143170
}
144171

145-
public getCandidateStickyLinesIntersectingFromStickyModel(
172+
/**
173+
* Get sticky line candidates intersecting a given range from the sticky model.
174+
*/
175+
private getCandidateStickyLinesIntersectingFromStickyModel(
146176
range: StickyRange,
147177
outlineModel: StickyElement,
148178
result: StickyLineCandidate[],
@@ -167,38 +197,44 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi
167197

168198
for (let i = lowerBound; i <= upperBound; i++) {
169199
const child = outlineModel.children[i];
170-
if (!child) {
171-
return;
200+
if (!child || !child.range) {
201+
continue;
172202
}
173-
const childRange = child.range;
174-
if (childRange) {
175-
const childStartLine = childRange.startLineNumber;
176-
const childEndLine = childRange.endLineNumber;
177-
if (range.startLineNumber <= childEndLine + 1 && childStartLine - 1 <= range.endLineNumber && childStartLine !== lastLine) {
178-
lastLine = childStartLine;
179-
const lineHeight = this._editor.getLineHeightForPosition(new Position(childStartLine, 1));
180-
result.push(new StickyLineCandidate(childStartLine, childEndLine - 1, top, lineHeight));
181-
this.getCandidateStickyLinesIntersectingFromStickyModel(range, child, result, depth + 1, top + lineHeight, childStartLine);
182-
}
183-
} else {
184-
this.getCandidateStickyLinesIntersectingFromStickyModel(range, child, result, depth, top, lastStartLineNumber);
203+
const { startLineNumber, endLineNumber } = child.range;
204+
if (range.startLineNumber <= endLineNumber + 1 && startLineNumber - 1 <= range.endLineNumber && startLineNumber !== lastLine) {
205+
lastLine = startLineNumber;
206+
const lineHeight = this._editor.getLineHeightForPosition(new Position(startLineNumber, 1));
207+
result.push(new StickyLineCandidate(startLineNumber, endLineNumber - 1, top, lineHeight));
208+
this.getCandidateStickyLinesIntersectingFromStickyModel(range, child, result, depth + 1, top + lineHeight, startLineNumber);
185209
}
186210
}
187211
}
188212

189-
public getCandidateStickyLinesIntersecting(range: StickyRange): StickyLineCandidate[] {
190-
if (!this._model?.element) {
191-
return [];
213+
/**
214+
* Filter out sticky line candidates that are within hidden ranges.
215+
*/
216+
private filterHiddenRanges(stickyLineCandidates: StickyLineCandidate[]): StickyLineCandidate[] {
217+
const hiddenRanges = this._editor._getViewModel()?.getHiddenAreas();
218+
if (!hiddenRanges) {
219+
return stickyLineCandidates;
192220
}
193-
let stickyLineCandidates: StickyLineCandidate[] = [];
194-
this.getCandidateStickyLinesIntersectingFromStickyModel(range, this._model.element, stickyLineCandidates, 0, 0, -1);
195-
const hiddenRanges: Range[] | undefined = this._editor._getViewModel()?.getHiddenAreas();
221+
return stickyLineCandidates.filter(candidate => {
222+
return !hiddenRanges.some(hiddenRange =>
223+
candidate.startLineNumber >= hiddenRange.startLineNumber &&
224+
candidate.endLineNumber <= hiddenRange.endLineNumber + 1
225+
);
226+
});
227+
}
196228

197-
if (hiddenRanges) {
198-
for (const hiddenRange of hiddenRanges) {
199-
stickyLineCandidates = stickyLineCandidates.filter(stickyLine => !(stickyLine.startLineNumber >= hiddenRange.startLineNumber && stickyLine.endLineNumber <= hiddenRange.endLineNumber + 1));
200-
}
229+
/**
230+
* Update the binary search index.
231+
*/
232+
private updateIndex(index: number): number {
233+
if (index === -1) {
234+
return 0;
235+
} else if (index < 0) {
236+
return -index - 2;
201237
}
202-
return stickyLineCandidates;
238+
return index;
203239
}
204240
}

0 commit comments

Comments
 (0)