Hide unrelated this entries in Kotlin Watches mode
This commit is contained in:
@@ -23,6 +23,7 @@ import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
|||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||||
|
import com.intellij.debugger.ui.impl.watch.ThisDescriptorImpl
|
||||||
import com.intellij.xdebugger.frame.XValue
|
import com.intellij.xdebugger.frame.XValue
|
||||||
import com.intellij.xdebugger.frame.XValueChildrenList
|
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||||
import com.sun.jdi.Value
|
import com.sun.jdi.Value
|
||||||
@@ -47,50 +48,75 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
|
|
||||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager
|
val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager
|
||||||
|
|
||||||
removeThisObjectIfNeeded(evaluationContext, children)
|
|
||||||
|
|
||||||
fun addItem(variable: LocalVariableProxyImpl) {
|
fun addItem(variable: LocalVariableProxyImpl) {
|
||||||
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
||||||
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
val (thisReferences, otherVariables) = visibleVariables
|
val (thisReferences, otherVariables) = visibleVariables
|
||||||
.partition { it.name() == THIS_NAME || it.name().startsWith("$THIS_NAME ") }
|
.partition { it.name() == AsmUtil.THIS || it is ThisLocalVariable }
|
||||||
|
|
||||||
|
if (!removeThisObjectForContinuation(evaluationContext, children) && thisReferences.isNotEmpty()) {
|
||||||
|
val thisLabels = thisReferences.asSequence()
|
||||||
|
.filterIsInstance<ThisLocalVariable>()
|
||||||
|
.mapNotNullTo(hashSetOf()) { it.label }
|
||||||
|
|
||||||
|
remapThisObjectForOuterThis(evaluationContext, children, thisLabels)
|
||||||
|
}
|
||||||
|
|
||||||
thisReferences.forEach(::addItem)
|
thisReferences.forEach(::addItem)
|
||||||
otherVariables.forEach(::addItem)
|
otherVariables.forEach(::addItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeThisObjectIfNeeded(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) {
|
private fun removeThisObjectForContinuation(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
|
||||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
|
||||||
if (!thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) {
|
if (!thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ExistingInstanceThis.find(children)?.remove()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun remapThisObjectForOuterThis(
|
||||||
|
evaluationContext: EvaluationContextImpl,
|
||||||
|
children: XValueChildrenList,
|
||||||
|
existingThisLabels: Set<String>
|
||||||
|
) {
|
||||||
|
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
||||||
|
val variable = ExistingInstanceThis.find(children) ?: return
|
||||||
|
|
||||||
|
val thisLabel = thisObject.referenceType().name().substringAfterLast('.').substringAfterLast('$')
|
||||||
|
if (thisLabel.isEmpty() || thisLabel in existingThisLabels || thisLabel.all { it.isDigit() }) {
|
||||||
|
variable.remove()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ExistingVariable.find(children, "this")?.remove()
|
variable.remapName(getThisName(thisLabel))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Very Dirty Work-around.
|
// Very Dirty Work-around.
|
||||||
// Hopefully, there will be an API for that in 2019.1.
|
// Hopefully, there will be an API for that in 2019.1.
|
||||||
private class ExistingVariable(
|
private class ExistingInstanceThis(
|
||||||
private val children: XValueChildrenList,
|
private val children: XValueChildrenList,
|
||||||
private val index: Int,
|
private val index: Int,
|
||||||
private val name: String,
|
|
||||||
private val value: XValue,
|
private val value: XValue,
|
||||||
private val size: Int
|
private val size: Int
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun find(children: XValueChildrenList, name: String): ExistingVariable? {
|
private const val THIS_NAME = "this"
|
||||||
|
|
||||||
|
fun find(children: XValueChildrenList): ExistingInstanceThis? {
|
||||||
val size = children.size()
|
val size = children.size()
|
||||||
for (i in 0 until size) {
|
for (i in 0 until size) {
|
||||||
if (children.getName(i) == name) {
|
if (children.getName(i) == THIS_NAME) {
|
||||||
val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor
|
val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor
|
||||||
@Suppress("FoldInitializerAndIfToElvis")
|
@Suppress("FoldInitializerAndIfToElvis")
|
||||||
if (valueDescriptor !is ThisDescriptorImpl) {
|
if (valueDescriptor !is ThisDescriptorImpl) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExistingInstanceThis(children, i, name, children.getValue(i), size)
|
return ExistingInstanceThis(children, i, children.getValue(i), size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +124,11 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun remapName(newName: String) {
|
||||||
|
val (names, _) = getLists() ?: return
|
||||||
|
names[index] = newName
|
||||||
|
}
|
||||||
|
|
||||||
fun remove() {
|
fun remove() {
|
||||||
val (names, values) = getLists() ?: return
|
val (names, values) = getLists() ?: return
|
||||||
names.removeAt(index)
|
names.removeAt(index)
|
||||||
@@ -150,11 +181,18 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allVisibleVariables.asSequence()
|
val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
|
||||||
.filter { !isHidden(it) }
|
.filter { !isHidden(it) }
|
||||||
.map { remapVariableName(it) }
|
.partition { it.name() == AsmUtil.THIS || it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER) }
|
||||||
.distinctBy { it.name() }
|
|
||||||
.toList()
|
val (mainThis, otherThis) = thisVariables
|
||||||
|
.sortedByDescending { it.variable }
|
||||||
|
.let { it.firstOrNull() to it.drop(1) }
|
||||||
|
|
||||||
|
val remappedMainThis = mainThis?.clone(AsmUtil.THIS, null)
|
||||||
|
val remappedOtherThis = otherThis.map { it.remapThisName() }
|
||||||
|
|
||||||
|
return (listOfNotNull(remappedMainThis) + remappedOtherThis + otherVariables)
|
||||||
.sortedBy { it.variable }
|
.sortedBy { it.variable }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,13 +203,25 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
|| name == CONTINUATION_PARAMETER_NAME.asString()
|
|| name == CONTINUATION_PARAMETER_NAME.asString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun remapVariableName(variable: LocalVariableProxyImpl) = with(variable) {
|
private fun LocalVariableProxyImpl.remapThisName(): LocalVariableProxyImpl {
|
||||||
when {
|
return when {
|
||||||
isLabeledThisReference() -> {
|
isLabeledThisReference() -> {
|
||||||
val label = variable.name().drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
val label = this@remapThisName.name().drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
||||||
clone(withName = "$THIS_NAME (@$label)")
|
clone(getThisName(label), label)
|
||||||
}
|
}
|
||||||
else -> variable
|
this@remapThisName.name() == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(AsmUtil.THIS + " (receiver)", null)
|
||||||
|
else -> this@remapThisName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getThisName(label: String): String {
|
||||||
|
return "$THIS_NAME (@$label)"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl {
|
||||||
|
return object : LocalVariableProxyImpl(frame, variable), ThisLocalVariable {
|
||||||
|
override fun name() = name
|
||||||
|
override val label = label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,10 +231,8 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LocalVariableProxyImpl.clone(withName: String): LocalVariableProxyImpl {
|
private interface ThisLocalVariable {
|
||||||
return object : LocalVariableProxyImpl(frame, variable) {
|
val label: String?
|
||||||
override fun name() = withName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableProxyImpl {
|
private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableProxyImpl {
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package hideSyntheticThis
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
block("foo") {
|
||||||
|
//Breakpoint!
|
||||||
|
val b = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> block(t: T, block: T.() -> Unit) {
|
||||||
|
t.block()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRINT_FRAME
|
||||||
|
// SHOW_KOTLIN_VARIABLES
|
||||||
|
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
LineBreakpoint created at hideSyntheticThis.kt:6
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
hideSyntheticThis.kt:6
|
||||||
|
package hideSyntheticThis
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
block("foo") {
|
||||||
|
//Breakpoint!
|
||||||
|
val b = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> block(t: T, block: T.() -> Unit) {
|
||||||
|
t.block()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRINT_FRAME
|
||||||
|
// SHOW_KOTLIN_VARIABLES
|
||||||
|
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
|
||||||
|
frame = invoke
|
||||||
|
local = this (expression = this)
|
||||||
|
field = value (expression = this.value)
|
||||||
|
element = 0 (expression = this.value[0])
|
||||||
|
element = 1 (expression = this.value[1])
|
||||||
|
element = 2 (expression = this.value[2])
|
||||||
|
field = hash (expression = this.hash)
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package remapThis
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
Bar().bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar {
|
||||||
|
fun bar() {
|
||||||
|
"a".foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.foo() {
|
||||||
|
//Breakpoint!
|
||||||
|
val a = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRINT_FRAME
|
||||||
|
// SHOW_KOTLIN_VARIABLES
|
||||||
|
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
LineBreakpoint created at remapThis.kt:14
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
remapThis.kt:14
|
||||||
|
package remapThis
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
Bar().bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar {
|
||||||
|
fun bar() {
|
||||||
|
"a".foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.foo() {
|
||||||
|
//Breakpoint!
|
||||||
|
val a = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRINT_FRAME
|
||||||
|
// SHOW_KOTLIN_VARIABLES
|
||||||
|
// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION
|
||||||
|
frame = foo
|
||||||
|
this = this (@Bar) (expression = this)
|
||||||
|
-
|
||||||
|
local = this (expression = this)
|
||||||
|
field = value (expression = this.value)
|
||||||
|
element = 0 (expression = this.value[0])
|
||||||
|
field = hash (expression = this.hash)
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+3
-3
@@ -259,10 +259,10 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderLabel(descriptor: NodeDescriptorImpl): String {
|
fun renderLabel(node: TreeNode, descriptor: NodeDescriptorImpl): String {
|
||||||
return when {
|
return when {
|
||||||
descriptor is WatchItemDescriptor -> descriptor.calcValueName()
|
descriptor is WatchItemDescriptor -> descriptor.calcValueName()
|
||||||
viewOptions.toString().contains("NAME") -> descriptor.name ?: descriptor.label
|
viewOptions.toString().contains("NAME") -> (node as? XValueNodeImpl)?.name ?: descriptor.name ?: descriptor.label
|
||||||
else -> descriptor.label
|
else -> descriptor.label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +299,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
|
|||||||
if (descriptor is DefaultNodeDescriptor) return true
|
if (descriptor is DefaultNodeDescriptor) return true
|
||||||
if (config.variablesToSkipInPrintFrame.contains(descriptor.name)) return true
|
if (config.variablesToSkipInPrintFrame.contains(descriptor.name)) return true
|
||||||
|
|
||||||
var label = config.renderLabel(descriptor)
|
var label = config.renderLabel(node, descriptor)
|
||||||
|
|
||||||
// TODO: update presentation before calc label
|
// TODO: update presentation before calc label
|
||||||
if (label == NodeDescriptorImpl.UNKNOWN_VALUE_MESSAGE && descriptor is StaticDescriptor) {
|
if (label == NodeDescriptorImpl.UNKNOWN_VALUE_MESSAGE && descriptor is StaticDescriptor) {
|
||||||
|
|||||||
Generated
+10
@@ -740,6 +740,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("hideSyntheticThis.kt")
|
||||||
|
public void testHideSyntheticThis() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("remapThis.kt")
|
||||||
|
public void testRemapThis() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("suspendContinuation.kt")
|
@TestMetadata("suspendContinuation.kt")
|
||||||
public void testSuspendContinuation() throws Exception {
|
public void testSuspendContinuation() throws Exception {
|
||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/suspendContinuation.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/suspendContinuation.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user