[python] PY-7292 folding for cython

GitOrigin-RevId: aa71655356e64e8375c74bf038ca492d787c8058
This commit is contained in:
Morgan Bartholomew
2025-07-29 22:54:51 +10:00
committed by intellij-monorepo-bot
parent d6d7c0c3f3
commit 0c3f10457b

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python
import com.intellij.codeInsight.folding.CodeFoldingSettings
@@ -9,7 +9,7 @@ import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.FoldingGroup
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.LineTokenizer.Companion.tokenize
import com.intellij.openapi.util.text.LineTokenizer
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
@@ -19,7 +19,7 @@ import com.jetbrains.python.ast.*
import com.jetbrains.python.psi.PyStringLiteralCoreUtil
import kotlin.math.max
class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
open class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
override fun buildLanguageFoldRegions(
descriptors: MutableList<FoldingDescriptor?>,
root: PsiElement,
@@ -38,7 +38,7 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
val prefix = stringLiteralExpression.stringElements[0].prefix
if (stringLiteralExpression.isDocString()) {
val stringValue = stringLiteralExpression.stringValue.trim { it <= ' ' }
val lines = tokenize(stringValue, true)
val lines = LineTokenizer.tokenize(stringValue, true)
if (lines.size > 2 && lines[1].trim { it <= ' ' }.isEmpty()) {
return prefix + "\"\"\"" + lines[0].trim { it <= ' ' } + "...\"\"\""
}
@@ -57,11 +57,12 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
}
val elementType = node.elementType
if (elementType === PyElementTypes.STRING_LITERAL_EXPRESSION) {
if (getDocStringOwnerType(node) === PyElementTypes.FUNCTION_DECLARATION && CodeFoldingSettings.getInstance().COLLAPSE_METHODS) {
val docStringOwnerType = getDocStringOwnerType(node)
if (isFunction(docStringOwnerType) && CodeFoldingSettings.getInstance().COLLAPSE_METHODS) {
// method will be collapsed, no need to also collapse docstring
return false
}
if (getDocStringOwnerType(node) != null) {
if (docStringOwnerType != null) {
return CodeFoldingSettings.getInstance().COLLAPSE_DOC_COMMENTS
}
return PythonFoldingSettings.getInstance().isCollapseLongStrings
@@ -72,12 +73,15 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
if (elementType === PyElementTypes.ANNOTATION) {
return PythonFoldingSettings.getInstance().isCollapseTypeAnnotations
}
if (elementType === PyElementTypes.STATEMENT_LIST && node.treeParent.elementType === PyElementTypes.FUNCTION_DECLARATION) {
if (elementType === PyElementTypes.STATEMENT_LIST && isFunction(node.treeParent.elementType)) {
return CodeFoldingSettings.getInstance().COLLAPSE_METHODS
}
if (elementType in FOLDABLE_COLLECTIONS_LITERALS) {
return PythonFoldingSettings.getInstance().isCollapseLongCollections
}
if (isLanguageSpecificFoldableBlock(elementType)) {
return CodeFoldingSettings.getInstance().COLLAPSE_METHODS
}
return false
}
@@ -89,6 +93,10 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
return node.psi is PyAstFile || node.elementType === PyElementTypes.STATEMENT_LIST
}
protected open fun isLanguageSpecificFoldableBlock(elementType: IElementType): Boolean {
return false
}
private fun appendDescriptors(node: ASTNode, descriptors: MutableList<FoldingDescriptor?>) {
val elementType = node.elementType
if (node.psi is PyAstFile) {
@@ -122,6 +130,13 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
FoldingGroup.newGroup(PYTHON_TYPE_ANNOTATION_GROUP_NAME)))
}
}
else if (isLanguageSpecificFoldableBlock(elementType)) {
val nodeRange = node.textRange
if (!nodeRange.isEmpty) {
val colon = node.findChildByType(PyTokenTypes.COLON)
foldSegment(node, descriptors, nodeRange, colon)
}
}
var child = node.firstChildNode
while (child != null) {
appendDescriptors(child, descriptors)
@@ -212,18 +227,20 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
return
}
val elType = node.treeParent.elementType
if (elType === PyElementTypes.FUNCTION_DECLARATION || elType === PyElementTypes.CLASS_DECLARATION || checkFoldBlocks(node, elType)) {
val parentType = node.treeParent.elementType
if (isFunction(parentType) || isClass(parentType) || checkFoldBlocks(node, parentType)) {
val colon = node.treeParent.findChildByType(PyTokenTypes.COLON)
foldSegment(node, descriptors, nodeRange, colon)
}
}
private fun checkFoldBlocks(statementList: ASTNode, parentType: IElementType): Boolean {
protected open fun checkFoldBlocks(statementList: ASTNode, parentType: IElementType): Boolean {
val element = statementList.psi
assert(element is PyAstStatementList)
return parentType in PyElementTypes.PARTS || parentType === PyElementTypes.WITH_STATEMENT || parentType === PyElementTypes.CASE_CLAUSE
return parentType in PyElementTypes.PARTS ||
parentType === PyElementTypes.WITH_STATEMENT ||
parentType === PyElementTypes.CASE_CLAUSE
}
private fun foldLongStrings(node: ASTNode, descriptors: MutableList<FoldingDescriptor?>) {
@@ -235,15 +252,15 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
}
}
private fun getDocStringOwnerType(node: ASTNode): IElementType? {
protected open fun getDocStringOwnerType(node: ASTNode): IElementType? {
val treeParent = node.treeParent
val parentType = treeParent.elementType
if (parentType === PyElementTypes.EXPRESSION_STATEMENT && treeParent.treeParent != null) {
val parent2 = treeParent.treeParent
if (parent2.elementType === PyElementTypes.STATEMENT_LIST && parent2.treeParent != null && treeParent === parent2.firstChildNode) {
val parent3 = parent2.treeParent
if (parent3.elementType === PyElementTypes.FUNCTION_DECLARATION || parent3.elementType === PyElementTypes.CLASS_DECLARATION) {
return parent3.elementType
val parent3 = parent2.treeParent.elementType
if (isFunction(parent3) || isClass(parent3)) {
return parent3
}
}
else if (parent2 is PyAstFile) {
@@ -262,10 +279,18 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
return "..."
}
private fun isImport(node: ASTNode): Boolean {
protected open fun isImport(node: ASTNode): Boolean {
return node.elementType in PyElementTypes.IMPORT_STATEMENTS
}
protected open fun isFunction(elementType: IElementType?): Boolean {
return elementType === PyElementTypes.FUNCTION_DECLARATION
}
protected open fun isClass(elementType: IElementType?): Boolean {
return elementType === PyElementTypes.CLASS_DECLARATION
}
companion object {
val FOLDABLE_COLLECTIONS_LITERALS: TokenSet = TokenSet.create(
PyElementTypes.SET_LITERAL_EXPRESSION,
@@ -279,4 +304,4 @@ class PythonFoldingBuilder : CustomFoldingBuilder(), DumbAware {
const val PYTHON_TYPE_ANNOTATION_GROUP_NAME: String = "Python type annotation"
}
}
}