Path: blob/main/src/vs/workbench/browser/actions/listCommands.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { KeyMod, KeyCode, KeyChord } from '../../../base/common/keyCodes.js';6import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';7import { KeybindingsRegistry, KeybindingWeight } from '../../../platform/keybinding/common/keybindingsRegistry.js';8import { List } from '../../../base/browser/ui/list/listWidget.js';9import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen, WorkbenchListSupportsFind, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey, WorkbenchTreeStickyScrollFocused } from '../../../platform/list/browser/listService.js';10import { PagedList } from '../../../base/browser/ui/list/listPaging.js';11import { equals, range } from '../../../base/common/arrays.js';12import { ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';13import { ObjectTree } from '../../../base/browser/ui/tree/objectTree.js';14import { AsyncDataTree } from '../../../base/browser/ui/tree/asyncDataTree.js';15import { DataTree } from '../../../base/browser/ui/tree/dataTree.js';16import { ITreeNode } from '../../../base/browser/ui/tree/tree.js';17import { CommandsRegistry } from '../../../platform/commands/common/commands.js';18import { Table } from '../../../base/browser/ui/table/tableWidget.js';19import { AbstractTree, TreeFindMatchType, TreeFindMode } from '../../../base/browser/ui/tree/abstractTree.js';20import { isActiveElement } from '../../../base/browser/dom.js';21import { Action2, registerAction2 } from '../../../platform/actions/common/actions.js';22import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';23import { localize, localize2 } from '../../../nls.js';24import { IHoverService } from '../../../platform/hover/browser/hover.js';2526function ensureDOMFocus(widget: ListWidget | undefined): void {27// it can happen that one of the commands is executed while28// DOM focus is within another focusable control within the29// list/tree item. therefor we should ensure that the30// list/tree has DOM focus again after the command ran.31const element = widget?.getHTMLElement();32if (element && !isActiveElement(element)) {33widget?.domFocus();34}35}3637async function updateFocus(widget: WorkbenchListWidget, updateFocusFn: (widget: WorkbenchListWidget) => void | Promise<void>): Promise<void> {38if (!WorkbenchListSelectionNavigation.getValue(widget.contextKeyService)) {39return updateFocusFn(widget);40}4142const focus = widget.getFocus();43const selection = widget.getSelection();4445await updateFocusFn(widget);4647const newFocus = widget.getFocus();4849if (selection.length > 1 || !equals(focus, selection) || equals(focus, newFocus)) {50return;51}5253const fakeKeyboardEvent = new KeyboardEvent('keydown');54widget.setSelection(newFocus, fakeKeyboardEvent);55}5657async function navigate(widget: WorkbenchListWidget | undefined, updateFocusFn: (widget: WorkbenchListWidget) => void | Promise<void>): Promise<void> {58if (!widget) {59return;60}6162await updateFocus(widget, updateFocusFn);6364const listFocus = widget.getFocus();6566if (listFocus.length) {67widget.reveal(listFocus[0]);68}6970widget.setAnchor(listFocus[0]);71ensureDOMFocus(widget);72}7374KeybindingsRegistry.registerCommandAndKeybindingRule({75id: 'list.focusDown',76weight: KeybindingWeight.WorkbenchContrib,77when: WorkbenchListFocusContextKey,78primary: KeyCode.DownArrow,79mac: {80primary: KeyCode.DownArrow,81secondary: [KeyMod.WinCtrl | KeyCode.KeyN]82},83handler: (accessor, arg2) => {84navigate(accessor.get(IListService).lastFocusedList, async widget => {85const fakeKeyboardEvent = new KeyboardEvent('keydown');86await widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);87});88}89});9091KeybindingsRegistry.registerCommandAndKeybindingRule({92id: 'list.focusUp',93weight: KeybindingWeight.WorkbenchContrib,94when: WorkbenchListFocusContextKey,95primary: KeyCode.UpArrow,96mac: {97primary: KeyCode.UpArrow,98secondary: [KeyMod.WinCtrl | KeyCode.KeyP]99},100handler: (accessor, arg2) => {101navigate(accessor.get(IListService).lastFocusedList, async widget => {102const fakeKeyboardEvent = new KeyboardEvent('keydown');103await widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);104});105}106});107108KeybindingsRegistry.registerCommandAndKeybindingRule({109id: 'list.focusAnyDown',110weight: KeybindingWeight.WorkbenchContrib,111when: WorkbenchListFocusContextKey,112primary: KeyMod.Alt | KeyCode.DownArrow,113mac: {114primary: KeyMod.Alt | KeyCode.DownArrow,115secondary: [KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyN]116},117handler: (accessor, arg2) => {118navigate(accessor.get(IListService).lastFocusedList, async widget => {119const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });120await widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);121});122}123});124125KeybindingsRegistry.registerCommandAndKeybindingRule({126id: 'list.focusAnyUp',127weight: KeybindingWeight.WorkbenchContrib,128when: WorkbenchListFocusContextKey,129primary: KeyMod.Alt | KeyCode.UpArrow,130mac: {131primary: KeyMod.Alt | KeyCode.UpArrow,132secondary: [KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyP]133},134handler: (accessor, arg2) => {135navigate(accessor.get(IListService).lastFocusedList, async widget => {136const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });137await widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);138});139}140});141142KeybindingsRegistry.registerCommandAndKeybindingRule({143id: 'list.focusPageDown',144weight: KeybindingWeight.WorkbenchContrib,145when: WorkbenchListFocusContextKey,146primary: KeyCode.PageDown,147handler: (accessor) => {148navigate(accessor.get(IListService).lastFocusedList, async widget => {149const fakeKeyboardEvent = new KeyboardEvent('keydown');150await widget.focusNextPage(fakeKeyboardEvent);151});152}153});154155KeybindingsRegistry.registerCommandAndKeybindingRule({156id: 'list.focusPageUp',157weight: KeybindingWeight.WorkbenchContrib,158when: WorkbenchListFocusContextKey,159primary: KeyCode.PageUp,160handler: (accessor) => {161navigate(accessor.get(IListService).lastFocusedList, async widget => {162const fakeKeyboardEvent = new KeyboardEvent('keydown');163await widget.focusPreviousPage(fakeKeyboardEvent);164});165}166});167168KeybindingsRegistry.registerCommandAndKeybindingRule({169id: 'list.focusFirst',170weight: KeybindingWeight.WorkbenchContrib,171when: WorkbenchListFocusContextKey,172primary: KeyCode.Home,173handler: (accessor) => {174navigate(accessor.get(IListService).lastFocusedList, async widget => {175const fakeKeyboardEvent = new KeyboardEvent('keydown');176await widget.focusFirst(fakeKeyboardEvent);177});178}179});180181KeybindingsRegistry.registerCommandAndKeybindingRule({182id: 'list.focusLast',183weight: KeybindingWeight.WorkbenchContrib,184when: WorkbenchListFocusContextKey,185primary: KeyCode.End,186handler: (accessor) => {187navigate(accessor.get(IListService).lastFocusedList, async widget => {188const fakeKeyboardEvent = new KeyboardEvent('keydown');189await widget.focusLast(fakeKeyboardEvent);190});191}192});193194KeybindingsRegistry.registerCommandAndKeybindingRule({195id: 'list.focusAnyFirst',196weight: KeybindingWeight.WorkbenchContrib,197when: WorkbenchListFocusContextKey,198primary: KeyMod.Alt | KeyCode.Home,199handler: (accessor) => {200navigate(accessor.get(IListService).lastFocusedList, async widget => {201const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });202await widget.focusFirst(fakeKeyboardEvent);203});204}205});206207KeybindingsRegistry.registerCommandAndKeybindingRule({208id: 'list.focusAnyLast',209weight: KeybindingWeight.WorkbenchContrib,210when: WorkbenchListFocusContextKey,211primary: KeyMod.Alt | KeyCode.End,212handler: (accessor) => {213navigate(accessor.get(IListService).lastFocusedList, async widget => {214const fakeKeyboardEvent = new KeyboardEvent('keydown', { altKey: true });215await widget.focusLast(fakeKeyboardEvent);216});217}218});219220function expandMultiSelection(focused: WorkbenchListWidget, previousFocus: unknown): void {221222// List223if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {224const list = focused;225226const focus = list.getFocus() ? list.getFocus()[0] : undefined;227const selection = list.getSelection();228if (selection && typeof focus === 'number' && selection.indexOf(focus) >= 0) {229list.setSelection(selection.filter(s => s !== previousFocus));230} else {231if (typeof focus === 'number') {232list.setSelection(selection.concat(focus));233}234}235}236237// Tree238else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {239const list = focused;240241const focus = list.getFocus() ? list.getFocus()[0] : undefined;242243if (previousFocus === focus) {244return;245}246247const selection = list.getSelection();248const fakeKeyboardEvent = new KeyboardEvent('keydown', { shiftKey: true });249250if (selection && selection.indexOf(focus) >= 0) {251list.setSelection(selection.filter(s => s !== previousFocus), fakeKeyboardEvent);252} else {253list.setSelection(selection.concat(focus), fakeKeyboardEvent);254}255}256}257258function revealFocusedStickyScroll(tree: ObjectTree<any, any> | DataTree<any, any> | AsyncDataTree<any, any>, postRevealAction?: (focus: any) => void): void {259const focus = tree.getStickyScrollFocus();260261if (focus.length === 0) {262throw new Error(`StickyScroll has no focus`);263}264if (focus.length > 1) {265throw new Error(`StickyScroll can only have a single focused item`);266}267268tree.reveal(focus[0]);269tree.getHTMLElement().focus(); // domfocus() would focus stiky scroll dom and not the tree todo@benibenj270tree.setFocus(focus);271postRevealAction?.(focus[0]);272}273274KeybindingsRegistry.registerCommandAndKeybindingRule({275id: 'list.expandSelectionDown',276weight: KeybindingWeight.WorkbenchContrib,277when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),278primary: KeyMod.Shift | KeyCode.DownArrow,279handler: (accessor, arg2) => {280const widget = accessor.get(IListService).lastFocusedList;281282if (!widget) {283return;284}285286// Focus down first287const previousFocus = widget.getFocus() ? widget.getFocus()[0] : undefined;288const fakeKeyboardEvent = new KeyboardEvent('keydown');289widget.focusNext(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);290291// Then adjust selection292expandMultiSelection(widget, previousFocus);293294const focus = widget.getFocus();295296if (focus.length) {297widget.reveal(focus[0]);298}299300ensureDOMFocus(widget);301}302});303304KeybindingsRegistry.registerCommandAndKeybindingRule({305id: 'list.expandSelectionUp',306weight: KeybindingWeight.WorkbenchContrib,307when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),308primary: KeyMod.Shift | KeyCode.UpArrow,309handler: (accessor, arg2) => {310const widget = accessor.get(IListService).lastFocusedList;311312if (!widget) {313return;314}315316// Focus up first317const previousFocus = widget.getFocus() ? widget.getFocus()[0] : undefined;318const fakeKeyboardEvent = new KeyboardEvent('keydown');319widget.focusPrevious(typeof arg2 === 'number' ? arg2 : 1, false, fakeKeyboardEvent);320321// Then adjust selection322expandMultiSelection(widget, previousFocus);323324const focus = widget.getFocus();325326if (focus.length) {327widget.reveal(focus[0]);328}329330ensureDOMFocus(widget);331}332});333334KeybindingsRegistry.registerCommandAndKeybindingRule({335id: 'list.collapse',336weight: KeybindingWeight.WorkbenchContrib,337when: ContextKeyExpr.and(WorkbenchListFocusContextKey, ContextKeyExpr.or(WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent)),338primary: KeyCode.LeftArrow,339mac: {340primary: KeyCode.LeftArrow,341secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow]342},343handler: (accessor) => {344const widget = accessor.get(IListService).lastFocusedList;345346if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {347return;348}349350const tree = widget;351const focusedElements = tree.getFocus();352353if (focusedElements.length === 0) {354return;355}356357const focus = focusedElements[0];358359if (!tree.collapse(focus)) {360const parent = tree.getParentElement(focus);361362if (parent) {363navigate(widget, widget => {364const fakeKeyboardEvent = new KeyboardEvent('keydown');365widget.setFocus([parent], fakeKeyboardEvent);366});367}368}369}370});371372KeybindingsRegistry.registerCommandAndKeybindingRule({373id: 'list.stickyScroll.collapse',374weight: KeybindingWeight.WorkbenchContrib + 50,375when: WorkbenchTreeStickyScrollFocused,376primary: KeyCode.LeftArrow,377mac: {378primary: KeyCode.LeftArrow,379secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow]380},381handler: (accessor) => {382const widget = accessor.get(IListService).lastFocusedList;383384if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {385return;386}387388revealFocusedStickyScroll(widget, focus => widget.collapse(focus));389}390});391392KeybindingsRegistry.registerCommandAndKeybindingRule({393id: 'list.collapseAll',394weight: KeybindingWeight.WorkbenchContrib,395when: WorkbenchListFocusContextKey,396primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,397mac: {398primary: KeyMod.CtrlCmd | KeyCode.LeftArrow,399secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow]400},401handler: (accessor) => {402const focused = accessor.get(IListService).lastFocusedList;403404if (focused && !(focused instanceof List || focused instanceof PagedList || focused instanceof Table)) {405focused.collapseAll();406}407}408});409410KeybindingsRegistry.registerCommandAndKeybindingRule({411id: 'list.collapseAllToFocus',412weight: KeybindingWeight.WorkbenchContrib,413when: WorkbenchListFocusContextKey,414handler: accessor => {415const focused = accessor.get(IListService).lastFocusedList;416const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', true);417// Trees418if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {419const tree = focused;420const focus = tree.getFocus();421422if (focus.length > 0) {423tree.collapse(focus[0], true);424}425tree.setSelection(focus, fakeKeyboardEvent);426tree.setAnchor(focus[0]);427}428}429});430431432KeybindingsRegistry.registerCommandAndKeybindingRule({433id: 'list.focusParent',434weight: KeybindingWeight.WorkbenchContrib,435when: WorkbenchListFocusContextKey,436handler: (accessor) => {437const widget = accessor.get(IListService).lastFocusedList;438439if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {440return;441}442443const tree = widget;444const focusedElements = tree.getFocus();445if (focusedElements.length === 0) {446return;447}448const focus = focusedElements[0];449const parent = tree.getParentElement(focus);450if (parent) {451navigate(widget, widget => {452const fakeKeyboardEvent = new KeyboardEvent('keydown');453widget.setFocus([parent], fakeKeyboardEvent);454});455}456}457});458459KeybindingsRegistry.registerCommandAndKeybindingRule({460id: 'list.expand',461weight: KeybindingWeight.WorkbenchContrib,462when: ContextKeyExpr.and(WorkbenchListFocusContextKey, ContextKeyExpr.or(WorkbenchTreeElementCanExpand, WorkbenchTreeElementHasChild)),463primary: KeyCode.RightArrow,464handler: (accessor) => {465const widget = accessor.get(IListService).lastFocusedList;466467if (!widget) {468return;469}470471if (widget instanceof ObjectTree || widget instanceof DataTree) {472// TODO@Joao: instead of doing this here, just delegate to a tree method473const focusedElements = widget.getFocus();474475if (focusedElements.length === 0) {476return;477}478479const focus = focusedElements[0];480481if (!widget.expand(focus)) {482const child = widget.getFirstElementChild(focus);483484if (child) {485const node = widget.getNode(child);486487if (node.visible) {488navigate(widget, widget => {489const fakeKeyboardEvent = new KeyboardEvent('keydown');490widget.setFocus([child], fakeKeyboardEvent);491});492}493}494}495} else if (widget instanceof AsyncDataTree) {496// TODO@Joao: instead of doing this here, just delegate to a tree method497const focusedElements = widget.getFocus();498499if (focusedElements.length === 0) {500return;501}502503const focus = focusedElements[0];504widget.expand(focus).then(didExpand => {505if (focus && !didExpand) {506const child = widget.getFirstElementChild(focus);507508if (child) {509const node = widget.getNode(child);510511if (node.visible) {512navigate(widget, widget => {513const fakeKeyboardEvent = new KeyboardEvent('keydown');514widget.setFocus([child], fakeKeyboardEvent);515});516}517}518}519});520}521}522});523524function selectElement(accessor: ServicesAccessor, retainCurrentFocus: boolean): void {525const focused = accessor.get(IListService).lastFocusedList;526const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', retainCurrentFocus);527// List528if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {529const list = focused;530list.setAnchor(list.getFocus()[0]);531list.setSelection(list.getFocus(), fakeKeyboardEvent);532}533534// Trees535else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {536const tree = focused;537const focus = tree.getFocus();538539if (focus.length > 0) {540let toggleCollapsed = true;541542if (tree.expandOnlyOnTwistieClick === true) {543toggleCollapsed = false;544} else if (typeof tree.expandOnlyOnTwistieClick !== 'boolean' && tree.expandOnlyOnTwistieClick(focus[0])) {545toggleCollapsed = false;546}547548if (toggleCollapsed) {549tree.toggleCollapsed(focus[0]);550}551}552tree.setAnchor(focus[0]);553tree.setSelection(focus, fakeKeyboardEvent);554}555}556557KeybindingsRegistry.registerCommandAndKeybindingRule({558id: 'list.select',559weight: KeybindingWeight.WorkbenchContrib,560when: WorkbenchListFocusContextKey,561primary: KeyCode.Enter,562mac: {563primary: KeyCode.Enter,564secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow]565},566handler: (accessor) => {567selectElement(accessor, false);568}569});570571KeybindingsRegistry.registerCommandAndKeybindingRule({572id: 'list.stickyScrollselect',573weight: KeybindingWeight.WorkbenchContrib + 50, // priorities over file explorer574when: WorkbenchTreeStickyScrollFocused,575primary: KeyCode.Enter,576mac: {577primary: KeyCode.Enter,578secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow]579},580handler: (accessor) => {581const widget = accessor.get(IListService).lastFocusedList;582583if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {584return;585}586587revealFocusedStickyScroll(widget, focus => widget.setSelection([focus]));588}589});590591KeybindingsRegistry.registerCommandAndKeybindingRule({592id: 'list.selectAndPreserveFocus',593weight: KeybindingWeight.WorkbenchContrib,594when: WorkbenchListFocusContextKey,595handler: accessor => {596selectElement(accessor, true);597}598});599600KeybindingsRegistry.registerCommandAndKeybindingRule({601id: 'list.selectAll',602weight: KeybindingWeight.WorkbenchContrib,603when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey),604primary: KeyMod.CtrlCmd | KeyCode.KeyA,605handler: (accessor) => {606const focused = accessor.get(IListService).lastFocusedList;607608// List609if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {610const list = focused;611const fakeKeyboardEvent = new KeyboardEvent('keydown');612list.setSelection(range(list.length), fakeKeyboardEvent);613}614615// Trees616else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {617const tree = focused;618const focus = tree.getFocus();619const selection = tree.getSelection();620621// Which element should be considered to start selecting all?622let start: unknown | undefined = undefined;623624if (focus.length > 0 && (selection.length === 0 || !selection.includes(focus[0]))) {625start = focus[0];626}627628if (!start && selection.length > 0) {629start = selection[0];630}631632// What is the scope of select all?633let scope: unknown | undefined = undefined;634635if (!start) {636scope = undefined;637} else {638scope = tree.getParentElement(start);639}640641const newSelection: unknown[] = [];642const visit = (node: ITreeNode<unknown, unknown>) => {643for (const child of node.children) {644if (child.visible) {645newSelection.push(child.element);646647if (!child.collapsed) {648visit(child);649}650}651}652};653654// Add the whole scope subtree to the new selection655visit(tree.getNode(scope));656657// If the scope isn't the tree root, it should be part of the new selection658if (scope && selection.length === newSelection.length) {659newSelection.unshift(scope);660}661662const fakeKeyboardEvent = new KeyboardEvent('keydown');663tree.setSelection(newSelection, fakeKeyboardEvent);664}665}666});667668KeybindingsRegistry.registerCommandAndKeybindingRule({669id: 'list.toggleSelection',670weight: KeybindingWeight.WorkbenchContrib,671when: WorkbenchListFocusContextKey,672primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,673handler: (accessor) => {674const widget = accessor.get(IListService).lastFocusedList;675676if (!widget) {677return;678}679680const focus = widget.getFocus();681682if (focus.length === 0) {683return;684}685686const selection = widget.getSelection();687const index = selection.indexOf(focus[0]);688689if (index > -1) {690widget.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]);691} else {692widget.setSelection([...selection, focus[0]]);693}694}695});696697KeybindingsRegistry.registerCommandAndKeybindingRule({698id: 'list.showHover',699weight: KeybindingWeight.WorkbenchContrib,700primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyI),701when: WorkbenchListFocusContextKey,702handler: async (accessor: ServicesAccessor, ...args: any[]) => {703const listService = accessor.get(IListService);704const lastFocusedList = listService.lastFocusedList;705if (!lastFocusedList) {706return;707}708709// Check if a tree element is focused710const focus = lastFocusedList.getFocus();711if (!focus || (focus.length === 0)) {712return;713}714715// As the tree does not know anything about the rendered DOM elements716// we have to traverse the dom to find the HTMLElements717const treeDOM = lastFocusedList.getHTMLElement();718const scrollableElement = treeDOM.querySelector('.monaco-scrollable-element');719const listRows = scrollableElement?.querySelector('.monaco-list-rows');720const focusedElement = listRows?.querySelector('.focused');721if (!focusedElement) {722return;723}724725const elementWithHover = getCustomHoverForElement(focusedElement as HTMLElement);726if (elementWithHover) {727accessor.get(IHoverService).showManagedHover(elementWithHover as HTMLElement);728}729},730});731732function getCustomHoverForElement(element: HTMLElement): HTMLElement | undefined {733// Check if the element itself has a hover734if (element.matches('[custom-hover="true"]')) {735return element;736}737738// Only consider children that are not action items or have a tabindex739// as these element are focusable and the user is able to trigger them already740const noneFocusableElementWithHover = element.querySelector('[custom-hover="true"]:not([tabindex]):not(.action-item)');741if (noneFocusableElementWithHover) {742return noneFocusableElementWithHover as HTMLElement;743}744745return undefined;746}747748KeybindingsRegistry.registerCommandAndKeybindingRule({749id: 'list.toggleExpand',750weight: KeybindingWeight.WorkbenchContrib,751when: WorkbenchListFocusContextKey,752primary: KeyCode.Space,753handler: (accessor) => {754const focused = accessor.get(IListService).lastFocusedList;755756// Tree only757if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {758const tree = focused;759const focus = tree.getFocus();760761if (!tree.options.disableExpandOnSpacebar && focus.length > 0 && tree.isCollapsible(focus[0])) {762tree.toggleCollapsed(focus[0]);763return;764}765}766767selectElement(accessor, true);768}769});770771KeybindingsRegistry.registerCommandAndKeybindingRule({772id: 'list.stickyScrolltoggleExpand',773weight: KeybindingWeight.WorkbenchContrib + 50, // priorities over file explorer774when: WorkbenchTreeStickyScrollFocused,775primary: KeyCode.Space,776handler: (accessor) => {777const widget = accessor.get(IListService).lastFocusedList;778779if (!widget || !(widget instanceof ObjectTree || widget instanceof DataTree || widget instanceof AsyncDataTree)) {780return;781}782783revealFocusedStickyScroll(widget);784}785});786787KeybindingsRegistry.registerCommandAndKeybindingRule({788id: 'list.clear',789weight: KeybindingWeight.WorkbenchContrib,790when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListHasSelectionOrFocus),791primary: KeyCode.Escape,792handler: (accessor) => {793const widget = accessor.get(IListService).lastFocusedList;794795if (!widget) {796return;797}798799const selection = widget.getSelection();800const fakeKeyboardEvent = new KeyboardEvent('keydown');801802if (selection.length > 1) {803const useSelectionNavigation = WorkbenchListSelectionNavigation.getValue(widget.contextKeyService);804if (useSelectionNavigation) {805const focus = widget.getFocus();806widget.setSelection([focus[0]], fakeKeyboardEvent);807} else {808widget.setSelection([], fakeKeyboardEvent);809}810} else {811widget.setSelection([], fakeKeyboardEvent);812widget.setFocus([], fakeKeyboardEvent);813}814815widget.setAnchor(undefined);816}817});818819CommandsRegistry.registerCommand({820id: 'list.triggerTypeNavigation',821handler: (accessor) => {822const widget = accessor.get(IListService).lastFocusedList;823widget?.triggerTypeNavigation();824}825});826827CommandsRegistry.registerCommand({828id: 'list.toggleFindMode',829handler: (accessor) => {830const widget = accessor.get(IListService).lastFocusedList;831832if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {833const tree = widget;834tree.findMode = tree.findMode === TreeFindMode.Filter ? TreeFindMode.Highlight : TreeFindMode.Filter;835}836}837});838839CommandsRegistry.registerCommand({840id: 'list.toggleFindMatchType',841handler: (accessor) => {842const widget = accessor.get(IListService).lastFocusedList;843844if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {845const tree = widget;846tree.findMatchType = tree.findMatchType === TreeFindMatchType.Contiguous ? TreeFindMatchType.Fuzzy : TreeFindMatchType.Contiguous;847}848}849});850851// Deprecated commands852CommandsRegistry.registerCommandAlias('list.toggleKeyboardNavigation', 'list.triggerTypeNavigation');853CommandsRegistry.registerCommandAlias('list.toggleFilterOnType', 'list.toggleFindMode');854855KeybindingsRegistry.registerCommandAndKeybindingRule({856id: 'list.find',857weight: KeybindingWeight.WorkbenchContrib,858when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchListSupportsFind),859primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF,860secondary: [KeyCode.F3],861handler: (accessor) => {862const widget = accessor.get(IListService).lastFocusedList;863864// List865if (widget instanceof List || widget instanceof PagedList || widget instanceof Table) {866// TODO@joao867}868869// Tree870else if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {871const tree = widget;872tree.openFind();873}874}875});876877KeybindingsRegistry.registerCommandAndKeybindingRule({878id: 'list.closeFind',879weight: KeybindingWeight.WorkbenchContrib,880when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen),881primary: KeyCode.Escape,882handler: (accessor) => {883const widget = accessor.get(IListService).lastFocusedList;884885if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {886const tree = widget;887tree.closeFind();888}889}890});891892KeybindingsRegistry.registerCommandAndKeybindingRule({893id: 'list.scrollUp',894weight: KeybindingWeight.WorkbenchContrib,895// Since the default keybindings for list.scrollUp and widgetNavigation.focusPrevious896// are both Ctrl+UpArrow, we disable this command when the scrollbar is at897// top-most position. This will give chance for widgetNavigation.focusPrevious to execute898when: ContextKeyExpr.and(899WorkbenchListFocusContextKey,900WorkbenchListScrollAtTopContextKey?.negate()),901primary: KeyMod.CtrlCmd | KeyCode.UpArrow,902handler: accessor => {903const focused = accessor.get(IListService).lastFocusedList;904905if (!focused) {906return;907}908909focused.scrollTop -= 10;910}911});912913KeybindingsRegistry.registerCommandAndKeybindingRule({914id: 'list.scrollDown',915weight: KeybindingWeight.WorkbenchContrib,916// same as above917when: ContextKeyExpr.and(918WorkbenchListFocusContextKey,919WorkbenchListScrollAtBottomContextKey?.negate()),920primary: KeyMod.CtrlCmd | KeyCode.DownArrow,921handler: accessor => {922const focused = accessor.get(IListService).lastFocusedList;923924if (!focused) {925return;926}927928focused.scrollTop += 10;929}930});931932KeybindingsRegistry.registerCommandAndKeybindingRule({933id: 'list.scrollLeft',934weight: KeybindingWeight.WorkbenchContrib,935when: WorkbenchListFocusContextKey,936handler: accessor => {937const focused = accessor.get(IListService).lastFocusedList;938939if (!focused) {940return;941}942943focused.scrollLeft -= 10;944}945});946947KeybindingsRegistry.registerCommandAndKeybindingRule({948id: 'list.scrollRight',949weight: KeybindingWeight.WorkbenchContrib,950when: WorkbenchListFocusContextKey,951handler: accessor => {952const focused = accessor.get(IListService).lastFocusedList;953954if (!focused) {955return;956}957958focused.scrollLeft += 10;959}960});961962registerAction2(class ToggleStickyScroll extends Action2 {963constructor() {964super({965id: 'tree.toggleStickyScroll',966title: {967...localize2('toggleTreeStickyScroll', "Toggle Tree Sticky Scroll"),968mnemonicTitle: localize({ key: 'mitoggleTreeStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Tree Sticky Scroll"),969},970category: 'View',971metadata: { description: localize('toggleTreeStickyScrollDescription', "Toggles Sticky Scroll widget at the top of tree structures such as the File Explorer and Debug variables View.") },972f1: true973});974}975976run(accessor: ServicesAccessor) {977const configurationService = accessor.get(IConfigurationService);978const newValue = !configurationService.getValue<boolean>('workbench.tree.enableStickyScroll');979configurationService.updateValue('workbench.tree.enableStickyScroll', newValue);980}981});982983984