Path: blob/main/src/vs/workbench/contrib/debug/browser/debugViewlet.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 { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar.js';6import { IAction } from '../../../../base/common/actions.js';7import { DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js';8import './media/debugViewlet.css';9import * as nls from '../../../../nls.js';10import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';11import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';12import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';13import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';14import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';15import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';16import { IProgressService } from '../../../../platform/progress/common/progress.js';17import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';18import { IStorageService } from '../../../../platform/storage/common/storage.js';19import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';20import { IThemeService } from '../../../../platform/theme/common/themeService.js';21import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';22import { ViewPane } from '../../../browser/parts/views/viewPane.js';23import { ViewPaneContainer, ViewsSubMenu } from '../../../browser/parts/views/viewPaneContainer.js';24import { WorkbenchStateContext } from '../../../common/contextkeys.js';25import { IViewDescriptorService } from '../../../common/views.js';26import { IViewsService } from '../../../services/views/common/viewsService.js';27import { FocusSessionActionViewItem, StartDebugActionViewItem } from './debugActionViewItems.js';28import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_ID, FOCUS_SESSION_ID, SELECT_AND_START_ID, STOP_ID } from './debugCommands.js';29import { debugConfigure } from './debugIcons.js';30import { createDisconnectMenuItemAction } from './debugToolBar.js';31import { WelcomeView } from './welcomeView.js';32import { BREAKPOINTS_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, getStateLabel, IDebugService, ILaunch, REPL_VIEW_ID, State, VIEWLET_ID, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution } from '../common/debug.js';33import { IExtensionService } from '../../../services/extensions/common/extensions.js';34import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';35import { IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';36import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';37import { ILogService } from '../../../../platform/log/common/log.js';3839export class DebugViewPaneContainer extends ViewPaneContainer {4041private startDebugActionViewItem: StartDebugActionViewItem | undefined;42private progressResolve: (() => void) | undefined;43private breakpointView: ViewPane | undefined;44private paneListeners = new Map<string, IDisposable>();4546private readonly stopActionViewItemDisposables = this._register(new DisposableStore());4748constructor(49@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,50@ITelemetryService telemetryService: ITelemetryService,51@IProgressService private readonly progressService: IProgressService,52@IDebugService private readonly debugService: IDebugService,53@IInstantiationService instantiationService: IInstantiationService,54@IWorkspaceContextService contextService: IWorkspaceContextService,55@IStorageService storageService: IStorageService,56@IThemeService themeService: IThemeService,57@IContextMenuService contextMenuService: IContextMenuService,58@IExtensionService extensionService: IExtensionService,59@IConfigurationService configurationService: IConfigurationService,60@IContextViewService private readonly contextViewService: IContextViewService,61@IContextKeyService private readonly contextKeyService: IContextKeyService,62@IViewDescriptorService viewDescriptorService: IViewDescriptorService,63@ILogService logService: ILogService,64) {65super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, logService);6667// When there are potential updates to the docked debug toolbar we need to update it68this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state)));6970this._register(this.contextKeyService.onDidChangeContext(e => {71if (e.affectsSome(new Set([CONTEXT_DEBUG_UX_KEY, 'inDebugMode']))) {72this.updateTitleArea();73}74}));7576this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateTitleArea()));77this._register(this.configurationService.onDidChangeConfiguration(e => {78if (e.affectsConfiguration('debug.toolBarLocation') || e.affectsConfiguration('debug.hideLauncherWhileDebugging')) {79this.updateTitleArea();80}81}));82}8384override create(parent: HTMLElement): void {85super.create(parent);86parent.classList.add('debug-viewlet');87}8889override focus(): void {90super.focus();9192if (this.startDebugActionViewItem) {93this.startDebugActionViewItem.focus();94} else {95this.focusView(WelcomeView.ID);96}97}9899override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined {100if (action.id === DEBUG_START_COMMAND_ID) {101this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action, options);102return this.startDebugActionViewItem;103}104if (action.id === FOCUS_SESSION_ID) {105return new FocusSessionActionViewItem(action, undefined, this.debugService, this.contextViewService, this.configurationService);106}107108if (action.id === STOP_ID || action.id === DISCONNECT_ID) {109this.stopActionViewItemDisposables.clear();110const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor, { hoverDelegate: options.hoverDelegate }));111if (item) {112return item;113}114}115116return createActionViewItem(this.instantiationService, action, options);117}118119focusView(id: string): void {120const view = this.getView(id);121if (view) {122view.focus();123}124}125126private onDebugServiceStateChange(state: State): void {127if (this.progressResolve) {128this.progressResolve();129this.progressResolve = undefined;130}131132if (state === State.Initializing) {133this.progressService.withProgress({ location: VIEWLET_ID, }, _progress => {134return new Promise<void>(resolve => this.progressResolve = resolve);135});136}137}138139override addPanes(panes: { pane: ViewPane; size: number; index?: number; disposable: IDisposable }[]): void {140super.addPanes(panes);141142for (const { pane: pane } of panes) {143// attach event listener to144if (pane.id === BREAKPOINTS_VIEW_ID) {145this.breakpointView = pane;146this.updateBreakpointsMaxSize();147} else {148this.paneListeners.set(pane.id, pane.onDidChange(() => this.updateBreakpointsMaxSize()));149}150}151}152153override removePanes(panes: ViewPane[]): void {154super.removePanes(panes);155for (const pane of panes) {156dispose(this.paneListeners.get(pane.id));157this.paneListeners.delete(pane.id);158}159}160161private updateBreakpointsMaxSize(): void {162if (this.breakpointView) {163// We need to update the breakpoints view since all other views are collapsed #25384164const allOtherCollapsed = this.panes.every(view => !view.isExpanded() || view === this.breakpointView);165this.breakpointView.maximumBodySize = allOtherCollapsed ? Number.POSITIVE_INFINITY : this.breakpointView.minimumBodySize;166}167}168}169170MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, {171when: ContextKeyExpr.and(172ContextKeyExpr.equals('viewContainer', VIEWLET_ID),173CONTEXT_DEBUG_UX.notEqualsTo('simple'),174WorkbenchStateContext.notEqualsTo('empty'),175ContextKeyExpr.or(176CONTEXT_DEBUG_STATE.isEqualTo('inactive'),177ContextKeyExpr.notEquals('config.debug.toolBarLocation', 'docked')178),179ContextKeyExpr.or(180ContextKeyExpr.not('config.debug.hideLauncherWhileDebugging'),181ContextKeyExpr.not('inDebugMode')182)183),184order: 10,185group: 'navigation',186command: {187precondition: CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)),188id: DEBUG_START_COMMAND_ID,189title: DEBUG_START_LABEL190}191});192193registerAction2(class extends Action2 {194constructor() {195super({196id: DEBUG_CONFIGURE_COMMAND_ID,197title: {198value: DEBUG_CONFIGURE_LABEL,199original: 'Open \'launch.json\'',200mnemonicTitle: nls.localize({ key: 'miOpenConfigurations', comment: ['&& denotes a mnemonic'] }, "Open &&Configurations")201},202metadata: {203description: nls.localize2('openLaunchConfigDescription', 'Opens the file used to configure how your program is debugged')204},205f1: true,206icon: debugConfigure,207precondition: CONTEXT_DEBUG_UX.notEqualsTo('simple'),208menu: [{209id: MenuId.ViewContainerTitle,210group: 'navigation',211order: 20,212when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_UX.notEqualsTo('simple'), WorkbenchStateContext.notEqualsTo('empty'),213ContextKeyExpr.or(CONTEXT_DEBUG_STATE.isEqualTo('inactive'), ContextKeyExpr.notEquals('config.debug.toolBarLocation', 'docked')))214}, {215id: MenuId.ViewContainerTitle,216order: 20,217// Show in debug viewlet secondary actions when debugging and debug toolbar is docked218when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), ContextKeyExpr.equals('config.debug.toolBarLocation', 'docked'))219}, {220id: MenuId.MenubarDebugMenu,221group: '2_configuration',222order: 1,223when: CONTEXT_DEBUGGERS_AVAILABLE224}]225});226}227228async run(accessor: ServicesAccessor, opts?: { addNew?: boolean }): Promise<void> {229const debugService = accessor.get(IDebugService);230const quickInputService = accessor.get(IQuickInputService);231const configurationManager = debugService.getConfigurationManager();232let launch: ILaunch | undefined;233if (configurationManager.selectedConfiguration.name) {234launch = configurationManager.selectedConfiguration.launch;235} else {236const launches = configurationManager.getLaunches().filter(l => !l.hidden);237if (launches.length === 1) {238launch = launches[0];239} else {240const picks = launches.map(l => ({ label: l.name, launch: l }));241const picked = await quickInputService.pick<{ label: string; launch: ILaunch }>(picks, {242activeItem: picks[0],243placeHolder: nls.localize({ key: 'selectWorkspaceFolder', comment: ['User picks a workspace folder or a workspace configuration file here. Workspace configuration files can contain settings and thus a launch.json configuration can be written into one.'] }, "Select a workspace folder to create a launch.json file in or add it to the workspace config file")244});245if (picked) {246launch = picked.launch;247}248}249}250251if (launch) {252const { editor } = await launch.openConfigFile({ preserveFocus: false });253if (editor && opts?.addNew) {254const codeEditor = <ICodeEditor>editor.getControl();255if (codeEditor) {256await codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID)?.addLaunchConfiguration();257}258}259}260}261});262263264registerAction2(class extends Action2 {265constructor() {266super({267id: 'debug.toggleReplIgnoreFocus',268title: nls.localize('debugPanel', "Debug Console"),269toggled: ContextKeyExpr.has(`view.${REPL_VIEW_ID}.visible`),270menu: [{271id: ViewsSubMenu,272group: '3_toggleRepl',273order: 30,274when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID))275}]276});277}278279async run(accessor: ServicesAccessor): Promise<void> {280const viewsService = accessor.get(IViewsService);281if (viewsService.isViewVisible(REPL_VIEW_ID)) {282viewsService.closeView(REPL_VIEW_ID);283} else {284await viewsService.openView(REPL_VIEW_ID);285}286}287});288289MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, {290when: ContextKeyExpr.and(291ContextKeyExpr.equals('viewContainer', VIEWLET_ID),292CONTEXT_DEBUG_STATE.notEqualsTo('inactive'),293ContextKeyExpr.or(294ContextKeyExpr.equals('config.debug.toolBarLocation', 'docked'),295ContextKeyExpr.has('config.debug.hideLauncherWhileDebugging')296)297),298order: 10,299command: {300id: SELECT_AND_START_ID,301title: nls.localize('startAdditionalSession', "Start Additional Session"),302}303});304305306