Path: blob/main/src/vs/workbench/browser/parts/views/viewPane.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 './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;109110private readonly disposables = new DisposableStore();111private readonly enabledDisposables = this.disposables.add(new DisposableStore());112private readonly renderDisposables = this.disposables.add(new DisposableStore());113114constructor(115private readonly container: HTMLElement,116private readonly delegate: IViewWelcomeDelegate,117@IInstantiationService private instantiationService: IInstantiationService,118@IOpenerService protected openerService: IOpenerService,119@IContextKeyService private contextKeyService: IContextKeyService,120@ILifecycleService lifecycleService: ILifecycleService121) {122this.disposables.add(Event.runAndSubscribe(this.delegate.onDidChangeViewWelcomeState, () => this.onDidChangeViewWelcomeState()));123this.disposables.add(lifecycleService.onWillShutdown(() => this.dispose())); // Fixes https://github.com/microsoft/vscode/issues/208878124}125126layout(height: number, width: number) {127if (!this._enabled) {128return;129}130131this.element!.style.height = `${height}px`;132this.element!.style.width = `${width}px`;133this.element!.classList.toggle('wide', width > 640);134this.scrollableElement!.scanDomNode();135}136137focus() {138if (!this._enabled) {139return;140}141142this.element!.focus();143}144145private onDidChangeViewWelcomeState(): void {146const enabled = this.delegate.shouldShowWelcome();147148if (this._enabled === enabled) {149return;150}151152this._enabled = enabled;153154if (!enabled) {155this.enabledDisposables.clear();156return;157}158159this.container.classList.add('welcome');160const viewWelcomeContainer = append(this.container, $('.welcome-view'));161this.element = $('.welcome-view-content', { tabIndex: 0 });162this.scrollableElement = new DomScrollableElement(this.element, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: ScrollbarVisibility.Visible, });163append(viewWelcomeContainer, this.scrollableElement.getDomNode());164165this.enabledDisposables.add(toDisposable(() => {166this.container.classList.remove('welcome');167this.scrollableElement!.dispose();168viewWelcomeContainer.remove();169this.scrollableElement = undefined;170this.element = undefined;171}));172173this.contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.enabledDisposables);174Event.chain(viewsRegistry.onDidChangeViewWelcomeContent, $ => $.filter(id => id === this.delegate.id))175(this.onDidChangeViewWelcomeContent, this, this.enabledDisposables);176this.onDidChangeViewWelcomeContent();177}178179private onDidChangeViewWelcomeContent(): void {180const descriptors = viewsRegistry.getViewWelcomeContent(this.delegate.id);181182this.items = [];183184for (const descriptor of descriptors) {185if (descriptor.when === 'default') {186this.defaultItem = { descriptor, visible: true };187} else {188const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;189this.items.push({ descriptor, visible });190}191}192193this.render();194}195196private onDidChangeContext(): void {197let didChange = false;198199for (const item of this.items) {200if (!item.descriptor.when || item.descriptor.when === 'default') {201continue;202}203204const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);205206if (item.visible === visible) {207continue;208}209210item.visible = visible;211didChange = true;212}213214if (didChange) {215this.render();216}217}218219private render(): void {220this.renderDisposables.clear();221this.element!.textContent = '';222223const contents = this.getContentDescriptors();224225if (contents.length === 0) {226this.container.classList.remove('welcome');227this.scrollableElement!.scanDomNode();228return;229}230231let buttonsCount = 0;232for (const { content, precondition, renderSecondaryButtons } of contents) {233const lines = content.split('\n');234235for (let line of lines) {236line = line.trim();237238if (!line) {239continue;240}241242const linkedText = parseLinkedText(line);243244if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {245const node = linkedText.nodes[0];246const buttonContainer = append(this.element!, $('.button-container'));247const button = new Button(buttonContainer, { title: node.title, supportIcons: true, secondary: renderSecondaryButtons && buttonsCount > 0 ? true : false, ...defaultButtonStyles, });248button.label = node.label;249button.onDidClick(_ => {250this.openerService.open(node.href, { allowCommands: true });251}, null, this.renderDisposables);252this.renderDisposables.add(button);253buttonsCount++;254255if (precondition) {256const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);257updateEnablement();258259const keys = new Set(precondition.keys());260const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));261onDidChangeContext(updateEnablement, null, this.renderDisposables);262}263} else {264const p = append(this.element!, $('p'));265266for (const node of linkedText.nodes) {267if (typeof node === 'string') {268append(p, ...renderLabelWithIcons(node));269} else {270const link = this.renderDisposables.add(this.instantiationService.createInstance(Link, p, node, {}));271272if (precondition && node.href.startsWith('command:')) {273const updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition);274updateEnablement();275276const keys = new Set(precondition.keys());277const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));278onDidChangeContext(updateEnablement, null, this.renderDisposables);279}280}281}282}283}284}285286this.container.classList.add('welcome');287this.scrollableElement!.scanDomNode();288}289290private getContentDescriptors(): IViewContentDescriptor[] {291const visibleItems = this.items.filter(v => v.visible);292293if (visibleItems.length === 0 && this.defaultItem) {294return [this.defaultItem.descriptor];295}296297return visibleItems.map(v => v.descriptor);298}299300dispose(): void {301this.disposables.dispose();302}303}304305export abstract class ViewPane extends Pane implements IView {306307private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';308309private _onDidFocus = this._register(new Emitter<void>());310readonly onDidFocus: Event<void> = this._onDidFocus.event;311312private _onDidBlur = this._register(new Emitter<void>());313readonly onDidBlur: Event<void> = this._onDidBlur.event;314315private _onDidChangeBodyVisibility = this._register(new Emitter<boolean>());316readonly onDidChangeBodyVisibility: Event<boolean> = this._onDidChangeBodyVisibility.event;317318protected _onDidChangeTitleArea = this._register(new Emitter<void>());319readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;320321protected _onDidChangeViewWelcomeState = this._register(new Emitter<void>());322readonly onDidChangeViewWelcomeState: Event<void> = this._onDidChangeViewWelcomeState.event;323324private _isVisible: boolean = false;325readonly id: string;326327private _title: string;328public get title(): string {329return this._title;330}331332private _titleDescription: string | undefined;333public get titleDescription(): string | undefined {334return this._titleDescription;335}336337private _singleViewPaneContainerTitle: string | undefined;338public get singleViewPaneContainerTitle(): string | undefined {339return this._singleViewPaneContainerTitle;340}341342readonly menuActions: ViewMenuActions;343344private progressBar?: ProgressBar;345private progressIndicator?: IProgressIndicator;346347private toolbar?: WorkbenchToolBar;348private readonly showActions: ViewPaneShowActions;349private headerContainer?: HTMLElement;350private titleContainer?: HTMLElement;351private titleContainerHover?: IManagedHover;352private titleDescriptionContainer?: HTMLElement;353private titleDescriptionContainerHover?: IManagedHover;354private iconContainer?: HTMLElement;355private iconContainerHover?: IManagedHover;356protected twistiesContainer?: HTMLElement;357private viewWelcomeController?: ViewWelcomeController;358359private readonly headerActionViewItems: DisposableMap<string, IActionViewItem> = this._register(new DisposableMap());360361protected readonly scopedContextKeyService: IContextKeyService;362363constructor(364options: IViewPaneOptions,365@IKeybindingService protected keybindingService: IKeybindingService,366@IContextMenuService protected contextMenuService: IContextMenuService,367@IConfigurationService protected readonly configurationService: IConfigurationService,368@IContextKeyService protected contextKeyService: IContextKeyService,369@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,370@IInstantiationService protected instantiationService: IInstantiationService,371@IOpenerService protected openerService: IOpenerService,372@IThemeService protected themeService: IThemeService,373@IHoverService protected readonly hoverService: IHoverService,374protected readonly accessibleViewInformationService?: IAccessibleViewInformationService375) {376super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });377378this.id = options.id;379this._title = options.title;380this._titleDescription = options.titleDescription;381this._singleViewPaneContainerTitle = options.singleViewPaneContainerTitle;382this.showActions = options.showActions ?? ViewPaneShowActions.Default;383384this.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));385this.scopedContextKeyService.createKey('view', this.id);386const viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!));387this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!))));388389const childInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])));390this.menuActions = this._register(childInstantiationService.createInstance(ViewMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs, renderShortTitle: true }));391this._register(this.menuActions.onDidChange(() => this.updateActions()));392}393394override get headerVisible(): boolean {395return super.headerVisible;396}397398override set headerVisible(visible: boolean) {399super.headerVisible = visible;400this.element.classList.toggle('merged-header', !visible);401}402403setVisible(visible: boolean): void {404if (this._isVisible !== visible) {405this._isVisible = visible;406407if (this.isExpanded()) {408this._onDidChangeBodyVisibility.fire(visible);409}410}411}412413isVisible(): boolean {414return this._isVisible;415}416417isBodyVisible(): boolean {418return this._isVisible && this.isExpanded();419}420421override setExpanded(expanded: boolean): boolean {422const changed = super.setExpanded(expanded);423if (changed) {424this._onDidChangeBodyVisibility.fire(expanded);425}426this.updateTwistyIcon();427return changed;428}429430override render(): void {431super.render();432433const focusTracker = trackFocus(this.element);434this._register(focusTracker);435this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));436this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));437}438439protected renderHeader(container: HTMLElement): void {440this.headerContainer = container;441442this.twistiesContainer = append(container, $(`.twisty-container${ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))}`));443444this.renderHeaderTitle(container, this.title);445446const actions = append(container, $('.actions'));447actions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always);448actions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded);449this.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, {450orientation: ActionsOrientation.HORIZONTAL,451actionViewItemProvider: (action, options) => {452const item = this.createActionViewItem(action, options);453if (item) {454this.headerActionViewItems.set(item.action.id, item);455}456return item;457},458ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),459getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),460renderDropdownAsChildElement: true,461actionRunner: this.getActionRunner(),462resetMenu: this.menuActions.menuId463});464465this._register(this.toolbar);466this.setActions();467468this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));469470const viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id);471if (viewContainerModel) {472this._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title)));473} else {474console.error(`View container model not found for view ${this.id}`);475}476477const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));478this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));479this.updateActionsVisibility();480}481482protected override updateHeader(): void {483super.updateHeader();484this.updateTwistyIcon();485}486487private updateTwistyIcon(): void {488if (this.twistiesContainer) {489this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!this._expanded)));490this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(this._expanded)));491}492}493494protected getTwistyIcon(expanded: boolean): ThemeIcon {495return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;496}497498override style(styles: IPaneStyles): void {499super.style(styles);500501const icon = this.getIcon();502if (this.iconContainer) {503const fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground));504if (URI.isUri(icon)) {505// Apply background color to activity bar item provided with iconUrls506this.iconContainer.style.backgroundColor = fgColor;507this.iconContainer.style.color = '';508} else {509// Apply foreground color to activity bar items provided with codicons510this.iconContainer.style.color = fgColor;511this.iconContainer.style.backgroundColor = '';512}513}514}515516private getIcon(): ThemeIcon | URI {517return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;518}519520protected renderHeaderTitle(container: HTMLElement, title: string): void {521this.iconContainer = append(container, $('.icon', undefined));522const icon = this.getIcon();523524let cssClass: string | undefined = undefined;525if (URI.isUri(icon)) {526cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`;527const iconClass = `.pane-header .icon.${cssClass}`;528529createCSSRule(iconClass, `530mask: ${asCSSUrl(icon)} no-repeat 50% 50%;531mask-size: 24px;532-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;533-webkit-mask-size: 16px;534`);535} else if (ThemeIcon.isThemeIcon(icon)) {536cssClass = ThemeIcon.asClassName(icon);537}538539if (cssClass) {540this.iconContainer.classList.add(...cssClass.split(' '));541}542543const calculatedTitle = this.calculateTitle(title);544this.titleContainer = append(container, $('h3.title', {}, calculatedTitle));545this.titleContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle));546547if (this._titleDescription) {548this.setTitleDescription(this._titleDescription);549}550551this.iconContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.iconContainer, calculatedTitle));552this.iconContainer.setAttribute('aria-label', this._getAriaLabel(calculatedTitle, this._titleDescription));553}554555private _getAriaLabel(title: string, description: string | undefined): string {556const viewHasAccessibilityHelpContent = this.viewDescriptorService.getViewDescriptorById(this.id)?.accessibilityHelpContent;557const accessibleViewHasShownForView = this.accessibleViewInformationService?.hasShownAccessibleView(this.id);558if (!viewHasAccessibilityHelpContent || accessibleViewHasShownForView) {559if (description) {560return `${title} - ${description}`;561} else {562return title;563}564}565566return nls.localize('viewAccessibilityHelp', 'Use Alt+F1 for accessibility help {0}', title);567}568569protected updateTitle(title: string): void {570const calculatedTitle = this.calculateTitle(title);571if (this.titleContainer) {572this.titleContainer.textContent = calculatedTitle;573this.titleContainerHover?.update(calculatedTitle);574}575576this.updateAriaHeaderLabel(calculatedTitle, this._titleDescription);577578this._title = title;579this._onDidChangeTitleArea.fire();580}581582private updateAriaHeaderLabel(title: string, description: string | undefined) {583const ariaLabel = this._getAriaLabel(title, description);584if (this.iconContainer) {585this.iconContainerHover?.update(title);586this.iconContainer.setAttribute('aria-label', ariaLabel);587}588this.ariaHeaderLabel = this.getAriaHeaderLabel(ariaLabel);589}590591private setTitleDescription(description: string | undefined) {592if (this.titleDescriptionContainer) {593this.titleDescriptionContainer.textContent = description ?? '';594this.titleDescriptionContainerHover?.update(description ?? '');595}596else if (description && this.titleContainer) {597this.titleDescriptionContainer = after(this.titleContainer, $('span.description', {}, description));598this.titleDescriptionContainerHover = this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.titleDescriptionContainer, description));599}600}601602protected updateTitleDescription(description?: string | undefined): void {603this.setTitleDescription(description);604this.updateAriaHeaderLabel(this._title, description);605this._titleDescription = description;606this._onDidChangeTitleArea.fire();607}608609private calculateTitle(title: string): string {610const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;611const model = this.viewDescriptorService.getViewContainerModel(viewContainer);612const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);613const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;614615if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {616return `${viewDescriptor.containerTitle}: ${title}`;617}618619return title;620}621622protected renderBody(container: HTMLElement): void {623this.viewWelcomeController = this._register(this.instantiationService.createInstance(ViewWelcomeController, container, this));624}625626protected layoutBody(height: number, width: number): void {627this.viewWelcomeController?.layout(height, width);628}629630onDidScrollRoot() {631// noop632}633634getProgressIndicator() {635if (this.progressBar === undefined) {636this.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles));637this.progressBar.hide();638}639640if (this.progressIndicator === undefined) {641const that = this;642this.progressIndicator = this._register(new ScopedProgressIndicator(assertReturnsDefined(this.progressBar), new class extends AbstractProgressScope {643constructor() {644super(that.id, that.isBodyVisible());645this._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id)));646}647}()));648}649return this.progressIndicator;650}651652protected getProgressLocation(): string {653return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;654}655656protected getLocationBasedColors(): IViewPaneLocationColors {657return getLocationBasedViewColors(this.viewDescriptorService.getViewLocationById(this.id));658}659660focus(): void {661if (this.viewWelcomeController?.enabled) {662this.viewWelcomeController.focus();663} else if (this.element) {664this.element.focus();665}666if (isActiveElement(this.element) || isAncestorOfActiveElement(this.element)) {667this._onDidFocus.fire();668}669}670671private setActions(): void {672if (this.toolbar) {673const primaryActions = [...this.menuActions.getPrimaryActions()];674if (this.shouldShowFilterInHeader()) {675primaryActions.unshift(VIEWPANE_FILTER_ACTION);676}677this.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions()));678this.toolbar.context = this.getActionsContext();679}680}681682private updateActionsVisibility(): void {683if (!this.headerContainer) {684return;685}686const shouldAlwaysShowActions = this.configurationService.getValue<boolean>('workbench.view.alwaysShowHeaderActions');687this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);688}689690protected updateActions(): void {691this.setActions();692this._onDidChangeTitleArea.fire();693}694695createActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined {696if (action.id === VIEWPANE_FILTER_ACTION.id) {697const that = this;698return new class extends BaseActionViewItem {699constructor() { super(null, action); }700override setFocusable(): void { /* noop input elements are focusable by default */ }701override get trapsArrowNavigation(): boolean { return true; }702override render(container: HTMLElement): void {703container.classList.add('viewpane-filter-container');704const filter = that.getFilterWidget()!;705append(container, filter.element);706filter.relayout();707}708};709}710return createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } });711}712713getActionsContext(): unknown {714return undefined;715}716717getActionRunner(): IActionRunner | undefined {718return undefined;719}720721getOptimalWidth(): number {722return 0;723}724725saveState(): void {726// Subclasses to implement for saving state727}728729shouldShowWelcome(): boolean {730return false;731}732733getFilterWidget(): FilterWidget | undefined {734return undefined;735}736737shouldShowFilterInHeader(): boolean {738return false;739}740}741742export abstract class FilterViewPane extends ViewPane {743744readonly filterWidget: FilterWidget;745private dimension: Dimension | undefined;746private filterContainer: HTMLElement | undefined;747748constructor(749options: IFilterViewPaneOptions,750@IKeybindingService keybindingService: IKeybindingService,751@IContextMenuService contextMenuService: IContextMenuService,752@IConfigurationService configurationService: IConfigurationService,753@IContextKeyService contextKeyService: IContextKeyService,754@IViewDescriptorService viewDescriptorService: IViewDescriptorService,755@IInstantiationService instantiationService: IInstantiationService,756@IOpenerService openerService: IOpenerService,757@IThemeService themeService: IThemeService,758@IHoverService hoverService: IHoverService,759accessibleViewService?: IAccessibleViewInformationService760) {761super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService, accessibleViewService);762const childInstantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])));763this.filterWidget = this._register(childInstantiationService.createInstance(FilterWidget, options.filterOptions));764}765766override getFilterWidget(): FilterWidget {767return this.filterWidget;768}769770protected override renderBody(container: HTMLElement): void {771super.renderBody(container);772this.filterContainer = append(container, $('.viewpane-filter-container'));773}774775protected override layoutBody(height: number, width: number): void {776super.layoutBody(height, width);777778this.dimension = new Dimension(width, height);779const wasFilterShownInHeader = !this.filterContainer?.hasChildNodes();780const shouldShowFilterInHeader = this.shouldShowFilterInHeader();781if (wasFilterShownInHeader !== shouldShowFilterInHeader) {782if (shouldShowFilterInHeader) {783reset(this.filterContainer!);784}785this.updateActions();786if (!shouldShowFilterInHeader) {787append(this.filterContainer!, this.filterWidget.element);788}789}790if (!shouldShowFilterInHeader) {791height = height - 44;792}793this.filterWidget.layout(width);794this.layoutBodyContent(height, width);795}796797override shouldShowFilterInHeader(): boolean {798return !(this.dimension && this.dimension.width < 600 && this.dimension.height > 100);799}800801protected abstract layoutBodyContent(height: number, width: number): void;802803}804805export interface IViewPaneLocationColors {806background: string;807overlayBackground: string;808listOverrideStyles: PartialExcept<IListStyles, 'listBackground' | 'treeStickyScrollBackground'>;809}810811export function getLocationBasedViewColors(location: ViewContainerLocation | null): IViewPaneLocationColors {812let background, overlayBackground, stickyScrollBackground, stickyScrollBorder, stickyScrollShadow;813814switch (location) {815case ViewContainerLocation.Panel:816background = PANEL_BACKGROUND;817overlayBackground = PANEL_SECTION_DRAG_AND_DROP_BACKGROUND;818stickyScrollBackground = PANEL_STICKY_SCROLL_BACKGROUND;819stickyScrollBorder = PANEL_STICKY_SCROLL_BORDER;820stickyScrollShadow = PANEL_STICKY_SCROLL_SHADOW;821break;822823case ViewContainerLocation.Sidebar:824case ViewContainerLocation.AuxiliaryBar:825default:826background = SIDE_BAR_BACKGROUND;827overlayBackground = SIDE_BAR_DRAG_AND_DROP_BACKGROUND;828stickyScrollBackground = SIDE_BAR_STICKY_SCROLL_BACKGROUND;829stickyScrollBorder = SIDE_BAR_STICKY_SCROLL_BORDER;830stickyScrollShadow = SIDE_BAR_STICKY_SCROLL_SHADOW;831}832833return {834background,835overlayBackground,836listOverrideStyles: {837listBackground: background,838treeStickyScrollBackground: stickyScrollBackground,839treeStickyScrollBorder: stickyScrollBorder,840treeStickyScrollShadow: stickyScrollShadow841}842};843}844845export abstract class ViewAction<T extends IView> extends Action2 {846override readonly desc: Readonly<IAction2Options> & { viewId: string };847constructor(desc: Readonly<IAction2Options> & { viewId: string }) {848super(desc);849this.desc = desc;850}851852run(accessor: ServicesAccessor, ...args: any[]): unknown {853const view = accessor.get(IViewsService).getActiveViewWithId(this.desc.viewId);854if (view) {855return this.runInView(accessor, <T>view, ...args);856}857return undefined;858}859860abstract runInView(accessor: ServicesAccessor, view: T, ...args: any[]): unknown;861}862863864