Debugger: Convert java expression to kotlin when open EE dialog from Variables View
#KT-6805 Fixed
This commit is contained in:
@@ -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
|
||||
Vendored
+19
@@ -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
|
||||
|
||||
+103
-30
@@ -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>()
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user