mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[rd debugger] IJPL-219714 Extract breakpoint install methods
GitOrigin-RevId: 84b279e24da016cc7af578598034ce67993e5619
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1da198663b
commit
467a156b19
@@ -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<Boolean> 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<? extends FrontendXLineBreakpointVariant> 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 <T> @NotNull T getBestMatchingBreakpoint(int caretOffset, Iterator<@NotNull T> breakpoints, Function<T, @Nullable TextRange> 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<XLineBreakpointTypeProxy> 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<FrontendXLineBreakpointVariant> 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<XLineBreakpointProxy> 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 <P extends XBreakpointProperties> XLineBreakpoint<P> addLineBreakpoint(XBreakpointManager breakpointManager,
|
||||
XLineBreakpointType<P>.XLineBreakpointVariant variant,
|
||||
VirtualFile file,
|
||||
|
||||
+314
@@ -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<Boolean> SHOW_BREAKPOINT_AD = new Ref<>(true);
|
||||
|
||||
private XBreakpointInstallUtils() { }
|
||||
|
||||
public static @NotNull CompletableFuture<@Nullable XLineBreakpointProxy> toggleAndReturnLineBreakpointProxy(
|
||||
final @NotNull Project project,
|
||||
@NotNull List<XLineBreakpointTypeProxy> 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<FrontendXLineBreakpointVariant> 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<XLineBreakpointProxy> 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<? extends FrontendXLineBreakpointVariant> 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 <T> @NotNull T getBestMatchingBreakpoint(int caretOffset,
|
||||
Iterator<@NotNull T> breakpoints,
|
||||
Function<T, @Nullable TextRange> 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;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user