diff --git a/CHANGELOG.md b/CHANGELOG.md index 7641ea362bf2b..e4d78f57a49ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ## v1.39.0 - 06/29/2023 +- [debug] added support for conditional exception breakpoints [#12445](https://github.com/eclipse-theia/theia/pull/12445) + [Breaking Changes:](#breaking_changes_1.39.0) - [repo] with the upgrade to Inversify 6.0, a few initialization methods were adjusted. See also [this migration guide entry](https://github.com/eclipse-theia/theia/blob/master/doc/Migration.md#inversify-60). Additionally, other changes include: [#12425](https://github.com/eclipse-theia/theia/pull/12425) diff --git a/packages/debug/src/browser/breakpoint/breakpoint-manager.ts b/packages/debug/src/browser/breakpoint/breakpoint-manager.ts index 2f763682d4c10..5a43f816a4bd6 100644 --- a/packages/debug/src/browser/breakpoint/breakpoint-manager.ts +++ b/packages/debug/src/browser/breakpoint/breakpoint-manager.ts @@ -201,6 +201,14 @@ export class BreakpointManager extends MarkerManager { } } + updateExceptionBreakpoint(filter: string, options: Partial>): void { + const breakpoint = this.getExceptionBreakpoint(filter); + if (breakpoint) { + Object.assign(breakpoint, options); + this.fireOnDidChangeMarkers(BreakpointManager.EXCEPTION_URI); + } + } + protected functionBreakpoints: FunctionBreakpoint[] = []; getFunctionBreakpoints(): FunctionBreakpoint[] { diff --git a/packages/debug/src/browser/breakpoint/breakpoint-marker.ts b/packages/debug/src/browser/breakpoint/breakpoint-marker.ts index f783a6d6a2deb..1564f4b3ee781 100644 --- a/packages/debug/src/browser/breakpoint/breakpoint-marker.ts +++ b/packages/debug/src/browser/breakpoint/breakpoint-marker.ts @@ -55,12 +55,14 @@ export namespace BreakpointMarker { export interface ExceptionBreakpoint { enabled: boolean; + condition?: string; raw: DebugProtocol.ExceptionBreakpointsFilter; } export namespace ExceptionBreakpoint { export function create(data: DebugProtocol.ExceptionBreakpointsFilter, origin?: ExceptionBreakpoint): ExceptionBreakpoint { return { enabled: origin ? origin.enabled : false, + condition: origin ? origin.condition : undefined, raw: { ...(origin && origin.raw), ...data diff --git a/packages/debug/src/browser/debug-frontend-application-contribution.ts b/packages/debug/src/browser/debug-frontend-application-contribution.ts index 27d745ed2966f..ccdd2749f4dd6 100644 --- a/packages/debug/src/browser/debug-frontend-application-contribution.ts +++ b/packages/debug/src/browser/debug-frontend-application-contribution.ts @@ -55,6 +55,7 @@ import { DebugBreakpoint } from './model/debug-breakpoint'; import { nls } from '@theia/core/lib/common/nls'; import { DebugInstructionBreakpoint } from './model/debug-instruction-breakpoint'; import { DebugConfiguration } from '../common/debug-configuration'; +import { DebugExceptionBreakpoint } from './view/debug-exception-breakpoint'; export namespace DebugMenus { export const DEBUG = [...MAIN_MENU_BAR, '6_debug']; @@ -212,6 +213,11 @@ export namespace DebugCommands { originalLabel: 'Edit Logpoint...', label: nlsEditBreakpoint('Logpoint') }, '', DEBUG_CATEGORY_KEY); + export const EDIT_BREAKPOINT_CONDITION = Command.toLocalizedCommand({ + id: 'debug.breakpoint.editCondition', + category: DEBUG_CATEGORY, + label: 'Edit Condition...' + }, '', DEBUG_CATEGORY_KEY); export const REMOVE_BREAKPOINT = Command.toLocalizedCommand({ id: 'debug.breakpoint.remove', category: DEBUG_CATEGORY, @@ -596,7 +602,8 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi registerMenuActions(DebugBreakpointsWidget.EDIT_MENU, DebugCommands.EDIT_BREAKPOINT, - DebugCommands.EDIT_LOGPOINT + DebugCommands.EDIT_LOGPOINT, + DebugCommands.EDIT_BREAKPOINT_CONDITION ); registerMenuActions(DebugBreakpointsWidget.REMOVE_MENU, DebugCommands.REMOVE_BREAKPOINT, @@ -787,6 +794,16 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi isEnabled: () => !!this.selectedLogpoint, isVisible: () => !!this.selectedLogpoint }); + registry.registerCommand(DebugCommands.EDIT_BREAKPOINT_CONDITION, { + execute: async () => { + const { selectedExceptionBreakpoint } = this; + if (selectedExceptionBreakpoint) { + await selectedExceptionBreakpoint.editCondition(); + } + }, + isEnabled: () => !!this.selectedExceptionBreakpoint?.data.raw.supportsCondition, + isVisible: () => !!this.selectedExceptionBreakpoint?.data.raw.supportsCondition + }); registry.registerCommand(DebugCommands.REMOVE_BREAKPOINT, { execute: () => { const selectedBreakpoint = this.selectedSettableBreakpoint; @@ -1216,6 +1233,11 @@ export class DebugFrontendApplicationContribution extends AbstractViewContributi return this.selectedAnyBreakpoint; } } + get selectedExceptionBreakpoint(): DebugExceptionBreakpoint | undefined { + const { breakpoints } = this; + const selectedElement = breakpoints && breakpoints.selectedElement; + return selectedElement instanceof DebugExceptionBreakpoint ? selectedElement : undefined; + } get selectedSettableBreakpoint(): DebugFunctionBreakpoint | DebugInstructionBreakpoint | DebugSourceBreakpoint | undefined { const selected = this.selectedAnyBreakpoint; diff --git a/packages/debug/src/browser/debug-session.tsx b/packages/debug/src/browser/debug-session.tsx index 6ca43f36762f7..050eec8c889fa 100644 --- a/packages/debug/src/browser/debug-session.tsx +++ b/packages/debug/src/browser/debug-session.tsx @@ -693,13 +693,21 @@ export class DebugSession implements CompositeTreeElement { } protected async sendExceptionBreakpoints(): Promise { - const filters = []; + const filters: string[] = []; + const filterOptions: DebugProtocol.ExceptionFilterOptions[] | undefined = this.capabilities.supportsExceptionFilterOptions ? [] : undefined; for (const breakpoint of this.breakpoints.getExceptionBreakpoints()) { if (breakpoint.enabled) { - filters.push(breakpoint.raw.filter); + if (filterOptions) { + filterOptions.push({ + filterId: breakpoint.raw.filter, + condition: breakpoint.condition + }); + } else { + filters.push(breakpoint.raw.filter); + } } } - await this.sendRequest('setExceptionBreakpoints', { filters }); + await this.sendRequest('setExceptionBreakpoints', { filters, filterOptions }); } protected async sendFunctionBreakpoints(affectedUri: URI): Promise { diff --git a/packages/debug/src/browser/view/debug-exception-breakpoint.tsx b/packages/debug/src/browser/view/debug-exception-breakpoint.tsx index a5e3234ba62dd..583db16efa0b8 100644 --- a/packages/debug/src/browser/view/debug-exception-breakpoint.tsx +++ b/packages/debug/src/browser/view/debug-exception-breakpoint.tsx @@ -18,6 +18,9 @@ import * as React from '@theia/core/shared/react'; import { TreeElement } from '@theia/core/lib/browser/source-tree'; import { BreakpointManager } from '../breakpoint/breakpoint-manager'; import { ExceptionBreakpoint } from '../breakpoint/breakpoint-marker'; +import { SingleTextInputDialog } from '@theia/core/lib/browser/dialogs'; +import { TREE_NODE_INFO_CLASS } from '@theia/core/lib/browser'; +import { nls } from '@theia/core'; export class DebugExceptionBreakpoint implements TreeElement { @@ -31,13 +34,35 @@ export class DebugExceptionBreakpoint implements TreeElement { } render(): React.ReactNode { - return
+ return
- {this.data.raw.label} + + {this.data.raw.label} + {this.data.condition && + {this.data.condition} } +
; } protected toggle = () => this.breakpoints.toggleExceptionBreakpoint(this.data.raw.filter); + async editCondition(): Promise { + const inputDialog = new SingleTextInputDialog({ + title: this.data.raw.label, + placeholder: this.data.raw.conditionDescription, + initialValue: this.data.condition + }); + let condition = await inputDialog.open(); + if (condition === undefined) { + return; + } + if (condition === '') { + condition = undefined; + } + if (condition !== this.data.condition) { + this.breakpoints.updateExceptionBreakpoint(this.data.raw.filter, { condition }); + } + } }