Skip to content

Commit f215fbe

Browse files
committed
Disable command list.scrollUp/scrollDown when the list scrollbar is at boundary
1 parent 4390ebc commit f215fbe

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

src/vs/platform/list/browser/listService.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ export class ListService implements IListService {
113113
}
114114
}
115115

116+
export const RawWorkbenchListScrollAtBoundaryContextKey = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('listScrollAtBoundary', 'none');
117+
export const WorkbenchListScrollAtTopContextKey = ContextKeyExpr.or(
118+
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('top'),
119+
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('both'));
120+
export const WorkbenchListScrollAtBottomContextKey = ContextKeyExpr.or(
121+
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('bottom'),
122+
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('both'));
123+
116124
export const RawWorkbenchListFocusContextKey = new RawContextKey<boolean>('listFocus', true);
117125
export const WorkbenchListSupportsMultiSelectContextKey = new RawContextKey<boolean>('listSupportsMultiselect', true);
118126
export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey));
@@ -139,6 +147,33 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
139147
return result;
140148
}
141149

150+
// Note: We must declare IScrollObservarable as the arithmetic of concrete classes,
151+
// instead of object type like { onDidScroll: Event<any>; ... }. The latter will not mark
152+
// those properties as referenced during tree-shaking, causing them to be shaked away.
153+
type IScrollObservarable = Exclude<WorkbenchListWidget, WorkbenchPagedList<any>> | List<any>;
154+
155+
function createScrollObserver(contextKeyService: IContextKeyService, widget: IScrollObservarable): IDisposable {
156+
const listScrollAt = RawWorkbenchListScrollAtBoundaryContextKey.bindTo(contextKeyService);
157+
const update = () => {
158+
const atTop = widget.scrollTop === 0;
159+
160+
// We need a threshold `1` since scrollHeight is rounded.
161+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
162+
const atBottom = widget.scrollHeight - widget.renderHeight - widget.scrollTop < 1;
163+
if (atTop && atBottom) {
164+
listScrollAt.set('both');
165+
} else if (atTop) {
166+
listScrollAt.set('top');
167+
} else if (atBottom) {
168+
listScrollAt.set('bottom');
169+
} else {
170+
listScrollAt.set('none');
171+
}
172+
};
173+
update();
174+
return widget.onDidScroll(update);
175+
}
176+
142177
const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
143178
const openModeSettingKey = 'workbench.list.openMode';
144179
const horizontalScrollingKey = 'workbench.list.horizontalScrolling';
@@ -259,6 +294,8 @@ export class WorkbenchList<T> extends List<T> {
259294

260295
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
261296

297+
this.disposables.add(createScrollObserver(this.contextKeyService, this));
298+
262299
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
263300
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
264301

@@ -390,6 +427,8 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
390427

391428
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
392429

430+
this.disposables.add(createScrollObserver(this.contextKeyService, this.widget));
431+
393432
this.horizontalScrolling = options.horizontalScrolling;
394433

395434
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
@@ -514,6 +553,8 @@ export class WorkbenchTable<TRow> extends Table<TRow> {
514553

515554
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
516555

556+
this.disposables.add(createScrollObserver(this.contextKeyService, this));
557+
517558
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
518559
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
519560

@@ -1165,6 +1206,8 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
11651206
) {
11661207
this.contextKeyService = createScopedContextKeyService(contextKeyService, tree);
11671208

1209+
this.disposables.push(createScrollObserver(this.contextKeyService, tree));
1210+
11681211
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
11691212
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
11701213

src/vs/workbench/browser/actions/listCommands.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
77
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
88
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
99
import { List } from 'vs/base/browser/ui/list/listWidget';
10-
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen, WorkbenchListSupportsFind } from 'vs/platform/list/browser/listService';
10+
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen, WorkbenchListSupportsFind, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey } from 'vs/platform/list/browser/listService';
1111
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
1212
import { equals, range } from 'vs/base/common/arrays';
1313
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
@@ -692,7 +692,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
692692
KeybindingsRegistry.registerCommandAndKeybindingRule({
693693
id: 'list.scrollUp',
694694
weight: KeybindingWeight.WorkbenchContrib,
695-
when: WorkbenchListFocusContextKey,
695+
// Since the default keybindings for list.scrollUp and widgetNavigation.focusPrevious
696+
// are both Ctrl+UpArrow, we disable this command when the scrollbar is at
697+
// top-most position. This will give chance for widgetNavigation.focusPrevious to execute
698+
when: ContextKeyExpr.and(
699+
WorkbenchListFocusContextKey,
700+
WorkbenchListScrollAtTopContextKey?.negate()),
696701
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
697702
handler: accessor => {
698703
const focused = accessor.get(IListService).lastFocusedList;
@@ -708,7 +713,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
708713
KeybindingsRegistry.registerCommandAndKeybindingRule({
709714
id: 'list.scrollDown',
710715
weight: KeybindingWeight.WorkbenchContrib,
711-
when: WorkbenchListFocusContextKey,
716+
// same as above
717+
when: ContextKeyExpr.and(
718+
WorkbenchListFocusContextKey,
719+
WorkbenchListScrollAtBottomContextKey?.negate()),
712720
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
713721
handler: accessor => {
714722
const focused = accessor.get(IListService).lastFocusedList;

0 commit comments

Comments
 (0)