Support labels evaluation in debugger
This commit is contained in:
@@ -16,34 +16,51 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||||
|
|
||||||
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
|
|
||||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.psi.JavaCodeFragment
|
|
||||||
import org.jetbrains.kotlin.idea.JetFileType
|
|
||||||
import org.jetbrains.kotlin.psi.JetExpressionCodeFragment
|
|
||||||
import com.intellij.psi.PsiCodeBlock
|
|
||||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import org.jetbrains.kotlin.psi.JetExpression
|
|
||||||
import org.jetbrains.kotlin.psi.JetBlockCodeFragment
|
|
||||||
import org.jetbrains.kotlin.asJava.KotlinLightClass
|
|
||||||
import com.intellij.debugger.DebuggerManagerEx
|
import com.intellij.debugger.DebuggerManagerEx
|
||||||
import org.jetbrains.kotlin.psi.JetCodeFragment
|
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
|
||||||
import org.jetbrains.kotlin.types.JetType
|
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||||
import com.intellij.util.concurrency.Semaphore
|
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
import com.intellij.openapi.progress.ProgressManager
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.psi.JavaCodeFragment
|
||||||
|
import com.intellij.psi.PsiCodeBlock
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.util.concurrency.Semaphore
|
||||||
|
import com.intellij.xdebugger.XDebuggerManager
|
||||||
|
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||||
|
import com.sun.jdi.ObjectReference
|
||||||
|
import com.sun.jdi.Value
|
||||||
|
import org.jetbrains.kotlin.asJava.KotlinLightClass
|
||||||
|
import org.jetbrains.kotlin.idea.JetFileType
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.types.JetType
|
||||||
|
import java.util.HashMap
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
||||||
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||||
val codeFragment = if (item.getKind() == CodeFragmentKind.EXPRESSION) {
|
val codeFragment = if (item.getKind() == CodeFragmentKind.EXPRESSION) {
|
||||||
JetExpressionCodeFragment(project, "fragment.kt", item.getText(), item.getImports(), getContextElement(context))
|
JetExpressionCodeFragment(
|
||||||
|
project,
|
||||||
|
"fragment.kt",
|
||||||
|
item.getText(),
|
||||||
|
item.getImports(),
|
||||||
|
getWrappedContextElement(project, context)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
JetBlockCodeFragment(project, "fragment.kt", item.getText(), item.getImports(), getContextElement(context))
|
JetBlockCodeFragment(
|
||||||
|
project,
|
||||||
|
"fragment.kt",
|
||||||
|
item.getText(),
|
||||||
|
item.getImports(),
|
||||||
|
getWrappedContextElement(project, context)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
codeFragment.putCopyableUserData(JetCodeFragment.RUNTIME_TYPE_EVALUATOR, {
|
codeFragment.putCopyableUserData(JetCodeFragment.RUNTIME_TYPE_EVALUATOR, {
|
||||||
@@ -79,6 +96,9 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
return codeFragment
|
return codeFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getWrappedContextElement(project: Project, context: PsiElement?)
|
||||||
|
= wrapContextIfNeeded(project, getContextElement(context))
|
||||||
|
|
||||||
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||||
return createCodeFragment(item, context, project)
|
return createCodeFragment(item, context, project)
|
||||||
}
|
}
|
||||||
@@ -96,6 +116,9 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
override fun getEvaluatorBuilder() = KotlinEvaluationBuilder
|
override fun getEvaluatorBuilder() = KotlinEvaluationBuilder
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
public val LABEL_VARIABLE_VALUE_KEY: Key<Value> = Key.create<Value>("_label_variable_value_key_")
|
||||||
|
public val DEBUG_LABEL_SUFFIX: String = "_DebugLabel"
|
||||||
|
|
||||||
fun getContextElement(elementAt: PsiElement?): PsiElement? {
|
fun getContextElement(elementAt: PsiElement?): PsiElement? {
|
||||||
if (elementAt == null) return null
|
if (elementAt == null) return null
|
||||||
|
|
||||||
@@ -107,16 +130,65 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
|
|||||||
return getContextElement(elementAt.getOrigin())
|
return getContextElement(elementAt.getOrigin())
|
||||||
}
|
}
|
||||||
|
|
||||||
// If label for some variable is set
|
|
||||||
if (elementAt.getParent() is JetCodeFragment) {
|
|
||||||
return elementAt.getParent().getContext()
|
|
||||||
}
|
|
||||||
|
|
||||||
val expressionAtOffset = PsiTreeUtil.findElementOfClassAtOffset(elementAt.getContainingFile()!!, elementAt.getTextOffset(), javaClass<JetExpression>(), false)
|
val expressionAtOffset = PsiTreeUtil.findElementOfClassAtOffset(elementAt.getContainingFile()!!, elementAt.getTextOffset(), javaClass<JetExpression>(), false)
|
||||||
if (expressionAtOffset != null) {
|
if (expressionAtOffset != null) {
|
||||||
return expressionAtOffset
|
return expressionAtOffset
|
||||||
}
|
}
|
||||||
return KotlinEditorTextProvider.findExpressionInner(elementAt, true)
|
return KotlinEditorTextProvider.findExpressionInner(elementAt, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//internal for tests
|
||||||
|
fun createCodeFragmentForLabeledObjects(markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, ObjectReference>> {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
val labeledObjects = HashMap<String, ObjectReference>()
|
||||||
|
for ((value, markup) in markupMap.entrySet()) {
|
||||||
|
val labelName = markup.getText()
|
||||||
|
if (!Name.isValidIdentifier(labelName)) continue
|
||||||
|
|
||||||
|
val objectRef = value as ObjectReference
|
||||||
|
|
||||||
|
val typeName = value.type().name()
|
||||||
|
val labelNameWithSuffix = labelName + DEBUG_LABEL_SUFFIX
|
||||||
|
sb.append("val ").append(labelNameWithSuffix).append(": ").append(typeName).append("? = null\n")
|
||||||
|
|
||||||
|
labeledObjects.put(labelNameWithSuffix, objectRef)
|
||||||
|
}
|
||||||
|
sb.append("val _debug_context_val = 1")
|
||||||
|
return sb.toString() to labeledObjects
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private fun wrapContextIfNeeded(project: Project, originalContext: PsiElement?): PsiElement? {
|
||||||
|
val session = XDebuggerManager.getInstance(project).getCurrentSession() as? XDebugSessionImpl
|
||||||
|
?: return originalContext
|
||||||
|
|
||||||
|
val markupMap = session.getValueMarkers()?.getAllMarkers()
|
||||||
|
if (markupMap == null || markupMap.isEmpty()) return originalContext
|
||||||
|
|
||||||
|
val (text, labels) = createCodeFragmentForLabeledObjects(markupMap)
|
||||||
|
if (text.isEmpty()) return originalContext
|
||||||
|
|
||||||
|
return createWrappingContext(text, labels, originalContext, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal for test
|
||||||
|
fun createWrappingContext(
|
||||||
|
newFragmentText: String,
|
||||||
|
labels: Map<String, ObjectReference>,
|
||||||
|
originalContext: PsiElement?,
|
||||||
|
project: Project
|
||||||
|
): PsiElement? {
|
||||||
|
val codeFragment = JetPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext)
|
||||||
|
|
||||||
|
codeFragment.accept(object : JetTreeVisitorVoid() {
|
||||||
|
override fun visitProperty(property: JetProperty) {
|
||||||
|
val reference = labels.get(property.getName())
|
||||||
|
if (reference != null) {
|
||||||
|
property.putUserData(LABEL_VARIABLE_VALUE_KEY, reference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return getContextElement(codeFragment.findElementAt(codeFragment.getText().length() - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ import com.sun.jdi.request.EventRequest
|
|||||||
import org.jetbrains.eval4j.*
|
import org.jetbrains.eval4j.*
|
||||||
import org.jetbrains.eval4j.jdi.JDIEval
|
import org.jetbrains.eval4j.jdi.JDIEval
|
||||||
import org.jetbrains.eval4j.jdi.asJdiValue
|
import org.jetbrains.eval4j.jdi.asJdiValue
|
||||||
|
import org.jetbrains.eval4j.jdi.asValue
|
||||||
import org.jetbrains.eval4j.jdi.makeInitialFrame
|
import org.jetbrains.eval4j.jdi.makeInitialFrame
|
||||||
import org.jetbrains.kotlin.codegen.*
|
import org.jetbrains.kotlin.codegen.*
|
||||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||||
@@ -80,7 +81,7 @@ import org.jetbrains.kotlin.types.Flexibility
|
|||||||
import org.jetbrains.org.objectweb.asm.*
|
import org.jetbrains.org.objectweb.asm.*
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
|
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||||
import java.util.Collections
|
import java.util.*
|
||||||
|
|
||||||
private val RECEIVER_NAME = "\$receiver"
|
private val RECEIVER_NAME = "\$receiver"
|
||||||
private val THIS_NAME = "this"
|
private val THIS_NAME = "this"
|
||||||
@@ -176,7 +177,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
if (extractionResult == null) {
|
if (extractionResult == null) {
|
||||||
throw IllegalStateException("Code fragment cannot be extracted to function")
|
throw IllegalStateException("Code fragment cannot be extracted to function")
|
||||||
}
|
}
|
||||||
val parametersDescriptor = extractionResult.getParametersForDebugger()
|
val parametersDescriptor = extractionResult.getParametersForDebugger(codeFragment)
|
||||||
val extractedFunction = extractionResult.declaration as JetNamedFunction
|
val extractedFunction = extractionResult.declaration as JetNamedFunction
|
||||||
|
|
||||||
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor)
|
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor)
|
||||||
@@ -287,8 +288,22 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
return jdiValue.asJdiValue(vm, jdiValue.asmType)
|
return jdiValue.asJdiValue(vm, jdiValue.asmType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ExtractionResult.getParametersForDebugger(): ParametersDescriptor {
|
private fun ExtractionResult.getParametersForDebugger(fragment: JetCodeFragment): ParametersDescriptor {
|
||||||
return runReadAction {
|
return runReadAction {
|
||||||
|
val valuesForLabels = HashMap<String, Value>()
|
||||||
|
|
||||||
|
val contextElementFile = fragment.getContext()?.getContainingFile()
|
||||||
|
if (contextElementFile is JetCodeFragment) {
|
||||||
|
contextElementFile.accept(object: JetTreeVisitorVoid() {
|
||||||
|
override fun visitProperty(property: JetProperty) {
|
||||||
|
val value = property.getUserData(KotlinCodeFragmentFactory.LABEL_VARIABLE_VALUE_KEY)
|
||||||
|
if (value != null) {
|
||||||
|
valuesForLabels.put(property.getName(), value.asValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
val parameters = ParametersDescriptor()
|
val parameters = ParametersDescriptor()
|
||||||
val receiver = config.descriptor.receiverParameter
|
val receiver = config.descriptor.receiverParameter
|
||||||
if (receiver != null) {
|
if (receiver != null) {
|
||||||
@@ -301,7 +316,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
param.argumentText.startsWith("::") -> param.argumentText.substring(2)
|
param.argumentText.startsWith("::") -> param.argumentText.substring(2)
|
||||||
else -> param.argumentText
|
else -> param.argumentText
|
||||||
}
|
}
|
||||||
parameters.add(paramName, param.getParameterType(true))
|
parameters.add(paramName, param.getParameterType(true), valuesForLabels[paramName])
|
||||||
}
|
}
|
||||||
parameters
|
parameters
|
||||||
}
|
}
|
||||||
@@ -310,7 +325,12 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
private fun EvaluationContextImpl.getArgumentsForEval4j(parameters: ParametersDescriptor, parameterTypes: Array<Type>): List<Value> {
|
private fun EvaluationContextImpl.getArgumentsForEval4j(parameters: ParametersDescriptor, parameterTypes: Array<Type>): List<Value> {
|
||||||
val frameVisitor = FrameVisitor(this)
|
val frameVisitor = FrameVisitor(this)
|
||||||
return parameters.zip(parameterTypes).map {
|
return parameters.zip(parameterTypes).map {
|
||||||
frameVisitor.findValue(it.first.callText, it.second, checkType = false, failIfNotFound = true)!!
|
if (it.first.value != null) {
|
||||||
|
it.first.value!!
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frameVisitor.findValue(it.first.callText, it.second, checkType = false, failIfNotFound = true)!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-18
@@ -153,6 +153,11 @@ private fun getExpressionToAddDebugExpressionBefore(tmpFile: JetFile, contextEle
|
|||||||
return CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, lineStart) ?: elementAtOffset
|
return CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, lineStart) ?: elementAtOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val containingFile = contextElement.getContainingFile()
|
||||||
|
if (containingFile is JetCodeFragment) {
|
||||||
|
return getExpressionToAddDebugExpressionBefore(tmpFile, containingFile.getContext(), line)
|
||||||
|
}
|
||||||
|
|
||||||
fun shouldStop(el: PsiElement?, p: PsiElement?) = p is JetBlockExpression || el is JetDeclaration
|
fun shouldStop(el: PsiElement?, p: PsiElement?) = p is JetBlockExpression || el is JetDeclaration
|
||||||
|
|
||||||
var elementAt = tmpFile.getElementInCopy(contextElement)
|
var elementAt = tmpFile.getElementInCopy(contextElement)
|
||||||
@@ -239,28 +244,25 @@ private fun addDebugExpressionBeforeContextElement(codeFragment: JetCodeFragment
|
|||||||
|
|
||||||
parent.addBefore(psiFactory.createNewLine(), elementBefore)
|
parent.addBefore(psiFactory.createNewLine(), elementBefore)
|
||||||
|
|
||||||
val debugExpression = codeFragment.getContentElement() ?: return emptyList()
|
fun insertExpression(expr: JetElement?): List<JetExpression> {
|
||||||
|
when (expr) {
|
||||||
val expressions = ArrayList<JetExpression>()
|
is JetBlockExpression -> return expr.getStatements().flatMap { insertExpression(it) }
|
||||||
|
is JetExpression -> {
|
||||||
fun insertExpression(expr: JetExpression) {
|
val newDebugExpression = parent.addBefore(expr, elementBefore) ?: return emptyList()
|
||||||
val newDebugExpression = parent.addBefore(expr, elementBefore) ?: return
|
parent.addBefore(psiFactory.createNewLine(), elementBefore)
|
||||||
|
return listOf(newDebugExpression as JetExpression)
|
||||||
expressions.add(newDebugExpression as JetExpression)
|
|
||||||
|
|
||||||
parent.addBefore(psiFactory.createNewLine(), elementBefore)
|
|
||||||
}
|
|
||||||
|
|
||||||
when (debugExpression) {
|
|
||||||
is JetBlockExpression -> {
|
|
||||||
for (statement in debugExpression.getStatements()) {
|
|
||||||
insertExpression(statement)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is JetExpression -> insertExpression(debugExpression)
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
return expressions
|
val containingFile = codeFragment.getContext()?.getContainingFile()
|
||||||
|
if (containingFile is JetCodeFragment) {
|
||||||
|
insertExpression(containingFile.getContentElement() as? JetExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
val debugExpression = codeFragment.getContentElement() ?: return emptyList()
|
||||||
|
return insertExpression(debugExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replaceByRunFunction(expression: JetExpression): JetCallExpression {
|
private fun replaceByRunFunction(expression: JetExpression): JetCallExpression {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
LineBreakpoint created at lCallOnLabeledObj.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 !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! lCallOnLabeledObj.LCallOnLabeledObjPackage
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
lCallOnLabeledObj.kt:6
|
||||||
|
Compile bytecode for myClass_DebugLabel.foo()
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
LineBreakpoint created at lSeveralLabels.kt:8
|
||||||
|
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! lSeveralLabels.LSeveralLabelsPackage
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
lSeveralLabels.kt:8
|
||||||
|
Compile bytecode for a_DebugLabel
|
||||||
|
Compile bytecode for b_DebugLabel
|
||||||
|
Compile bytecode for c_DebugLabel
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
LineBreakpoint created at lSimple.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 !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! lSimple.LSimplePackage
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
lSimple.kt:6
|
||||||
|
Compile bytecode for a
|
||||||
|
Compile bytecode for aLabel_DebugLabel
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package lCallOnLabeledObj
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val myClass = MyClass()
|
||||||
|
//Breakpoint!
|
||||||
|
val a = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyClass {
|
||||||
|
fun foo() = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG_LABEL: myClass = myClass
|
||||||
|
|
||||||
|
// EXPRESSION: myClass_DebugLabel.foo()
|
||||||
|
// RESULT: 1: I
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package lSeveralLabels
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val a = "str1"
|
||||||
|
val b = "str2"
|
||||||
|
val c = "str3"
|
||||||
|
//Breakpoint!
|
||||||
|
val d = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG_LABEL: a = a
|
||||||
|
// DEBUG_LABEL: b = b
|
||||||
|
// DEBUG_LABEL: c = c
|
||||||
|
|
||||||
|
// EXPRESSION: a_DebugLabel
|
||||||
|
// RESULT: "str1": Ljava/lang/String;
|
||||||
|
|
||||||
|
// EXPRESSION: b_DebugLabel
|
||||||
|
// RESULT: "str2": Ljava/lang/String;
|
||||||
|
|
||||||
|
// EXPRESSION: c_DebugLabel
|
||||||
|
// RESULT: "str3": Ljava/lang/String;
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package lSimple
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val a = "str"
|
||||||
|
//Breakpoint!
|
||||||
|
val b = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG_LABEL: a = aLabel
|
||||||
|
|
||||||
|
// EXPRESSION: a
|
||||||
|
// RESULT: "str": Ljava/lang/String;
|
||||||
|
|
||||||
|
// EXPRESSION: aLabel_DebugLabel
|
||||||
|
// RESULT: "str": Ljava/lang/String;
|
||||||
+33
-9
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||||
|
|
||||||
import com.intellij.debugger.DebuggerInvocationUtil
|
|
||||||
import com.intellij.debugger.DebuggerManagerEx
|
|
||||||
import com.intellij.debugger.SourcePosition
|
import com.intellij.debugger.SourcePosition
|
||||||
import com.intellij.debugger.engine.ContextUtil
|
import com.intellij.debugger.engine.ContextUtil
|
||||||
import com.intellij.debugger.engine.SourcePositionProvider
|
import com.intellij.debugger.engine.SourcePositionProvider
|
||||||
@@ -35,13 +33,11 @@ import com.intellij.debugger.ui.tree.LocalVariableDescriptor
|
|||||||
import com.intellij.debugger.ui.tree.StackFrameDescriptor
|
import com.intellij.debugger.ui.tree.StackFrameDescriptor
|
||||||
import com.intellij.debugger.ui.tree.StaticDescriptor
|
import com.intellij.debugger.ui.tree.StaticDescriptor
|
||||||
import com.intellij.execution.process.ProcessOutputTypes
|
import com.intellij.execution.process.ProcessOutputTypes
|
||||||
import com.intellij.openapi.application.ModalityState
|
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiManager
|
|
||||||
import com.intellij.psi.search.FilenameIndex
|
|
||||||
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
|
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||||
import com.sun.jdi.ObjectReference
|
import com.sun.jdi.ObjectReference
|
||||||
import org.apache.log4j.AppenderSkeleton
|
import org.apache.log4j.AppenderSkeleton
|
||||||
import org.apache.log4j.Level
|
import org.apache.log4j.Level
|
||||||
@@ -58,6 +54,7 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
|||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
import kotlin.test.fail
|
||||||
|
|
||||||
public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
|
public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
|
||||||
private val logger = Logger.getLogger(javaClass<KotlinEvaluateExpressionCache>())!!
|
private val logger = Logger.getLogger(javaClass<KotlinEvaluateExpressionCache>())!!
|
||||||
@@ -278,12 +275,39 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestB
|
|||||||
return mainFile.getParentFile()?.listFiles()?.filter { it.name.startsWith(mainFileName) && it.name != mainFileName } ?: Collections.emptyList()
|
return mainFile.getParentFile()?.listFiles()?.filter { it.name.startsWith(mainFileName) && it.name != mainFileName } ?: Collections.emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createContextElement(context: SuspendContextImpl): PsiElement {
|
||||||
|
val contextElement = ContextUtil.getContextElement(debuggerContext)
|
||||||
|
Assert.assertTrue("KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. ContextElement = ${contextElement.getText()}",
|
||||||
|
KotlinCodeFragmentFactory().isContextAccepted(contextElement))
|
||||||
|
|
||||||
|
if (contextElement != null) {
|
||||||
|
val labelsAsText = InTextDirectivesUtils.findLinesWithPrefixesRemoved(contextElement.getContainingFile().getText(), "// DEBUG_LABEL: ")
|
||||||
|
if (labelsAsText.isEmpty()) return contextElement
|
||||||
|
|
||||||
|
val markupMap = hashMapOf<ObjectReference, ValueMarkup>()
|
||||||
|
for (labelAsText in labelsAsText) {
|
||||||
|
val labelParts = labelAsText.splitBy("=")
|
||||||
|
assert(labelParts.size() == 2) { "Wrong format for DEBUG_LABEL directive: // DEBUG_LABEL: {localVariableName} = {labelText}"}
|
||||||
|
val localVariableName = labelParts[0].trim()
|
||||||
|
val labelName = labelParts[1].trim()
|
||||||
|
val localVariable = context.getFrameProxy().visibleVariableByName(localVariableName)
|
||||||
|
assert(localVariable != null) { "Couldn't find localVariable for label: name = $localVariableName" }
|
||||||
|
val localVariableValue = context.getFrameProxy().getValue(localVariable) as? ObjectReference
|
||||||
|
assert(localVariableValue != null) { "Local variable $localVariableName should be an ObjectReference" }
|
||||||
|
markupMap.put(localVariableValue, ValueMarkup(labelName, null, labelName))
|
||||||
|
}
|
||||||
|
|
||||||
|
val (text, labels) = KotlinCodeFragmentFactory.createCodeFragmentForLabeledObjects(markupMap)
|
||||||
|
return KotlinCodeFragmentFactory().createWrappingContext(text, labels, KotlinCodeFragmentFactory.getContextElement(contextElement), getProject())!!
|
||||||
|
}
|
||||||
|
|
||||||
|
return contextElement!!
|
||||||
|
}
|
||||||
|
|
||||||
private fun SuspendContextImpl.evaluate(text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String) {
|
private fun SuspendContextImpl.evaluate(text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String) {
|
||||||
runReadAction {
|
runReadAction {
|
||||||
val sourcePosition = ContextUtil.getSourcePosition(this)
|
val sourcePosition = ContextUtil.getSourcePosition(this)
|
||||||
val contextElement = ContextUtil.getContextElement(sourcePosition)!!
|
val contextElement = createContextElement(this)
|
||||||
Assert.assertTrue("KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. ContextElement = ${contextElement.getText()}",
|
|
||||||
KotlinCodeFragmentFactory().isContextAccepted(contextElement))
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
|||||||
+27
@@ -526,6 +526,33 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class Labels extends AbstractKotlinEvaluateExpressionTest {
|
||||||
|
public void testAllFilesPresentInLabels() throws Exception {
|
||||||
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("lCallOnLabeledObj.kt")
|
||||||
|
public void testLCallOnLabeledObj() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/lCallOnLabeledObj.kt");
|
||||||
|
doSingleBreakpointTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("lSeveralLabels.kt")
|
||||||
|
public void testLSeveralLabels() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/lSeveralLabels.kt");
|
||||||
|
doSingleBreakpointTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("lSimple.kt")
|
||||||
|
public void testLSimple() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels/lSimple.kt");
|
||||||
|
doSingleBreakpointTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas")
|
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user