Skip to content

Commit

Permalink
Merge pull request #166202 from gayanper/wait_for_breakpoint
Browse files Browse the repository at this point in the history
Add support for breakpoint dependencies
  • Loading branch information
connor4312 authored Jan 10, 2024
2 parents c973ec7 + 207197a commit ea77026
Show file tree
Hide file tree
Showing 14 changed files with 458 additions and 111 deletions.
1 change: 1 addition & 0 deletions src/vs/base/common/codicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const Codicon = {
closeDirty: register('close-dirty', 0xea71),
debugBreakpoint: register('debug-breakpoint', 0xea71),
debugBreakpointDisabled: register('debug-breakpoint-disabled', 0xea71),
debugBreakpointPending: register('debug-breakpoint-pending', 0xebd9),
debugHint: register('debug-hint', 0xea71),
primitiveSquare: register('primitive-square', 0xea72),
edit: register('edit', 0xea73),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ export function createBreakpointDecorations(accessor: ServicesAccessor, model: I
function getBreakpointDecorationOptions(accessor: ServicesAccessor, model: ITextModel, breakpoint: IBreakpoint, state: State, breakpointsActivated: boolean, showBreakpointsInOverviewRuler: boolean, hasOtherBreakpointsOnLine: boolean): IModelDecorationOptions {
const debugService = accessor.get(IDebugService);
const languageService = accessor.get(ILanguageService);
const { icon, message, showAdapterUnverifiedMessage } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, undefined);
const labelService = accessor.get(ILabelService);
const { icon, message, showAdapterUnverifiedMessage } = getBreakpointMessageAndIcon(state, breakpointsActivated, breakpoint, labelService, debugService.getModel());
let glyphMarginHoverMessage: MarkdownString | undefined;

let unverifiedMessage: string | undefined;
Expand Down Expand Up @@ -284,7 +285,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi

if (isShiftPressed) {
breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!enabled, bp));
} else if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition)) {
} else if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition || !!bp.triggeredBy)) {
// Show the dialog if there is a potential condition to be accidently lost.
// Do not show dialog on linux due to electron issue freezing the mouse #50026
const logPoint = breakpoints.every(bp => !!bp.logMessage);
Expand Down Expand Up @@ -454,6 +455,13 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
true,
() => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE))
));
actions.push(new Action(
'addTriggeredBreakpoint',
nls.localize('addTriggeredBreakpoint', "Add Triggered Breakpoint.."),
undefined,
true,
() => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.TRIGGER_POINT))
));
}

if (this.debugService.state === State.Stopped) {
Expand Down Expand Up @@ -522,7 +530,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
// Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there
// In practice this happens for the first breakpoint that was set on a line
// We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information
const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.breakpoint.disabled;
const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService, this.debugService.getModel()).icon : icons.breakpoint.disabled;
const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn);
const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions);

Expand Down Expand Up @@ -796,6 +804,13 @@ registerThemingParticipant((theme, collector) => {
color: ${debugIconBreakpointColor} !important;
}
`);

collector.addRule(`
.monaco-workbench ${ThemeIcon.asCSSSelector(icons.breakpoint.pending)} {
color: ${debugIconBreakpointColor} !important;
font-size: 12px !important;
}
`);
}

const debugIconBreakpointDisabledColor = theme.getColor(debugIconBreakpointDisabledForeground);
Expand Down
168 changes: 122 additions & 46 deletions src/vs/workbench/contrib/debug/browser/breakpointWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,46 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import 'vs/css!./media/breakpointWidget';
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Button } from 'vs/base/browser/ui/button/button';
import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { CancellationToken } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
import * as lifecycle from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug';
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { IModelService } from 'vs/editor/common/services/model';
import { URI as uri } from 'vs/base/common/uri';
import { CompletionList, CompletionContext, CompletionItemKind } from 'vs/editor/common/languages';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModel } from 'vs/editor/common/model';
import { provideSuggestionItems, CompletionOptions } from 'vs/editor/contrib/suggest/browser/suggest';
import 'vs/css!./media/breakpointWidget';
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorCommand, ServicesAccessor, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CompletionContext, CompletionItemKind, CompletionList } from 'vs/editor/common/languages';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IModelService } from 'vs/editor/common/services/model';
import { CompletionOptions, provideSuggestionItems } from 'vs/editor/contrib/suggest/browser/suggest';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ILabelService } from 'vs/platform/label/common/label';
import { defaultButtonStyles, defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, CONTEXT_IN_BREAKPOINT_WIDGET, BreakpointWidgetContext as Context, DEBUG_SCHEME, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugService } from 'vs/workbench/contrib/debug/common/debug';

const $ = dom.$;
const IPrivateBreakpointWidgetService = createDecorator<IPrivateBreakpointWidgetService>('privateBreakpointWidgetService');
Expand Down Expand Up @@ -78,6 +81,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi

private selectContainer!: HTMLElement;
private inputContainer!: HTMLElement;
private selectBreakpointContainer!: HTMLElement;
private input!: IActiveCodeEditor;
private toDispose: lifecycle.IDisposable[];
private conditionInput = '';
Expand All @@ -86,6 +90,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
private breakpoint: IBreakpoint | undefined;
private context: Context;
private heightInPx: number | undefined;
private triggeredByBreakpointInput: IBreakpoint | undefined;

constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, context: Context | undefined,
@IContextViewService private readonly contextViewService: IContextViewService,
Expand All @@ -98,6 +103,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@ILabelService private readonly labelService: ILabelService,
) {
super(editor, { showFrame: true, showArrow: false, frameWidth: 1, isAccessible: true });

Expand All @@ -114,6 +120,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
this.context = Context.LOG_MESSAGE;
} else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) {
this.context = Context.HIT_COUNT;
} else if (this.breakpoint && this.breakpoint.triggeredBy) {
this.context = Context.TRIGGER_POINT;
} else {
this.context = Context.CONDITION;
}
Expand Down Expand Up @@ -156,16 +164,18 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
}

private rememberInput(): void {
const value = this.input.getModel().getValue();
switch (this.context) {
case Context.LOG_MESSAGE:
this.logMessageInput = value;
break;
case Context.HIT_COUNT:
this.hitCountInput = value;
break;
default:
this.conditionInput = value;
if (this.context !== Context.TRIGGER_POINT) {
const value = this.input.getModel().getValue();
switch (this.context) {
case Context.LOG_MESSAGE:
this.logMessageInput = value;
break;
case Context.HIT_COUNT:
this.hitCountInput = value;
break;
default:
this.conditionInput = value;
}
}
}

Expand All @@ -189,17 +199,13 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi

protected _fillContainer(container: HTMLElement): void {
this.setCssClass('breakpoint-widget');
const selectBox = new SelectBox(<ISelectOptionItem[]>[{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') });
const selectBox = new SelectBox(<ISelectOptionItem[]>[{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }, { text: nls.localize('triggeredBy', "Wait For Breakpoint") }], this.context, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') });
this.selectContainer = $('.breakpoint-select-container');
selectBox.render(dom.append(container, this.selectContainer));
selectBox.onDidSelect(e => {
this.rememberInput();
this.context = e.index;
this.setInputMode();

const value = this.getInputValue(this.breakpoint);
this.input.getModel().setValue(value);
this.input.focus();
this.updateContextInput();
});

this.inputContainer = $('.inputContainer');
Expand All @@ -210,10 +216,69 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
this.fitHeightToContent();
}));
this.input.setPosition({ lineNumber: 1, column: this.input.getModel().getLineMaxColumn(1) });

this.createTriggerBreakpointInput(container);

this.updateContextInput();
// Due to an electron bug we have to do the timeout, otherwise we do not get focus
setTimeout(() => this.input.focus(), 150);
}

private createTriggerBreakpointInput(container: HTMLElement) {
const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint);

const index = breakpoints.findIndex((bp) => this.breakpoint?.triggeredBy === bp.getId());
let select = 0;
if (index > -1) {
select = index + 1;
}
const items: ISelectOptionItem[] = [{ text: nls.localize('noTriggerByBreakpoint', 'None') }];
breakpoints.map(bp => <ISelectOptionItem>{ text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}` })
.forEach(i => items.push(i));

