Debugger: Add Kotlin Function breakpoints (KT-22116)
Now it's possible to put a function breakpoint. In JVM, function breakpoints behave as JVM method breakpoints. Normally, they're triggered twice – once on enter, and once on exit.
This commit is contained in:
@@ -242,6 +242,7 @@ kotlin.compiler.js.option.output.copy.dir=O&utput directory for library &runtime
|
|||||||
debugger.filter.ignore.internal.classes=Do not step into Kotlin runtime library implementation classes
|
debugger.filter.ignore.internal.classes=Do not step into Kotlin runtime library implementation classes
|
||||||
debugger.data.view.delegated.properties=Calculate values of delegated properties (may affect program execution)
|
debugger.data.view.delegated.properties=Calculate values of delegated properties (may affect program execution)
|
||||||
debugger.field.watchpoints.tab.title=Kotlin Field Watchpoints
|
debugger.field.watchpoints.tab.title=Kotlin Field Watchpoints
|
||||||
|
debugger.function.breakpoints.tab.title=Kotlin Function Breakpoints
|
||||||
debugger.field.watchpoints.properties.panel.field.access.label=Field &access
|
debugger.field.watchpoints.properties.panel.field.access.label=Field &access
|
||||||
debugger.field.watchpoints.properties.panel.field.modification.label=Field &modification
|
debugger.field.watchpoints.properties.panel.field.modification.label=Field &modification
|
||||||
debugger.field.watchpoints.properties.panel.field.initialization.label=Field &initialization
|
debugger.field.watchpoints.properties.panel.field.initialization.label=Field &initialization
|
||||||
|
|||||||
+8
-1
@@ -26,11 +26,18 @@ class KotlinFieldBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KotlinLineBreakpointHandlerFactory: JavaBreakpointHandlerFactory {
|
class KotlinLineBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
|
||||||
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||||
return KotlinLineBreakpointHandler(process)
|
return KotlinLineBreakpointHandler(process)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class KotlinFunctionBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
|
||||||
|
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||||
|
return KotlinFunctionBreakpointHandler(process)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFieldBreakpointType::class.java, process)
|
class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFieldBreakpointType::class.java, process)
|
||||||
class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinLineBreakpointType::class.java, process)
|
class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinLineBreakpointType::class.java, process)
|
||||||
|
class KotlinFunctionBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFunctionBreakpointType::class.java, process)
|
||||||
+667
@@ -0,0 +1,667 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger.breakpoints;
|
||||||
|
|
||||||
|
import com.intellij.debugger.DebuggerBundle;
|
||||||
|
import com.intellij.debugger.DebuggerManagerEx;
|
||||||
|
import com.intellij.debugger.SourcePosition;
|
||||||
|
import com.intellij.debugger.engine.*;
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
|
||||||
|
import com.intellij.debugger.engine.requests.RequestManagerImpl;
|
||||||
|
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||||
|
import com.intellij.debugger.impl.DebuggerUtilsImpl;
|
||||||
|
import com.intellij.debugger.impl.PositionUtil;
|
||||||
|
import com.intellij.debugger.jdi.ClassesByNameProvider;
|
||||||
|
import com.intellij.debugger.jdi.MethodBytecodeUtil;
|
||||||
|
import com.intellij.debugger.requests.Requestor;
|
||||||
|
import com.intellij.debugger.ui.breakpoints.*;
|
||||||
|
import com.intellij.icons.AllIcons;
|
||||||
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.application.ReadAction;
|
||||||
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
|
import com.intellij.openapi.editor.Document;
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicator;
|
||||||
|
import com.intellij.openapi.progress.ProgressManager;
|
||||||
|
import com.intellij.openapi.progress.util.ProgressWindow;
|
||||||
|
import com.intellij.openapi.project.IndexNotReadyException;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.util.Comparing;
|
||||||
|
import com.intellij.openapi.util.InvalidDataException;
|
||||||
|
import com.intellij.openapi.util.JDOMExternalizerUtil;
|
||||||
|
import com.intellij.openapi.util.Key;
|
||||||
|
import com.intellij.psi.*;
|
||||||
|
import com.intellij.ui.LayeredIcon;
|
||||||
|
import com.intellij.util.DocumentUtil;
|
||||||
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
|
import com.intellij.util.containers.MultiMap;
|
||||||
|
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||||
|
import com.intellij.xdebugger.breakpoints.XBreakpointListener;
|
||||||
|
import com.intellij.xdebugger.impl.XDebugSessionImpl;
|
||||||
|
import com.sun.jdi.*;
|
||||||
|
import com.sun.jdi.event.LocatableEvent;
|
||||||
|
import com.sun.jdi.event.MethodEntryEvent;
|
||||||
|
import com.sun.jdi.event.MethodExitEvent;
|
||||||
|
import com.sun.jdi.request.*;
|
||||||
|
import one.util.streamex.StreamEx;
|
||||||
|
import org.jdom.Element;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.java.debugger.breakpoints.properties.JavaMethodBreakpointProperties;
|
||||||
|
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass;
|
||||||
|
import org.jetbrains.kotlin.psi.KtDeclaration;
|
||||||
|
import org.jetbrains.org.objectweb.asm.Label;
|
||||||
|
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
// This class is copied from com.intellij.debugger.ui.breakpoints.MethodBreakpoint.
|
||||||
|
// Changed parts are marked with '// MODIFICATION: ' comments.
|
||||||
|
// This should be deleted when IDEA opens the method breakpoint API (presumably in 193).
|
||||||
|
public class KotlinFunctionBreakpoint extends BreakpointWithHighlighter<JavaMethodBreakpointProperties> implements MethodBreakpointBase {
|
||||||
|
private static final Logger LOG = Logger.getInstance(KotlinFunctionBreakpoint.class);
|
||||||
|
@Nullable private JVMName mySignature;
|
||||||
|
private boolean myIsStatic;
|
||||||
|
|
||||||
|
protected KotlinFunctionBreakpoint(@NotNull Project project, XBreakpoint breakpoint) {
|
||||||
|
super(project, breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStatic() {
|
||||||
|
return myIsStatic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Key<MethodBreakpoint> getCategory() {
|
||||||
|
return MethodBreakpoint.CATEGORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return super.isValid() && getMethodName() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reload() {
|
||||||
|
super.reload();
|
||||||
|
|
||||||
|
setMethodName(null);
|
||||||
|
mySignature = null;
|
||||||
|
|
||||||
|
SourcePosition sourcePosition = getSourcePosition();
|
||||||
|
if (sourcePosition != null) {
|
||||||
|
MethodDescriptor descriptor = getMethodDescriptor(myProject, sourcePosition);
|
||||||
|
if (descriptor != null) {
|
||||||
|
setMethodName(descriptor.methodName);
|
||||||
|
mySignature = descriptor.methodSignature;
|
||||||
|
myIsStatic = descriptor.isStatic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PsiClass psiClass = getPsiClass();
|
||||||
|
if (psiClass != null) {
|
||||||
|
getProperties().myClassPattern = psiClass.getQualifiedName();
|
||||||
|
}
|
||||||
|
if (myIsStatic) {
|
||||||
|
setInstanceFiltersEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createRequestForSubClasses(@NotNull MethodBreakpointBase breakpoint,
|
||||||
|
@NotNull DebugProcessImpl debugProcess,
|
||||||
|
@NotNull ReferenceType baseType) {
|
||||||
|
DebuggerManagerThreadImpl.assertIsManagerThread();
|
||||||
|
RequestManagerImpl requestsManager = debugProcess.getRequestsManager();
|
||||||
|
ClassPrepareRequest request = requestsManager.createClassPrepareRequest((debuggerProcess, referenceType) -> {
|
||||||
|
if (DebuggerUtilsImpl.instanceOf(referenceType, baseType)) {
|
||||||
|
createRequestForPreparedClassEmulated(breakpoint, debugProcess, referenceType, false);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
if (request != null) {
|
||||||
|
requestsManager.registerRequest(breakpoint, request);
|
||||||
|
request.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicReference<ProgressWindow> indicatorRef = new AtomicReference<>();
|
||||||
|
ApplicationManager.getApplication().invokeAndWait(
|
||||||
|
() -> {
|
||||||
|
ProgressWindow progress =
|
||||||
|
new ProgressWindow(true, false, debugProcess.getProject(), "Cancel emulation");
|
||||||
|
progress.setDelayInMillis(2000);
|
||||||
|
indicatorRef.set(progress);
|
||||||
|
});
|
||||||
|
ProgressWindow indicator = indicatorRef.get();
|
||||||
|
|
||||||
|
AtomicBoolean changed = new AtomicBoolean();
|
||||||
|
XBreakpointListener<XBreakpoint<?>> listener = new XBreakpointListener<XBreakpoint<?>>() {
|
||||||
|
void changed(@NotNull XBreakpoint b) {
|
||||||
|
if (b == breakpoint.getXBreakpoint()) {
|
||||||
|
changed.set(true);
|
||||||
|
indicator.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void breakpointRemoved(@NotNull XBreakpoint b) {
|
||||||
|
changed(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void breakpointChanged(@NotNull XBreakpoint b) {
|
||||||
|
changed(b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debugProcess.getProject().getMessageBus().connect(indicator).subscribe(XBreakpointListener.TOPIC, listener);
|
||||||
|
ProgressManager.getInstance().executeProcessUnderProgress(
|
||||||
|
() -> processPreparedSubTypes(baseType,
|
||||||
|
(subType, classesByName) ->
|
||||||
|
createRequestForPreparedClassEmulated(breakpoint, debugProcess, subType, classesByName, false),
|
||||||
|
indicator),
|
||||||
|
indicator);
|
||||||
|
if (indicator.isCanceled() && !changed.get()) {
|
||||||
|
breakpoint.disableEmulation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableEmulation() {
|
||||||
|
MethodBreakpointBase.disableEmulation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createRequestForPreparedClassEmulated(
|
||||||
|
@NotNull MethodBreakpointBase breakpoint,
|
||||||
|
@NotNull DebugProcessImpl debugProcess,
|
||||||
|
@NotNull ReferenceType classType,
|
||||||
|
boolean base
|
||||||
|
) {
|
||||||
|
createRequestForPreparedClassEmulated(breakpoint, debugProcess, classType, debugProcess.getVirtualMachineProxy().getClassesByNameProvider(), base);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldCreateRequest(
|
||||||
|
Requestor requestor,
|
||||||
|
XBreakpoint xBreakpoint,
|
||||||
|
DebugProcessImpl debugProcess,
|
||||||
|
boolean forPreparedClass
|
||||||
|
) {
|
||||||
|
return ReadAction.compute(() -> {
|
||||||
|
JavaDebugProcess process = debugProcess.getXdebugProcess();
|
||||||
|
return process != null
|
||||||
|
&& debugProcess.isAttached()
|
||||||
|
&& (xBreakpoint == null || ((XDebugSessionImpl)process.getSession()).isBreakpointActive(xBreakpoint))
|
||||||
|
&& (forPreparedClass || debugProcess.getRequestsManager().findRequests(requestor).isEmpty());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createRequestForPreparedClassEmulated(
|
||||||
|
@NotNull MethodBreakpointBase breakpoint,
|
||||||
|
@NotNull DebugProcessImpl debugProcess,
|
||||||
|
@NotNull ReferenceType classType,
|
||||||
|
@NotNull ClassesByNameProvider classesByName,
|
||||||
|
boolean base
|
||||||
|
) {
|
||||||
|
if (!MethodBreakpointBase.canBeEmulated(debugProcess)) {
|
||||||
|
breakpoint.disableEmulation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!base && !shouldCreateRequest(breakpoint, breakpoint.getXBreakpoint(), debugProcess, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Method lambdaMethod = MethodBytecodeUtil.getLambdaMethod(classType, classesByName);
|
||||||
|
if (lambdaMethod != null &&
|
||||||
|
!breakpoint
|
||||||
|
.matchingMethods(StreamEx.of(((ClassType)classType).interfaces()).flatCollection(ReferenceType::allMethods), debugProcess)
|
||||||
|
.findFirst().isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StreamEx<Method> methods = lambdaMethod != null
|
||||||
|
? StreamEx.of(lambdaMethod)
|
||||||
|
: breakpoint.matchingMethods(StreamEx.of(classType.methods()).filter(m -> base || !m.isAbstract()), debugProcess);
|
||||||
|
boolean found = false;
|
||||||
|
for (Method method : methods) {
|
||||||
|
found = true;
|
||||||
|
if (method.isNative()) {
|
||||||
|
breakpoint.disableEmulation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Method target = MethodBytecodeUtil.getBridgeTargetMethod(method, classesByName);
|
||||||
|
if (target != null && !ContainerUtil.isEmpty(DebuggerUtilsEx.allLineLocations(target))) {
|
||||||
|
method = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Location> allLineLocations = DebuggerUtilsEx.allLineLocations(method);
|
||||||
|
if (allLineLocations == null && !method.isBridge()) { // no line numbers
|
||||||
|
breakpoint.disableEmulation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ContainerUtil.isEmpty(allLineLocations)) {
|
||||||
|
if (breakpoint.isWatchEntry()) {
|
||||||
|
createLocationBreakpointRequest(breakpoint, ContainerUtil.getFirstItem(allLineLocations), debugProcess, true);
|
||||||
|
}
|
||||||
|
if (breakpoint.isWatchExit()) {
|
||||||
|
MethodBytecodeUtil.visit(method, new MethodVisitor(Opcodes.API_VERSION) {
|
||||||
|
int myLastLine = 0;
|
||||||
|
@Override
|
||||||
|
public void visitLineNumber(int line, Label start) {
|
||||||
|
myLastLine = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitInsn(int opcode) {
|
||||||
|
switch (opcode) {
|
||||||
|
case Opcodes.RETURN:
|
||||||
|
case Opcodes.IRETURN:
|
||||||
|
case Opcodes.FRETURN:
|
||||||
|
case Opcodes.ARETURN:
|
||||||
|
case Opcodes.LRETURN:
|
||||||
|
case Opcodes.DRETURN:
|
||||||
|
//case Opcodes.ATHROW:
|
||||||
|
allLineLocations.stream()
|
||||||
|
.filter(l -> l.lineNumber() == myLastLine)
|
||||||
|
.findFirst().ifPresent(location -> createLocationBreakpointRequest(breakpoint, location, debugProcess, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (base && found) {
|
||||||
|
// desired class found - now also track all new classes
|
||||||
|
createRequestForSubClasses(breakpoint, debugProcess, classType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createLocationBreakpointRequest(@NotNull FilteredRequestor requestor,
|
||||||
|
@Nullable Location location,
|
||||||
|
@NotNull DebugProcessImpl debugProcess,
|
||||||
|
boolean methodEntry) {
|
||||||
|
BreakpointRequest request = createLocationBreakpointRequest(requestor, location, debugProcess);
|
||||||
|
if (request != null) {
|
||||||
|
request.putProperty(METHOD_ENTRY_KEY, methodEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static BreakpointRequest createLocationBreakpointRequest(
|
||||||
|
@NotNull FilteredRequestor requestor,
|
||||||
|
@Nullable Location location,
|
||||||
|
@NotNull DebugProcessImpl debugProcess
|
||||||
|
) {
|
||||||
|
if (location != null) {
|
||||||
|
RequestManagerImpl requestsManager = debugProcess.getRequestsManager();
|
||||||
|
BreakpointRequest request = requestsManager.createBreakpointRequest(requestor, location);
|
||||||
|
requestsManager.enableRequest(request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createRequestForPreparedClass(@NotNull DebugProcessImpl debugProcess, @NotNull ReferenceType classType) {
|
||||||
|
if (isEmulated()) {
|
||||||
|
createRequestForPreparedClassEmulated(this, debugProcess, classType, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createRequestForPreparedClassOriginal(debugProcess, classType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRequestForPreparedClassOriginal(@NotNull DebugProcessImpl debugProcess, @NotNull ReferenceType classType) {
|
||||||
|
try {
|
||||||
|
boolean hasMethod = false;
|
||||||
|
for (Method method : classType.allMethods()) {
|
||||||
|
String signature = method.signature();
|
||||||
|
String name = method.name();
|
||||||
|
|
||||||
|
String nameFromProperties = getMethodName();
|
||||||
|
if (nameFromProperties != null && mySignature != null) {
|
||||||
|
if (nameFromProperties.equals(name) && mySignature.getName(debugProcess).equals(signature)) {
|
||||||
|
hasMethod = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hasMethod) {
|
||||||
|
debugProcess.getRequestsManager().setInvalid(
|
||||||
|
this, DebuggerBundle.message("error.invalid.breakpoint.method.not.found", classType.name())
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestManagerImpl requestManager = debugProcess.getRequestsManager();
|
||||||
|
if (isWatchEntry()) {
|
||||||
|
MethodEntryRequest entryRequest = findRequest(debugProcess, MethodEntryRequest.class, this);
|
||||||
|
if (entryRequest == null) {
|
||||||
|
entryRequest = requestManager.createMethodEntryRequest(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entryRequest.disable();
|
||||||
|
}
|
||||||
|
//entryRequest.addClassFilter(myClassQualifiedName);
|
||||||
|
// use addClassFilter(ReferenceType) in order to stop on subclasses also!
|
||||||
|
entryRequest.addClassFilter(classType);
|
||||||
|
debugProcess.getRequestsManager().enableRequest(entryRequest);
|
||||||
|
}
|
||||||
|
if (isWatchExit()) {
|
||||||
|
MethodExitRequest exitRequest = findRequest(debugProcess, MethodExitRequest.class, this);
|
||||||
|
if (exitRequest == null) {
|
||||||
|
exitRequest = requestManager.createMethodExitRequest(this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
exitRequest.disable();
|
||||||
|
}
|
||||||
|
//exitRequest.addClassFilter(myClassQualifiedName);
|
||||||
|
exitRequest.addClassFilter(classType);
|
||||||
|
debugProcess.getRequestsManager().enableRequest(exitRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
LOG.debug(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEventMessage(@NotNull LocatableEvent event) {
|
||||||
|
return getEventMessage(event, getFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getEventMessage(@NotNull LocatableEvent event, @NotNull String defaultFileName) {
|
||||||
|
Location location = event.location();
|
||||||
|
if (event instanceof MethodEntryEvent) {
|
||||||
|
return getEventMessage(true, ((MethodEntryEvent)event).method(), location, defaultFileName);
|
||||||
|
}
|
||||||
|
if (event instanceof MethodExitEvent) {
|
||||||
|
return getEventMessage(false, ((MethodExitEvent)event).method(), location, defaultFileName);
|
||||||
|
}
|
||||||
|
Object entryProperty = event.request().getProperty(METHOD_ENTRY_KEY);
|
||||||
|
if (entryProperty instanceof Boolean) {
|
||||||
|
return getEventMessage((Boolean)entryProperty, location.method(), location, defaultFileName);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getEventMessage(boolean entry, Method method, Location location, String defaultFileName) {
|
||||||
|
String locationQName = DebuggerUtilsEx.getLocationMethodQName(location);
|
||||||
|
String locationFileName = DebuggerUtilsEx.getSourceName(location, e -> defaultFileName);
|
||||||
|
int locationLine = location.lineNumber();
|
||||||
|
return DebuggerBundle.message(entry ? "status.method.entry.breakpoint.reached" : "status.method.exit.breakpoint.reached",
|
||||||
|
method.declaringType().name() + "." + method.name() + "()",
|
||||||
|
locationQName,
|
||||||
|
locationFileName,
|
||||||
|
locationLine
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PsiElement getEvaluationElement() {
|
||||||
|
return getPsiClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Icon getDisabledIcon(boolean isMuted) {
|
||||||
|
if (DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().findMasterBreakpoint(this) != null && isMuted) {
|
||||||
|
return AllIcons.Debugger.Db_muted_dep_method_breakpoint;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Icon getVerifiedIcon(boolean isMuted) {
|
||||||
|
return isSuspend() ? AllIcons.Debugger.Db_verified_method_breakpoint : AllIcons.Debugger.Db_verified_no_suspend_method_breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
protected Icon getVerifiedWarningsIcon(boolean isMuted) {
|
||||||
|
return new LayeredIcon(isMuted ? AllIcons.Debugger.Db_muted_method_breakpoint : AllIcons.Debugger.Db_method_breakpoint,
|
||||||
|
AllIcons.General.WarningDecorator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
if(isValid()) {
|
||||||
|
String className = getClassName();
|
||||||
|
boolean classNameExists = className != null && className.length() > 0;
|
||||||
|
if (classNameExists) {
|
||||||
|
buffer.append(className);
|
||||||
|
}
|
||||||
|
if(getMethodName() != null) {
|
||||||
|
if (classNameExists) {
|
||||||
|
buffer.append(".");
|
||||||
|
}
|
||||||
|
buffer.append(getMethodName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer.append(DebuggerBundle.message("status.breakpoint.invalid"));
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evaluateCondition(@NotNull EvaluationContextImpl context, @NotNull LocatableEvent event) throws EvaluateException {
|
||||||
|
if (!matchesEvent(event, context.getDebugProcess())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.evaluateCondition(context, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesEvent(@NotNull LocatableEvent event, DebugProcessImpl process) throws EvaluateException {
|
||||||
|
if (isEmulated()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (getMethodName() == null || mySignature == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Method method = event.location().method();
|
||||||
|
return method != null && method.name().equals(getMethodName()) && method.signature().equals(mySignature.getName(process));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static KotlinFunctionBreakpoint create(@NotNull Project project, XBreakpoint xBreakpoint) {
|
||||||
|
KotlinFunctionBreakpoint breakpoint = new KotlinFunctionBreakpoint(project, xBreakpoint);
|
||||||
|
return (KotlinFunctionBreakpoint)breakpoint.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
//public boolean canMoveTo(final SourcePosition position) {
|
||||||
|
// return super.canMoveTo(position) && PositionUtil.getPsiElementAt(getProject(), PsiMethod.class, position) != null;
|
||||||
|
//}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finds FQ method's class name and method's signature
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static MethodDescriptor getMethodDescriptor(@NotNull Project project, @NotNull SourcePosition sourcePosition) {
|
||||||
|
Document document = PsiDocumentManager.getInstance(project).getDocument(sourcePosition.getFile());
|
||||||
|
if (document == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//final int endOffset = document.getLineEndOffset(sourcePosition);
|
||||||
|
//final MethodDescriptor descriptor = docManager.commitAndRunReadAction(new Computable<MethodDescriptor>() {
|
||||||
|
// conflicts with readAction on initial breakpoints creation
|
||||||
|
MethodDescriptor descriptor = ReadAction.compute(() -> {
|
||||||
|
// MODIFICATION: Start Kotlin implementation
|
||||||
|
PsiMethod method = resolveJvmMethodFromKotlinDeclaration(project, sourcePosition);
|
||||||
|
// MODIFICATION: End Kotlin implementation
|
||||||
|
if (method == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int methodOffset = method.getTextOffset();
|
||||||
|
if (!DocumentUtil.isValidOffset(methodOffset, document) || document.getLineNumber(methodOffset) < sourcePosition.getLine()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsiIdentifier identifier = method.getNameIdentifier();
|
||||||
|
int methodNameOffset = identifier != null? identifier.getTextOffset() : methodOffset;
|
||||||
|
MethodDescriptor res =
|
||||||
|
new MethodDescriptor();
|
||||||
|
res.methodName = JVMNameUtil.getJVMMethodName(method);
|
||||||
|
try {
|
||||||
|
res.methodSignature = JVMNameUtil.getJVMSignature(method);
|
||||||
|
res.isStatic = method.hasModifierProperty(PsiModifier.STATIC);
|
||||||
|
}
|
||||||
|
catch (IndexNotReadyException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
res.methodLine = document.getLineNumber(methodNameOffset);
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
if (descriptor == null || descriptor.methodName == null || descriptor.methodSignature == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODIFICATION: Start Kotlin implementation
|
||||||
|
@Nullable
|
||||||
|
private static PsiMethod resolveJvmMethodFromKotlinDeclaration(@NotNull Project project, @NotNull SourcePosition sourcePosition) {
|
||||||
|
KtDeclaration declaration = PositionUtil.getPsiElementAt(project, KtDeclaration.class, sourcePosition);
|
||||||
|
|
||||||
|
if (declaration instanceof KtClass) {
|
||||||
|
declaration = ((KtClass) declaration).getPrimaryConstructor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declaration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PsiElement element : LightClassUtilsKt.toLightElements(declaration)) {
|
||||||
|
if (element instanceof PsiMethod) {
|
||||||
|
// TODO handle all light methods
|
||||||
|
return (PsiMethod) element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// MODIFICATION: End Kotlin implementation
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T extends EventRequest> T findRequest(
|
||||||
|
@NotNull DebugProcessImpl debugProcess,
|
||||||
|
Class<T> requestClass,
|
||||||
|
Requestor requestor
|
||||||
|
) {
|
||||||
|
return StreamEx.of(debugProcess.getRequestsManager().findRequests(requestor)).select(requestClass).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readExternal(@NotNull Element breakpointNode) throws InvalidDataException {
|
||||||
|
super.readExternal(breakpointNode);
|
||||||
|
try {
|
||||||
|
getProperties().WATCH_ENTRY = Boolean.valueOf(JDOMExternalizerUtil.readField(breakpointNode, "WATCH_ENTRY"));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
getProperties().WATCH_EXIT = Boolean.valueOf(JDOMExternalizerUtil.readField(breakpointNode, "WATCH_EXIT"));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmulated() {
|
||||||
|
return getProperties().EMULATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWatchEntry() {
|
||||||
|
return getProperties().WATCH_ENTRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWatchExit() {
|
||||||
|
return getProperties().WATCH_EXIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreamEx matchingMethods(StreamEx<Method> methods, DebugProcessImpl debugProcess) {
|
||||||
|
try {
|
||||||
|
String methodName = getMethodName();
|
||||||
|
String signature = mySignature != null ? mySignature.getName(debugProcess) : null;
|
||||||
|
return methods.filter(m -> Comparing.equal(methodName, m.name()) && Comparing.equal(signature, m.signature())).limit(1);
|
||||||
|
}
|
||||||
|
catch (EvaluateException e) {
|
||||||
|
LOG.warn(e);
|
||||||
|
}
|
||||||
|
return StreamEx.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getMethodName() {
|
||||||
|
return getProperties().myMethodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMethodName(@Nullable String methodName) {
|
||||||
|
getProperties().myMethodName = methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MethodDescriptor {
|
||||||
|
String methodName;
|
||||||
|
JVMName methodSignature;
|
||||||
|
boolean isStatic;
|
||||||
|
int methodLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processPreparedSubTypes(ReferenceType classType,
|
||||||
|
BiConsumer<ReferenceType, ClassesByNameProvider> consumer,
|
||||||
|
ProgressIndicator progressIndicator) {
|
||||||
|
long start = 0;
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
start = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
progressIndicator.setIndeterminate(false);
|
||||||
|
progressIndicator.start();
|
||||||
|
progressIndicator.setText(DebuggerBundle.message("label.method.breakpoints.processing.classes"));
|
||||||
|
try {
|
||||||
|
MultiMap<ReferenceType, ReferenceType> inheritance = new MultiMap<>();
|
||||||
|
List<ReferenceType> allTypes = classType.virtualMachine().allClasses();
|
||||||
|
for (int i = 0; i < allTypes.size(); i++) {
|
||||||
|
if (progressIndicator.isCanceled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReferenceType type = allTypes.get(i);
|
||||||
|
if (type.isPrepared()) {
|
||||||
|
try {
|
||||||
|
DebuggerUtilsImpl.supertypes(type).forEach(st -> inheritance.putValue(st, type));
|
||||||
|
}
|
||||||
|
catch (ObjectCollectedException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progressIndicator.setText2(i + "/" + allTypes.size());
|
||||||
|
progressIndicator.setFraction((double)i / allTypes.size());
|
||||||
|
}
|
||||||
|
List<ReferenceType> types = StreamEx.ofTree(classType, t -> StreamEx.of(inheritance.get(t))).skip(1).toList();
|
||||||
|
|
||||||
|
progressIndicator.setText(DebuggerBundle.message("label.method.breakpoints.setting.breakpoints"));
|
||||||
|
|
||||||
|
ClassesByNameProvider classesByName = ClassesByNameProvider.createCache(allTypes);
|
||||||
|
|
||||||
|
for (int i = 0; i < types.size(); i++) {
|
||||||
|
if (progressIndicator.isCanceled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
consumer.accept(types.get(i), classesByName);
|
||||||
|
|
||||||
|
progressIndicator.setText2(i + "/" + types.size());
|
||||||
|
progressIndicator.setFraction((double)i / types.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Processed " + types.size() + " classes in " + (System.currentTimeMillis() - start) + "ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
progressIndicator.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger.breakpoints;
|
||||||
|
|
||||||
|
import com.intellij.debugger.ui.breakpoints.Breakpoint;
|
||||||
|
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointTypeBase;
|
||||||
|
import com.intellij.debugger.ui.breakpoints.MethodBreakpointPropertiesPanel;
|
||||||
|
import com.intellij.icons.AllIcons;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.util.registry.Registry;
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
|
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||||
|
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
|
||||||
|
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.java.debugger.breakpoints.properties.JavaMethodBreakpointProperties;
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinBundle;
|
||||||
|
import org.jetbrains.kotlin.psi.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import static org.jetbrains.kotlin.idea.debugger.breakpoints.BreakpointTypeUtilsKt.isBreakpointApplicable;
|
||||||
|
|
||||||
|
// This class is copied from com.intellij.debugger.ui.breakpoints.MethodBreakpoint.
|
||||||
|
// Changed parts are marked with '// MODIFICATION: ' comments.
|
||||||
|
// This should be deleted when IDEA opens the method breakpoint API (presumably in 193).
|
||||||
|
public class KotlinFunctionBreakpointType extends JavaLineBreakpointTypeBase<JavaMethodBreakpointProperties> {
|
||||||
|
// MODIFICATION: Start Kotlin implementation
|
||||||
|
public KotlinFunctionBreakpointType() {
|
||||||
|
super("kotlin-function", KotlinBundle.message("debugger.function.breakpoints.tab.title"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 120;
|
||||||
|
}
|
||||||
|
// MODIFICATION: End Kotlin implementation
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Icon getEnabledIcon() {
|
||||||
|
return AllIcons.Debugger.Db_method_breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Icon getDisabledIcon() {
|
||||||
|
return AllIcons.Debugger.Db_disabled_method_breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Icon getSuspendNoneIcon() {
|
||||||
|
return AllIcons.Debugger.Db_no_suspend_method_breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Icon getMutedEnabledIcon() {
|
||||||
|
return AllIcons.Debugger.Db_muted_method_breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Icon getMutedDisabledIcon() {
|
||||||
|
return AllIcons.Debugger.Db_muted_disabled_method_breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Icon getInactiveDependentIcon() {
|
||||||
|
return AllIcons.Debugger.Db_dep_method_breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getShortText(XLineBreakpoint<JavaMethodBreakpointProperties> breakpoint) {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
String className = breakpoint.getProperties().myClassPattern;
|
||||||
|
boolean classNameExists = className != null && className.length() > 0;
|
||||||
|
|
||||||
|
if (classNameExists) {
|
||||||
|
buffer.append(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (breakpoint.getProperties().myMethodName != null) {
|
||||||
|
if (classNameExists) {
|
||||||
|
buffer.append(".");
|
||||||
|
}
|
||||||
|
buffer.append(breakpoint.getProperties().myMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public XBreakpointCustomPropertiesPanel createCustomPropertiesPanel(@NotNull Project project) {
|
||||||
|
return new MethodBreakpointPropertiesPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public JavaMethodBreakpointProperties createProperties() {
|
||||||
|
return new JavaMethodBreakpointProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public JavaMethodBreakpointProperties createBreakpointProperties(@NotNull VirtualFile file, int line) {
|
||||||
|
JavaMethodBreakpointProperties properties = new JavaMethodBreakpointProperties();
|
||||||
|
if (Registry.is("debugger.emulate.method.breakpoints")) {
|
||||||
|
properties.EMULATED = true; // create all new emulated
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Breakpoint<JavaMethodBreakpointProperties> createJavaBreakpoint(Project project, XBreakpoint breakpoint) {
|
||||||
|
return new KotlinFunctionBreakpoint(project, breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBeHitInOtherPlaces() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODIFICATION: Start Kotlin implementation
|
||||||
|
@Override
|
||||||
|
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
|
||||||
|
return isBreakpointApplicable(file, line, project, element -> {
|
||||||
|
if (element instanceof KtConstructor) {
|
||||||
|
return ApplicabilityResult.DEFINITELY_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element instanceof KtClass) {
|
||||||
|
KtClass clazz = (KtClass) element;
|
||||||
|
return ApplicabilityResult.maybe(
|
||||||
|
!(clazz instanceof KtEnumEntry)
|
||||||
|
&& !clazz.isAnnotation()
|
||||||
|
&& !clazz.isInterface()
|
||||||
|
&& clazz.hasPrimaryConstructor()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element instanceof KtFunction) {
|
||||||
|
KtFunction function = (KtFunction) element;
|
||||||
|
return ApplicabilityResult.maybe(function.hasBody() && !KtPsiUtil.isLocal(function));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element instanceof KtPropertyAccessor) {
|
||||||
|
KtPropertyAccessor accessor = (KtPropertyAccessor) element;
|
||||||
|
KtProperty property = accessor.getProperty();
|
||||||
|
return ApplicabilityResult.maybe(accessor.hasBody() && !KtPsiUtil.isLocal(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.UNKNOWN;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// MODIFICATION: End Kotlin implementation
|
||||||
|
}
|
||||||
+71
-3
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.debugger.breakpoints
|
|||||||
|
|
||||||
import com.intellij.debugger.SourcePosition
|
import com.intellij.debugger.SourcePosition
|
||||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
|
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
|
||||||
|
import com.intellij.openapi.editor.Document
|
||||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
@@ -34,9 +35,7 @@ import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
|||||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||||
import org.jetbrains.kotlin.idea.debugger.findElementAtLine
|
import org.jetbrains.kotlin.idea.debugger.findElementAtLine
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -85,6 +84,75 @@ fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass
|
|||||||
return result == breakpointTypeClass
|
return result == breakpointTypeClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ApplicabilityResult(val isApplicable: Boolean, val shouldStop: Boolean) {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun definitely(result: Boolean) = ApplicabilityResult(result, shouldStop = true)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun maybe(result: Boolean) = ApplicabilityResult(result, shouldStop = false)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val UNKNOWN = ApplicabilityResult(isApplicable = false, shouldStop = false)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val DEFINITELY_YES = ApplicabilityResult(isApplicable = true, shouldStop = true)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val MAYBE_YES = ApplicabilityResult(isApplicable = true, shouldStop = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isBreakpointApplicable(file: VirtualFile, line: Int, project: Project, checker: (PsiElement) -> ApplicabilityResult): Boolean {
|
||||||
|
val psiFile = PsiManager.getInstance(project).findFile(file)
|
||||||
|
|
||||||
|
if (psiFile == null || psiFile.virtualFile?.fileType != KotlinFileType.INSTANCE) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
|
||||||
|
var isApplicable = false
|
||||||
|
|
||||||
|
val checked = HashSet<PsiElement>()
|
||||||
|
|
||||||
|
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean {
|
||||||
|
if (element is PsiWhiteSpace || element.getParentOfType<PsiComment>(false) != null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val parent = getTopmostParentOnLineOrSelf(element, document, line)
|
||||||
|
if (!checked.add(parent)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = checker(parent)
|
||||||
|
|
||||||
|
if (result.shouldStop && !result.isApplicable) {
|
||||||
|
isApplicable = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isApplicable = isApplicable or result.isApplicable
|
||||||
|
return !result.shouldStop
|
||||||
|
})
|
||||||
|
|
||||||
|
return isApplicable
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTopmostParentOnLineOrSelf(element: PsiElement, document: Document, line: Int): PsiElement {
|
||||||
|
var current = element
|
||||||
|
var parent = current.parent
|
||||||
|
while (parent != null) {
|
||||||
|
val offset = parent.textOffset
|
||||||
|
if (offset >= 0 && document.getLineNumber(offset) != line) break
|
||||||
|
|
||||||
|
current = parent
|
||||||
|
parent = current.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
fun computeVariants(
|
fun computeVariants(
|
||||||
project: Project,
|
project: Project,
|
||||||
position: XSourcePosition,
|
position: XSourcePosition,
|
||||||
|
|||||||
@@ -106,9 +106,11 @@
|
|||||||
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
|
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
|
||||||
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
|
||||||
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
|
||||||
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointType"/>
|
||||||
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
|
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
|
||||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
|
||||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
|
||||||
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointHandlerFactory"/>
|
||||||
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
|
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
|
||||||
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
|
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
|
||||||
|
|
||||||
|
|||||||
@@ -105,9 +105,11 @@
|
|||||||
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
|
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
|
||||||
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
|
||||||
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
|
||||||
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointType"/>
|
||||||
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
|
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
|
||||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
|
||||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
|
||||||
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointHandlerFactory"/>
|
||||||
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
|
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
|
||||||
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
|
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
|
||||||
|
|
||||||
|
|||||||
@@ -106,9 +106,11 @@
|
|||||||
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
|
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
|
||||||
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
|
||||||
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
|
||||||
|
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointType"/>
|
||||||
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
|
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
|
||||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
|
||||||
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
|
||||||
|
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFunctionBreakpointHandlerFactory"/>
|
||||||
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
|
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
|
||||||
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
|
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user