Path: blob/main/src/vs/workbench/browser/parts/views/viewPane.ts
5272 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 './media/paneviewlet.css';6import * as nls from '../../../../nls.js';7import { Event, Emitter } from '../../../../base/common/event.js';8import { asCssVariable, foreground } from '../../../../platform/theme/common/colorRegistry.js';9import { after, append, $, trackFocus, EventType, addDisposableListener, Dimension, reset, isAncestorOfActiveElement, isActiveElement } from '../../../../base/browser/dom.js';10import { createCSSRule } from '../../../../base/browser/domStylesheets.js';11import { asCssValueWithDefault, asCSSUrl } from '../../../../base/browser/cssValue.js';12import { DisposableMap, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';13import { Action, IAction, IActionRunner } from '../../../../base/common/actions.js';14import { ActionsOrientation, IActionViewItem, prepareActions } from '../../../../base/browser/ui/actionbar/actionbar.js';15import { Registry } from '../../../../platform/registry/common/platform.js';16import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';17import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';18import { IThemeService } from '../../../../platform/theme/common/themeService.js';19import { ThemeIcon } from '../../../../base/common/themables.js';20import { IPaneOptions, Pane, IPaneStyles } from '../../../../base/browser/ui/splitview/paneview.js';21import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';22import { Extensions as ViewContainerExtensions, IView, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewContentDescriptor, defaultViewIcon, ViewContainerLocationToString } from '../../../common/views.js';23import { IViewsService } from '../../../services/views/common/viewsService.js';24import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';25import { assertReturnsDefined, PartialExcept } from '../../../../base/common/types.js';26import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';27import { MenuId, Action2, IAction2Options, SubmenuItemAction } from '../../../../platform/actions/common/actions.js';28import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';29import { parseLinkedText } from '../../../../base/common/linkedText.js';30import { IOpenerService } from '../../../../platform/opener/common/opener.js';31import { Button } from '../../../../base/browser/ui/button/button.js';32import { Link } from '../../../../platform/opener/browser/link.js';33import { Orientation } from '../../../../base/browser/ui/sash/sash.js';34import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js';35import { AbstractProgressScope, ScopedProgressIndicator } from '../../../services/progress/browser/progressIndicator.js';36import { IProgressIndicator } from '../../../../platform/progress/common/progress.js';37import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';38import { ScrollbarVisibility } from '../../../../base/common/scrollable.js';39import { URI } from '../../../../base/common/uri.js';40import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';41import { Codicon } from '../../../../base/common/codicons.js';42import { IDropdownMenuActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js';43import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';44import { FilterWidget, IFilterWidgetOptions } from './viewFilter.js';45import { BaseActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';46import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';47import { defaultButtonStyles, defaultProgressBarStyles } from '../../../../platform/theme/browser/defaultStyles.js';48import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';49import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';50import type { IManagedHover } from '../../../../base/browser/ui/hover/hover.js';51import { IHoverService } from '../../../../platform/hover/browser/hover.js';52import { IListStyles } from '../../../../base/browser/ui/list/listWidget.js';53import { PANEL_BACKGROUND, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_STICKY_SCROLL_BACKGROUND, PANEL_STICKY_SCROLL_BORDER, PANEL_STICKY_SCROLL_SHADOW, SIDE_BAR_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BORDER, SIDE_BAR_STICKY_SCROLL_SHADOW } from '../../../common/theme.js';54import { IAccessibleViewInformationService } from '../../../services/accessibility/common/accessibleViewInformationService.js';55import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js';56import { ViewMenuActions } from './viewMenuActions.js';5758export enum ViewPaneShowActions {59/** Show the actions when the view is hovered. This is the default behavior. */60Default,6162/** Always shows the actions when the view is expanded */63WhenExpanded,6465/** Always shows the actions */66Always,67}6869export interface IViewPaneOptions extends IPaneOptions {70readonly id: string;71readonly showActions?: ViewPaneShowActions;72readonly titleMenuId?: MenuId;73readonly donotForwardArgs?: boolean;74// The title of the container pane when it is merged with the view container75readonly singleViewPaneContainerTitle?: string;76}7778export interface IFilterViewPaneOptions extends IViewPaneOptions {79filterOptions: IFilterWidgetOptions;80}8182export const VIEWPANE_FILTER_ACTION = new Action('viewpane.action.filter');8384const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.'));85const viewPaneContainerCollapsedIcon = registerIcon('view-pane-container-collapsed', Codicon.chevronRight, nls.localize('viewPaneContainerCollapsedIcon', 'Icon for a collapsed view pane container.'));8687const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);8889interface IItem {90readonly descriptor: IViewContentDescriptor;91visible: boolean;92}9394interface IViewWelcomeDelegate {95readonly id: string;96readonly onDidChangeViewWelcomeState: Event<void>;97shouldShowWelcome(): boolean;98}99100class ViewWelcomeController {101102private defaultItem: IItem | undefined;103private items: IItem[] = [];104105get enabled(): boolean { return this._enabled; }106private _enabled: boolean = false;107private element: HTMLElement | undefined;108private scrollableElement: DomScrollableElement | undefined;109private _wide: boolean = false;110111private readonly disposables = new DisposableStore();112private readonly enabledDisposables = this.disposables.add(new DisposableStore());113private readonly renderDisposables = this.disposables.add(new DisposableStore());114115constructor(116private readonly container: HTMLElement,117private readonly delegate: IViewWelcomeDelegate,118@IInstantiationService private instantiationService: IInstantiationService,119@IOpenerService protected openerService: IOpenerService,120@IContextKeyService private contextKeyService: IContextKeyService,121@ILifecycleService lifecycleService: ILifecycleService122) {123this.disposables.add(Event.runAndSubscribe(this.delegate.onDidChangeViewWelcomeState, () => this.onDidChangeViewWelcomeState()));124this.disposables.add(lifecycleService.onWillShutdown(() => this.dispose())); // Fixes https://github.com/microsoft/vscode/issues/208878125}126127layout(height: number, width: number) {128if (!this._enabled) {129return;130}131132this.element!.style.height = `${height}px`;133this.element!.style.width = `${width}px`;134this._wide = width > 640;135this.element!.classList.toggle('wide', this._wide);136this.scrollableElement!.scanDomNode();137}138139focus() {140if (!this._enabled) {141return;142}143144this.element!.focus();145}146147private onDidChangeViewWelcomeState(): void {148const enabled = this.delegate.shouldShowWelcome();149150if (this._enabled === enabled) {151return;152}153154this._enabled = enabled;155156if (!enabled) {157this.enabledDisposables.clear();158return;159}160161this.container.classList.add('welcome');162const viewWelcomeContainer = append(this.container, $('.welcome-view'));163this.element = $('.welcome-view-content', { tabIndex: 0, role: 'region', 'aria-label': nls.localize('welcomeViewAriaLabel', "Welcome") });164if (this._wide) {165this.element.classList.add('wide');166}167this.scrollableElement = new DomScrollableElement(this.element, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: ScrollbarVisibility.Visible, });168append(viewWelcomeContainer, this.scrollableElement.getDomNode());169170this.enabledDisposables.add(toDisposable(() => {171this.container.classList.remove('welcome');172this.scrollableElement!.dispose();173viewWelcomeContainer.remove();174this.scrollableElement = undefined;175this.element = undefined;176}));177178this.contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.enabledDisposables);179Event.chain(viewsRegistry.onDidChangeViewWelcomeContent, $ => $.filter(id => id === this.delegate.id))180(this.onDidChangeViewWelcomeContent, this, this.enabledDisposables);181this.onDidChangeViewWelcomeContent();182}183184private onDidChangeViewWelcomeContent(): void {185const descriptors = viewsRegistry.getViewWelcomeContent(this.delegate.id);186187this.items = [];188189for (const descriptor of descriptors) {190if (descriptor.when === 'default') {191this.defaultItem = { descriptor, visible: true };192} else {193const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;194this.items.push({ descriptor, visible });195}196}197198this.render();199}200201private onDidChangeContext(): void {202let didChange = false;203204for (const item of this.items) {205if (!item.descriptor.when || item.descriptor.when === 'default') {206continue;207}208209const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);210211if (item.visible === visible) {212continue;213}214215item.visible = visible;216didChange = true;217}218219if (didChange) {220this.render();221}222}223224private render(): void {225this.renderDisposables.clear();226this.element!.textContent = '';227228const contents = this.getContentDescriptors();229230if (contents.length === 0) {231this.container.classList.remove('welcome');232this.scrollableElement!.scanDomNode();233return;234}235236let buttonsCount = 0;237for (const { content, precondition, renderSecondaryButtons } of contents) {238const lines = content.split('\n');239240for (let line of lines) {241line = line.trim();242243if (!line) {244continue;245}246247const linkedText = parseLinkedText(line);248249if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {250const node = linkedText.nodes[0];251const buttonContainer = append(this.element!, $('.button-container'));252const button = new Button(buttonContainer, { title: node.title, supportIcons: true, secondary: !!(renderSecondaryButtons && buttonsCount > 0), ...defaultButtonStyles, });253button.label = node.label;254button.onDidClick(_ => {255this.openerService.open(node.href, { allowCommands: true });256}, null, this.renderDisposables);257this.renderDisposables.add(button);258buttonsCount++;259260if (precondition) {261const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);262updateEnablement();263264const keys = new Set(precondition.keys());265const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));266onDidChangeContext(updateEnablement, null, this.renderDisposables);267}268} else {269const p = append(this.element!, $('p'));270271for (const node of linkedText.nodes) {272if (typeof node === 'string') {273append(p, ...renderLabelWithIcons(node));274} else {275const link = this.renderDisposables.add(this.instantiationService.createInstance(Link, p, node, {}));276277if (precondition && node.href.startsWith('command:')) {278const updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition);279updateEnablement();280281const keys = new Set(precondition.keys());282const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));283onDidChangeContext(updateEnablement, null, this.renderDisposables);284}285}286}287}288}289}290291this.container.classList.add('welcome');292this.scrollableElement!.scanDomNode();293}294295private getContentDescriptors(): IViewContentDescriptor[] {296const visibleItems = this.items.filter(v => v.visible);297298if (visibleItems.length === 0 && this.defaultItem) {299return [this.defaultItem.descriptor];300}301302return visibleItems.map(v => v.descriptor);303}304305dispose(): void {306this.disposables.dispose();307}308}309310export abstract class ViewPane extends Pane implements IView {311312private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';313314private _onDidFocus = this._register(new Emitter<void>());315readonly onDidFocus: Event<void> = this._onDidFocus.event;316317private _onDidBlur = this._register(new Emitter<void>());318readonly onDidBlur: Event<void> = this._onDidBlur.event;319320private _onDidChangeBodyVisibility = this._register(new Emitter<boolean>());321readonly onDidChangeBodyVisibility: Event<boolean> = this._onDidChangeBodyVisibility.event;322323protected _onDidChangeTitleArea = this._register(new Emitter<void>());324readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;325326protected _onDidChangeViewWelcomeState = this._register(new Emitter<void>());327readonly onDidChangeViewWelcomeState: Event<void> = this._onDidChangeViewWelcomeState.event;328329private _isVisible: boolean = false;330readonly id: string;331332private _title: string;333public get title(): string {334return this._title;335}336337private _titleDescription: string | undefined;338public get titleDescription(): string | undefined {339return this._titleDescription;340}341342private _singleViewPaneContainerTitle: string | undefined;343public get singleViewPaneContainerTitle(): string | undefined {344return this._singleViewPaneContainerTitle;345}346347readonly menuActions: ViewMenuActions;348349private progressBar?: ProgressBar;350private progressIndicator?: IProgressIndicator;351352private toolbar?: WorkbenchToolBar;353private readonly showActions: ViewPaneShowActions;354private headerContainer?: HTMLElement;355private titleContainer?: HTMLElement;356private titleContainerHover?: IManagedHover;357private titleDescriptionContainer?: HTMLElement;358private titleDescriptionContainerHover?: IManagedHover;359private iconContainer?: HTMLElement;360private iconContainerHover?: IManagedHover;361protected twistiesContainer?: HTMLElement;362private viewWelcomeController?: ViewWelcomeController;363364private readonly headerActionViewItems: DisposableMap<string, IActionViewItem> = this._register(new DisposableMap());365366protected readonly scopedContextKeyService: IContextKeyService;367368constructor(369options: IViewPaneOptions,370@IKeybindingService protected keybindingService: IKeybindingService,371@IContextMenuService protected contextMenuService: IContextMenuService,372@IConfigurationService protected readonly configurationService: IConfigurationService,373@IContextKeyService protected contextKeyService: IContextKeyService,374@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,375@IInstantiationService protected instantiationService: IInstantiationService,376@IOpenerService protected openerService: IOpenerService,377@IThemeService protected themeService: IThemeService,378@IHoverService protected readonly hoverService: IHoverService,379protected readonly accessibleViewInformationService?: IAccessibleViewInformationService380) {381super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });382383this.id = options.id;384this._title = options.title;385this._titleDescription = options.titleDescription;386this._singleViewPaneContainerTitle = options.singleViewPaneContainerTitle;387this.showActions = options.showActions ?? ViewPaneShowActions.Default;388389this.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));390this.scopedContextKeyService.createKey('view', this.id);391const viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!));392this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!))));393394const childInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])));395this.menuActions = this._register(childInstantiationService.createInstance(ViewMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs, renderShortTitle: true }));396this._register(this.menuActions.onDidChange(() => this.updateActions()));397}398399override get headerVisible(): boolean {400return super.headerVisible;401}402403override set headerVisible(visible: boolean) {404super.headerVisible = visible;405this.element.classList.toggle('merged-header', !visible);406}407408setVisible(visible: boolean): void {409if (this._isVisible !== visible) {410this._isVisible = visible;411412if (this.isExpanded()) {413this._onDidChangeBodyVisibility.fire(visible);414}415}416}417418isVisible(): boolean {419return this._isVisible;420}421422isBodyVisible(): boolean {423return this._isVisible && this.isExpanded();424}425426override setExpanded(expanded: boolean): boolean {427const changed = super.setExpanded(expanded);428if (changed) {429this._onDidChangeBodyVisibility.fire(expanded);430}431this.updateTwistyIcon();432return changed;433}434435override render(): void {436super.render();437438const focusTracker = trackFocus(this.element);439this._register(focusTracker);440this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));441this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));442}443444protected renderHeader(container: HTMLElement): void {445this.headerContainer = container;446447this.twistiesContainer = append(container, $(`.twisty-container${ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))}`));448449this.renderHeaderTitle(container, this.title);450451const actions = append(container, $('.actions'));452actions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always);453actions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded);454this.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, {455orientation: ActionsOrientation.HORIZONTAL,456actionViewItemProvider: (action, options) => {457const item = this.createActionViewItem(action, options);458if (item) {459this.headerActionViewItems.set(item.action.id, item);460}461return item;462},463ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),464getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),465renderDropdownAsChildElement: true,466actionRunner: this.getActionRunner(),467resetMenu: this.menuActions.menuId468});469470this._register(this.toolbar);471this.setActions();472473this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));474475const viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id);476if (viewContainerModel) {477this._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title)));478} else {479console.error(`View container model not found for view ${this.id}`);480}481482const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));483this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));484this.updateActionsVisibility();485}486487protected override updateHeader(): void {488super.updateHeader();489this.updateTwistyIcon();490}491492private updateTwistyIcon(): void {493if (this.twistiesContainer) {494this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!this._expanded)));495this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(this._expanded)));496}497}498499protected getTwistyIcon(expanded: boolean): ThemeIcon {500return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;501}502503override style(styles: IPaneStyles): void {504super.style(styles);505506const icon = this.getIcon();507if (this.iconContainer) {508const fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground));509if (URI.isUri(icon)) {510// Apply background color to activity bar item provided with iconUrls511this.iconContainer.style.backgroundColor = fgColor;512this.iconContainer.style.color = '';513} else {514// Apply foreground color to activity bar items provided with codicons515this.iconContainer.style.color = fgColor;516this.iconContainer.style.backgroundColor = '';517}518}519}520521private getIcon(): ThemeIcon | URI {522return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;523}524525protected renderHeaderTitle(container: HTMLElement, title: string): void {526this.iconContainer = append(container, $('.icon', undefined));527const icon = this.getIcon();528529let cssClass: string | undefined = undefined;530if (URI.isUri(icon)) {531cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`;532const iconClass = `.pane-header .icon.${cssClass}`;533534createCSSRule(iconClass, `535mask: ${asCSSUrl(icon)} no-repeat 50% 50%;536mask-size: 24px;537-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;538-webkit-mask-size: 16px;539`);540} else if (ThemeIcon.isThemeIcon(icon)) {541cssClass = ThemeIcon.asClassName(icon);542}543544if (cssClass) {545this.iconContainer.classList.add(...cssClass.split(' '));546}547548const calculatedTitle = this.calculateTitle(title);549this.titleContainer = append(container, $('h3.title', {}, calculatedTitle));550this.titleContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle));551552if (this._titleDescription) {553this.setTitleDescription(this._titleDescription);554}555556this.iconContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.iconContainer, calculatedTitle));557this.iconContainer.setAttribute('aria-label', this._getAriaLabel(calculatedTitle, this._titleDescription));558}559560private _getAriaLabel(title: string, description: string | undefined): string {561const viewHasAccessibilityHelpContent = this.viewDescriptorService.getViewDescriptorById(this.id)?.accessibilityHelpContent;562const accessibleViewHasShownForView = this.accessibleViewInformationService?.hasShownAccessibleView(this.id);563if (!viewHasAccessibilityHelpContent || accessibleViewHasShownForView) {564if (description) {565return `${title} - ${description}`;566} else {567return title;568}569}570571return nls.localize('viewAccessibilityHelp', 'Use Alt+F1 for accessibility help {0}', title);572}573574protected updateTitle(title: string): void {575const calculatedTitle = this.calculateTitle(title);576if (this.titleContainer) {577this.titleContainer.textContent = calculatedTitle;578this.titleContainerHover?.update(calculatedTitle);579}580581this.updateAriaHeaderLabel(calculatedTitle, this._titleDescription);582583this._title = title;584this._onDidChangeTitleArea.fire();585}586587private updateAriaHeaderLabel(title: string, description: string | undefined) {588const ariaLabel = this._getAriaLabel(title, description);589if (this.iconContainer) {590this.iconContainerHover?.update(title);591this.iconContainer.setAttribute('aria-label', ariaLabel);592}593this.ariaHeaderLabel = this.getAriaHeaderLabel(ariaLabel);594}595596private setTitleDescription(description: string | undefined) {597if (this.titleDescriptionContainer) {598this.titleDescriptionContainer.textContent = description ?? '';599this.titleDescriptionContainerHover?.update(description ?? '');600}601else if (description && this.titleContainer) {602this.titleDescriptionContainer = after(this.titleContainer, $('span.description', {}, description));603this.titleDescriptionContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.titleDescriptionContainer, description));604}605}606607protected updateTitleDescription(description?: string | undefined): void {608this.setTitleDescription(description);609this.updateAriaHeaderLabel(this._title, description);610this._titleDescription = description;611this._onDidChangeTitleArea.fire();612}613614private calculateTitle(title: string): string {615const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;616const model = this.viewDescriptorService.getViewContainerModel(viewContainer);617const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);618const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;619620if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle && title !== viewDescriptor.containerTitle) {621return `${viewDescriptor.containerTitle}: ${title}`;622}623624return title;625}626627protected renderBody(container: HTMLElement): void {628this.viewWelcomeController = this._register(this.instantiationService.createInstance(ViewWelcomeController, container, this));629}630631protected layoutBody(height: number, width: number): void {632this.viewWelcomeController?.layout(height, width);633}634635onDidScrollRoot() {636// noop637}638639getProgressIndicator() {640if (this.progressBar === undefined) {641this.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles));642this.progressBar.hide();643}644645if (this.progressIndicator === undefined) {646const that = this;647this.progressIndicator = this._register(new ScopedProgressIndicator(assertReturnsDefined(this.progressBar), this._register(new class extends AbstractProgressScope {648constructor() {649super(that.id, that.isBodyVisible());650this._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id)));651}652}())));653}654return this.progressIndicator;655}656657protected getProgressLocation(): string {658return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;659}660661protected getLocationBasedColors(): IViewPaneLocationColors {662return getLocationBasedViewColors(this.viewDescriptorService.getViewLocationById(this.id));663}664665focus(): void {666if (this.viewWelcomeController?.enabled) {667this.viewWelcomeController.focus();668} else if (this.element) {669this.element.focus();670}671if (isActiveElement(this.element) || isAncestorOfActiveElement(this.element)) {672this._onDidFocus.fire();673}674}675676private setActions(): void {677if (this.toolbar) {678const primaryActions = [...this.menuActions.getPrimaryActions()];679if (this.shouldShowFilterInHeader()) {680primaryActions.unshift(VIEWPANE_FILTER_ACTION);681}682this.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions()));683this.toolbar.context = this.getActionsContext();684}685}686687private updateActionsVisibility(): void {688if (!this.headerContainer) {689return;690}691const shouldAlwaysShowActions = this.configurationService.getValue<boolean>('workbench.view.alwaysShowHeaderActions');692this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);693}694695protected updateActions(): void {696this.setActions();697this._onDidChangeTitleArea.fire();698}699700createActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined {701if (action.id === VIEWPANE_FILTER_ACTION.id) {702const that = this;703return new class extends BaseActionViewItem {704constructor() { super(null, action); }705override setFocusable(): void { /* noop input elements are focusable by default */ }706override get trapsArrowNavigation(): boolean { return true; }707override render(container: HTMLElement): void {708container.classList.add('viewpane-filter-container');709const filter = that.getFilterWidget()!;710append(container, filter.element);711filter.relayout();712}713};714}715return createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } });716}717718getActionsContext(): unknown {719return undefined;720}721722getActionRunner(): IActionRunner | undefined {723return undefined;724}725726getOptimalWidth(): number {727return 0;728}729730saveState(): void {731// Subclasses to implement for saving state732}733734shouldShowWelcome(): boolean {735return false;736}737738getFilterWidget(): FilterWidget | undefined {739return undefined;740}741742shouldShowFilterInHeader(): boolean {743return false;744}745}746747export abstract class FilterViewPane extends ViewPane {748749readonly filterWidget: FilterWidget;750private dimension: Dimension | undefined;751protected filterContainer: HTMLElement | undefined;752753constructor(754options: IFilterViewPaneOptions,755@IKeybindingService keybindingService: IKeybindingService,756@IContextMenuService contextMenuService: IContextMenuService,757@IConfigurationService configurationService: IConfigurationService,758@IContextKeyService contextKeyService: IContextKeyService,759@IViewDescriptorService viewDescriptorService: IViewDescriptorService,760@IInstantiationService instantiationService: IInstantiationService,761@IOpenerService openerService: IOpenerService,762@IThemeService themeService: IThemeService,763@IHoverService hoverService: IHoverService,764accessibleViewService?: IAccessibleViewInformationService765) {766super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService, accessibleViewService);767const childInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])));768this.filterWidget = this._register(childInstantiationService.createInstance(FilterWidget, options.filterOptions));769this._register(this.filterWidget.onDidAcceptFilterText(() => this.focusBodyContent()));770}771772override getFilterWidget(): FilterWidget {773return this.filterWidget;774}775776protected override renderBody(container: HTMLElement): void {777super.renderBody(container);778this.filterContainer = append(container, $('.viewpane-filter-container'));779}780781protected override layoutBody(height: number, width: number): void {782super.layoutBody(height, width);783784this.dimension = new Dimension(width, height);785const wasFilterShownInHeader = !this.filterContainer?.hasChildNodes();786const shouldShowFilterInHeader = this.shouldShowFilterInHeader();787if (wasFilterShownInHeader !== shouldShowFilterInHeader) {788if (shouldShowFilterInHeader) {789reset(this.filterContainer!);790}791this.updateActions();792if (!shouldShowFilterInHeader) {793append(this.filterContainer!, this.filterWidget.element);794}795}796if (!shouldShowFilterInHeader) {797height = height - 44;798}799this.filterWidget.layout(width);800this.layoutBodyContent(height, width);801}802803override shouldShowFilterInHeader(): boolean {804return !(this.dimension && this.dimension.width < 600 && this.dimension.height > 100);805}806807protected abstract layoutBodyContent(height: number, width: number): void;808809protected focusBodyContent(): void {810this.focus();811}812}813814export interface IViewPaneLocationColors {815background: string;816overlayBackground: string;817listOverrideStyles: PartialExcept<IListStyles, 'listBackground' | 'treeStickyScrollBackground'>;818}819820export function getLocationBasedViewColors(location: ViewContainerLocation | null): IViewPaneLocationColors {821let background, overlayBackground, stickyScrollBackground, stickyScrollBorder, stickyScrollShadow;822823switch (location) {824case ViewContainerLocation.Panel:825background = PANEL_BACKGROUND;826overlayBackground = PANEL_SECTION_DRAG_AND_DROP_BACKGROUND;827stickyScrollBackground = PANEL_STICKY_SCROLL_BACKGROUND;828stickyScrollBorder = PANEL_STICKY_SCROLL_BORDER;829stickyScrollShadow = PANEL_STICKY_SCROLL_SHADOW;830break;831832case ViewContainerLocation.Sidebar:833case ViewContainerLocation.AuxiliaryBar:834default:835background = SIDE_BAR_BACKGROUND;836overlayBackground = SIDE_BAR_DRAG_AND_DROP_BACKGROUND;837stickyScrollBackground = SIDE_BAR_STICKY_SCROLL_BACKGROUND;838stickyScrollBorder = SIDE_BAR_STICKY_SCROLL_BORDER;839stickyScrollShadow = SIDE_BAR_STICKY_SCROLL_SHADOW;840}841842return {843background,844overlayBackground,845listOverrideStyles: {846listBackground: background,847treeStickyScrollBackground: stickyScrollBackground,848treeStickyScrollBorder: stickyScrollBorder,849treeStickyScrollShadow: stickyScrollShadow850}851};852}853854export abstract class ViewAction<T extends IView> extends Action2 {855override readonly desc: Readonly<IAction2Options> & { viewId: string };856constructor(desc: Readonly<IAction2Options> & { viewId: string }) {857super(desc);858this.desc = desc;859}860861run(accessor: ServicesAccessor, ...args: unknown[]): unknown {862const view = accessor.get(IViewsService).getActiveViewWithId(this.desc.viewId);863if (view) {864return this.runInView(accessor, <T>view, ...args);865}866return undefined;867}868869abstract runInView(accessor: ServicesAccessor, view: T, ...args: unknown[]): unknown;870}871872873