diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerUtilImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerUtilImpl.java index 70d12c99a014..1d4765c72894 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerUtilImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerUtilImpl.java @@ -8,34 +8,22 @@ import com.intellij.lang.Language; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.markup.HighlighterTargetArea; -import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.PopupStep; -import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.platform.debugger.impl.shared.DebuggerAsyncActionUtilsKt; import com.intellij.platform.debugger.impl.shared.XDebuggerUtilImplShared; import com.intellij.platform.debugger.impl.shared.proxy.XDebugManagerProxy; -import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointHighlighterRange; -import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointInstallationInfo; import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointManagerProxy; -import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointProxy; -import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointTypeProxy; import com.intellij.platform.debugger.impl.ui.XDebuggerEntityConverter; import com.intellij.pom.Navigatable; import com.intellij.psi.PsiComment; @@ -48,15 +36,10 @@ import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.SimpleColoredText; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.popup.list.ListPopupImpl; -import com.intellij.util.DocumentUtil; -import com.intellij.util.ModalityUiUtil; import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.xdebugger.XDebugSession; -import com.intellij.xdebugger.XDebuggerBundle; import com.intellij.xdebugger.XDebuggerManager; import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.XExpression; @@ -74,6 +57,7 @@ import com.intellij.xdebugger.frame.XExecutionStack; import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.frame.XSuspendContext; import com.intellij.xdebugger.frame.XValueContainer; +import com.intellij.xdebugger.impl.breakpoints.XBreakpointInstallUtils; import com.intellij.xdebugger.impl.breakpoints.XBreakpointManagerImpl; import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil; import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl; @@ -87,7 +71,6 @@ import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree; import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase; import com.intellij.xdebugger.settings.XDebuggerSettings; -import com.intellij.xdebugger.ui.DebuggerColors; import kotlin.Unit; import one.util.streamex.StreamEx; import org.jetbrains.annotations.ApiStatus; @@ -97,19 +80,11 @@ import org.jetbrains.concurrency.Promise; import org.jetbrains.concurrency.Promises; import javax.swing.Icon; -import javax.swing.JList; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; -import java.util.stream.Stream; import static org.jetbrains.concurrency.Promises.asPromise; import static org.jetbrains.concurrency.Promises.rejectedPromise; @@ -117,8 +92,6 @@ import static org.jetbrains.concurrency.Promises.rejectedPromise; @ApiStatus.Internal public class XDebuggerUtilImpl extends XDebuggerUtil { private static final Logger LOG = Logger.getInstance(XDebuggerUtilImpl.class); - - private static final Ref SHOW_BREAKPOINT_AD = new Ref<>(true); @Override public XLineBreakpointType[] getLineBreakpointTypes() { @@ -265,49 +238,6 @@ public class XDebuggerUtilImpl extends XDebuggerUtil { }; } - private static int getIndexOfBestMatchingInlineVariant(int caretOffset, List variants) { - assert !variants.isEmpty(); - TextRange bestRange = null; - int bestIndex = -1; - for (int i = 0; i < variants.size(); i++) { - var variant = variants.get(i); - TextRange range = variant.getHighlightRange(); - if (range != null && range.contains(caretOffset)) { - if (bestRange == null || bestRange.getLength() > range.getLength()) { - bestRange = range; - bestIndex = i; - } - } - } - // Use first variant if nothing interesting is found. - return bestIndex == -1 ? 0 : bestIndex; - } - - private static @NotNull T getBestMatchingBreakpoint(int caretOffset, Iterator<@NotNull T> breakpoints, Function rangeProvider) { - // Best matching = closest to the insertion point and minimal by range of all breakpoints or breakpoint variants - T bestBreakpoint = null; - int bestDistance = Integer.MAX_VALUE; - int bestRangeLength = Integer.MAX_VALUE; - while (breakpoints.hasNext()) { - var b = breakpoints.next(); - TextRange range = rangeProvider.apply(b); - int rangeLength = range != null ? range.getLength() : Integer.MAX_VALUE; - // note that range = null means "whole line" - int distance = range == null ? - 0 : - range.containsOffset(caretOffset) ? //include end offset - 0 : - Math.min(Math.abs(range.getStartOffset() - caretOffset), Math.abs(range.getEndOffset() - caretOffset)); - if (bestBreakpoint == null || distance < bestDistance || (distance == bestDistance && rangeLength < bestRangeLength)) { - bestBreakpoint = b; - bestDistance = distance; - bestRangeLength = rangeLength; - } - } - assert bestBreakpoint != null; - return bestBreakpoint; - } - /** * @deprecated use {@link #toggleAndReturnLineBreakpoint(Project, List, XSourcePosition, boolean, boolean, Editor, boolean)} */ @@ -322,7 +252,7 @@ public class XDebuggerUtilImpl extends XDebuggerUtil { } /** - * Use proxy option instead. + * Use {@link XBreakpointInstallUtils#toggleAndReturnLineBreakpointProxy} instead. */ @ApiStatus.Obsolete public static @NotNull Promise<@Nullable XLineBreakpoint> toggleAndReturnLineBreakpoint( @@ -337,7 +267,7 @@ public class XDebuggerUtilImpl extends XDebuggerUtil { var proxyTypes = XDebugManagerProxy.getInstance().getBreakpointManagerProxy(project).getLineBreakpointTypes().stream() .filter(type -> ContainerUtil.exists(types, t -> t.getId().equals(type.getId()))) .toList(); - var future = toggleAndReturnLineBreakpointProxy( + var future = XBreakpointInstallUtils.toggleAndReturnLineBreakpointProxy( project, proxyTypes, position, selectVariantByPositionColumn, temporary, editor, canRemove, false, null); return asPromise(future).then(b -> { @@ -350,220 +280,6 @@ public class XDebuggerUtilImpl extends XDebuggerUtil { }); } - public static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> toggleAndReturnLineBreakpointProxy( - final @NotNull Project project, - @NotNull List types, - final @NotNull XSourcePosition position, - boolean selectVariantByPositionColumn, - final boolean temporary, - final @Nullable Editor editor, - boolean canRemove, - boolean isLogging, - @Nullable String logExpression - ) { - var breakpointInfo = new XLineBreakpointInstallationInfo(types, position, temporary, isLogging, logExpression, canRemove); - return toggleAndReturnLineBreakpointProxy(project, editor, breakpointInfo, selectVariantByPositionColumn); - } - - public static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> toggleAndReturnLineBreakpointProxy( - @NotNull Project project, - @Nullable Editor editor, - @NotNull XLineBreakpointInstallationInfo breakpointInfo, - boolean selectVariantByPositionColumn - ) { - if (areInlineBreakpointsEnabled(breakpointInfo.getPosition().getFile())) { - return processInlineBreakpoints(project, editor, breakpointInfo, selectVariantByPositionColumn); - } - else { - return selectBreakpointVariantWithPopup(project, breakpointInfo, editor); - } - } - - private static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> selectBreakpointVariantWithPopup( - @NotNull Project project, - @NotNull XLineBreakpointInstallationInfo breakpointInfo, - @Nullable Editor editor - ) { - final VirtualFile file = breakpointInfo.getPosition().getFile(); - final int line = breakpointInfo.getPosition().getLine(); - var breakpointManager = XDebugManagerProxy.getInstance().getBreakpointManagerProxy(project); - - for (XLineBreakpointTypeProxy type : breakpointInfo.getTypes()) { - XLineBreakpointProxy breakpoint = breakpointManager.findBreakpointAtLine(type, file, line); - if (breakpoint != null) { - XBreakpointUtil.removeBreakpointIfPossible(project, breakpointInfo, breakpoint); - return CompletableFuture.completedFuture(null); - } - } - return FrontendXLineBreakpointVariantKt.computeBreakpointProxy(project, editor, breakpointInfo, variantChoice -> { - assert !variantChoice.getVariants().isEmpty(); - ModalityUiUtil.invokeLaterIfNeeded(ModalityState.defaultModalityState(), () -> { - for (XLineBreakpointTypeProxy type : breakpointInfo.getTypes()) { - if (breakpointManager.findBreakpointAtLine(type, file, line) != null) { - variantChoice.breakpointRemoved(); - return; - } - } - RelativePoint relativePoint = editor != null ? DebuggerUIUtil.getPositionForPopup(editor, line) : null; - if (variantChoice.getVariants().size() > 1 && relativePoint != null) { - showBreakpointSelectionPopup(project, breakpointInfo.getPosition(), editor, variantChoice, relativePoint); - } - else { - variantChoice.select(variantChoice.getVariants().get(0)); - } - }); - return Unit.INSTANCE; - }); - } - - private static void showBreakpointSelectionPopup( - @NotNull Project project, - @NotNull XSourcePosition position, - @NotNull Editor editor, - VariantChoiceData choiceData, - RelativePoint relativePoint - ) { - final int line = position.getLine(); - class MySelectionListener implements ListSelectionListener { - RangeHighlighter myHighlighter = null; - - @Override - public void valueChanged(ListSelectionEvent e) { - if (!e.getValueIsAdjusting()) { - updateHighlighter(((JList)e.getSource()).getSelectedValue()); - } - } - - public void initialSet(Object value) { - if (myHighlighter == null) { - updateHighlighter(value); - } - } - - void updateHighlighter(Object value) { - clearHighlighter(); - if (value instanceof FrontendXLineBreakpointVariant variant) { - TextRange range = variant.getHighlightRange(); - TextRange lineRange = DocumentUtil.getLineTextRange(editor.getDocument(), line); - if (range == null) { - range = lineRange; - } - if (!range.isEmpty() && range.intersectsStrict(lineRange)) { - myHighlighter = editor.getMarkupModel().addRangeHighlighter( - DebuggerColors.BREAKPOINT_ATTRIBUTES, range.getStartOffset(), range.getEndOffset(), - DebuggerColors.BREAKPOINT_HIGHLIGHTER_LAYER, - HighlighterTargetArea.EXACT_RANGE); - } - } - } - - private void clearHighlighter() { - if (myHighlighter != null) { - myHighlighter.dispose(); - } - } - } - - final int defaultIndex = getIndexOfBestMatchingInlineVariant(position.getOffset(), choiceData.getVariants()); - - final MySelectionListener selectionListener = new MySelectionListener(); - BaseListPopupStep step = - new BaseListPopupStep<>(XDebuggerBundle.message("popup.title.set.breakpoint"), choiceData.getVariants()) { - @Override - public @NotNull String getTextFor(FrontendXLineBreakpointVariant value) { - return value.getText(); - } - - @Override - public Icon getIconFor(FrontendXLineBreakpointVariant value) { - return value.getIcon(); - } - - @Override - public void canceled() { - selectionListener.clearHighlighter(); - choiceData.cancel(); - } - - @Override - public PopupStep onChosen(FrontendXLineBreakpointVariant selectedValue, boolean finalChoice) { - selectionListener.clearHighlighter(); - choiceData.select(selectedValue); - return FINAL_CHOICE; - } - - @Override - public int getDefaultOptionIndex() { - return defaultIndex; - } - }; - ListPopupImpl popup = new ListPopupImpl(project, step) { - @Override - protected void afterShow() { - super.afterShow(); - selectionListener.initialSet(getList().getSelectedValue()); - } - }; - DebuggerUIUtil.registerExtraHandleShortcuts(popup, SHOW_BREAKPOINT_AD, IdeActions.ACTION_TOGGLE_LINE_BREAKPOINT); - - popup.addListSelectionListener(selectionListener); - popup.show(relativePoint); - } - - private static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> processInlineBreakpoints( - @NotNull Project project, - @Nullable Editor editor, - @NotNull XLineBreakpointInstallationInfo breakpointInfo, - boolean selectVariantByPositionColumn - ) { - return FrontendXLineBreakpointVariantKt.computeBreakpointProxy(project, editor, breakpointInfo, variantChoice -> { - var variants = variantChoice.getVariants().stream().filter(v -> v.getUseAsInlineVariant()).toList(); - if (variants.isEmpty()) { - LOG.error("Unexpected empty variants"); - variantChoice.cancel(); - return Unit.INSTANCE; - } - - List breakpoints = XBreakpointUtil.findBreakpointsAtLine(project, breakpointInfo); - - FrontendXLineBreakpointVariant variant; - if (selectVariantByPositionColumn) { - Object breakpointOrVariant = getBestMatchingBreakpoint(breakpointInfo.getPosition().getOffset(), - Stream.concat(breakpoints.stream(), variants.stream()).iterator(), - o -> - o instanceof XLineBreakpointProxy b ? rangeOrNull(b.getHighlightRange()) - : ((FrontendXLineBreakpointVariant)o).getHighlightRange()); - - if (breakpointOrVariant instanceof XLineBreakpointProxy existingBreakpoint) { - XBreakpointUtil.removeBreakpointIfPossible(project, breakpointInfo, existingBreakpoint); - variantChoice.breakpointRemoved(); - return Unit.INSTANCE; - } - - variant = (FrontendXLineBreakpointVariant)breakpointOrVariant; - } - else { - if (!breakpoints.isEmpty()) { - XBreakpointUtil.removeBreakpointIfPossible(project, breakpointInfo, breakpoints.toArray(XLineBreakpointProxy[]::new)); - variantChoice.breakpointRemoved(); - return Unit.INSTANCE; - } - - variant = variants.stream().max(Comparator.comparing(v -> v.getPriority())).get(); - } - - variantChoice.select(variant); - return Unit.INSTANCE; - }); - } - - private static TextRange rangeOrNull(XLineBreakpointHighlighterRange range) { - if (range instanceof XLineBreakpointHighlighterRange.Available available) { - return available.getRange(); - } - return null; - } - public static

XLineBreakpoint

addLineBreakpoint(XBreakpointManager breakpointManager, XLineBreakpointType

.XLineBreakpointVariant variant, VirtualFile file, diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointInstallUtils.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointInstallUtils.java new file mode 100644 index 000000000000..0524ba76fc16 --- /dev/null +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointInstallUtils.java @@ -0,0 +1,314 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.xdebugger.impl.breakpoints; + +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.markup.HighlighterTargetArea; +import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.platform.debugger.impl.shared.proxy.XDebugManagerProxy; +import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointHighlighterRange; +import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointInstallationInfo; +import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointProxy; +import com.intellij.platform.debugger.impl.shared.proxy.XLineBreakpointTypeProxy; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.util.DocumentUtil; +import com.intellij.util.ModalityUiUtil; +import com.intellij.xdebugger.XDebuggerBundle; +import com.intellij.xdebugger.XDebuggerUtil; +import com.intellij.xdebugger.XSourcePosition; +import com.intellij.xdebugger.impl.FrontendXLineBreakpointVariant; +import com.intellij.xdebugger.impl.FrontendXLineBreakpointVariantKt; +import com.intellij.xdebugger.impl.VariantChoiceData; +import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; +import com.intellij.xdebugger.ui.DebuggerColors; +import kotlin.Unit; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.Icon; +import javax.swing.JList; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Stream; + +@ApiStatus.Internal +public final class XBreakpointInstallUtils { + private static final Logger LOG = Logger.getInstance(XBreakpointInstallUtils.class); + private static final Ref SHOW_BREAKPOINT_AD = new Ref<>(true); + + private XBreakpointInstallUtils() { } + + public static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> toggleAndReturnLineBreakpointProxy( + final @NotNull Project project, + @NotNull List types, + final @NotNull XSourcePosition position, + boolean selectVariantByPositionColumn, + final boolean temporary, + final @Nullable Editor editor, + boolean canRemove, + boolean isLogging, + @Nullable String logExpression + ) { + var breakpointInfo = new XLineBreakpointInstallationInfo(types, position, temporary, isLogging, logExpression, canRemove); + return toggleAndReturnLineBreakpointProxy(project, editor, breakpointInfo, selectVariantByPositionColumn); + } + + public static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> toggleAndReturnLineBreakpointProxy( + @NotNull Project project, + @Nullable Editor editor, + @NotNull XLineBreakpointInstallationInfo breakpointInfo, + boolean selectVariantByPositionColumn + ) { + if (XDebuggerUtil.areInlineBreakpointsEnabled(breakpointInfo.getPosition().getFile())) { + return processInlineBreakpoints(project, editor, breakpointInfo, selectVariantByPositionColumn); + } + else { + return selectBreakpointVariantWithPopup(project, breakpointInfo, editor); + } + } + + private static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> selectBreakpointVariantWithPopup( + @NotNull Project project, + @NotNull XLineBreakpointInstallationInfo breakpointInfo, + @Nullable Editor editor + ) { + final VirtualFile file = breakpointInfo.getPosition().getFile(); + final int line = breakpointInfo.getPosition().getLine(); + var breakpointManager = XDebugManagerProxy.getInstance().getBreakpointManagerProxy(project); + + for (XLineBreakpointTypeProxy type : breakpointInfo.getTypes()) { + XLineBreakpointProxy breakpoint = breakpointManager.findBreakpointAtLine(type, file, line); + if (breakpoint != null) { + XBreakpointUtil.removeBreakpointIfPossible(project, breakpointInfo, breakpoint); + return CompletableFuture.completedFuture(null); + } + } + return FrontendXLineBreakpointVariantKt.computeBreakpointProxy(project, editor, breakpointInfo, variantChoice -> { + assert !variantChoice.getVariants().isEmpty(); + ModalityUiUtil.invokeLaterIfNeeded(ModalityState.defaultModalityState(), () -> { + for (XLineBreakpointTypeProxy type : breakpointInfo.getTypes()) { + if (breakpointManager.findBreakpointAtLine(type, file, line) != null) { + variantChoice.breakpointRemoved(); + return; + } + } + RelativePoint relativePoint = editor != null ? DebuggerUIUtil.getPositionForPopup(editor, line) : null; + if (variantChoice.getVariants().size() > 1 && relativePoint != null) { + showBreakpointSelectionPopup(project, breakpointInfo.getPosition(), editor, variantChoice, relativePoint); + } + else { + variantChoice.select(variantChoice.getVariants().get(0)); + } + }); + return Unit.INSTANCE; + }); + } + + private static void showBreakpointSelectionPopup( + @NotNull Project project, + @NotNull XSourcePosition position, + @NotNull Editor editor, + VariantChoiceData choiceData, + RelativePoint relativePoint + ) { + final int line = position.getLine(); + class MySelectionListener implements ListSelectionListener { + RangeHighlighter myHighlighter = null; + + @Override + public void valueChanged(ListSelectionEvent e) { + if (!e.getValueIsAdjusting()) { + updateHighlighter(((JList)e.getSource()).getSelectedValue()); + } + } + + public void initialSet(Object value) { + if (myHighlighter == null) { + updateHighlighter(value); + } + } + + void updateHighlighter(Object value) { + clearHighlighter(); + if (value instanceof FrontendXLineBreakpointVariant variant) { + TextRange range = variant.getHighlightRange(); + TextRange lineRange = DocumentUtil.getLineTextRange(editor.getDocument(), line); + if (range == null) { + range = lineRange; + } + if (!range.isEmpty() && range.intersectsStrict(lineRange)) { + myHighlighter = editor.getMarkupModel().addRangeHighlighter( + DebuggerColors.BREAKPOINT_ATTRIBUTES, range.getStartOffset(), range.getEndOffset(), + DebuggerColors.BREAKPOINT_HIGHLIGHTER_LAYER, + HighlighterTargetArea.EXACT_RANGE); + } + } + } + + private void clearHighlighter() { + if (myHighlighter != null) { + myHighlighter.dispose(); + } + } + } + + final int defaultIndex = getIndexOfBestMatchingInlineVariant(position.getOffset(), choiceData.getVariants()); + + final MySelectionListener selectionListener = new MySelectionListener(); + BaseListPopupStep step = + new BaseListPopupStep<>(XDebuggerBundle.message("popup.title.set.breakpoint"), choiceData.getVariants()) { + @Override + public @NotNull String getTextFor(FrontendXLineBreakpointVariant value) { + return value.getText(); + } + + @Override + public Icon getIconFor(FrontendXLineBreakpointVariant value) { + return value.getIcon(); + } + + @Override + public void canceled() { + selectionListener.clearHighlighter(); + choiceData.cancel(); + } + + @Override + public PopupStep onChosen(FrontendXLineBreakpointVariant selectedValue, boolean finalChoice) { + selectionListener.clearHighlighter(); + choiceData.select(selectedValue); + return FINAL_CHOICE; + } + + @Override + public int getDefaultOptionIndex() { + return defaultIndex; + } + }; + ListPopupImpl popup = new ListPopupImpl(project, step) { + @Override + protected void afterShow() { + super.afterShow(); + selectionListener.initialSet(getList().getSelectedValue()); + } + }; + DebuggerUIUtil.registerExtraHandleShortcuts(popup, SHOW_BREAKPOINT_AD, IdeActions.ACTION_TOGGLE_LINE_BREAKPOINT); + + popup.addListSelectionListener(selectionListener); + popup.show(relativePoint); + } + + private static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> processInlineBreakpoints( + @NotNull Project project, + @Nullable Editor editor, + @NotNull XLineBreakpointInstallationInfo breakpointInfo, + boolean selectVariantByPositionColumn + ) { + return FrontendXLineBreakpointVariantKt.computeBreakpointProxy(project, editor, breakpointInfo, variantChoice -> { + var variants = variantChoice.getVariants().stream().filter(v -> v.getUseAsInlineVariant()).toList(); + if (variants.isEmpty()) { + LOG.error("Unexpected empty variants"); + variantChoice.cancel(); + return Unit.INSTANCE; + } + + List breakpoints = XBreakpointUtil.findBreakpointsAtLine(project, breakpointInfo); + + FrontendXLineBreakpointVariant variant; + if (selectVariantByPositionColumn) { + Object breakpointOrVariant = getBestMatchingBreakpoint(breakpointInfo.getPosition().getOffset(), + Stream.concat(breakpoints.stream(), variants.stream()).iterator(), + o -> + o instanceof XLineBreakpointProxy b ? rangeOrNull(b.getHighlightRange()) + : ((FrontendXLineBreakpointVariant)o).getHighlightRange()); + + if (breakpointOrVariant instanceof XLineBreakpointProxy existingBreakpoint) { + XBreakpointUtil.removeBreakpointIfPossible(project, breakpointInfo, existingBreakpoint); + variantChoice.breakpointRemoved(); + return Unit.INSTANCE; + } + + variant = (FrontendXLineBreakpointVariant)breakpointOrVariant; + } + else { + if (!breakpoints.isEmpty()) { + XBreakpointUtil.removeBreakpointIfPossible(project, breakpointInfo, breakpoints.toArray(XLineBreakpointProxy[]::new)); + variantChoice.breakpointRemoved(); + return Unit.INSTANCE; + } + + variant = variants.stream().max(Comparator.comparing(v -> v.getPriority())).get(); + } + + variantChoice.select(variant); + return Unit.INSTANCE; + }); + } + + private static int getIndexOfBestMatchingInlineVariant(int caretOffset, List variants) { + assert !variants.isEmpty(); + TextRange bestRange = null; + int bestIndex = -1; + for (int i = 0; i < variants.size(); i++) { + var variant = variants.get(i); + TextRange range = variant.getHighlightRange(); + if (range != null && range.contains(caretOffset)) { + if (bestRange == null || bestRange.getLength() > range.getLength()) { + bestRange = range; + bestIndex = i; + } + } + } + // Use first variant if nothing interesting is found. + return bestIndex == -1 ? 0 : bestIndex; + } + + private static @NotNull T getBestMatchingBreakpoint(int caretOffset, + Iterator<@NotNull T> breakpoints, + Function rangeProvider) { + // Best matching = closest to the insertion point and minimal by range of all breakpoints or breakpoint variants + T bestBreakpoint = null; + int bestDistance = Integer.MAX_VALUE; + int bestRangeLength = Integer.MAX_VALUE; + while (breakpoints.hasNext()) { + var b = breakpoints.next(); + TextRange range = rangeProvider.apply(b); + int rangeLength = range != null ? range.getLength() : Integer.MAX_VALUE; + // note that range = null means "whole line" + int distance = range == null ? + 0 : + range.containsOffset(caretOffset) ? //include end offset + 0 : + Math.min(Math.abs(range.getStartOffset() - caretOffset), Math.abs(range.getEndOffset() - caretOffset)); + if (bestBreakpoint == null || distance < bestDistance || (distance == bestDistance && rangeLength < bestRangeLength)) { + bestBreakpoint = b; + bestDistance = distance; + bestRangeLength = rangeLength; + } + } + assert bestBreakpoint != null; + return bestBreakpoint; + } + + private static TextRange rangeOrNull(XLineBreakpointHighlighterRange range) { + if (range instanceof XLineBreakpointHighlighterRange.Available available) { + return available.getRange(); + } + return null; + } +} diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointUtil.kt b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointUtil.kt index 17cae8b87220..30c438208897 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointUtil.kt +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointUtil.kt @@ -40,7 +40,6 @@ import com.intellij.xdebugger.breakpoints.XBreakpointProperties import com.intellij.xdebugger.breakpoints.XBreakpointType import com.intellij.xdebugger.breakpoints.XLineBreakpoint import com.intellij.xdebugger.breakpoints.XLineBreakpointType -import com.intellij.xdebugger.impl.XDebuggerUtilImpl import com.intellij.xdebugger.impl.XSourcePositionImpl import com.intellij.xdebugger.impl.breakpoints.ui.BreakpointItem import com.intellij.xdebugger.impl.ui.DebuggerUIUtil @@ -252,7 +251,7 @@ object XBreakpointUtil { val lineStart = position.line val winPosition = if (lineStart == lineWinner) position else XSourcePositionImpl.create(position.file, lineWinner) - val res = XDebuggerUtilImpl.toggleAndReturnLineBreakpointProxy( + val res = XBreakpointInstallUtils.toggleAndReturnLineBreakpointProxy( project, typeWinner, winPosition, selectVariantByPositionColumn, temporary, editor, canRemove, isLogging, logExpression) if (lineStart != lineWinner) { val offset = editor.document.getLineStartOffset(lineWinner)