Path: blob/main/src/vs/workbench/contrib/debug/browser/debugActionViewItems.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 * as nls from '../../../../nls.js';6import { IAction } from '../../../../base/common/actions.js';7import { KeyCode } from '../../../../base/common/keyCodes.js';8import * as dom from '../../../../base/browser/dom.js';9import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';10import { SelectBox, ISelectOptionItem } from '../../../../base/browser/ui/selectBox/selectBox.js';11import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';12import { ICommandService } from '../../../../platform/commands/common/commands.js';13import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch, State } from '../common/debug.js';14import { ThemeIcon } from '../../../../base/common/themables.js';15import { selectBorder, selectBackground, asCssVariable } from '../../../../platform/theme/common/colorRegistry.js';16import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';17import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';18import { IDisposable, dispose } from '../../../../base/common/lifecycle.js';19import { ADD_CONFIGURATION_ID } from './debugCommands.js';20import { BaseActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';21import { debugStart } from './debugIcons.js';22import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';23import { defaultSelectBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js';24import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';25import { IHoverService } from '../../../../platform/hover/browser/hover.js';26import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';27import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js';28import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';29import { hasNativeContextMenu } from '../../../../platform/window/common/window.js';30import { Gesture, EventType as TouchEventType } from '../../../../base/browser/touch.js';3132const $ = dom.$;3334export class StartDebugActionViewItem extends BaseActionViewItem {3536private static readonly SEPARATOR = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';3738private container!: HTMLElement;39private start!: HTMLElement;40private selectBox: SelectBox;41private debugOptions: { label: string; handler: (() => Promise<boolean>) }[] = [];42private toDispose: IDisposable[];43private selected = 0;44private providers: { label: string; type: string; pick: () => Promise<{ launch: ILaunch; config: IConfig } | undefined> }[] = [];4546constructor(47private context: unknown,48action: IAction,49options: IBaseActionViewItemOptions,50@IDebugService private readonly debugService: IDebugService,51@IConfigurationService private readonly configurationService: IConfigurationService,52@ICommandService private readonly commandService: ICommandService,53@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,54@IContextViewService contextViewService: IContextViewService,55@IKeybindingService private readonly keybindingService: IKeybindingService,56@IHoverService private readonly hoverService: IHoverService,57@IContextKeyService private readonly contextKeyService: IContextKeyService58) {59super(context, action, options);60this.toDispose = [];61this.selectBox = new SelectBox([], -1, contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations'), useCustomDrawn: !hasNativeContextMenu(this.configurationService) });62this.selectBox.setFocusable(false);63this.toDispose.push(this.selectBox);6465this.registerListeners();66}6768private registerListeners(): void {69this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {70if (e.affectsConfiguration('launch')) {71this.updateOptions();72}73}));74this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => {75this.updateOptions();76}));77}7879override render(container: HTMLElement): void {80this.container = container;81container.classList.add('start-debug-action-item');82this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart)));83const keybinding = this.keybindingService.lookupKeybinding(this.action.id)?.getLabel();84const keybindingLabel = keybinding ? ` (${keybinding})` : '';85const title = this.action.label + keybindingLabel;86this.toDispose.push(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.start, title));87this.start.setAttribute('role', 'button');88this._setAriaLabel(title);8990this._register(Gesture.addTarget(this.start));91for (const event of [dom.EventType.CLICK, TouchEventType.Tap]) {92this.toDispose.push(dom.addDisposableListener(this.start, event, () => {93this.start.blur();94if (this.debugService.state !== State.Initializing) {95this.actionRunner.run(this.action, this.context);96}97}));98}99100this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => {101if (this.action.enabled && e.button === 0) {102this.start.classList.add('active');103}104}));105this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_UP, () => {106this.start.classList.remove('active');107}));108this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_OUT, () => {109this.start.classList.remove('active');110}));111112this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {113const event = new StandardKeyboardEvent(e);114if (event.equals(KeyCode.RightArrow)) {115this.start.tabIndex = -1;116this.selectBox.focus();117event.stopPropagation();118}119}));120this.toDispose.push(this.selectBox.onDidSelect(async e => {121const target = this.debugOptions[e.index];122const shouldBeSelected = target.handler ? await target.handler() : false;123if (shouldBeSelected) {124this.selected = e.index;125} else {126// Some select options should not remain selected https://github.com/microsoft/vscode/issues/31526127this.selectBox.select(this.selected);128}129}));130131const selectBoxContainer = $('.configuration');132this.selectBox.render(dom.append(container, selectBoxContainer));133this.toDispose.push(dom.addDisposableListener(selectBoxContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {134const event = new StandardKeyboardEvent(e);135if (event.equals(KeyCode.LeftArrow)) {136this.selectBox.setFocusable(false);137this.start.tabIndex = 0;138this.start.focus();139event.stopPropagation();140event.preventDefault();141}142}));143this.container.style.border = `1px solid ${asCssVariable(selectBorder)}`;144selectBoxContainer.style.borderLeft = `1px solid ${asCssVariable(selectBorder)}`;145this.container.style.backgroundColor = asCssVariable(selectBackground);146147const configManager = this.debugService.getConfigurationManager();148const updateDynamicConfigs = () => configManager.getDynamicProviders().then(providers => {149if (providers.length !== this.providers.length) {150this.providers = providers;151this.updateOptions();152}153});154155this.toDispose.push(configManager.onDidChangeConfigurationProviders(updateDynamicConfigs));156updateDynamicConfigs();157this.updateOptions();158}159160override setActionContext(context: any): void {161this.context = context;162}163164override isEnabled(): boolean {165return true;166}167168override focus(fromRight?: boolean): void {169if (fromRight) {170this.selectBox.focus();171} else {172this.start.tabIndex = 0;173this.start.focus();174}175}176177override blur(): void {178this.start.tabIndex = -1;179this.selectBox.blur();180this.container.blur();181}182183override setFocusable(focusable: boolean): void {184if (focusable) {185this.start.tabIndex = 0;186} else {187this.start.tabIndex = -1;188this.selectBox.setFocusable(false);189}190}191192override dispose(): void {193this.toDispose = dispose(this.toDispose);194super.dispose();195}196197private updateOptions(): void {198this.selected = 0;199this.debugOptions = [];200const manager = this.debugService.getConfigurationManager();201const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;202let lastGroup: string | undefined;203const disabledIdxs: number[] = [];204manager.getAllConfigurations().forEach(({ launch, name, presentation }) => {205if (lastGroup !== presentation?.group) {206lastGroup = presentation?.group;207if (this.debugOptions.length) {208this.debugOptions.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) });209disabledIdxs.push(this.debugOptions.length - 1);210}211}212if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {213this.selected = this.debugOptions.length;214}215216const label = inWorkspace ? `${name} (${launch.name})` : name;217this.debugOptions.push({218label, handler: async () => {219await manager.selectConfiguration(launch, name);220return true;221}222});223});224225// Only take 3 elements from the recent dynamic configurations to not clutter the dropdown226manager.getRecentDynamicConfigurations().slice(0, 3).forEach(({ name, type }) => {227if (type === manager.selectedConfiguration.type && manager.selectedConfiguration.name === name) {228this.selected = this.debugOptions.length;229}230this.debugOptions.push({231label: name,232handler: async () => {233await manager.selectConfiguration(undefined, name, undefined, { type });234return true;235}236});237});238239if (this.debugOptions.length === 0) {240this.debugOptions.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: async () => false });241}242243this.debugOptions.push({ label: StartDebugActionViewItem.SEPARATOR, handler: () => Promise.resolve(false) });244disabledIdxs.push(this.debugOptions.length - 1);245246this.providers.forEach(p => {247248this.debugOptions.push({249label: `${p.label}...`,250handler: async () => {251const picked = await p.pick();252if (picked) {253await manager.selectConfiguration(picked.launch, picked.config.name, picked.config, { type: p.type });254return true;255}256return false;257}258});259});260261manager.getLaunches().filter(l => !l.hidden).forEach(l => {262const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");263this.debugOptions.push({264label, handler: async () => {265await this.commandService.executeCommand(ADD_CONFIGURATION_ID, l.uri.toString());266return false;267}268});269});270271this.selectBox.setOptions(this.debugOptions.map((data, index): ISelectOptionItem => ({ text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1 })), this.selected);272}273274private _setAriaLabel(title: string): void {275let ariaLabel = title;276let keybinding: string | undefined;277const verbose = this.configurationService.getValue(AccessibilityVerbositySettingId.Debug);278if (verbose) {279keybinding = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp, this.contextKeyService)?.getLabel() ?? undefined;280}281if (keybinding) {282ariaLabel = nls.localize('commentLabelWithKeybinding', "{0}, use ({1}) for accessibility help", ariaLabel, keybinding);283} else {284ariaLabel = nls.localize('commentLabelWithKeybindingNoKeybinding', "{0}, run the command Open Accessibility Help which is currently not triggerable via keybinding.", ariaLabel);285}286this.start.ariaLabel = ariaLabel;287}288}289290export class FocusSessionActionViewItem extends SelectActionViewItem<IDebugSession> {291constructor(292action: IAction,293session: IDebugSession | undefined,294@IDebugService protected readonly debugService: IDebugService,295@IContextViewService contextViewService: IContextViewService,296@IConfigurationService private readonly configurationService: IConfigurationService297) {298super(null, action, [], -1, contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('debugSession', 'Debug Session'), useCustomDrawn: !hasNativeContextMenu(configurationService) });299300this._register(this.debugService.getViewModel().onDidFocusSession(() => {301const session = this.getSelectedSession();302if (session) {303const index = this.getSessions().indexOf(session);304this.select(index);305}306}));307308this._register(this.debugService.onDidNewSession(session => {309const sessionListeners: IDisposable[] = [];310sessionListeners.push(session.onDidChangeName(() => this.update()));311sessionListeners.push(session.onDidEndAdapter(() => dispose(sessionListeners)));312this.update();313}));314this.getSessions().forEach(session => {315this._register(session.onDidChangeName(() => this.update()));316});317this._register(this.debugService.onDidEndSession(() => this.update()));318319const selectedSession = session ? this.mapFocusedSessionToSelected(session) : undefined;320this.update(selectedSession);321}322323protected override getActionContext(_: string, index: number): IDebugSession {324return this.getSessions()[index];325}326327private update(session?: IDebugSession) {328if (!session) {329session = this.getSelectedSession();330}331const sessions = this.getSessions();332const names = sessions.map(s => {333const label = s.getLabel();334if (s.parentSession) {335// Indent child sessions so they look like children336return `\u00A0\u00A0${label}`;337}338339return label;340});341this.setOptions(names.map((data): ISelectOptionItem => ({ text: data })), session ? sessions.indexOf(session) : undefined);342}343344private getSelectedSession(): IDebugSession | undefined {345const session = this.debugService.getViewModel().focusedSession;346return session ? this.mapFocusedSessionToSelected(session) : undefined;347}348349protected getSessions(): ReadonlyArray<IDebugSession> {350const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;351const sessions = this.debugService.getModel().getSessions();352353return showSubSessions ? sessions : sessions.filter(s => !s.parentSession);354}355356protected mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession {357const showSubSessions = this.configurationService.getValue<IDebugConfiguration>('debug').showSubSessionsInToolBar;358while (focusedSession.parentSession && !showSubSessions) {359focusedSession = focusedSession.parentSession;360}361return focusedSession;362}363}364365366