implement folding for docstrings and "collapse by default | method bodies" option (PY-2068)

This commit is contained in:
Dmitry Jemerov
2011-07-05 16:17:34 +02:00
parent 6a51d99f8b
commit 2edd245dda
3 changed files with 84 additions and 19 deletions
@@ -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<FoldingDescriptor> 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<FoldingDescriptor> 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;
}
}
+7
View File
@@ -0,0 +1,7 @@
def foo():<fold text='...'>
<fold text='"""This is a docstring...."""'>"""
This is a docstring.
It spans several lines.
"""</fold>:
pass</fold>
@@ -13,4 +13,8 @@ public class PyFoldingTest extends PyLightFixtureTestCase {
public void testClassTrailingSpace() { // PY-2544
doTest();
}
public void testDocString() {
doTest();
}
}