Support multiple breakpoints at one line (especially for lambdas)

This commit is contained in:
Natalia Ukhorskaya
2015-10-27 10:40:07 +03:00
parent c071bf5c63
commit e534636f03
20 changed files with 452 additions and 83 deletions
@@ -54,6 +54,7 @@ import org.jetbrains.kotlin.fileClasses.getInternalName
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
import org.jetbrains.kotlin.idea.decompiler.JetClsFile
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
import org.jetbrains.kotlin.idea.util.DebuggerUtils
@@ -64,6 +65,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.utils.toReadOnlyList
import java.util.*
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
@@ -135,10 +137,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
if (start == null || end == null) return null
val literalsOrFunctions = CodeInsightUtils.
findElementsOfClassInRange(file, start, end, KtFunctionLiteral::class.java, KtNamedFunction::class.java).
filter { KtPsiUtil.getParentCallIfPresent(it as KtExpression) != null }
val literalsOrFunctions = getLambdasAtLineIfAny(file, lineNumber)
if (literalsOrFunctions.isEmpty()) return null;
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
@@ -149,17 +148,16 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).internalName
for (literal in literalsOrFunctions) {
val functionLiteral = literal as KtFunction
if (isInlinedLambda(functionLiteral, typeMapper.bindingContext)) {
if (isInsideInlineArgument(functionLiteral, location, myDebugProcess as DebugProcessImpl)) {
return functionLiteral
if (isInlinedLambda(literal, typeMapper.bindingContext)) {
if (isInsideInlineArgument(literal, location, myDebugProcess as DebugProcessImpl)) {
return literal
}
continue
}
val internalClassName = getInternalClassNameForElement(literal.firstChild, typeMapper, file, isInLibrary).className
if (internalClassName == currentLocationClassName) {
return functionLiteral
return literal
}
}
@@ -232,7 +230,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
}
private fun classNameForPositionAndInlinedOnes(sourcePosition: SourcePosition): List<String> {
val result = arrayListOf<String>()
val result = hashSetOf<String>()
val name = classNameForPosition(sourcePosition)
if (name != null) {
result.add(name)
@@ -240,7 +238,22 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
val list = findInlinedCalls(sourcePosition.elementAt, sourcePosition.file)
result.addAll(list)
return result;
val lambdas = findLambdas(sourcePosition)
result.addAll(lambdas)
return result.toReadOnlyList();
}
private fun findLambdas(sourcePosition: SourcePosition): List<String> {
return runReadAction {
val lambdas = getLambdasAtLineIfAny(sourcePosition)
val file = sourcePosition.file.containingFile as KtFile
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
lambdas.map {
val typeMapper = if (!isInLibrary) prepareTypeMapper(file) else createTypeMapperForLibraryFile(it, file)
getInternalClassNameForElement(it, typeMapper, file, isInLibrary).className
}.filterNotNull()
}
}
public fun classNameForPosition(sourcePosition: SourcePosition): String? {
@@ -17,17 +17,29 @@
package org.jetbrains.kotlin.idea.debugger.breakpoints;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.ui.breakpoints.Breakpoint;
import com.intellij.debugger.ui.breakpoints.BreakpointManager;
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
import org.jetbrains.kotlin.idea.KotlinIcons;
import org.jetbrains.kotlin.psi.KtFunction;
import javax.swing.*;
import java.util.List;
public class KotlinLineBreakpointType extends JavaLineBreakpointType {
public KotlinLineBreakpointType() {
super("kotlin-line", "Kotlin Line Breakpoints");
@@ -35,11 +47,16 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType {
@Override
public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
if (super.matchesPosition(breakpoint, position)) return true;
JavaBreakpointProperties properties = getProperties(breakpoint);
if (properties == null || properties instanceof JavaLineBreakpointProperties) {
if (properties != null && ((JavaLineBreakpointProperties)properties).getLambdaOrdinal() == null) return true;
PsiElement containingMethod = getContainingMethod(breakpoint);
if (containingMethod == null) return false;
return inTheMethod(position, containingMethod);
PsiElement containingMethod = getContainingMethod(breakpoint);
if (containingMethod == null) return false;
return inTheMethod(position, containingMethod);
}
return true;
}
@Override
@@ -48,9 +65,34 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType {
SourcePosition position = breakpoint.getSourcePosition();
if (position == null) return null;
JavaBreakpointProperties properties = getProperties(breakpoint);
if (properties instanceof JavaLineBreakpointProperties) {
Integer ordinal = ((JavaLineBreakpointProperties) properties).getLambdaOrdinal();
PsiElement lambda = getLambdaByOrdinal(position, ordinal);
if (lambda != null) return lambda;
}
return getContainingMethod(position.getElementAt());
}
@Nullable
private static JavaBreakpointProperties getProperties(@NotNull LineBreakpoint<?> breakpoint) {
XBreakpoint<?> xBreakpoint = breakpoint.getXBreakpoint();
return xBreakpoint != null ? (JavaBreakpointProperties) xBreakpoint.getProperties() : null;
}
@Nullable
private static KtFunction getLambdaByOrdinal(SourcePosition position, Integer ordinal) {
if (ordinal != null && ordinal >= 0) {
List<KtFunction> lambdas = BreakpointTypeUtilsKt.getLambdasAtLineIfAny(position);
if (lambdas.size() >= ordinal) {
return lambdas.get(ordinal);
}
}
return null;
}
@Nullable
public static PsiElement getContainingMethod(@Nullable PsiElement elem) {
//noinspection unchecked
@@ -68,5 +110,42 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType {
return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass());
}
@NotNull
@Override
public List<JavaBreakpointVariant> computeVariants(@NotNull Project project, @NotNull XSourcePosition position) {
return BreakpointTypeUtilsKt.computeVariants(project, position, this);
}
@Nullable
@Override
public TextRange getHighlightRange(XLineBreakpoint<JavaLineBreakpointProperties> breakpoint) {
JavaLineBreakpointProperties properties = breakpoint.getProperties();
if (properties != null) {
Integer ordinal = properties.getLambdaOrdinal();
if (ordinal != null) {
Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint);
if (javaBreakpoint instanceof LineBreakpoint) {
SourcePosition position = ((LineBreakpoint) javaBreakpoint).getSourcePosition();
if (position != null) {
KtFunction lambda = getLambdaByOrdinal(position, ordinal);
if (lambda != null) {
return lambda.getTextRange();
}
}
}
}
}
return null;
}
public class ExactKotlinJavaBreakpointVariant extends ExactJavaBreakpointVariant {
public ExactKotlinJavaBreakpointVariant(XSourcePosition position, KtFunction function, Integer lambdaOrdinal) {
super(position, function, lambdaOrdinal);
}
@Override
public Icon getIcon() {
return KotlinIcons.LAMBDA;
}
}
}
@@ -16,19 +16,32 @@
package org.jetbrains.kotlin.idea.debugger.breakpoints
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.XSourcePosition
import com.intellij.xdebugger.impl.XSourcePositionImpl
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.refactoring.getLineEndOffset
import org.jetbrains.kotlin.idea.core.refactoring.getLineNumber
import org.jetbrains.kotlin.idea.core.refactoring.getLineStartOffset
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import java.util.*
fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean {
val psiFile = PsiManager.getInstance(project).findFile(file)
@@ -80,4 +93,73 @@ fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass
return result == breakpointTypeClass
}
fun computeVariants(
project: Project, position: XSourcePosition,
kotlinBreakpointType: KotlinLineBreakpointType
): List<JavaLineBreakpointType.JavaBreakpointVariant> {
val file = PsiManager.getInstance(project).findFile(position.file) as? KtFile ?: return emptyList()
val pos = SourcePosition.createFromLine(file, position.line)
val lambdas = getLambdasAtLineIfAny(pos)
if (lambdas.isEmpty()) return emptyList()
val result = LinkedList<JavaLineBreakpointType.JavaBreakpointVariant>()
val mainMethod = KotlinLineBreakpointType.getContainingMethod(pos.elementAt)
if (mainMethod != null) {
result.add((kotlinBreakpointType as JavaLineBreakpointType).ExactJavaBreakpointVariant(
XSourcePositionImpl.createByElement(mainMethod),
mainMethod, -1))
}
lambdas.forEachIndexed { ordinal, lambda ->
result.add(kotlinBreakpointType.ExactKotlinJavaBreakpointVariant(
XSourcePositionImpl.createByElement(lambda.bodyExpression), lambda, ordinal))
}
if (result.size > 1) {
val allBreakpoint = (kotlinBreakpointType as JavaLineBreakpointType).JavaBreakpointVariant(position)
result.addFirst(allBreakpoint)
}
return result
}
fun getLambdasAtLineIfAny(sourcePosition: SourcePosition): List<KtFunction> {
val file = sourcePosition.file as? KtFile ?: return emptyList()
val lineNumber = sourcePosition.line
return getLambdasAtLineIfAny(file, lineNumber)
}
fun getLambdasAtLineIfAny(file: KtFile, line: Int): List<KtFunction> {
var lineStartOffset = file.getLineStartOffset(line) ?: return emptyList()
var lineEndOffset = file.getLineEndOffset(line) ?: return emptyList()
var topMostElement: PsiElement? = null
var elementAt: PsiElement?
while (topMostElement !is KtElement && lineStartOffset < lineEndOffset) {
elementAt = file.findElementAt(lineStartOffset)
if (elementAt != null) {
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, lineStartOffset)
}
lineStartOffset++
}
if (topMostElement !is KtElement) return emptyList()
val start = topMostElement.startOffset
val end = topMostElement.endOffset
val allInlineFunctionCalls = CodeInsightUtils.
findElementsOfClassInRange(file, start, end, KtFunction::class.java)
.filter { KtPsiUtil.getParentCallIfPresent(it as KtExpression) != null }
.filterIsInstance<KtFunction>()
.toSet()
return allInlineFunctionCalls.filter {
val statement = (it.bodyExpression as? KtBlockExpression)?.statements?.firstOrNull() ?: it
statement.getLineNumber() == line && statement.getLineNumber(false) == line
}
}
@@ -36,11 +36,11 @@ public class KotlinLambdaMethodFilter(
private val myLastStatementLine: Int
init {
if (lambda.isMultiLine()) {
val body = lambda.bodyExpression
if (body != null && lambda.isMultiLine()) {
var firstStatementPosition: SourcePosition? = null
var lastStatementPosition: SourcePosition? = null
val body = lambda.bodyExpression as KtBlockExpression
val statements = body.statements
val statements = (body as? KtBlockExpression)?.statements ?: listOf(body)
if (statements.isNotEmpty()) {
firstStatementPosition = SourcePosition.createFromElement(statements.first())
if (firstStatementPosition != null) {
@@ -1,4 +1,4 @@
LineBreakpoint created at ceAnonymousObjectCapturedInClosure.kt:7
LineBreakpoint created at ceAnonymousObjectCapturedInClosure.kt:7 lambdaOrdinal = -1
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! ceAnonymousObjectCapturedInClosure.CeAnonymousObjectCapturedInClosureKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceAnonymousObjectCapturedInClosure.kt:7
@@ -0,0 +1,38 @@
LineBreakpoint created at multipleBreakpointsAtLine.kt:9 lambdaOrdinal = -1
LineBreakpoint created at multipleBreakpointsAtLine.kt:14 lambdaOrdinal = 0
LineBreakpoint created at multipleBreakpointsAtLine.kt:19 lambdaOrdinal = 1
LineBreakpoint created at multipleBreakpointsAtLine.kt:30
LineBreakpoint created at multipleBreakpointsAtLine.kt:38 lambdaOrdinal = -1
LineBreakpoint created at multipleBreakpointsAtLine.kt:43 lambdaOrdinal = 0
LineBreakpoint created at multipleBreakpointsAtLine.kt:48 lambdaOrdinal = 1
LineBreakpoint created at multipleBreakpointsAtLine.kt:62
LineBreakpoint created at multipleBreakpointsAtLine.kt:67 lambdaOrdinal = 1
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! multipleBreakpointsAtLine.MultipleBreakpointsAtLineKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
multipleBreakpointsAtLine.kt:9
multipleBreakpointsAtLine.kt:14
Compile bytecode for it + 2
multipleBreakpointsAtLine.kt:19
Compile bytecode for it + 3
multipleBreakpointsAtLine.kt:30
multipleBreakpointsAtLine.kt:30
Compile bytecode for it + 5
multipleBreakpointsAtLine.kt:30
Compile bytecode for it + 6
multipleBreakpointsAtLine.kt:38
multipleBreakpointsAtLine.kt:38
multipleBreakpointsAtLine.kt:43
Compile bytecode for it + 9
multipleBreakpointsAtLine.kt:48
Compile bytecode for it + 10
multipleBreakpointsAtLine.kt:62
multipleBreakpointsAtLine.kt:62
Compile bytecode for it + 12
multipleBreakpointsAtLine.kt:62
multipleBreakpointsAtLine.kt:62
Compile bytecode for it + 14
multipleBreakpointsAtLine.kt:67
Compile bytecode for it + 15
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
+1 -1
View File
@@ -1,4 +1,4 @@
LineBreakpoint created at severalFunLiterals.kt:6
LineBreakpoint created at severalFunLiterals.kt:6 lambdaOrdinal = -1
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! severalFunLiterals.SeveralFunLiteralsKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
severalFunLiterals.kt:6
@@ -1,4 +1,4 @@
LineBreakpoint created at severalFunLiteralsInClass.kt:20
LineBreakpoint created at severalFunLiteralsInClass.kt:20 lambdaOrdinal = -1
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! severalFunLiteralsInClass.SeveralFunLiteralsInClassKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
severalFunLiteralsInClass.kt:20
@@ -2,12 +2,14 @@ LineBreakpoint created at smartStepIntoInlinedFunLiteral.kt:6
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! smartStepIntoInlinedFunLiteral.SmartStepIntoInlinedFunLiteralKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
smartStepIntoInlinedFunLiteral.kt:6
smartStepIntoInlinedFunLiteral.kt:9
smartStepIntoInlinedFunLiteral.kt:10
smartStepIntoInlinedFunLiteral.kt:13
smartStepIntoInlinedFunLiteral.kt:14
smartStepIntoInlinedFunLiteral.kt:11
smartStepIntoInlinedFunLiteral.kt:12
smartStepIntoInlinedFunLiteral.kt:17
smartStepIntoInlinedFunLiteral.kt:18
smartStepIntoInlinedFunLiteral.kt:20
smartStepIntoInlinedFunLiteral.kt:24
smartStepIntoInlinedFunLiteral.kt:26
smartStepIntoInlinedFunLiteral.kt:32
smartStepIntoInlinedFunLiteral.kt:32
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -2,12 +2,14 @@ LineBreakpoint created at smartStepIntoInlinedFunctionalExpression.kt:6
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! smartStepIntoInlinedFunctionalExpression.SmartStepIntoInlinedFunctionalExpressionKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
smartStepIntoInlinedFunctionalExpression.kt:6
smartStepIntoInlinedFunctionalExpression.kt:9
smartStepIntoInlinedFunctionalExpression.kt:10
smartStepIntoInlinedFunctionalExpression.kt:13
smartStepIntoInlinedFunctionalExpression.kt:14
smartStepIntoInlinedFunctionalExpression.kt:11
smartStepIntoInlinedFunctionalExpression.kt:12
smartStepIntoInlinedFunctionalExpression.kt:17
smartStepIntoInlinedFunctionalExpression.kt:18
smartStepIntoInlinedFunctionalExpression.kt:20
smartStepIntoInlinedFunctionalExpression.kt:24
smartStepIntoInlinedFunctionalExpression.kt:26
smartStepIntoInlinedFunctionalExpression.kt:31
smartStepIntoInlinedFunctionalExpression.kt:31
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -1,4 +1,4 @@
LineBreakpoint created at twoLambdasOnOneLineFirst.kt:9
LineBreakpoint created at twoLambdasOnOneLineFirst.kt:9 lambdaOrdinal = -1
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! twoLambdasOnOneLineFirst.TwoLambdasOnOneLineFirstKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
twoLambdasOnOneLineFirst.kt:9
@@ -0,0 +1,82 @@
package multipleBreakpointsAtLine
fun main(args: Array<String>) {
val a = A()
// EXPRESSION: it + 1
// RESULT: Unresolved reference: it
//Breakpoint! (-1)
a.foo(1) { 1 }.foo(2) { 1 }
// EXPRESSION: it + 2
// RESULT: 3: I
//Breakpoint! (0)
a.foo(1) { 1 }.foo(2) { 1 }
// EXPRESSION: it + 3
// RESULT: 5: I
//Breakpoint! (1)
a.foo(1) { 1 }.foo(2) { 1 }
// EXPRESSION: it + 4
// RESULT: Unresolved reference: it
// EXPRESSION: it + 5
// RESULT: 6: I
// EXPRESSION: it + 6
// RESULT: 8: I
//Breakpoint!
a.foo(1) { 1 }.foo(2) { 1 }
// EXPRESSION: it + 7
// RESULT: Unresolved reference: it
// EXPRESSION: it + 8
// RESULT: Unresolved reference: it
//Breakpoint! (-1)
a.bar(1) { 1 }.bar(2) { 1 }
// EXPRESSION: it + 9
// RESULT: 10: I
//Breakpoint! (0)
a.bar(1) { 1 }.bar(2) { 1 }
// EXPRESSION: it + 10
// RESULT: 12: I
//Breakpoint! (1)
a.bar(1) { 1 }.bar(2) { 1 }
// EXPRESSION: it + 11
// RESULT: Unresolved reference: it
// EXPRESSION: it + 12
// RESULT: 13: I
// EXPRESSION: it + 13
// RESULT: Unresolved reference: it
// EXPRESSION: it + 14
// RESULT: 16: I
//Breakpoint!
a.bar(1) { 1 }.bar(2) { 1 }
// EXPRESSION: it + 15
// RESULT: 17: I
//Breakpoint! (1)
a.bar(1) { 1 }.bar(2) { 1 + 1
1 + 1
}
}
class A {
fun foo(i: Int, f: (i: Int) -> Int): A {
f(i)
return this
}
inline fun bar(i: Int, f: (i: Int) -> Int): A {
f(i)
return this
}
}
@@ -3,7 +3,7 @@ package ceAnonymousObjectCapturedInClosure
fun main(args: Array<String>) {
val localObject = object { fun test() = 1 }
var localObjectVar = object { fun test() = 1 }
//Breakpoint!
//Breakpoint! (-1)
lambda { localObjectVar.test() }
}
@@ -5,7 +5,7 @@ fun main(args: Array<String>) {
// EXPRESSION: it
// RESULT: 1: I
// STEP_INTO: 2
//Breakpoint!
//Breakpoint! (-1)
a.foo { a }.foo { a }
}
@@ -2,7 +2,7 @@ package severalFunLiterals
fun main(args: Array<String>) {
val myClass = MyClass()
//Breakpoint!
//Breakpoint! (-1)
myClass.f1 { test() }.f2 {
test()
}
@@ -22,4 +22,4 @@ class MyClass {
fun test() {}
// SMART_STEP_INTO_BY_INDEX: 4
// SMART_STEP_INTO_BY_INDEX: 4
@@ -16,7 +16,7 @@ class MyClass {
}
fun inClass() {
//Breakpoint!
//Breakpoint! (-1)
f1 { test() }.f2 {
test()
}
@@ -25,4 +25,4 @@ class MyClass {
fun test() {}
// SMART_STEP_INTO_BY_INDEX: 4
// SMART_STEP_INTO_BY_INDEX: 4
@@ -5,20 +5,31 @@ fun main(args: Array<String>) {
//Breakpoint!
val myClass = MyClass()
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// smart step into f2.invoke(), one-line lambda
myClass.f1 { test() }
.f2 { test() }
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 2
// smart step into map.invoke(), multiline lambda
array.map {
it *2
}
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// smart step into filter.invoke()
array.map { it * 2 }
.filter {
it > 2
}
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// smart step into f2.invoke(), one-line lambda
myClass.f1 { test() }.f2 { test() }
}
class MyClass {
@@ -35,11 +46,4 @@ class MyClass {
}
}
fun test() {}
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 2
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
fun test() {}
@@ -5,20 +5,32 @@ fun main(args: Array<String>) {
//Breakpoint!
val myClass = MyClass()
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// smart step into f2.invoke(), one-line lambda
myClass.f1(fun () { test() })
.f2(fun () { test() })
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 2
// smart step into map.invoke(), multiline lambda
array.map(fun (it): Int {
return it * 2
})
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// smart step into filter.invoke()
array.map(fun (it): Int { return it * 2 })
.filter(fun (it): Boolean {
return it > 2
})
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 2
myClass.f3(fun () = myClass.f3 {
println()
})
}
class MyClass {
@@ -33,13 +45,11 @@ class MyClass {
f1Param()
return this
}
inline fun f3(f1Param: () -> Unit): Unit {
test()
f1Param()
}
}
fun test() {}
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 2
// STEP_OVER: 1
// SMART_STEP_INTO_BY_INDEX: 4
fun test() {}
@@ -26,25 +26,28 @@ import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.impl.PositionUtil
import com.intellij.debugger.settings.DebuggerSettings
import com.intellij.debugger.ui.breakpoints.Breakpoint
import com.intellij.debugger.ui.breakpoints.BreakpointManager
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.roots.JdkOrderEntry
import com.intellij.openapi.roots.libraries.LibraryUtil
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.search.FilenameIndex
import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.XDebuggerUtil
import com.intellij.xdebugger.breakpoints.XBreakpoint
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
import com.intellij.xdebugger.breakpoints.XBreakpointType
import com.intellij.xdebugger.breakpoints.XLineBreakpointType
import com.intellij.xdebugger.breakpoints.*
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
import org.jetbrains.kotlin.idea.core.refactoring.getLineNumber
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType
import org.jetbrains.kotlin.idea.debugger.stepping.*
import org.jetbrains.kotlin.idea.test.JetJdkAndLibraryProjectDescriptor
import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -261,49 +264,68 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
}
override fun createBreakpoints(file: PsiFile?) {
super.createBreakpoints(file)
if (file == null) return
val document = PsiDocumentManager.getInstance(myProject).getDocument(file) ?: return
val document = runReadAction { PsiDocumentManager.getInstance(myProject).getDocument(file) } ?: return
val breakpointManager = XDebuggerManager.getInstance(myProject).getBreakpointManager()
val breakpointType = javaClass<KotlinFieldBreakpointType>() as Class<out XBreakpointType<XBreakpoint<XBreakpointProperties<*>>, XBreakpointProperties<*>>>
val type = XDebuggerUtil.getInstance().findBreakpointType<XBreakpoint<XBreakpointProperties<*>>>(breakpointType) as KotlinFieldBreakpointType
val kotlinFieldBreakpointType = findBreakpointType(KotlinFieldBreakpointType::class.java)
val kotlinLineBreakpointType = findBreakpointType(KotlinLineBreakpointType::class.java)
val virtualFile = file.getVirtualFile()
val runnable = {
var offset = -1;
while (true) {
val fileText = document.getText()
offset = fileText.indexOf("FieldWatchpoint!", offset + 1)
offset = fileText.indexOf("point!", offset + 1)
if (offset == -1) break
val commentLine = document.getLineNumber(offset)
val comment = fileText.substring(document.getLineStartOffset(commentLine), document.getLineEndOffset(commentLine))
val comment = fileText.substring(document.getLineStartOffset(commentLine), document.getLineEndOffset(commentLine)).trim()
val lineIndex = commentLine + 1
val fieldName = comment.substringAfter("//FieldWatchpoint! (").substringBefore(")")
if (!type.canPutAt(virtualFile, lineIndex, myProject)) continue
val xBreakpoint = runWriteAction {
breakpointManager.addLineBreakpoint(
type as XLineBreakpointType<XBreakpointProperties<*>>,
virtualFile.getUrl(),
if (comment.startsWith("//FieldWatchpoint!")) {
val javaBreakpoint = createBreakpointOfType(
breakpointManager,
kotlinFieldBreakpointType as XLineBreakpointType<XBreakpointProperties<*>>,
lineIndex,
type.createBreakpointProperties(virtualFile, lineIndex)
)
virtualFile)
if (javaBreakpoint is KotlinFieldBreakpoint) {
val fieldName = comment.substringAfter("//FieldWatchpoint! (").substringBefore(")")
javaBreakpoint.setFieldName(fieldName)
javaBreakpoint.setWatchAccess(fileText.getValueForSetting("WATCH_FIELD_ACCESS", true))
javaBreakpoint.setWatchModification(fileText.getValueForSetting("WATCH_FIELD_MODIFICATION", true))
javaBreakpoint.setWatchInitialization(fileText.getValueForSetting("WATCH_FIELD_INITIALISATION", false))
BreakpointManager.addBreakpoint(javaBreakpoint)
println("KotlinFieldBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM)
}
}
else if (comment.startsWith("//Breakpoint!")) {
val javaBreakpoint = createBreakpointOfType(
breakpointManager,
kotlinLineBreakpointType as XLineBreakpointType<XBreakpointProperties<*>>,
lineIndex,
virtualFile)
if (javaBreakpoint is LineBreakpoint<*>) {
val javaBreakpoint = BreakpointManager.getJavaBreakpoint(xBreakpoint)
if (javaBreakpoint is KotlinFieldBreakpoint) {
javaBreakpoint.setFieldName(fieldName)
javaBreakpoint.setWatchAccess(fileText.getValueForSetting("WATCH_FIELD_ACCESS", true))
javaBreakpoint.setWatchModification(fileText.getValueForSetting("WATCH_FIELD_MODIFICATION", true))
javaBreakpoint.setWatchInitialization(fileText.getValueForSetting("WATCH_FIELD_INITIALISATION", false))
BreakpointManager.addBreakpoint(javaBreakpoint)
println("KotlinFieldBreakpoint created at ${file.getVirtualFile().getName()}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM)
if (comment.contains("(")) {
val ordinal = comment.substringAfter("//Breakpoint! (").substringBefore(")").toInt()
val properties = javaBreakpoint.xBreakpoint.properties as? JavaLineBreakpointProperties ?: continue
properties.lambdaOrdinal = ordinal
BreakpointManager.addBreakpoint(javaBreakpoint)
println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1} lambdaOrdinal = $ordinal", ProcessOutputTypes.SYSTEM)
}
else {
BreakpointManager.addBreakpoint(javaBreakpoint)
println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM)
}
}
}
else {
throw AssertionError("Cannot create breakpoint at line ${lineIndex + 1}")
}
}
}
@@ -316,6 +338,29 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
}
}
private fun createBreakpointOfType(
breakpointManager: XBreakpointManager,
breakpointType: XLineBreakpointType<XBreakpointProperties<*>>,
lineIndex: Int,
virtualFile: VirtualFile
): Breakpoint<out JavaBreakpointProperties<*>>? {
if (!breakpointType.canPutAt(virtualFile, lineIndex, myProject)) return null
val xBreakpoint = runWriteAction {
breakpointManager.addLineBreakpoint(
breakpointType,
virtualFile.url,
lineIndex,
breakpointType.createBreakpointProperties(virtualFile, lineIndex)
)
}
return BreakpointManager.getJavaBreakpoint(xBreakpoint)
}
private inline fun <reified T> findBreakpointType(javaClass: Class<T>): T {
val kotlinFieldBreakpointTypeClass = javaClass as Class<out XBreakpointType<XBreakpoint<XBreakpointProperties<*>>, XBreakpointProperties<*>>>
return XDebuggerUtil.getInstance().findBreakpointType<XBreakpoint<XBreakpointProperties<*>>>(kotlinFieldBreakpointTypeClass) as T
}
protected fun createAdditionalBreakpoints(fileText: String) {
val breakpoints = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// ADDITIONAL_BREAKPOINT: ")
for (breakpoint in breakpoints) {
@@ -688,6 +688,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doMultipleBreakpointsTest(fileName);
}
@TestMetadata("multipleBreakpointsAtLine.kt")
public void testMultipleBreakpointsAtLine() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt");
doMultipleBreakpointsTest(fileName);
}
@TestMetadata("whenEntry.kt")
public void testWhenEntry() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt");
@@ -706,6 +712,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doMultipleBreakpointsTest(fileName);
}
@TestMetadata("withoutBodyProperties2.kt")
public void testWithoutBodyProperties2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyProperties2.kt");
doMultipleBreakpointsTest(fileName);
}
@TestMetadata("withoutBodyTypeParameters.kt")
public void testWithoutBodyTypeParameters() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt");