diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 23bfdb2c7bf82..133ec93504619 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -38,12 +38,14 @@ export interface AbstractScrollbarOptions { visibility: ScrollbarVisibility; extraScrollbarClassName: string; scrollable: Scrollable; + scrollByPage: boolean; } export abstract class AbstractScrollbar extends Widget { protected _host: ScrollbarHost; protected _scrollable: Scrollable; + protected _scrollByPage: boolean; private _lazyRender: boolean; protected _scrollbarState: ScrollbarState; private _visibilityController: ScrollbarVisibilityController; @@ -59,6 +61,7 @@ export abstract class AbstractScrollbar extends Widget { this._lazyRender = opts.lazyRender; this._host = opts.host; this._scrollable = opts.scrollable; + this._scrollByPage = opts.scrollByPage; this._scrollbarState = opts.scrollbarState; this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName)); this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()); @@ -210,7 +213,14 @@ export abstract class AbstractScrollbar extends Widget { offsetX = e.posx - domNodePosition.left; offsetY = e.posy - domNodePosition.top; } - this._setDesiredScrollPositionNow(this._scrollbarState.getDesiredScrollPositionFromOffset(this._mouseDownRelativePosition(offsetX, offsetY))); + + const offset = this._mouseDownRelativePosition(offsetX, offsetY); + this._setDesiredScrollPositionNow( + this._scrollByPage + ? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset) + : this._scrollbarState.getDesiredScrollPositionFromOffset(offset) + ); + if (e.leftButton) { e.preventDefault(); this._sliderMouseDown(e, () => { /*nothing to do*/ }); diff --git a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts index 6e7f132e99f83..636751f72c682 100644 --- a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts @@ -33,7 +33,8 @@ export class HorizontalScrollbar extends AbstractScrollbar { ), visibility: options.horizontal, extraScrollbarClassName: 'horizontal', - scrollable: scrollable + scrollable: scrollable, + scrollByPage: options.scrollByPage }); if (options.horizontalHasArrows) { diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 24fbe00f76971..115864f3dfb22 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -617,7 +617,9 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme vertical: (typeof opts.vertical !== 'undefined' ? opts.vertical : ScrollbarVisibility.Auto), verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10), verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false), - verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0) + verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0), + + scrollByPage: (typeof opts.scrollByPage !== 'undefined' ? opts.scrollByPage : false) }; result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize); diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index afb227be73b9f..556d208391231 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -114,6 +114,11 @@ export interface ScrollableElementCreationOptions { * Defaults to false. */ verticalHasArrows?: boolean; + /** + * Scroll gutter clicks move by page vs. jump to position. + * Defaults to false. + */ + scrollByPage?: boolean; } export interface ScrollableElementChangeOptions { @@ -146,4 +151,5 @@ export interface ScrollableElementResolvedOptions { verticalScrollbarSize: number; verticalSliderSize: number; verticalHasArrows: boolean; + scrollByPage: boolean; } diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts index 48e20a5a03379..d0d98b9a81055 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts @@ -202,6 +202,28 @@ export class ScrollbarState { return Math.round(desiredSliderPosition / this._computedSliderRatio); } + /** + * Compute a desired `scrollPosition` from if offset is before or after the slider position. + * If offset is before slider, treat as a page up (or left). If after, page down (or right). + * `offset` and `_computedSliderPosition` are based on the same coordinate system. + * `_visibleSize` corresponds to a "page" of lines in the returned coordinate system. + */ + public getDesiredScrollPositionFromOffsetPaged(offset: number): number { + if (!this._computedIsNeeded) { + // no need for a slider + return 0; + } + + let correctedOffset = offset - this._arrowSize; // compensate if has arrows + let desiredScrollPosition = this._scrollPosition; + if (correctedOffset < this._computedSliderPosition) { + desiredScrollPosition -= this._visibleSize; // page up/left + } else { + desiredScrollPosition += this._visibleSize; // page down/right + } + return desiredScrollPosition; + } + /** * Compute a desired `scrollPosition` such that the slider moves by `delta`. */ diff --git a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts index 296913a3fd8b5..4f8e8f30a19a8 100644 --- a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts @@ -33,7 +33,8 @@ export class VerticalScrollbar extends AbstractScrollbar { ), visibility: options.vertical, extraScrollbarClassName: 'vertical', - scrollable: scrollable + scrollable: scrollable, + scrollByPage: options.scrollByPage }); if (options.verticalHasArrows) { diff --git a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts index 5ab03d9cd4ccb..4d3c0944a0c7a 100644 --- a/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts +++ b/src/vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts @@ -18,8 +18,11 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 249); - assert.equal(actual.getDesiredScrollPositionFromOffset(259), 32849); + + // 259 is greater than 230 so page down, 32787 + 339 = 33126 + assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(259), 33126); + actual.setScrollPosition(32849); assert.equal(actual.getArrowSize(), 0); assert.equal(actual.getScrollPosition(), 32849); @@ -41,8 +44,11 @@ suite('ScrollbarState', () => { assert.equal(actual.getSliderSize(), 20); assert.equal(actual.getSliderPosition(), 230); - assert.equal(actual.getDesiredScrollPositionFromOffset(240 + 12), 32811); + + // 240 + 12 = 252; greater than 230 so page down, 32787 + 339 = 33126 + assert.equal(actual.getDesiredScrollPositionFromOffsetPaged(240 + 12), 33126); + actual.setScrollPosition(32811); assert.equal(actual.getArrowSize(), 12); assert.equal(actual.getScrollPosition(), 32811); diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index 21193bac130b6..5a6df759b50da 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -56,6 +56,7 @@ export class EditorScrollbar extends ViewPart { mouseWheelScrollSensitivity: mouseWheelScrollSensitivity, fastScrollSensitivity: fastScrollSensitivity, scrollPredominantAxis: scrollPredominantAxis, + scrollByPage: scrollbar.scrollByPage, }; this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.getScrollable())); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c2c0141e294f1..636eceaf91a7a 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2920,6 +2920,11 @@ export interface IEditorScrollbarOptions { * Defaults to `horizontalScrollbarSize`. */ horizontalSliderSize?: number; + /** + * Scroll gutter clicks move by page vs jump to position. + * Defaults to false. + */ + scrollByPage?: boolean; } export interface InternalEditorScrollbarOptions { @@ -2935,6 +2940,7 @@ export interface InternalEditorScrollbarOptions { readonly horizontalSliderSize: number; readonly verticalScrollbarSize: number; readonly verticalSliderSize: number; + readonly scrollByPage: boolean; } function _scrollbarVisibilityFromString(visibility: string | undefined, defaultValue: ScrollbarVisibility): ScrollbarVisibility { @@ -2965,7 +2971,8 @@ class EditorScrollbar extends BaseEditorOption { horizontalSliderSize: EditorOptions.scrollbar.defaultValue.horizontalSliderSize, verticalScrollbarSize: input.verticalScrollbarWidth, verticalSliderSize: EditorOptions.scrollbar.defaultValue.verticalSliderSize, + scrollByPage: EditorOptions.scrollbar.defaultValue.scrollByPage, }; options._write(EditorOption.scrollbar, scrollbarOptions); const lineNumbersOptions: InternalEditorRenderLineNumbersOptions = { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index a932e9f18de7d..29db9c7ef1e8e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3660,6 +3660,11 @@ declare namespace monaco.editor { * Defaults to `horizontalScrollbarSize`. */ horizontalSliderSize?: number; + /** + * Scroll gutter clicks move by page vs jump to position. + * Defaults to false. + */ + scrollByPage?: boolean; } export interface InternalEditorScrollbarOptions { @@ -3675,6 +3680,7 @@ declare namespace monaco.editor { readonly horizontalSliderSize: number; readonly verticalScrollbarSize: number; readonly verticalSliderSize: number; + readonly scrollByPage: boolean; } /**