Support code folding for standard library collection factory functions (KT-5206)
#KT-5206 Fixed
This commit is contained in:
committed by
Nikolay Krasko
parent
c4cdc0543d
commit
3a83983bfc
@@ -22,21 +22,70 @@ import com.intellij.lang.folding.CustomFoldingBuilder
|
|||||||
import com.intellij.lang.folding.FoldingDescriptor
|
import com.intellij.lang.folding.FoldingDescriptor
|
||||||
import com.intellij.openapi.editor.Document
|
import com.intellij.openapi.editor.Document
|
||||||
import com.intellij.openapi.project.DumbAware
|
import com.intellij.openapi.project.DumbAware
|
||||||
|
import com.intellij.openapi.project.DumbService
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiWhiteSpace
|
import com.intellij.psi.PsiWhiteSpace
|
||||||
import org.jetbrains.kotlin.KtNodeTypes
|
import org.jetbrains.kotlin.KtNodeTypes
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
|
import org.jetbrains.kotlin.idea.references.KtReference
|
||||||
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
|
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtImportList
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
||||||
|
|
||||||
class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware {
|
class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware {
|
||||||
|
|
||||||
|
private val collectionFactoryFunctions: Set<FqName> =
|
||||||
|
setOf(
|
||||||
|
FqName("kotlin.arrayOf"),
|
||||||
|
FqName("kotlin.booleanArrayOf"),
|
||||||
|
FqName("kotlin.byteArrayOf"),
|
||||||
|
FqName("kotlin.charArrayOf"),
|
||||||
|
FqName("kotlin.doubleArrayOf"),
|
||||||
|
FqName("kotlin.floatArrayOf"),
|
||||||
|
FqName("kotlin.intArrayOf"),
|
||||||
|
FqName("kotlin.longArrayOf"),
|
||||||
|
FqName("kotlin.shortArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.arrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.booleanArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.byteArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.charArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.doubleArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.floatArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.intArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.longArrayOf"),
|
||||||
|
FqName("kotlin.kotlin_builtins.shortArrayOf"),
|
||||||
|
FqName("kotlin.collections.arrayListOf"),
|
||||||
|
FqName("kotlin.collections.hashMapOf"),
|
||||||
|
FqName("kotlin.collections.hashSetOf"),
|
||||||
|
FqName("kotlin.collections.linkedMapOf"),
|
||||||
|
FqName("kotlin.collections.linkedSetOf"),
|
||||||
|
FqName("kotlin.collections.linkedStringMapOf"),
|
||||||
|
FqName("kotlin.collections.linkedStringSetOf"),
|
||||||
|
FqName("kotlin.collections.listOf"),
|
||||||
|
FqName("kotlin.collections.listOfNotNull"),
|
||||||
|
FqName("kotlin.collections.mapOf"),
|
||||||
|
FqName("kotlin.collections.mutableListOf"),
|
||||||
|
FqName("kotlin.collections.mutableMapOf"),
|
||||||
|
FqName("kotlin.collections.mutableSetOf"),
|
||||||
|
FqName("kotlin.collections.setOf"),
|
||||||
|
FqName("kotlin.collections.sortedMapOf"),
|
||||||
|
FqName("kotlin.collections.sortedSetOf"),
|
||||||
|
FqName("kotlin.collections.stringMapOf"),
|
||||||
|
FqName("kotlin.collections.stringSetOf")
|
||||||
|
)
|
||||||
|
|
||||||
override fun buildLanguageFoldRegions(
|
override fun buildLanguageFoldRegions(
|
||||||
descriptors: MutableList<FoldingDescriptor>,
|
descriptors: MutableList<FoldingDescriptor>,
|
||||||
root: PsiElement, document: Document, quick: Boolean
|
root: PsiElement, document: Document, quick: Boolean
|
||||||
@@ -81,10 +130,42 @@ class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware {
|
|||||||
private fun needFolding(node: ASTNode): Boolean {
|
private fun needFolding(node: ASTNode): Boolean {
|
||||||
val type = node.elementType
|
val type = node.elementType
|
||||||
val parentType = node.treeParent?.elementType
|
val parentType = node.treeParent?.elementType
|
||||||
|
|
||||||
return type == KtNodeTypes.FUNCTION_LITERAL ||
|
return type == KtNodeTypes.FUNCTION_LITERAL ||
|
||||||
(type == KtNodeTypes.BLOCK && parentType != KtNodeTypes.FUNCTION_LITERAL) ||
|
(type == KtNodeTypes.BLOCK && parentType != KtNodeTypes.FUNCTION_LITERAL) ||
|
||||||
type == KtNodeTypes.CLASS_BODY || type == KtTokens.BLOCK_COMMENT || type == KDocTokens.KDOC ||
|
type == KtNodeTypes.CLASS_BODY || type == KtTokens.BLOCK_COMMENT || type == KDocTokens.KDOC ||
|
||||||
type == KtNodeTypes.STRING_TEMPLATE || type == KtNodeTypes.PRIMARY_CONSTRUCTOR
|
type == KtNodeTypes.STRING_TEMPLATE || type == KtNodeTypes.PRIMARY_CONSTRUCTOR ||
|
||||||
|
node.shouldFoldCollection()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ASTNode.shouldFoldCollection(): Boolean = with((psi as? KtCallExpression)?.referenceExpression()) {
|
||||||
|
if (this == null || DumbService.isDumb(project)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KtReference.targets(bindingContext: BindingContext): Collection<DeclarationDescriptor> =
|
||||||
|
bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) }
|
||||||
|
?: resolveToDescriptors(bindingContext)
|
||||||
|
|
||||||
|
for (reference in references) {
|
||||||
|
if (reference !is KtReference) continue
|
||||||
|
|
||||||
|
val names = reference.resolvesByNames
|
||||||
|
val bindingContext = analyze()
|
||||||
|
val targets = reference.targets(bindingContext)
|
||||||
|
|
||||||
|
for (target in targets) {
|
||||||
|
val importableDescriptor = target.getImportableDescriptor()
|
||||||
|
if (importableDescriptor.name !in names) continue // resolved via alias
|
||||||
|
|
||||||
|
val importableFqName = target.importableFqName ?: continue
|
||||||
|
if (target !is PackageViewDescriptor && importableFqName in collectionFactoryFunctions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRangeToFold(node: ASTNode): TextRange {
|
private fun getRangeToFold(node: ASTNode): TextRange {
|
||||||
@@ -96,6 +177,16 @@ class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware {
|
|||||||
return TextRange(lbrace.startOffset, rbrace.endOffset)
|
return TextRange(lbrace.startOffset, rbrace.endOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.elementType == KtNodeTypes.CALL_EXPRESSION) {
|
||||||
|
val valueArgumentList = (node.psi as? KtCallExpression)?.valueArgumentList
|
||||||
|
val leftParenthesis = valueArgumentList?.leftParenthesis
|
||||||
|
val rightParenthesis = valueArgumentList?.rightParenthesis
|
||||||
|
if (leftParenthesis != null && rightParenthesis != null) {
|
||||||
|
return TextRange(leftParenthesis.startOffset, rightParenthesis.endOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return node.textRange
|
return node.textRange
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +194,7 @@ class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware {
|
|||||||
node.elementType == KtTokens.BLOCK_COMMENT -> "/${getFirstLineOfComment(node)}.../"
|
node.elementType == KtTokens.BLOCK_COMMENT -> "/${getFirstLineOfComment(node)}.../"
|
||||||
node.elementType == KDocTokens.KDOC -> "/**${getFirstLineOfComment(node)}...*/"
|
node.elementType == KDocTokens.KDOC -> "/**${getFirstLineOfComment(node)}...*/"
|
||||||
node.elementType == KtNodeTypes.STRING_TEMPLATE -> "\"\"\"${getTrimmedFirstLineOfString(node).addSpaceIfNeeded()}...\"\"\""
|
node.elementType == KtNodeTypes.STRING_TEMPLATE -> "\"\"\"${getTrimmedFirstLineOfString(node).addSpaceIfNeeded()}...\"\"\""
|
||||||
node.elementType == KtNodeTypes.PRIMARY_CONSTRUCTOR -> "(...)"
|
node.elementType == KtNodeTypes.PRIMARY_CONSTRUCTOR || node.elementType == KtNodeTypes.CALL_EXPRESSION -> "(...)"
|
||||||
node.psi is KtImportList -> "..."
|
node.psi is KtImportList -> "..."
|
||||||
else -> "{...}"
|
else -> "{...}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
val array = arrayOf <fold text='(...)' expand='true'>(
|
||||||
|
11 to 0,
|
||||||
|
12 to 1,
|
||||||
|
13 to 3,
|
||||||
|
14 to 4,
|
||||||
|
15 to 5,
|
||||||
|
16 to 6,
|
||||||
|
17 to 7,
|
||||||
|
18 to 8,
|
||||||
|
19 to 9,
|
||||||
|
20 to 10
|
||||||
|
)</fold>
|
||||||
@@ -84,6 +84,11 @@ public class KotlinFoldingTestGenerated extends AbstractKotlinFoldingTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/folding/checkCollapse"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/folding/checkCollapse"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("collectionFactoryFunctions.kt")
|
||||||
|
public void testCollectionFactoryFunctions() throws Exception {
|
||||||
|
runTest("idea/testData/folding/checkCollapse/collectionFactoryFunctions.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("customRegions.kt")
|
@TestMetadata("customRegions.kt")
|
||||||
public void testCustomRegions() throws Exception {
|
public void testCustomRegions() throws Exception {
|
||||||
runTest("idea/testData/folding/checkCollapse/customRegions.kt");
|
runTest("idea/testData/folding/checkCollapse/customRegions.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user