Skip to content

Instantly share code, notes, and snippets.

@dkandalov
Last active March 24, 2022 22:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dkandalov/708664109a37c3c0ff15 to your computer and use it in GitHub Desktop.
Save dkandalov/708664109a37c3c0ff15 to your computer and use it in GitHub Desktop.
Mini-plugin to create simplified view on java code (can be executed using this plugin https://github.com/dkandalov/live-plugin)
import com.intellij.codeInsight.folding.impl.EditorFoldingInfo
import com.intellij.codeInsight.folding.impl.FoldingUtil
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.FoldRegion
import com.intellij.openapi.editor.ex.FoldingModelEx
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.javadoc.PsiDocComment
import static com.intellij.util.containers.CollectionFactory.createWeakMap
import static liveplugin.PluginUtil.*
// depends-on-plugin com.intellij.java
registerAction("javaCodeCollapse", "ctrl alt shift 0") { AnActionEvent event ->
changeGlobalVar("javaCodeCollapseState") { Map state ->
if (state == null) {
state = [
originalFoldingByEditor: createWeakMap(),
foldingByEditor: createWeakMap()
]
}
def editor = currentEditorIn(event.project)
if (!state.foldingByEditor.containsKey(editor)) {
def result = foldCurrentFileIn(event.project)
state.foldingByEditor.put(editor, result.newFolding)
state.originalFoldingByEditor.put(editor, result.removedFolding)
} else {
unfoldCurrentFileIn(event.project, state.foldingByEditor[editor], state.originalFoldingByEditor[editor])
state.foldingByEditor.remove(editor)
state.originalFoldingByEditor.remove(editor)
}
state
}
}
Map unfoldCurrentFileIn(Project project, List newFoldings, List originalFoldings) {
def editor = currentEditorIn(project)
editor.foldingModel.runBatchFoldingOperation(new Runnable() {
@Override void run() {
newFoldings.each{ editor.foldingModel.removeFoldRegion(it) }
originalFoldings.each{ editor.foldingModel.addFoldRegion(it) }
}
})
}
Map foldCurrentFileIn(Project project) {
def editor = currentEditorIn(project)
def psiFile = currentPsiFileIn(project)
def parameterOffsets = []
def parameterWithSpaceOffsets = []
def localVarOffsets = []
def methodOffsets = []
def staticMethodExprOffsets = []
def staticFieldExprOffsets = []
def tokenOffsets = []
def docOffsets = []
def commentOffsets = []
def testMethodNames = []
psiFile.accept(new JavaRecursiveElementVisitor() {
@Override void visitParameter(PsiParameter parameter) {
super.visitParameter(parameter)
def parentChildren = parameter.parent.children
def i = parentChildren.findIndexOf{ it == parameter } - 1
if (i >= 0 && parentChildren[i] instanceof PsiWhiteSpace) {
parameterWithSpaceOffsets.add([
from: parentChildren[i].node.startOffset,
to: parameter.nameIdentifier.textOffset
])
} else {
parameterOffsets.add([
from: parameter.node.startOffset,
to: parameter.nameIdentifier.textOffset
])
}
}
@Override void visitLocalVariable(PsiLocalVariable variable) {
super.visitLocalVariable(variable)
localVarOffsets.add([
from: variable.modifierList.node.startOffset,
to: variable.nameIdentifier.node.startOffset
])
}
@Override void visitMethod(PsiMethod method) {
super.visitMethod(method)
methodOffsets.add([
from: method.modifierList.node.startOffset,
to: method.nameIdentifier.node.startOffset
])
// TODO collapsing "throw list" can hide whitespace before curly bracket (e.g. "foo(...){")
methodOffsets.add([
from: method.throwsList.node.startOffset,
to: (method.body != null ?
method.body.node.startOffset :
method.throwsList.node.startOffset + method.throwsList.node.textLength)
])
if (method.modifierList.annotations.any{ it.qualifiedName == "org.junit.Test" }) {
testMethodNames.add([
from: method.nameIdentifier.node.startOffset,
to: method.nameIdentifier.node.startOffset + method.nameIdentifier.node.textLength,
name: method.name
])
}
}
@Override void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression)
def ref = expression.resolve()
def id = expression.children.find { it instanceof PsiIdentifier }
if (ref instanceof PsiMethod && ref.modifierList.hasModifierProperty(PsiModifier.STATIC) &&
id != null && expression.parent instanceof PsiMethodCallExpression) {
staticMethodExprOffsets.add([
from: expression.node.startOffset,
to: id.node.startOffset
])
}
if (ref instanceof PsiField && ref.modifierList.hasModifierProperty(PsiModifier.STATIC)) {
staticFieldExprOffsets.add([
from: expression.node.startOffset,
to: id.node.startOffset
])
}
}
@Override void visitJavaToken(PsiJavaToken token) {
super.visitJavaToken(token)
// hide all semicolons except for those in "for" statements
if (token.tokenType == JavaTokenType.SEMICOLON &&
!(token?.parent instanceof PsiForStatement) &&
!(token?.parent?.parent?.parent instanceof PsiForStatement)) {
tokenOffsets.add([
from: token.textOffset,
to: token.textOffset + token.textLength,
])
}
}
@Override void visitDocComment(PsiDocComment comment) {
super.visitDocComment(comment)
docOffsets.add([
from: comment.node.startOffset,
to: comment.node.startOffset + comment.node.textLength,
])
}
@Override void visitComment(PsiComment comment) {
super.visitComment(comment)
// this is an attempt to make collapsed comments leave no blank lines
// (looking back as in the code below might cause several foldings to be created
// for what could be one folding block, seems to work though)
def parentChildren = comment.parent.children
def firstElement = comment
def i = parentChildren.findIndexOf{ it == comment } - 1
while (i >= 0 &&
(parentChildren[i] instanceof PsiWhiteSpace ||
parentChildren[i] instanceof PsiComment)) {
firstElement = parentChildren[i]
i--
}
commentOffsets.add([
from: firstElement.textOffset,
to: comment.textOffset + comment.textLength,
])
}
})
def foldResults = []
editor.foldingModel.runBatchFoldingOperation(new Runnable() {
@Override void run() {
parameterWithSpaceOffsets.each {
foldResults << foldText(it.from, it.to, " ", editor)
}
testMethodNames.each {
def methodName = asSentenceWithSpaces(it.name)
foldResults << foldText(it.from, it.to, methodName, editor)
}
(parameterOffsets + localVarOffsets + methodOffsets + tokenOffsets + docOffsets +
commentOffsets + staticMethodExprOffsets + staticFieldExprOffsets).each {
foldResults << foldText(it.from, it.to, "", editor)
}
}
})
foldResults.inject([newFolding: [], oldFolding: []]) { Map map, entry ->
if (entry.added != null) map.newFolding << entry.added
if (entry.removed != null) map.oldFolding << entry.removed
map
}
}
static String asSentenceWithSpaces(String s) {
s.chars.toList().collect{ char c ->
if (Character.isUpperCase(c)) " " + Character.toLowerCase(c)
else if (c == '_') " "
else c
}.join("")
}
/**
* Based on com.intellij.codeInsight.folding.impl.CollapseSelectionHandler
*/
static Map foldText(int start, int end, String placeHolderText, Editor editor) {
if (start + 1 > end) return [:]
if (start < end && editor.document.charsSequence.charAt(end - 1) == '\n') end--
FoldRegion oldRegion = FoldingUtil.findFoldRegion(editor, start, end)
if (oldRegion != null) {
EditorFoldingInfo info = EditorFoldingInfo.get(editor)
editor.foldingModel.removeFoldRegion(oldRegion)
info.removeRegion(oldRegion)
}
FoldRegion region = ((FoldingModelEx)editor.foldingModel).addFoldRegion(start, end, placeHolderText)
if (region == null) return [removed: oldRegion]
region.expanded = false
[removed: oldRegion, added: region]
}
if (!isIdeStartup) show("reloaded")
return
@dkandalov
Copy link
Author

@MikeAndrson thank you! I updated the gist.

The code was out-of-date because PsiMethod was moved out of core IntelliJ API some time ago and is now part of java plugin API which can be added with // depends-on-plugin com.intellij.java.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment