IDEA-CR-1488: Extend and improve API of PinToTop feature.

Type names were replaced with more flexible abstract tags.
Now a member can provide parent tag itself which will be used instead of traversing to parent node.
Members can provide custom name to use it instead of node name.
Members can provide their pinned status itself.
All checking logic (canBePinned, isPinned, getPinInfo) were extracted to a separate file.

(cherry picked from commit 85b5c3796d3a61701bcf890645f4ed6b475e62cc)

GitOrigin-RevId: 1f370966512f49c57ecf2332016712dffcfd0d64
This commit is contained in:
Artem Bukhonov
2020-06-03 00:20:37 +03:00
committed by intellij-monorepo-bot
parent f08d71239d
commit 5b2efaaa08
7 changed files with 109 additions and 63 deletions
@@ -6,4 +6,27 @@ import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
interface PinToTopMemberValue : PinToTopValue {
fun canBePinned() : Boolean
/**
* When not null the name will be used as member name in PinToTop instead of node name. May be useful in a case when
* a value name presentation differs from its real name
*/
@JvmDefault
val customMemberName: String?
get() = null
/**
* When not null this tag will be used instead of getting tag from parent node
*/
@JvmDefault
val customParentTag: String?
get() = null
/**
* When not null the value will be used as 'pinned' status instead of checking the status inside [XDebuggerPinToTopManager] maps.
* It may be useful if you want to implement pinning logic inside your values by listening [XDebuggerPinToTopListener]
*/
@JvmDefault
val isPinned: Boolean?
get() = null
}
@@ -5,5 +5,17 @@ import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
interface PinToTopParentValue : PinToTopValue {
fun getTypeName() : String?
@ApiStatus.ScheduledForRemoval(inVersion = "2021.1")
@Deprecated("Implement property 'tag' instead", ReplaceWith("tag"))
fun getTypeName() : String? = null
/**
* Tag used as identifier of a parent node. Pinned status is determined by checking of presence a 'tag - member name' pair
* in [XDebuggerPinToTopManager]
*/
@Suppress("DEPRECATION")
@JvmDefault
val tag: String?
get() = getTypeName()
}
@@ -0,0 +1,43 @@
package com.intellij.xdebugger.impl.pinned.items
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
fun XValueNodeImpl?.isPinned(pinToTopManager: XDebuggerPinToTopManager): Boolean {
val container = this?.valueContainer
if (container is PinToTopMemberValue) {
val pinned = container.isPinned
if (pinned != null)
return pinned
val pinInfo = this.getPinInfo()
if (pinInfo != null)
return pinToTopManager.isPinned(pinInfo)
return false
}
return false
}
fun XValueNodeImpl?.canBePinned(): Boolean {
val container = this?.valueContainer
if (container is PinToTopMemberValue) {
return container.canBePinned() && this.getPinInfo() != null
}
return false
}
fun XValueNodeImpl?.getPinInfo() : PinnedItemInfo? {
val container = this?.valueContainer
if (container is PinToTopMemberValue) {
val parentTag = container.customParentTag ?: parentPinToTopValue?.tag
val memberName = container.customMemberName ?: this?.name
if (parentTag != null && memberName != null)
return PinnedItemInfo(parentTag, memberName)
return null
}
return null
}
val XValueNodeImpl?.parentPinToTopValue: PinToTopParentValue? get() {
return ((this?.parent as? XValueContainerNode<*>)?.valueContainer as? PinToTopParentValue)
}
@@ -4,15 +4,15 @@ package com.intellij.xdebugger.impl.pinned.items
import com.intellij.util.xmlb.annotations.Attribute
data class PinnedItemInfo(
@Attribute("typeName") var typeName: String,
@Attribute("fieldName") var fieldName: String) {
@Attribute("parentTag") var parentTag: String,
@Attribute("memberName") var memberName: String) {
companion object {
fun getKey(typeName: String, fieldName: String) = "$typeName:$fieldName"
fun getKey(parentTag: String, memberName: String) = "$parentTag:$memberName"
}
@Suppress("unused")
constructor() : this("", "")
fun getKey() = getKey(typeName, fieldName)
fun getKey() = getKey(parentTag, memberName)
}
@@ -26,7 +26,7 @@ open class XDebuggerPinToTopManager {
private var myNodeHoverLifetime : Disposable? = null
private var myActiveNode: XDebuggerTreeNode? = null
private var myPinnedMembers = HashMap<String, PinnedItemInfo>()
private var myPinnedMembers = HashSet<PinnedItemInfo>()
private val myPinToTopIconAlarm = Alarm()
val pinToTopComparator : Comparator<XValueNodeImpl> = Comparator.comparing<XValueNodeImpl, Boolean> { !isItemPinned(it) }
@@ -51,12 +51,7 @@ open class XDebuggerPinToTopManager {
}
val valueNode = node as? XValueNodeImpl ?: return
val pinnedValue = node.valueContainer as? PinToTopMemberValue ?: return
if ((valueNode.parent as? XValueNodeImpl)?.valueContainer !is PinToTopParentValue) {
return
}
if (!pinnedValue.canBePinned() || isItemPinned(node)) {
if (!valueNode.canBePinned() || node.isPinned(this)) {
return
}
var oldIcon = valueNode.icon
@@ -88,29 +83,26 @@ open class XDebuggerPinToTopManager {
myListeners.remove(listener)
}
fun getPinnedItemInfos() = myPinnedMembers.values.toList()
fun getPinnedItemInfos() = myPinnedMembers.toList()
fun addItemInfo(typeName: String, fieldName: String) {
val info = PinnedItemInfo(typeName, fieldName)
myPinnedMembers[info.getKey()] = info
fun addItemInfo(info: PinnedItemInfo) {
myPinnedMembers.add(info)
for (listener in myListeners) {
listener.onPinnedItemAdded(info)
}
}
fun removeItemInfo(typeName: String, fieldName: String) {
val key = PinnedItemInfo.getKey(typeName, fieldName)
val info = myPinnedMembers[key] ?: return
myPinnedMembers.remove(key)
fun removeItemInfo(info: PinnedItemInfo) {
myPinnedMembers.remove(info)
for (listener in myListeners) {
listener.onPinnedItemRemoved(info)
}
}
fun isItemPinned(node: XValueNodeImpl?) : Boolean {
val typeName = ((node?.parent as? XValueContainerNode<*>)?.valueContainer as? PinToTopParentValue)?.getTypeName() ?: return false
return myPinnedMembers.containsKey(
PinnedItemInfo.getKey(typeName, node.name ?: ""))
fun isItemPinned(node: XValueNodeImpl?) : Boolean = node.isPinned(this)
fun isPinned(pinnedItemInfo: PinnedItemInfo): Boolean {
return myPinnedMembers.contains(pinnedItemInfo)
}
private fun disposeCurrentNodeHoverSubscription() {
@@ -119,11 +111,11 @@ open class XDebuggerPinToTopManager {
}
fun saveState(state: PinToTopManagerState) {
state.pinnedMembersList = myPinnedMembers.toList().map { it.second }.toMutableList()
state.pinnedMembersList = myPinnedMembers.toMutableList()
}
fun loadState(state: PinToTopManagerState) {
myPinnedMembers.putAll(state.pinnedMembersList.map { Pair(it.getKey(), it) })
myPinnedMembers.addAll(state.pinnedMembersList)
}
fun isPinToTopSupported(node: XDebuggerTreeNode?) : Boolean {
@@ -2,12 +2,9 @@
package com.intellij.xdebugger.impl.pinned.items.actions
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.project.Project
import com.intellij.xdebugger.XDebuggerBundle
import com.intellij.xdebugger.impl.XDebuggerUtilImpl
import com.intellij.xdebugger.impl.pinned.items.PinToTopMemberValue
import com.intellij.xdebugger.impl.pinned.items.PinToTopParentValue
import com.intellij.xdebugger.impl.pinned.items.XDebuggerPinToTopManager
import com.intellij.xdebugger.impl.pinned.items.*
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
@@ -57,7 +54,7 @@ class XDebuggerPinToTopAction : XDebuggerTreeActionBase() {
return
}
presentation.isVisible = true
presentation.isEnabled = (node.parent as? XValueNodeImpl)?.valueContainer is PinToTopParentValue && valueContainer.canBePinned()
presentation.isEnabled = node.canBePinned()
presentation.icon = if (pinToTopManager.isItemPinned(node)) PlatformDebuggerImplIcons.PinToTop.UnpinnedItem else PlatformDebuggerImplIcons.PinToTop.PinnedItem
presentation.text = if (pinToTopManager.isItemPinned(node)) XDebuggerBundle.message("xdebugger.unpin.action") else XDebuggerBundle.message("xdebugger.pin.to.top.action")
@@ -66,30 +63,21 @@ class XDebuggerPinToTopAction : XDebuggerTreeActionBase() {
override fun perform(node: XValueNodeImpl?, nodeName: String, e: AnActionEvent) {
node ?: return
val project = e.project ?: return
val nodeValue = node.valueContainer as? PinToTopMemberValue ?: return
if (!nodeValue.canBePinned()) {
return
}
val parentType = ((node.parent as? XValueNodeImpl)?.valueContainer as? PinToTopParentValue)?.getTypeName()
if (parentType.isNullOrEmpty()) {
if (!node.canBePinned())
return
}
if (XDebuggerPinToTopManager.getInstance(project).isItemPinned(node)) {
removePrioritizedItem(parentType, nodeName, project)
val pinToTopManager = XDebuggerPinToTopManager.getInstance(project)
val pinInfo = node.getPinInfo()
?: return
if (node.isPinned(pinToTopManager)) {
pinToTopManager.removeItemInfo(pinInfo)
} else {
addPrioritizedItem(parentType, nodeName, project)
pinToTopManager.addItemInfo(pinInfo)
}
XDebuggerUtilImpl.rebuildTreeAndViews(node.tree)
}
private fun addPrioritizedItem(parentType: String, nodeName: String, project: Project) {
XDebuggerPinToTopManager.getInstance(project).addItemInfo(parentType, nodeName)
}
private fun removePrioritizedItem(parentType: String, nodeName: String, project: Project) {
XDebuggerPinToTopManager.getInstance(project).removeItemInfo(parentType, nodeName)
}
}
@@ -24,6 +24,7 @@ import com.intellij.xdebugger.impl.frame.XValueWithInlinePresentation;
import com.intellij.xdebugger.impl.frame.XVariablesView;
import com.intellij.xdebugger.impl.pinned.items.PinToTopMemberValue;
import com.intellij.xdebugger.impl.pinned.items.PinToTopParentValue;
import com.intellij.xdebugger.impl.pinned.items.PinToTopUtilKt;
import com.intellij.xdebugger.impl.pinned.items.actions.XDebuggerPinToTopAction;
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
@@ -307,21 +308,8 @@ public class XValueNodeImpl extends XValueContainerNode<XValue> implements XValu
return null;
}
if (!(myValueContainer instanceof PinToTopMemberValue)) {
if (!PinToTopUtilKt.canBePinned(this))
return null;
}
final PinToTopMemberValue pinToTopMemberValue = (PinToTopMemberValue)myValueContainer;
if (!pinToTopMemberValue.canBePinned()) {
return null;
}
if(!(myParent instanceof XValueNodeImpl)) {
return null;
}
if (!(((XValueNodeImpl)myParent).myValueContainer instanceof PinToTopParentValue)) {
return null;
}
return new XDebuggerTreeNodeHyperlink(XDebuggerBundle.message("xdebugger.pin.to.top.action")) {
@Override