@@ -9,7 +9,6 @@ import { ILanguageFeaturesService } from '../../../common/services/languageFeatu
9
9
import { CancellationToken , CancellationTokenSource , } from '../../../../base/common/cancellation.js' ;
10
10
import { EditorOption } from '../../../common/config/editorOptions.js' ;
11
11
import { RunOnceScheduler } from '../../../../base/common/async.js' ;
12
- import { Range } from '../../../common/core/range.js' ;
13
12
import { binarySearch } from '../../../../base/common/arrays.js' ;
14
13
import { Event , Emitter } from '../../../../base/common/event.js' ;
15
14
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js' ;
@@ -27,17 +26,33 @@ export class StickyLineCandidate {
27
26
}
28
27
29
28
export interface IStickyLineCandidateProvider {
30
-
29
+ /**
30
+ * Dispose resources used by the provider.
31
+ */
31
32
dispose ( ) : void ;
33
+
34
+ /**
35
+ * Get the version ID of the sticky model.
36
+ */
32
37
getVersionId ( ) : number | undefined ;
38
+
39
+ /**
40
+ * Update the sticky line candidates.
41
+ */
33
42
update ( ) : Promise < void > ;
43
+
44
+ /**
45
+ * Get sticky line candidates intersecting a given range.
46
+ */
34
47
getCandidateStickyLinesIntersecting ( range : StickyRange ) : StickyLineCandidate [ ] ;
35
- onDidChangeStickyScroll : Event < void > ;
36
48
49
+ /**
50
+ * Event triggered when sticky scroll changes.
51
+ */
52
+ onDidChangeStickyScroll : Event < void > ;
37
53
}
38
54
39
55
export class StickyLineCandidateProvider extends Disposable implements IStickyLineCandidateProvider {
40
-
41
56
static readonly ID = 'store.contrib.stickyScrollController' ;
42
57
43
58
private readonly _onDidChangeStickyScroll = this . _register ( new Emitter < void > ( ) ) ;
@@ -69,19 +84,19 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi
69
84
this . readConfiguration ( ) ;
70
85
}
71
86
87
+ /**
88
+ * Read and apply the sticky scroll configuration.
89
+ */
72
90
private readConfiguration ( ) {
73
91
this . _sessionStore . clear ( ) ;
74
92
const options = this . _editor . getOption ( EditorOption . stickyScroll ) ;
75
93
if ( ! options . enabled ) {
76
94
return ;
77
95
}
78
96
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.
81
97
this . _model = null ;
82
98
this . updateStickyModelProvider ( ) ;
83
99
this . _onDidChangeStickyScroll . fire ( ) ;
84
-
85
100
this . update ( ) ;
86
101
} ) ) ;
87
102
this . _sessionStore . add ( this . _editor . onDidChangeHiddenAreas ( ( ) => this . update ( ) ) ) ;
@@ -95,54 +110,69 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi
95
110
this . update ( ) ;
96
111
}
97
112
113
+ /**
114
+ * Get the version ID of the sticky model.
115
+ */
98
116
public getVersionId ( ) : number | undefined {
99
117
return this . _model ?. version ;
100
118
}
101
119
120
+ /**
121
+ * Update the sticky model provider.
122
+ */
102
123
private updateStickyModelProvider ( ) {
103
124
this . _stickyModelProvider ?. dispose ( ) ;
104
125
this . _stickyModelProvider = null ;
105
- const editor = this . _editor ;
106
- if ( editor . hasModel ( ) ) {
126
+ if ( this . _editor . hasModel ( ) ) {
107
127
this . _stickyModelProvider = new StickyModelProvider (
108
- editor ,
128
+ this . _editor ,
109
129
( ) => this . _updateSoon . schedule ( ) ,
110
130
this . _languageConfigurationService ,
111
131
this . _languageFeaturesService
112
132
) ;
113
133
}
114
134
}
115
135
136
+ /**
137
+ * Update the sticky line candidates.
138
+ */
116
139
public async update ( ) : Promise < void > {
117
140
this . _cts ?. dispose ( true ) ;
118
141
this . _cts = new CancellationTokenSource ( ) ;
119
142
await this . updateStickyModel ( this . _cts . token ) ;
120
143
this . _onDidChangeStickyScroll . fire ( ) ;
121
144
}
122
145
146
+ /**
147
+ * Update the sticky model based on the current editor state.
148
+ */
123
149
private async updateStickyModel ( token : CancellationToken ) : Promise < void > {
124
150
if ( ! this . _editor . hasModel ( ) || ! this . _stickyModelProvider || this . _editor . getModel ( ) . isTooLargeForTokenization ( ) ) {
125
151
this . _model = null ;
126
152
return ;
127
153
}
128
154
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 ;
132
157
}
133
- this . _model = model ;
134
158
}
135
159
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 [ ] ;
141
166
}
142
- return index ;
167
+ const stickyLineCandidates : StickyLineCandidate [ ] = [ ] ;
168
+ this . getCandidateStickyLinesIntersectingFromStickyModel ( range , this . _model . element , stickyLineCandidates , 0 , 0 , - 1 ) ;
169
+ return this . filterHiddenRanges ( stickyLineCandidates ) ;
143
170
}
144
171
145
- public getCandidateStickyLinesIntersectingFromStickyModel (
172
+ /**
173
+ * Get sticky line candidates intersecting a given range from the sticky model.
174
+ */
175
+ private getCandidateStickyLinesIntersectingFromStickyModel (
146
176
range : StickyRange ,
147
177
outlineModel : StickyElement ,
148
178
result : StickyLineCandidate [ ] ,
@@ -167,38 +197,44 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi
167
197
168
198
for ( let i = lowerBound ; i <= upperBound ; i ++ ) {
169
199
const child = outlineModel . children [ i ] ;
170
- if ( ! child ) {
171
- return ;
200
+ if ( ! child || ! child . range ) {
201
+ continue ;
172
202
}
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 ) ;
185
209
}
186
210
}
187
211
}
188
212
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 ;
192
220
}
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
+ }
196
228
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 ;
201
237
}
202
- return stickyLineCandidates ;
238
+ return index ;
203
239
}
204
240
}
0 commit comments