Skip to content

Commit 28db47f

Browse files
authored
Try to maintain the viewport start position stable when hidden areas change (microsoft#164456)
Fixes microsoft#161396: Try to maintain the viewport start position stable when hidden areas change
1 parent 62190e7 commit 28db47f

File tree

1 file changed

+33
-17
lines changed

1 file changed

+33
-17
lines changed

src/vs/editor/common/viewModel/viewModelImpl.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -212,16 +212,19 @@ export class ViewModel extends Disposable implements IViewModel {
212212
this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewCompositionEndEvent());
213213
}
214214

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) {
220219
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);
222222
}
223-
let restorePreviousViewportStart = false;
223+
return new StableViewport(null, 0);
224+
}
224225

226+
private _onConfigurationChanged(eventsCollector: ViewModelEventsCollector, e: ConfigurationChangedEvent): void {
227+
const stableViewport = this._captureStableViewport();
225228
const options = this._configuration.options;
226229
const fontInfo = options.get(EditorOption.fontInfo);
227230
const wrappingStrategy = options.get(EditorOption.wrappingStrategy);
@@ -236,11 +239,6 @@ export class ViewModel extends Disposable implements IViewModel {
236239
this._decorations.onLineMappingChanged();
237240
this.viewLayout.onFlushed(this.getLineCount());
238241

239-
if (this.viewLayout.getCurrentScrollTop() !== 0) {
240-
// Never change the scroll position from 0 to something else...
241-
restorePreviousViewportStart = true;
242-
}
243-
244242
this._updateConfigurationViewLineCount.schedule();
245243
}
246244

@@ -253,11 +251,7 @@ export class ViewModel extends Disposable implements IViewModel {
253251
eventsCollector.emitViewEvent(new viewEvents.ViewConfigurationChangedEvent(e));
254252
this.viewLayout.onConfigurationChanged(e);
255253

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);
261255

262256
if (CursorConfiguration.shouldRecreate(e)) {
263257
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 {
477471

478472
this.previousHiddenAreas = mergedRanges;
479473

474+
const stableViewport = this._captureStableViewport();
475+
480476
let lineMappingChanged = false;
481477
try {
482478
const eventsCollector = this._eventDispatcher.beginEmitViewEvents();
@@ -490,6 +486,7 @@ export class ViewModel extends Disposable implements IViewModel {
490486
this.viewLayout.onFlushed(this.getLineCount());
491487
this.viewLayout.onHeightMaybeChanged();
492488
}
489+
stableViewport.recoverViewportStart(this.coordinatesConverter, this.viewLayout);
493490
} finally {
494491
this._eventDispatcher.endEmitViewEvents();
495492
}
@@ -1253,3 +1250,22 @@ function rangeArraysEqual(arr1: Range[], arr2: Range[]): boolean {
12531250
}
12541251
return true;
12551252
}
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

Comments
 (0)