Fold Labeled Statements
Hi there,
Objective: to be able to fold blocks in a Spock test (e.g. given, when, then, etc) for more readability and better code navigation in the IDE
For example, having something like this:
I'd like to be able to make it like this:
I wanted to implement this as a plugin, but it seemed to be an overkill and was hoping if LivePlugin can help with this.
I tried to find something in the repo and docs regarding folding, but couldn't find anything. Does LivePlugin support such functionalities, more specifically FoldingBuilder?
I never developed plugins so might not be super helpful, but happy to contribute in any way I can. Cheers.
Hi,
LivePlugin is just compiling and loading classes into the JVM at runtime. It has a thin wrapper around some of the IDE APIs but fundamentally LivePlugin doesn't restrict you from doing anything (except that some IntelliJ APIs are not necessarily designed to be reloaded at runtime).
Here is an example of FoldingBuilder that I've been using for the last couple years to fold Kotlin code https://gist.github.com/dkandalov/82f37b0d3a6f8b3e4c6f1f2296a63e41
If you want something a bit more basic, here is an older collapse java keywords into symbols action https://gist.github.com/dkandalov/5553999
This is awesome. Thank you. I tried something quick based on what you said:
import com.intellij.lang.ASTNode
import com.intellij.lang.LanguageExtensionPoint
import com.intellij.lang.folding.CustomFoldingBuilder
import com.intellij.lang.folding.FoldingBuilder
import com.intellij.lang.folding.FoldingDescriptor
import com.intellij.lang.folding.LanguageFolding
import com.intellij.openapi.editor.Document
import com.intellij.openapi.extensions.DefaultPluginDescriptor
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.elementType
import com.intellij.util.KeyedLazyInstance
val pluginDescriptor =
DefaultPluginDescriptor(PluginId.getId("LivePlugin"), SpockFoldingBuilder::class.java.classLoader)
val extensionPoint: KeyedLazyInstance<FoldingBuilder> =
LanguageExtensionPoint("Groovy", "Plugin\$SpockFoldingBuilder", pluginDescriptor)
LanguageFolding.EP_NAME.point.registerExtension(extensionPoint, pluginDisposable)
class SpockFoldingBuilder : CustomFoldingBuilder(), DumbAware {
private val labels = setOf("and:", "expect:", "given:", "then:", "when:", "where:")
override fun buildLanguageFoldRegions(
descriptors: MutableList<FoldingDescriptor>,
root: PsiElement,
document: Document,
quick: Boolean,
) {
if (root.elementType?.language?.id != "Groovy") return
addSpockLabelFoldRegions(descriptors, root)
}
private fun addSpockLabelFoldRegions(descriptors: MutableList<FoldingDescriptor>, e: PsiElement) {
for (child in e.children) {
if (child.isSpockLabel())
addSpockLabelFoldRegion(descriptors, child)
addSpockLabelFoldRegions(descriptors, child)
}
}
private fun addSpockLabelFoldRegion(descriptors: MutableList<FoldingDescriptor>, e: PsiElement) {
var next = e.nextSibling
val start = e.textRange.startOffset
var end = e.textRange.endOffset
while (next != null && !(next.isSpockLabel())) {
if (!next.isWhiteSpaceOrNewLine())
end = next.textRange.endOffset
next = next.nextSibling
}
if (end > start) {
val range = TextRange(start, end)
descriptors.add(FoldingDescriptor(e.node, range))
}
}
@Suppress("UnstableApiUsage")
private fun PsiElement.isWhiteSpaceOrNewLine() =
this is PsiWhiteSpace || node.elementType.debugName == "new line"
@Suppress("UnstableApiUsage")
private fun PsiElement.isSpockLabel(): Boolean = this.elementType?.debugName == "LABELED_STATEMENT" &&
labels.stream().anyMatch { text.startsWith(it) }
override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange) = node.text
override fun isRegionCollapsedByDefault(node: ASTNode) = false
}
But unfortunately it doesn't seem to work. I'm not sure what the issue is, but the above works just fine in a proper plugin project. Do you know what could the issue? Or maybe another question, is there a way to debug scripts in LivePlugin? (a break-point or stdout?)
Thanks.
I tried SpockFoldingBuilder above and it worked fine for me.
Maybe for some reason the code editor didn't refresh folding regions 🤷
To debug you can show notifications or print to stdout (which ends up in the IDE log file). For example, see https://github.com/dkandalov/live-plugin/blob/master/plugin-examples/kotlin/hello-world/plugin.kts
Not sure if breakpoints are possible because it will be JVM debugging itself.
You’re right. Now it works. Not sure what happened. Thanks for the support. Great work BTW. 👍