Debugger: Convert java expression to kotlin when open EE dialog from Variables View

#KT-6805 Fixed
This commit is contained in:
Natalia Ukhorskaya
2016-03-29 10:52:48 +03:00
parent e0adfd453f
commit a93db70995
6 changed files with 188 additions and 32 deletions
+1
View File
@@ -26,6 +26,7 @@
- [KT-11638](https://youtrack.jetbrains.com/issue/KT-11638) Fixed hashCode() implementation in "Generate equals/hashCode" action
- Configure Kotlin: show only changed files in the notification "Kotlin not configured"
- Configure Kotlin: restore all changed files in undo action
- Convert java expression to kotlin when open Evaluate Expression dialog from Variables View
### Java to Kotlin converter
@@ -28,6 +28,7 @@ import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.IncorrectOperationException
import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.impl.XDebugSessionImpl
@@ -40,9 +41,9 @@ import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.asJava.KtLightClass
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
import org.jetbrains.kotlin.idea.refactoring.j2kText
import org.jetbrains.kotlin.idea.refactoring.quoteIfNeeded
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
@@ -117,7 +118,31 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
= wrapContextIfNeeded(project, getContextElement(context))
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
return createCodeFragment(item, context, project)
val kotlinCodeFragment = createCodeFragment(item, context, project)
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
val javaExpression = try {
PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context)
}
catch(e: IncorrectOperationException) {
null
}
if (javaExpression != null) {
val newText = javaExpression.j2kText()
if (newText != null) {
val convertedFragment = KtExpressionCodeFragment(
project,
kotlinCodeFragment.name,
newText,
kotlinCodeFragment.importsToString(),
kotlinCodeFragment.context
)
return convertedFragment
}
}
}
return kotlinCodeFragment
}
override fun isContextAccepted(contextElement: PsiElement?): Boolean {
@@ -0,0 +1,32 @@
LineBreakpoint created at createExpressionSimple.kt:14
!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! createExpressionSimple.CreateExpressionSimpleKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
createExpressionSimple.kt:14
package createExpressionSimple
class MyClass: Base() {
val i = 1
}
open class Base {
val baseI = 2
}
fun main(args: Array<String>) {
val myClass: Base = MyClass()
//Breakpoint!
val a = 1
}
// PRINT_FRAME
// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION
frame = main
local = args (expression = args)
local = myClass (expression = myClass)
field = i (expression = (myClass as MyClass).i)
field = baseI (expression = (myClass as MyClass).baseI)
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,19 @@
package createExpressionSimple
class MyClass: Base() {
val i = 1
}
open class Base {
val baseI = 2
}
fun main(args: Array<String>) {
val myClass: Base = MyClass()
//Breakpoint!
val a = 1
}
// PRINT_FRAME
// DESCRIPTOR_VIEW_OPTIONS: NAME_AND_EXPRESSION
@@ -23,7 +23,7 @@ import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl
import com.intellij.debugger.impl.DescriptorTestCase
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.settings.NodeRendererSettings
import com.intellij.debugger.ui.impl.watch.*
import com.intellij.debugger.ui.tree.FieldDescriptor
@@ -34,6 +34,7 @@ import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiExpression
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl
import com.intellij.xdebugger.impl.frame.XDebugViewSessionListener
@@ -54,8 +55,9 @@ import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerTestBase
import org.jetbrains.kotlin.idea.debugger.KotlinFrameExtraVariablesProvider
import org.jetbrains.kotlin.idea.debugger.evaluate.AbstractKotlinEvaluateExpressionTest.PrinterConfig.DescriptorViewOptions
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.InTextDirectivesUtils.*
import org.junit.Assert
import java.io.File
import java.util.*
@@ -109,13 +111,14 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
configureSettings(fileText)
createAdditionalBreakpoints(fileText)
val shouldPrintFrame = InTextDirectivesUtils.isDirectiveDefined(fileText, "// PRINT_FRAME")
val skipInPrintFrame = if (shouldPrintFrame) InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// SKIP: ") else emptyList()
val shouldPrintFrame = isDirectiveDefined(fileText, "// PRINT_FRAME")
val skipInPrintFrame = if (shouldPrintFrame) findLinesWithPrefixesRemoved(fileText, "// SKIP: ") else emptyList()
val descriptorViewOptions = DescriptorViewOptions.valueOf(findStringWithPrefixes(fileText, "// DESCRIPTOR_VIEW_OPTIONS: ") ?: "FULL")
val expressions = loadTestDirectivesPairs(fileText, "// EXPRESSION: ", "// RESULT: ")
val blocks = findFilesWithBlocks(file).map { FileUtil.loadFile(it, true) }
val expectedBlockResults = blocks.map { InTextDirectivesUtils.findLinesWithPrefixesRemoved(it, "// RESULT: ").joinToString("\n") }
val expectedBlockResults = blocks.map { findLinesWithPrefixesRemoved(it, "// RESULT: ").joinToString("\n") }
createDebugProcess(path)
@@ -141,7 +144,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
}
finally {
if (shouldPrintFrame) {
printFrame(variablesView, watchesView, PrinterConfig(skipInPrintFrame))
printFrame(variablesView, watchesView, PrinterConfig(skipInPrintFrame, descriptorViewOptions))
println(fileText, ProcessOutputTypes.SYSTEM)
}
else {
@@ -203,7 +206,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
val tree = variablesView.tree!!
expandAll(
tree,
Runnable {
{
try {
Printer(config).printTree(tree)
@@ -218,7 +221,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
},
hashSetOf(),
// TODO why this is needed? Otherwise some tests are never ended
DescriptorTestCase.NodeFilter { it !is XValueNodeImpl || it.name != "cause" },
{ it !is XValueNodeImpl || it.name != "cause" },
this
)
}
@@ -227,9 +230,37 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
return KotlinFrameExtraVariablesProvider().collectVariables(debuggerContext.sourcePosition, evaluationContext, hashSetOf())
}
private class PrinterConfig(
val variablesToSkipInPrintFrame: List<String> = emptyList()
)
internal class PrinterConfig(
val variablesToSkipInPrintFrame: List<String> = emptyList(),
val descriptorOptionsOptions: DescriptorViewOptions = DescriptorViewOptions.FULL
) {
enum class DescriptorViewOptions {
FULL,
NAME_AND_EXPRESSION
}
fun shouldRenderSourcesPosition(): Boolean {
return when(descriptorOptionsOptions) {
DescriptorViewOptions.FULL -> true
else -> false
}
}
fun shouldRenderExpression(): Boolean {
return when(descriptorOptionsOptions) {
DescriptorViewOptions.NAME_AND_EXPRESSION -> true
else -> false
}
}
fun renderLabel(descriptor: NodeDescriptorImpl): String {
return when {
descriptor is WatchItemDescriptor -> descriptor.calcValueName()
descriptorOptionsOptions == DescriptorViewOptions.NAME_AND_EXPRESSION -> descriptor.name ?: descriptor.label
else -> descriptor.label
}
}
}
private inner class Printer(private val config: PrinterConfig) {
fun printTree(tree: XDebuggerTree) {
@@ -237,7 +268,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
printNode(root, 0)
}
private fun printNode(node: TreeNode, indent: Int) {
private fun printNode(node: TreeNode, indent: Int) {
val descriptor = when {
node is DebuggerTreeNodeImpl -> node.descriptor
node is XValueNodeImpl -> (node.valueContainer as? JavaValue)?.descriptor ?: MessageDescriptor(node.text.toString())
@@ -249,38 +280,80 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
else -> MessageDescriptor(node.toString())
}
if (descriptor != null && printDescriptor(descriptor, indent)) return
if (descriptor != null && printDescriptor(node, descriptor, indent)) return
printChildren(node, indent + 2)
}
fun printDescriptor(descriptor: NodeDescriptorImpl, indent: Int): Boolean {
fun printDescriptor(node: TreeNode, descriptor: NodeDescriptorImpl, indent: Int): Boolean {
if (descriptor is DefaultNodeDescriptor) return true
if (config.variablesToSkipInPrintFrame.contains(descriptor.name)) return true
var label = descriptor.label
var label = config.renderLabel(descriptor)
// TODO: update presentation before calc label
if (label == NodeDescriptorImpl.UNKNOWN_VALUE_MESSAGE && descriptor is StaticDescriptor) {
label = "static = " + NodeRendererSettings.getInstance().classRenderer.renderTypeName(descriptor.type.name())
}
if (label.endsWith(XDebuggerUIConstants.COLLECTING_DATA_MESSAGE)) return true
val curIndent = " ".repeat(indent)
when (descriptor) {
is StackFrameDescriptor -> logDescriptor(descriptor, "$curIndent frame = $label\n")
is WatchItemDescriptor -> logDescriptor(descriptor, "$curIndent extra = ${descriptor.calcValueName()}\n")
is LocalVariableDescriptor -> logDescriptor(descriptor, "$curIndent local = $label"
+ " (sp = ${render(SourcePositionProvider.getSourcePosition(descriptor, myProject, debuggerContext))})\n")
is StaticDescriptor -> logDescriptor(descriptor, "$curIndent static = $label\n")
is ThisDescriptorImpl -> logDescriptor(descriptor, "$curIndent this = $label\n")
is FieldDescriptor -> logDescriptor(descriptor, "$curIndent field = $label"
+ " (sp = ${render(SourcePositionProvider.getSourcePosition(descriptor, myProject, debuggerContext))})\n")
is MessageDescriptor -> logDescriptor(descriptor, "$curIndent - $label\n")
else -> logDescriptor(descriptor, "$curIndent unknown = $label\n")
val builder = StringBuilder()
with(builder) {
append(" ".repeat(indent + 1))
append(getPrefix(descriptor))
append(label)
if (config.shouldRenderSourcesPosition() && hasSourcePosition(descriptor)) {
val sp = SourcePositionProvider.getSourcePosition(descriptor, myProject, debuggerContext)
append(" (sp = ${render(sp)})")
}
if (config.shouldRenderExpression() && descriptor is ValueDescriptorImpl) {
var expression: PsiExpression? = null
debuggerContext.debugProcess!!.managerThread.invokeAndWait(object : DebuggerCommandImpl() {
override fun action() {
expression = runReadAction {
descriptor.getTreeEvaluation((node as XValueNodeImpl).valueContainer as JavaValue, debuggerContext) as? PsiExpression
}
}
})
if (expression != null) {
val text = KotlinCodeFragmentFactory().createPresentationCodeFragment(
TextWithImportsImpl(expression!!), debuggerContext.sourcePosition.elementAt, project
).text
append(" (expression = $text)")
}
}
append("\n")
}
logDescriptor(descriptor, builder.toString())
return false
}
private fun getPrefix(descriptor: NodeDescriptorImpl): String {
val prefix = when (descriptor) {
is StackFrameDescriptor -> "frame"
is WatchItemDescriptor -> "extra"
is LocalVariableDescriptor -> "local"
is StaticDescriptor -> "static"
is ThisDescriptorImpl -> "this"
is FieldDescriptor -> "field"
is MessageDescriptor -> ""
else -> "unknown"
}
return prefix + " ".repeat("unknown ".length - prefix.length) + if (descriptor is MessageDescriptor) " - " else " = "
}
private fun hasSourcePosition(descriptor: NodeDescriptorImpl): Boolean {
return when (descriptor) {
is LocalVariableDescriptor,
is FieldDescriptor -> true
else -> false
}
}
private fun printChildren(node: TreeNode, indent: Int) {
val e = node.children()
while (e.hasMoreElements()) {
@@ -312,8 +385,8 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
}
private fun loadTestDirectivesPairs(fileContent: String, directivePrefix: String, expectedPrefix: String): List<Pair<String, String>> {
val directives = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileContent, directivePrefix)
val expected = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileContent, expectedPrefix)
val directives = findLinesWithPrefixesRemoved(fileContent, directivePrefix)
val expected = findLinesWithPrefixesRemoved(fileContent, expectedPrefix)
assert(directives.size == expected.size) { "Sizes of test directives are different" }
return directives.zip(expected)
}
@@ -328,7 +401,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
Assert.assertTrue("KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. ContextElement = ${contextElement.text}",
KotlinCodeFragmentFactory().isContextAccepted(contextElement))
val labelsAsText = InTextDirectivesUtils.findLinesWithPrefixesRemoved(contextElement.containingFile.text, "// DEBUG_LABEL: ")
val labelsAsText = findLinesWithPrefixesRemoved(contextElement.containingFile.text, "// DEBUG_LABEL: ")
if (labelsAsText.isEmpty()) return contextElement
val markupMap = hashMapOf<com.sun.jdi.Value, ValueMarkup>()
@@ -495,6 +495,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("createExpressionSimple.kt")
public void testCreateExpressionSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/createExpressionSimple.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("delegatedPropertyInClass.kt")
public void testDelegatedPropertyInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt");