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.ui.impl.watch.MethodsTracker
|
||||
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.XValueChildrenList
|
||||
import com.sun.jdi.Value
|
||||
@@ -47,50 +48,75 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
||||
|
||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager
|
||||
|
||||
removeThisObjectIfNeeded(evaluationContext, children)
|
||||
|
||||
fun addItem(variable: LocalVariableProxyImpl) {
|
||||
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
||||
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
||||
}
|
||||
|
||||
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)
|
||||
otherVariables.forEach(::addItem)
|
||||
}
|
||||
|
||||
private fun removeThisObjectIfNeeded(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
||||
private fun removeThisObjectForContinuation(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
|
||||
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
|
||||
}
|
||||
|
||||
ExistingVariable.find(children, "this")?.remove()
|
||||
variable.remapName(getThisName(thisLabel))
|
||||
}
|
||||
|
||||
// Very Dirty Work-around.
|
||||
// Hopefully, there will be an API for that in 2019.1.
|
||||
private class ExistingVariable(
|
||||
private class ExistingInstanceThis(
|
||||
private val children: XValueChildrenList,
|
||||
private val index: Int,
|
||||
private val name: String,
|
||||
private val value: XValue,
|
||||
private val size: Int
|
||||
) {
|
||||
companion object {
|
||||
fun find(children: XValueChildrenList, name: String): ExistingVariable? {
|
||||
private const val THIS_NAME = "this"
|
||||
|
||||
fun find(children: XValueChildrenList): ExistingInstanceThis? {
|
||||
val size = children.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
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (valueDescriptor !is ThisDescriptorImpl) {
|
||||
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() {
|
||||
val (names, values) = getLists() ?: return
|
||||
names.removeAt(index)
|
||||
@@ -150,11 +181,18 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
||||
}
|
||||
}
|
||||
|
||||
return allVisibleVariables.asSequence()
|
||||
val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
|
||||
.filter { !isHidden(it) }
|
||||
.map { remapVariableName(it) }
|
||||
.distinctBy { it.name() }
|
||||
.toList()
|
||||
.partition { it.name() == AsmUtil.THIS || it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER) }
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -165,13 +203,25 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
||||
|| name == CONTINUATION_PARAMETER_NAME.asString()
|
||||
}
|
||||
|
||||
private fun remapVariableName(variable: LocalVariableProxyImpl) = with(variable) {
|
||||
when {
|
||||
private fun LocalVariableProxyImpl.remapThisName(): LocalVariableProxyImpl {
|
||||
return when {
|
||||
isLabeledThisReference() -> {
|
||||
val label = variable.name().drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
||||
clone(withName = "$THIS_NAME (@$label)")
|
||||
val label = this@remapThisName.name().drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
||||
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 {
|
||||
return object : LocalVariableProxyImpl(frame, variable) {
|
||||
override fun name() = withName
|
||||
}
|
||||
private interface ThisLocalVariable {
|
||||
val label: String?
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -299,7 +299,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
|
||||
if (descriptor is DefaultNodeDescriptor) 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
|
||||
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");
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testSuspendContinuation() throws Exception {
|
||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/suspendContinuation.kt");
|
||||
|
||||
Reference in New Issue
Block a user