From 2edd245ddadb6b66466b05443f10e25bfc662ddd Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 5 Jul 2011 16:13:47 +0200 Subject: [PATCH] implement folding for docstrings and "collapse by default | method bodies" option (PY-2068) --- .../python/PythonFoldingBuilder.java | 92 +++++++++++++++---- python/testData/folding/docString.py | 7 ++ .../com/jetbrains/python/PyFoldingTest.java | 4 + 3 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 python/testData/folding/docString.py diff --git a/python/src/com/jetbrains/python/PythonFoldingBuilder.java b/python/src/com/jetbrains/python/PythonFoldingBuilder.java index ada510dc311a..9b041fbc9912 100644 --- a/python/src/com/jetbrains/python/PythonFoldingBuilder.java +++ b/python/src/com/jetbrains/python/PythonFoldingBuilder.java @@ -7,10 +7,14 @@ import com.intellij.lang.folding.FoldingDescriptor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.LineTokenizer; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import com.jetbrains.python.psi.PyFileElementType; +import com.jetbrains.python.psi.PyStringLiteralExpression; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -49,25 +53,10 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { } } else if (node.getElementType() == PyElementTypes.STATEMENT_LIST) { - IElementType elType = node.getTreeParent().getElementType(); - if (elType == PyElementTypes.FUNCTION_DECLARATION || elType == PyElementTypes.CLASS_DECLARATION) { - ASTNode colon = node.getTreeParent().findChildByType(PyTokenTypes.COLON); - if (colon != null && colon.getStartOffset() + 1 < node.getTextRange().getEndOffset() - 1) { - final CharSequence chars = node.getChars(); - int nodeStart = node.getTextRange().getStartOffset(); - int endOffset = node.getTextRange().getEndOffset(); - while(endOffset > colon.getStartOffset()+2 && endOffset > nodeStart && Character.isWhitespace(chars.charAt(endOffset - nodeStart - 1))) { - endOffset--; - } - descriptors.add(new FoldingDescriptor(node, new TextRange(colon.getStartOffset() + 1, endOffset))); - } - else { - TextRange range = node.getTextRange(); - if (range.getStartOffset() < range.getEndOffset() - 1) { // only for ranges at least 1 char wide - descriptors.add(new FoldingDescriptor(node, range)); - } - } - } + foldStatementList(node, descriptors); + } + else if (node.getElementType() == PyElementTypes.STRING_LITERAL_EXPRESSION) { + foldDocString(node, descriptors); } ASTNode child = node.getFirstChildNode(); @@ -77,6 +66,53 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { } } + private static void foldStatementList(ASTNode node, List descriptors) { + IElementType elType = node.getTreeParent().getElementType(); + if (elType == PyElementTypes.FUNCTION_DECLARATION || elType == PyElementTypes.CLASS_DECLARATION) { + ASTNode colon = node.getTreeParent().findChildByType(PyTokenTypes.COLON); + if (colon != null && colon.getStartOffset() + 1 < node.getTextRange().getEndOffset() - 1) { + final CharSequence chars = node.getChars(); + int nodeStart = node.getTextRange().getStartOffset(); + int endOffset = node.getTextRange().getEndOffset(); + while(endOffset > colon.getStartOffset()+2 && endOffset > nodeStart && Character.isWhitespace(chars.charAt(endOffset - nodeStart - 1))) { + endOffset--; + } + descriptors.add(new FoldingDescriptor(node, new TextRange(colon.getStartOffset() + 1, endOffset))); + } + else { + TextRange range = node.getTextRange(); + if (range.getStartOffset() < range.getEndOffset() - 1) { // only for ranges at least 1 char wide + descriptors.add(new FoldingDescriptor(node, range)); + } + } + } + } + + private static void foldDocString(ASTNode node, List descriptors) { + if (getDocStringOwnerType(node) != null && StringUtil.countChars(node.getText(), '\n') > 1) { + descriptors.add(new FoldingDescriptor(node, node.getTextRange())); + } + } + + @Nullable + private static IElementType getDocStringOwnerType(ASTNode node) { + final ASTNode treeParent = node.getTreeParent(); + IElementType parentType = treeParent.getElementType(); + if (parentType == PyElementTypes.EXPRESSION_STATEMENT && treeParent.getTreeParent() != null) { + final ASTNode parent2 = treeParent.getTreeParent(); + if (parent2.getElementType() == PyElementTypes.STATEMENT_LIST && parent2.getTreeParent() != null && treeParent == parent2.getFirstChildNode()) { + final ASTNode parent3 = parent2.getTreeParent(); + if (parent3.getElementType() == PyElementTypes.FUNCTION_DECLARATION || parent3.getElementType() == PyElementTypes.CLASS_DECLARATION) { + return parent3.getElementType(); + } + } + else if (parent2.getElementType() instanceof PyFileElementType) { + return parent2.getElementType(); + } + } + return null; + } + private static boolean isImport(ASTNode node, boolean orWhitespace) { if (node == null) return false; IElementType elementType = node.getElementType(); @@ -90,6 +126,14 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { if (isImport(node, false)) { return "import ..."; } + if (node.getElementType() == PyElementTypes.STRING_LITERAL_EXPRESSION) { + final String stringValue = ((PyStringLiteralExpression)node.getPsi()).getStringValue().trim(); + final String[] lines = LineTokenizer.tokenize(stringValue, true); + if (lines.length > 2 && lines[1].trim().length() == 0) { + return "\"\"\"" + lines [0].trim() + "...\"\"\""; + } + return "\"\"\"...\"\"\""; + } return "..."; } @@ -97,6 +141,16 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { if (isImport(node, false)) { return CodeFoldingSettings.getInstance().COLLAPSE_IMPORTS; } + if (node.getElementType() == PyElementTypes.STRING_LITERAL_EXPRESSION) { + if (getDocStringOwnerType(node) == PyElementTypes.FUNCTION_DECLARATION && CodeFoldingSettings.getInstance().COLLAPSE_METHODS) { + // method will be collapsed, no need to also collapse docstring + return false; + } + return CodeFoldingSettings.getInstance().COLLAPSE_DOC_COMMENTS; + } + if (node.getElementType() == PyElementTypes.STATEMENT_LIST && node.getTreeParent().getElementType() == PyElementTypes.FUNCTION_DECLARATION) { + return CodeFoldingSettings.getInstance().COLLAPSE_METHODS; + } return false; } } diff --git a/python/testData/folding/docString.py b/python/testData/folding/docString.py new file mode 100644 index 000000000000..05cd8ca5e9c2 --- /dev/null +++ b/python/testData/folding/docString.py @@ -0,0 +1,7 @@ +def foo(): + """ + This is a docstring. + + It spans several lines. + """: + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyFoldingTest.java b/python/testSrc/com/jetbrains/python/PyFoldingTest.java index 3d58fe97a467..fd7d2cab82e7 100644 --- a/python/testSrc/com/jetbrains/python/PyFoldingTest.java +++ b/python/testSrc/com/jetbrains/python/PyFoldingTest.java @@ -13,4 +13,8 @@ public class PyFoldingTest extends PyLightFixtureTestCase { public void testClassTrailingSpace() { // PY-2544 doTest(); } + + public void testDocString() { + doTest(); + } }