[debugger] add logging breakpoint via gutter context menu, IJPL-195406

Also:
* make the logging expression text box as fancy as the condition text box
* focus the logging expression text box in the settings of a non-suspending breakpoint
* "add conditional/logging breakpoint" actions use selected text in editor as an expression

^IJPL-195406 fixed

GitOrigin-RevId: 40086d8cee89f4426811eaad8c4e002239644fd9
This commit is contained in:
Vladimir Parfinenko
2025-07-15 14:14:40 +00:00
committed by intellij-monorepo-bot
parent 1a8d68d26b
commit 7032c7cb27
6 changed files with 74 additions and 22 deletions
@@ -2914,6 +2914,7 @@ action.TopStripeUnPinButton.text=Unpin
group.Documentation.Navigation.text=Quick Documentation Navigation
action.AddBreakpoint.text=Add Breakpoint
action.AddConditionalBreakpoint.text=Add Conditional Breakpoint\u2026
action.AddLoggingBreakpoint.text=Add Logging Breakpoint\u2026
group.XDebugger.Attach.Dialog.Settings.text=Attach dialog settings
action.XDebugger.Show.Breakpoints.Over.Line.Numbers.text=Breakpoints Over Line Numbers
@@ -231,6 +231,7 @@
<group id="XDebugger.Hover.Breakpoint.Context.Menu">
<action id="AddBreakpoint" class="com.intellij.xdebugger.impl.actions.handlers.AddLineBreakpointAction"/>
<action id="AddConditionalBreakpoint" class="com.intellij.xdebugger.impl.actions.handlers.AddLineBreakpointAction$WithCondition"/>
<action id="AddLoggingBreakpoint" class="com.intellij.xdebugger.impl.actions.handlers.AddLineBreakpointAction$WithLogging"/>
</group>
<group id="XDebugger.Attach.Dialog.Settings" class="com.intellij.xdebugger.impl.ui.attach.dialog.AttachDialogSettings" popup="true"/>
@@ -16,9 +16,12 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ExperimentalUI;
import com.intellij.util.ModalityUiUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.SuspendPolicy;
import com.intellij.xdebugger.impl.XDebuggerManagerImpl;
import com.intellij.xdebugger.impl.XSourcePositionImpl;
import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil;
import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
import com.intellij.xdebugger.impl.breakpoints.XLineBreakpointProxy;
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -39,9 +42,10 @@ public class AddLineBreakpointAction extends DumbAwareAction implements ActionRe
if (editor == null) return;
XSourcePosition position = getLineBreakpointPosition(e);
assert position != null;
String selection = editor.getSelectionModel().getSelectedText();
XBreakpointUtil.toggleLineBreakpointProxy(project, position, false, editor, false, false, true)
.thenAccept(bp -> {
if (bp != null && isConditional()) {
if (bp != null && editBreakpointSettings(bp, selection)) {
ModalityUiUtil.invokeLaterIfNeeded(ModalityState.defaultModalityState(), () -> {
EditorGutterComponentEx gutter = (EditorGutterComponentEx)editor.getGutter();
int x = -gutter.getWidth() + gutter.getLineNumberAreaOffset() + gutter.getLineNumberAreaWidth() / 2;
@@ -82,13 +86,34 @@ public class AddLineBreakpointAction extends DumbAwareAction implements ActionRe
return null;
}
protected boolean isConditional() {
/**
* Tweak breakpoint settings after its creation.
* @return true, if a breakpoint editor UI should be shown
*/
@ApiStatus.OverrideOnly
protected boolean editBreakpointSettings(XLineBreakpointProxy bp, @Nullable String editorSelection) {
return false;
}
public static class WithCondition extends AddLineBreakpointAction implements ActionRemoteBehaviorSpecification.FrontendOtherwiseBackend {
@Override
protected boolean isConditional() {
protected boolean editBreakpointSettings(XLineBreakpointProxy bp, @Nullable String editorSelection) {
bp.setConditionEnabled(true);
bp.setConditionExpression(XExpressionImpl.fromText(editorSelection));
return true;
}
}
public static class WithLogging extends AddLineBreakpointAction implements ActionRemoteBehaviorSpecification.FrontendOtherwiseBackend {
@Override
protected boolean editBreakpointSettings(XLineBreakpointProxy bp, @Nullable String editorSelection) {
bp.setSuspendPolicy(SuspendPolicy.NONE);
if (editorSelection != null) {
bp.setLogExpressionObject(XExpressionImpl.fromText(editorSelection));
}
else {
bp.setLogMessage(true);
}
return true;
}
}
@@ -27,15 +27,14 @@
<properties/>
<border type="none"/>
<children>
<component id="4a782" class="javax.swing.JCheckBox" binding="myLogExpressionCheckBox" default-binding="true">
<grid id="4a782" binding="myLogExpressionCheckBoxPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="true"/>
<text resource-bundle="messages/XDebuggerBundle" key="xbreakpoints.log.expression.checkbox"/>
</properties>
</component>
<properties/>
<border type="none"/>
<children/>
</grid>
<grid id="16042" binding="myLogExpressionPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="1" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
@@ -77,7 +76,7 @@
<size top="4" left="0" bottom="0" right="0"/>
</border>
<children>
<component id="dc810" class="javax.swing.JLabel">
<component id="dc810" class="javax.swing.JLabel" binding="myLogMessageLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
@@ -2,8 +2,10 @@
package com.intellij.xdebugger.impl.breakpoints.ui;
import com.intellij.openapi.project.Project;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
@@ -25,8 +27,10 @@ import java.awt.event.ActionListener;
public class XBreakpointActionsPanel extends XBreakpointPropertiesSubPanel {
public static final String LOG_EXPRESSION_HISTORY_ID = "breakpointLogExpression";
private JLabel myLogMessageLabel;
private JCheckBox myLogMessageCheckBox;
private JCheckBox myLogExpressionCheckBox;
private JPanel myLogExpressionCheckBoxPanel;
private JPanel myLogExpressionPanel;
private JPanel myContentPane;
private JPanel myMainPanel;
@@ -36,8 +40,14 @@ public class XBreakpointActionsPanel extends XBreakpointPropertiesSubPanel {
private JCheckBox myLogStack;
private @Nullable XDebuggerExpressionComboBox myLogExpressionComboBox;
public void init(Project project, @NotNull XBreakpointProxy breakpoint, @Nullable XDebuggerEditorsProvider debuggerEditorsProvider) {
private boolean myShowAllOptions;
public void init(Project project,
@NotNull XBreakpointProxy breakpoint,
@Nullable XDebuggerEditorsProvider debuggerEditorsProvider,
boolean showAllOptions) {
init(project, breakpoint);
myShowAllOptions = showAllOptions;
if (debuggerEditorsProvider != null) {
ActionListener listener = new ActionListener() {
@Override
@@ -45,6 +55,7 @@ public class XBreakpointActionsPanel extends XBreakpointPropertiesSubPanel {
onCheckboxChanged();
}
};
myLogExpressionCheckBox = new JBCheckBox(XDebuggerBundle.message("xbreakpoints.log.expression.checkbox"));
myLogExpressionComboBox = new XDebuggerExpressionComboBox(project, debuggerEditorsProvider, LOG_EXPRESSION_HISTORY_ID,
null, true, false);
myLanguageChooserPanel.add(myLogExpressionComboBox.getLanguageChooser(), BorderLayout.CENTER);
@@ -100,7 +111,20 @@ public class XBreakpointActionsPanel extends XBreakpointPropertiesSubPanel {
if (myLogExpressionComboBox != null) {
XExpression logExpression = myBreakpoint.getLogExpressionObjectInt();
myLogExpressionComboBox.setExpression(logExpression);
myLogExpressionCheckBox.setSelected(myBreakpoint.isLogExpressionEnabled() && logExpression != null);
boolean hideCheckbox = !myShowAllOptions && logExpression == null;
myLogExpressionCheckBox.setSelected(hideCheckbox || (myBreakpoint.isLogExpressionEnabled() && logExpression != null));
myLogExpressionCheckBoxPanel.removeAll();
if (hideCheckbox) {
var label = new JLabel(XDebuggerBundle.message("xbreakpoints.log.expression.checkbox"));
label.setBorder(JBUI.Borders.empty(0, 4, 4, 0));
label.setLabelFor(myLogExpressionComboBox.getComboBox());
myLogExpressionCheckBoxPanel.add(label);
myLogExpressionPanel.setBorder(JBUI.Borders.emptyLeft(3));
myLogMessageLabel.setBorder(JBUI.Borders.empty(0, 4, 4, 0)); // to unify with other labels
} else {
myLogExpressionCheckBoxPanel.add(myLogExpressionCheckBox);
myLogExpressionPanel.setBorder(JBUI.Borders.emptyLeft(UIUtil.getCheckBoxTextHorizontalOffset(myLogExpressionCheckBox)));
}
}
onCheckboxChanged();
}
@@ -19,10 +19,7 @@ import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XDebuggerManager;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpointManager;
import com.intellij.xdebugger.breakpoints.XBreakpointType;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import com.intellij.xdebugger.breakpoints.XLineBreakpointType;
import com.intellij.xdebugger.breakpoints.*;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
import com.intellij.xdebugger.impl.XDebuggerUtilImpl;
@@ -152,7 +149,7 @@ public class XLightBreakpointPropertiesPanel implements XSuspendPolicyPanel.Dele
XDebuggerEditorsProvider debuggerEditorsProvider = breakpoint.getEditorsProvider();
if (breakpointType.getVisibleStandardPanels().contains(XBreakpointType.StandardPanels.ACTIONS)) {
myActionsPanel.init(project, breakpoint, debuggerEditorsProvider);
myActionsPanel.init(project, breakpoint, debuggerEditorsProvider, myShowAllOptions);
mySubPanels.add(myActionsPanel);
}
else {
@@ -229,12 +226,17 @@ public class XLightBreakpointPropertiesPanel implements XSuspendPolicyPanel.Dele
@Override
public void focusGained(FocusEvent event) {
JComponent compToFocus = null;
if (myConditionComboBox != null && myConditionComboBox.getComboBox().isEnabled()) {
compToFocus = myConditionComboBox.getEditorComponent();
}
else if (breakpointType.getVisibleStandardPanels().contains(XBreakpointType.StandardPanels.ACTIONS)) {
var actionsAvailable = breakpointType.getVisibleStandardPanels().contains(XBreakpointType.StandardPanels.ACTIONS);
var conditionEditable = myConditionComboBox != null && myConditionComboBox.getComboBox().isEnabled();
var isSuspending = breakpoint.getSuspendPolicy() != SuspendPolicy.NONE;
if (actionsAvailable && (!isSuspending || !conditionEditable)) {
// Focus actions panel in case of non-suspending breakpoint (or if condition is explicitly disabled).
// This is important for the "Add Logging Breakpoint" action which should focus on the logging expression.
compToFocus = myActionsPanel.getDefaultFocusComponent();
}
else if (conditionEditable) {
compToFocus = myConditionComboBox.getEditorComponent();
}
if (compToFocus != null) {
IdeFocusManager.findInstance().requestFocus(compToFocus, false);
}