const selectBreakpointBox = new SelectBox(items, select, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') });
selectBreakpointBox.onDidSelect(e => {
if (e.index === 0) {
this.triggeredByBreakpointInput = undefined;
} else {
this.triggeredByBreakpointInput = breakpoints[e.index - 1];
}
});
this.toDispose.push(selectBreakpointBox);
this.selectBreakpointContainer = $('.select-breakpoint-container');
this.toDispose.push(dom.addDisposableListener(this.selectBreakpointContainer, dom.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Escape)) {
this.close(false);
}
}));

const selectionWrapper = $('.select-box-container');
dom.append(this.selectBreakpointContainer, selectionWrapper);
selectBreakpointBox.render(selectionWrapper);

dom.append(container, this.selectBreakpointContainer);

const closeButton = new Button(this.selectBreakpointContainer, defaultButtonStyles);
closeButton.label = nls.localize('ok', "Ok");
this.toDispose.push(closeButton.onDidClick(() => this.close(true)));
this.toDispose.push(closeButton);
}

private updateContextInput() {
if (this.context === Context.TRIGGER_POINT) {
this.inputContainer.hidden = true;
this.selectBreakpointContainer.hidden = false;
} else {
this.inputContainer.hidden = false;
this.selectBreakpointContainer.hidden = true;
this.setInputMode();
const value = this.getInputValue(this.breakpoint);
this.input.getModel().setValue(value);
this.input.focus();
}
}

protected override _doLayout(heightInPixel: number, widthInPixel: number): void {
this.heightInPx = heightInPixel;
this.input.layout({ height: heightInPixel, width: widthInPixel - 113 });
Expand Down Expand Up @@ -321,6 +386,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
let condition = this.breakpoint && this.breakpoint.condition;
let hitCondition = this.breakpoint && this.breakpoint.hitCondition;
let logMessage = this.breakpoint && this.breakpoint.logMessage;
let triggeredBy = this.breakpoint && this.breakpoint.triggeredBy;

this.rememberInput();

if (this.conditionInput || this.context === Context.CONDITION) {
Expand All @@ -332,13 +399,21 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
if (this.logMessageInput || this.context === Context.LOG_MESSAGE) {
logMessage = this.logMessageInput;
}
if (this.context === Context.TRIGGER_POINT) {
// currently, trigger points don't support additional conditions:
condition = undefined;
hitCondition = undefined;
logMessage = undefined;
triggeredBy = this.triggeredByBreakpointInput?.getId();
}

if (this.breakpoint) {
const data = new Map<string, IBreakpointUpdateData>();
data.set(this.breakpoint.getId(), {
condition,
hitCondition,
logMessage
logMessage,
triggeredBy
});
this.debugService.updateBreakpoints(this.breakpoint.originalUri, data, false).then(undefined, onUnexpectedError);
} else {
Expand All @@ -350,7 +425,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
enabled: true,
condition,
hitCondition,
logMessage
logMessage,
triggeredBy
}]);
}
}
Expand Down
Loading

0 comments on commit ea77026

Please sign in to comment.