@@ -212,16 +212,19 @@ export class ViewModel extends Disposable implements IViewModel {
212
212
this . _eventDispatcher . emitSingleViewEvent ( new viewEvents . ViewCompositionEndEvent ( ) ) ;
213
213
}
214
214
215
- private _onConfigurationChanged ( eventsCollector : ViewModelEventsCollector , e : ConfigurationChangedEvent ) : void {
216
-
217
- // We might need to restore the current centered view range, so save it (if available)
218
- let previousViewportStartModelPosition : Position | null = null ;
219
- if ( this . _viewportStart . isValid ) {
215
+ private _captureStableViewport ( ) : StableViewport {
216
+ // We might need to restore the current start view range, so save it (if available)
217
+ // But only if the scroll position is not at the top of the file
218
+ if ( this . _viewportStart . isValid && this . viewLayout . getCurrentScrollTop ( ) > 0 ) {
220
219
const previousViewportStartViewPosition = new Position ( this . _viewportStart . viewLineNumber , this . getLineMinColumn ( this . _viewportStart . viewLineNumber ) ) ;
221
- previousViewportStartModelPosition = this . coordinatesConverter . convertViewPositionToModelPosition ( previousViewportStartViewPosition ) ;
220
+ const previousViewportStartModelPosition = this . coordinatesConverter . convertViewPositionToModelPosition ( previousViewportStartViewPosition ) ;
221
+ return new StableViewport ( previousViewportStartModelPosition , this . _viewportStart . startLineDelta ) ;
222
222
}
223
- let restorePreviousViewportStart = false ;
223
+ return new StableViewport ( null , 0 ) ;
224
+ }
224
225
226
+ private _onConfigurationChanged ( eventsCollector : ViewModelEventsCollector , e : ConfigurationChangedEvent ) : void {
227
+ const stableViewport = this . _captureStableViewport ( ) ;
225
228
const options = this . _configuration . options ;
226
229
const fontInfo = options . get ( EditorOption . fontInfo ) ;
227
230
const wrappingStrategy = options . get ( EditorOption . wrappingStrategy ) ;
@@ -236,11 +239,6 @@ export class ViewModel extends Disposable implements IViewModel {
236
239
this . _decorations . onLineMappingChanged ( ) ;
237
240
this . viewLayout . onFlushed ( this . getLineCount ( ) ) ;
238
241
239
- if ( this . viewLayout . getCurrentScrollTop ( ) !== 0 ) {
240
- // Never change the scroll position from 0 to something else...
241
- restorePreviousViewportStart = true ;
242
- }
243
-
244
242
this . _updateConfigurationViewLineCount . schedule ( ) ;
245
243
}
246
244
@@ -253,11 +251,7 @@ export class ViewModel extends Disposable implements IViewModel {
253
251
eventsCollector . emitViewEvent ( new viewEvents . ViewConfigurationChangedEvent ( e ) ) ;
254
252
this . viewLayout . onConfigurationChanged ( e ) ;
255
253
256
- if ( restorePreviousViewportStart && previousViewportStartModelPosition ) {
257
- const viewPosition = this . coordinatesConverter . convertModelPositionToViewPosition ( previousViewportStartModelPosition ) ;
258
- const viewPositionTop = this . viewLayout . getVerticalOffsetForLineNumber ( viewPosition . lineNumber ) ;
259
- this . viewLayout . setScrollPosition ( { scrollTop : viewPositionTop + this . _viewportStart . startLineDelta } , ScrollType . Immediate ) ;
260
- }
254
+ stableViewport . recoverViewportStart ( this . coordinatesConverter , this . viewLayout ) ;
261
255
262
256
if ( CursorConfiguration . shouldRecreate ( e ) ) {
263
257
this . cursorConfig = new CursorConfiguration ( this . model . getLanguageId ( ) , this . model . getOptions ( ) , this . _configuration , this . languageConfigurationService ) ;
@@ -477,6 +471,8 @@ export class ViewModel extends Disposable implements IViewModel {
477
471
478
472
this . previousHiddenAreas = mergedRanges ;
479
473
474
+ const stableViewport = this . _captureStableViewport ( ) ;
475
+
480
476
let lineMappingChanged = false ;
481
477
try {
482
478
const eventsCollector = this . _eventDispatcher . beginEmitViewEvents ( ) ;
@@ -490,6 +486,7 @@ export class ViewModel extends Disposable implements IViewModel {
490
486
this . viewLayout . onFlushed ( this . getLineCount ( ) ) ;
491
487
this . viewLayout . onHeightMaybeChanged ( ) ;
492
488
}
489
+ stableViewport . recoverViewportStart ( this . coordinatesConverter , this . viewLayout ) ;
493
490
} finally {
494
491
this . _eventDispatcher . endEmitViewEvents ( ) ;
495
492
}
@@ -1253,3 +1250,22 @@ function rangeArraysEqual(arr1: Range[], arr2: Range[]): boolean {
1253
1250
}
1254
1251
return true ;
1255
1252
}
1253
+
1254
+ /**
1255
+ * Maintain a stable viewport by trying to keep the first line in the viewport constant.
1256
+ */
1257
+ class StableViewport {
1258
+ constructor (
1259
+ public readonly viewportStartModelPosition : Position | null ,
1260
+ public readonly startLineDelta : number
1261
+ ) { }
1262
+
1263
+ public recoverViewportStart ( coordinatesConverter : ICoordinatesConverter , viewLayout : ViewLayout ) : void {
1264
+ if ( ! this . viewportStartModelPosition ) {
1265
+ return ;
1266
+ }
1267
+ const viewPosition = coordinatesConverter . convertModelPositionToViewPosition ( this . viewportStartModelPosition ) ;
1268
+ const viewPositionTop = viewLayout . getVerticalOffsetForLineNumber ( viewPosition . lineNumber ) ;
1269
+ viewLayout . setScrollPosition ( { scrollTop : viewPositionTop + this . startLineDelta } , ScrollType . Immediate ) ;
1270
+ }
1271
+ }
0 commit comments