Last active
January 15, 2025 11:51
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
@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
This seems to throw a
java.lang.ClassNotFoundException: com.intellij.psi.PsiMethod
with Android Studio Bumblebee | 2021.1.1 Patch 2.