http://ea.jetbrains.com/browser/ea_problems/14879
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / folding / impl / JavaFoldingBuilder.java
blobbdea5103dccd3e4c99ca370014e6d12604f9cb7c
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.folding.impl;
18 import com.intellij.codeInsight.folding.JavaCodeFoldingSettings;
19 import com.intellij.lang.ASTNode;
20 import com.intellij.lang.folding.FoldingBuilderEx;
21 import com.intellij.lang.folding.FoldingDescriptor;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Document;
24 import com.intellij.openapi.editor.FoldingGroup;
25 import com.intellij.openapi.progress.ProgressManager;
26 import com.intellij.openapi.project.DumbAware;
27 import com.intellij.openapi.util.TextRange;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.psi.*;
30 import com.intellij.psi.codeStyle.CodeStyleSettings;
31 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
32 import com.intellij.psi.impl.source.PsiClassReferenceType;
33 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
34 import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod;
35 import com.intellij.psi.javadoc.PsiDocComment;
36 import com.intellij.psi.util.PropertyUtil;
37 import com.intellij.psi.util.PsiTreeUtil;
38 import com.intellij.psi.util.PsiUtil;
39 import com.intellij.util.Function;
40 import com.intellij.util.text.CharArrayUtil;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.List;
49 public class JavaFoldingBuilder extends FoldingBuilderEx implements DumbAware {
50 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.folding.impl.JavaFoldingBuilder");
51 private static final String SMILEY = "<~>";
53 @NotNull
54 public FoldingDescriptor[] buildFoldRegions(@NotNull PsiElement element, @NotNull Document document, boolean quick) {
55 if (!(element instanceof PsiJavaFile)) {
56 return FoldingDescriptor.EMPTY;
58 PsiJavaFile file = (PsiJavaFile) element;
60 List<FoldingDescriptor> result = new ArrayList<FoldingDescriptor>();
61 PsiImportList importList = file.getImportList();
62 if (importList != null) {
63 PsiImportStatementBase[] statements = importList.getAllImportStatements();
64 if (statements.length > 1) {
65 final TextRange rangeToFold = getRangeToFold(importList);
66 if (rangeToFold != null && rangeToFold.getLength() > 1) {
67 result.add(new FoldingDescriptor(importList, rangeToFold));
72 PsiClass[] classes = file.getClasses();
73 for (PsiClass aClass : classes) {
74 ProgressManager.checkCanceled();
75 addElementsToFold(result, aClass, document, true, quick);
78 TextRange range = getFileHeader(file);
79 if (range != null && range.getLength() > 1 && document.getLineNumber(range.getEndOffset()) > document.getLineNumber(range.getStartOffset())) {
80 result.add(new FoldingDescriptor(file, range));
83 return result.toArray(new FoldingDescriptor[result.size()]);
86 private void addElementsToFold(List<FoldingDescriptor> list, PsiClass aClass, Document document, boolean foldJavaDocs, boolean quick) {
87 if (!(aClass.getParent() instanceof PsiJavaFile) || ((PsiJavaFile)aClass.getParent()).getClasses().length > 1) {
88 addToFold(list, aClass, document, true);
91 PsiDocComment docComment;
92 if (foldJavaDocs) {
93 docComment = aClass.getDocComment();
94 if (docComment != null) {
95 addToFold(list, docComment, document, true);
98 addAnnotationsToFold(aClass.getModifierList(), list, document);
100 PsiElement[] children = aClass.getChildren();
101 for (PsiElement child : children) {
102 ProgressManager.checkCanceled();
104 if (child instanceof PsiMethod) {
105 PsiMethod method = (PsiMethod)child;
106 addToFold(list, method, document, true);
107 addAnnotationsToFold(method.getModifierList(), list, document);
109 if (foldJavaDocs) {
110 docComment = method.getDocComment();
111 if (docComment != null) {
112 addToFold(list, docComment, document, true);
116 PsiCodeBlock body = method.getBody();
117 if (body != null) {
118 addCodeBlockFolds(body, list, document, quick);
121 else if (child instanceof PsiField) {
122 PsiField field = (PsiField)child;
123 if (foldJavaDocs) {
124 docComment = field.getDocComment();
125 if (docComment != null) {
126 addToFold(list, docComment, document, true);
129 addAnnotationsToFold(field.getModifierList(), list, document);
130 PsiExpression initializer = field.getInitializer();
131 if (initializer != null) {
132 addCodeBlockFolds(initializer, list, document, quick);
133 } else if (field instanceof PsiEnumConstant) {
134 addCodeBlockFolds(field, list, document, quick);
137 else if (child instanceof PsiClassInitializer) {
138 PsiClassInitializer initializer = (PsiClassInitializer)child;
139 addToFold(list, initializer, document, true);
140 addCodeBlockFolds(initializer, list, document, quick);
142 else if (child instanceof PsiClass) {
143 addElementsToFold(list, (PsiClass)child, document, true, quick);
148 @NotNull
149 public String getPlaceholderText(@NotNull final ASTNode node) {
150 return getPlaceholderText(SourceTreeToPsiMap.treeElementToPsi(node));
153 private static String getPlaceholderText(PsiElement element) {
154 if (element instanceof PsiImportList) {
155 return "...";
157 else if (element instanceof PsiMethod || element instanceof PsiClassInitializer || element instanceof PsiClass) {
158 return "{...}";
160 else if (element instanceof PsiDocComment) {
161 return "/**...*/";
163 else if (element instanceof PsiFile) {
164 return "/.../";
166 else if (element instanceof PsiAnnotation) {
167 return "@{...}";
169 if (element instanceof PsiReferenceParameterList) {
170 return SMILEY;
172 return "...";
175 public boolean isCollapsedByDefault(@NotNull final ASTNode node) {
176 final PsiElement element = SourceTreeToPsiMap.treeElementToPsi(node);
177 JavaCodeFoldingSettings settings = JavaCodeFoldingSettings.getInstance();
178 if (element instanceof PsiNewExpression || element instanceof PsiJavaToken) {
179 return settings.isCollapseLambdas();
181 if (element instanceof PsiReferenceParameterList) {
182 return settings.isCollapseConstructorGenericParameters();
185 if (element instanceof PsiImportList) {
186 return settings.isCollapseImports();
188 else if (element instanceof PsiMethod || element instanceof PsiClassInitializer || element instanceof PsiCodeBlock) {
189 if (!settings.isCollapseAccessors() && !settings.isCollapseMethods()) {
190 return false;
192 if (element instanceof PsiMethod && isSimplePropertyAccessor((PsiMethod)element)) {
193 return settings.isCollapseAccessors();
195 return settings.isCollapseMethods();
197 else if (element instanceof PsiAnonymousClass) {
198 return settings.isCollapseAnonymousClasses();
200 else if (element instanceof PsiClass) {
201 return !(element.getParent() instanceof PsiFile) && settings.isCollapseInnerClasses();
203 else if (element instanceof PsiDocComment) {
204 return settings.isCollapseJavadocs();
206 else if (element instanceof PsiJavaFile) {
207 return settings.isCollapseFileHeader();
209 else if (element instanceof PsiAnnotation) {
210 return settings.isCollapseAnnotations();
212 else {
213 LOG.error("Unknown element:" + element);
214 return false;
218 private static boolean isSimplePropertyAccessor(PsiMethod method) {
219 PsiCodeBlock body = method.getBody();
220 if (body == null) return false;
221 PsiStatement[] statements = body.getStatements();
222 if (statements.length != 1) return false;
223 PsiStatement statement = statements[0];
224 if (PropertyUtil.isSimplePropertyGetter(method)) {
225 if (statement instanceof PsiReturnStatement) {
226 PsiExpression returnValue = ((PsiReturnStatement)statement).getReturnValue();
227 if (returnValue instanceof PsiReferenceExpression) {
228 return ((PsiReferenceExpression)returnValue).resolve() instanceof PsiField;
232 else if (PropertyUtil.isSimplePropertySetter(method)) {
233 if (statement instanceof PsiExpressionStatement) {
234 PsiExpression expr = ((PsiExpressionStatement)statement).getExpression();
235 if (expr instanceof PsiAssignmentExpression) {
236 PsiExpression lhs = ((PsiAssignmentExpression)expr).getLExpression();
237 PsiExpression rhs = ((PsiAssignmentExpression)expr).getRExpression();
238 if (lhs instanceof PsiReferenceExpression && rhs instanceof PsiReferenceExpression) {
239 return ((PsiReferenceExpression)lhs).resolve() instanceof PsiField &&
240 ((PsiReferenceExpression)rhs).resolve() instanceof PsiParameter;
245 return false;
248 @Nullable
249 public static TextRange getRangeToFold(PsiElement element) {
250 if (element instanceof PsiMethod) {
251 if (element instanceof JspHolderMethod) return null;
252 PsiCodeBlock body = ((PsiMethod)element).getBody();
253 if (body == null) return null;
254 return body.getTextRange();
256 if (element instanceof PsiClassInitializer) {
257 return ((PsiClassInitializer)element).getBody().getTextRange();
259 if (element instanceof PsiClass) {
260 PsiClass aClass = (PsiClass)element;
261 PsiJavaToken lBrace = aClass.getLBrace();
262 if (lBrace == null) return null;
263 PsiJavaToken rBrace = aClass.getRBrace();
264 if (rBrace == null) return null;
265 return new TextRange(lBrace.getTextOffset(), rBrace.getTextOffset() + 1);
267 if (element instanceof PsiJavaFile) {
268 return getFileHeader((PsiJavaFile)element);
270 if (element instanceof PsiImportList) {
271 PsiImportList list = (PsiImportList)element;
272 PsiImportStatementBase[] statements = list.getAllImportStatements();
273 if (statements.length == 0) return null;
274 final PsiElement importKeyword = statements[0].getFirstChild();
275 if (importKeyword == null) return null;
276 int startOffset = importKeyword.getTextRange().getEndOffset() + 1;
277 int endOffset = statements[statements.length - 1].getTextRange().getEndOffset();
278 return new TextRange(startOffset, endOffset);
280 if (element instanceof PsiDocComment) {
281 return element.getTextRange();
283 if (element instanceof PsiAnnotation) {
284 int startOffset = element.getTextRange().getStartOffset();
285 PsiElement last = element;
286 while (element instanceof PsiAnnotation) {
287 last = element;
288 element = PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
291 return new TextRange(startOffset, last.getTextRange().getEndOffset());
293 return null;
296 @Nullable
297 private static TextRange getFileHeader(PsiJavaFile file) {
298 PsiElement first = file.getFirstChild();
299 if (first instanceof PsiWhiteSpace) first = first.getNextSibling();
300 PsiElement element = first;
301 while (element instanceof PsiComment) {
302 element = element.getNextSibling();
303 if (element instanceof PsiWhiteSpace) {
304 element = element.getNextSibling();
306 else {
307 break;
310 if (element == null) return null;
311 if (element.getPrevSibling() instanceof PsiWhiteSpace) element = element.getPrevSibling();
312 if (element == null || element.equals(first)) return null;
313 return new TextRange(first.getTextOffset(), element.getTextOffset());
316 private static void addAnnotationsToFold(PsiModifierList modifierList, List<FoldingDescriptor> foldElements, Document document) {
317 if (modifierList == null) return;
318 PsiElement[] children = modifierList.getChildren();
319 for (int i = 0; i < children.length; i++) {
320 PsiElement child = children[i];
321 if (child instanceof PsiAnnotation) {
322 addToFold(foldElements, child, document, false);
323 int j;
324 for (j = i + 1; j < children.length; j++) {
325 PsiElement nextChild = children[j];
326 if (nextChild instanceof PsiModifier) break;
329 //noinspection AssignmentToForLoopParameter
330 i = j;
335 private void addCodeBlockFolds(PsiElement scope, final List<FoldingDescriptor> foldElements, final Document document, final boolean quick) {
336 scope.accept(new JavaRecursiveElementWalkingVisitor() {
337 @Override public void visitClass(PsiClass aClass) {
338 if (!addClosureFolding(aClass, document, foldElements, quick)) {
339 addToFold(foldElements, aClass, document, true);
340 addElementsToFold(foldElements, aClass, document, false, quick);
344 @Override
345 public void visitMethodCallExpression(PsiMethodCallExpression expression) {
346 addMethodGenericParametersFolding(expression, foldElements, document, quick);
348 super.visitMethodCallExpression(expression);
351 @Override
352 public void visitNewExpression(PsiNewExpression expression) {
353 addGenericParametersFolding(expression, foldElements, document, quick);
355 super.visitNewExpression(expression);
360 private static void addMethodGenericParametersFolding(PsiMethodCallExpression expression, List<FoldingDescriptor> foldElements, Document document, boolean quick) {
361 final PsiReferenceExpression methodExpression = expression.getMethodExpression();
362 final PsiReferenceParameterList list = methodExpression.getParameterList();
363 if (list == null || list.getTextLength() <= 5) {
364 return;
367 PsiMethodCallExpression element = expression;
368 while (true) {
369 if (!quick && !resolvesCorrectly(element.getMethodExpression())) return;
370 final PsiElement parent = element.getParent();
371 if (!(parent instanceof PsiExpressionList) || !(parent.getParent() instanceof PsiMethodCallExpression)) break;
372 element = (PsiMethodCallExpression)parent.getParent();
375 addTypeParametersFolding(foldElements, document, list, 3, quick);
378 private static boolean resolvesCorrectly(PsiReferenceExpression expression) {
379 for (final JavaResolveResult result : expression.multiResolve(true)) {
380 if (!result.isValidResult()) {
381 return false;
384 return true;
387 private static void addGenericParametersFolding(PsiNewExpression expression, List<FoldingDescriptor> foldElements, Document document, boolean quick) {
388 final PsiElement parent = expression.getParent();
389 if (!(parent instanceof PsiVariable)) {
390 return;
393 final PsiType declType = ((PsiVariable)parent).getType();
394 if (!(declType instanceof PsiClassReferenceType)) {
395 return;
398 final PsiType[] parameters = ((PsiClassType)declType).getParameters();
399 if (parameters.length == 0) {
400 return;
403 PsiJavaCodeReferenceElement classReference = expression.getClassReference();
404 if (classReference == null) {
405 final PsiAnonymousClass anonymousClass = expression.getAnonymousClass();
406 if (anonymousClass != null) {
407 classReference = anonymousClass.getBaseClassReference();
409 if (quick || seemsLikeLambda(anonymousClass.getSuperClass())) {
410 return;
415 if (classReference != null) {
416 final PsiReferenceParameterList list = classReference.getParameterList();
417 if (list != null) {
418 if (quick) {
419 final PsiJavaCodeReferenceElement declReference = ((PsiClassReferenceType)declType).getReference();
420 final PsiReferenceParameterList declList = declReference.getParameterList();
421 if (declList == null || !list.getText().equals(declList.getText())) {
422 return;
424 } else {
425 if (!Arrays.equals(list.getTypeArguments(), parameters)) {
426 return;
430 addTypeParametersFolding(foldElements, document, list, 5, quick);
435 private static void addTypeParametersFolding(List<FoldingDescriptor> foldElements, Document document, PsiReferenceParameterList list,
436 final int ifLongerThan, boolean quick) {
437 if (!quick) {
438 for (final PsiType type : list.getTypeArguments()) {
439 if (!type.isValid()) {
440 return;
442 if (type instanceof PsiClassType || type instanceof PsiArrayType) {
443 if (PsiUtil.resolveClassInType(type) == null) {
444 return;
450 final String text = list.getText();
451 if (text.startsWith("<") && text.endsWith(">") && text.length() > ifLongerThan) {
452 final TextRange range = list.getTextRange();
453 addFoldRegion(foldElements, list, document, true, new TextRange(range.getStartOffset(), range.getEndOffset()));
457 private static boolean hasOnlyOneMethod(@NotNull PsiAnonymousClass anonymousClass) {
458 if (anonymousClass.getFields().length != 0) {
459 return false;
461 if (anonymousClass.getInitializers().length != 0) {
462 return false;
464 if (anonymousClass.getInnerClasses().length != 0) {
465 return false;
468 return anonymousClass.getMethods().length == 1;
471 private boolean addClosureFolding(final PsiClass aClass, final Document document, final List<FoldingDescriptor> foldElements, final boolean quick) {
472 if (!JavaCodeFoldingSettings.getInstance().isCollapseLambdas()) {
473 return false;
476 boolean isClosure = false;
477 if (aClass instanceof PsiAnonymousClass) {
478 final PsiAnonymousClass anonymousClass = (PsiAnonymousClass)aClass;
479 final PsiElement element = anonymousClass.getParent();
480 if (element instanceof PsiNewExpression) {
481 final PsiNewExpression expression = (PsiNewExpression)element;
482 final PsiExpressionList argumentList = expression.getArgumentList();
483 if (argumentList != null && argumentList.getExpressions().length == 0) {
484 final PsiMethod[] methods = anonymousClass.getMethods();
485 if (hasOnlyOneMethod(anonymousClass) && (quick || seemsLikeLambda(anonymousClass.getBaseClassType().resolve()))) {
486 final PsiMethod method = methods[0];
487 final PsiCodeBlock body = method.getBody();
488 if (body != null) {
489 isClosure = true;
490 int rangeStart = body.getTextRange().getStartOffset();
491 int rangeEnd = body.getTextRange().getEndOffset();
492 final PsiJavaToken lbrace = body.getLBrace();
493 if (lbrace != null) rangeStart = lbrace.getTextRange().getEndOffset();
494 final PsiJavaToken rbrace = body.getRBrace();
495 if (rbrace != null) rangeEnd = rbrace.getTextRange().getStartOffset();
497 final CharSequence seq = document.getCharsSequence();
498 final PsiJavaToken classRBrace = anonymousClass.getRBrace();
499 if (classRBrace != null && rbrace != null) {
500 final int methodEndLine = document.getLineNumber(rangeEnd);
501 final int methodEndLineStart = document.getLineStartOffset(methodEndLine);
502 if ("}".equals(seq.subSequence(methodEndLineStart, document.getLineEndOffset(methodEndLine)).toString().trim())) {
503 int classEndStart = classRBrace.getTextRange().getStartOffset();
504 int classEndCol = classEndStart - document.getLineStartOffset(document.getLineNumber(classEndStart));
505 rangeEnd = classEndCol + methodEndLineStart;
509 final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(aClass.getProject());
510 int firstLineStart = CharArrayUtil.shiftForward(seq, rangeStart, " \t");
511 if (firstLineStart < seq.length() - 1 && seq.charAt(firstLineStart) == '\n') firstLineStart++;
513 int lastLineEnd = CharArrayUtil.shiftBackward(seq, rangeEnd - 1, " \t");
514 if (lastLineEnd > 0 && seq.charAt(lastLineEnd) == '\n') lastLineEnd--;
515 if (lastLineEnd < firstLineStart) return false;
517 final String baseClassName = quick ? anonymousClass.getBaseClassReference().getReferenceName() : anonymousClass.getBaseClassType().resolve().getName();
518 if (lastLineEnd >= seq.length() || firstLineStart >= seq.length() || firstLineStart < 0) {
519 LOG.assertTrue(false, "llE=" +
520 lastLineEnd +
521 "; fLS=" +
522 firstLineStart +
523 "; len=" +
524 seq.length() +
525 "rE=" +
526 rangeEnd +
527 "; class=" + baseClassName);
530 final String params = StringUtil.join(method.getParameterList().getParameters(), new Function<PsiParameter, String>() {
531 public String fun(final PsiParameter psiParameter) {
532 String typeName;
533 if (quick) {
534 typeName = psiParameter.getTypeElement().getText();
536 else {
537 typeName = psiParameter.getType().getPresentableText();
539 int genStart = typeName.indexOf('<');
540 int genEnd = typeName.lastIndexOf('>');
541 if (genStart > 0 && genEnd > 0) {
542 typeName = typeName.substring(0, genStart) + typeName.substring(genEnd + 1);
544 return typeName + " " + psiParameter.getName();
546 }, ", ");
547 @NonNls final String lambdas = baseClassName + "(" + params + ") {";
549 final int closureStart = expression.getTextRange().getStartOffset();
550 final int closureEnd = expression.getTextRange().getEndOffset();
551 boolean oneLine = false;
552 String contents = seq.subSequence(firstLineStart, lastLineEnd).toString();
553 if (contents.indexOf('\n') < 0) {
554 final int beforeLength = closureStart - document.getLineStartOffset(document.getLineNumber(closureStart));
555 final int afterLength = document.getLineEndOffset(document.getLineNumber(closureEnd)) - closureEnd;
556 final int resultLineLength = beforeLength + lambdas.length() + contents.length() + 5 + afterLength;
558 if (resultLineLength <= settings.RIGHT_MARGIN) {
559 rangeStart = CharArrayUtil.shiftForward(seq, rangeStart, " \n\t");
560 rangeEnd = CharArrayUtil.shiftBackward(seq, rangeEnd - 1, " \n\t") + 1;
561 oneLine = true;
565 if (rangeStart >= rangeEnd) return false;
567 FoldingGroup group = FoldingGroup.newGroup("lambda");
569 final String prettySpace = oneLine ? " " : "";
571 foldElements.add(
572 new FoldingDescriptor(expression.getNode(), new TextRange(closureStart, rangeStart), group) {
573 @Override
574 public String getPlaceholderText() {
575 return lambdas + prettySpace;
579 if (rbrace != null && rangeEnd + 1 < closureEnd) {
580 foldElements
581 .add(new FoldingDescriptor(rbrace.getNode(), new TextRange(rangeEnd, closureEnd), group) {
582 @Override
583 public String getPlaceholderText() {
584 return prettySpace + "}";
588 addCodeBlockFolds(body, foldElements, document, quick);
594 return isClosure;
597 private static boolean seemsLikeLambda(@Nullable final PsiClass baseClass) {
598 if (baseClass == null) return false;
600 if (!baseClass.hasModifierProperty(PsiModifier.ABSTRACT)) return false;
602 final PsiMethod[] constructors = baseClass.getConstructors();
603 boolean hasEmptyConstructor = constructors.length == 0;
604 for (final PsiMethod method : constructors) {
605 if (method.getParameterList().getParametersCount() == 0) {
606 hasEmptyConstructor = true;
607 break;
611 if (!hasEmptyConstructor) return false;
613 for (final PsiMethod method : baseClass.getMethods()) {
614 if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
615 return true;
619 return false;
622 private static boolean addToFold(List<FoldingDescriptor> list, PsiElement elementToFold, Document document, boolean allowOneLiners) {
623 LOG.assertTrue(elementToFold.isValid());
624 TextRange range = getRangeToFold(elementToFold);
625 if (range == null) return false;
626 return addFoldRegion(list, elementToFold, document, allowOneLiners, range);
629 private static boolean addFoldRegion(final List<FoldingDescriptor> list, final PsiElement elementToFold, final Document document,
630 final boolean allowOneLiners,
631 final TextRange range) {
632 final TextRange fileRange = elementToFold.getContainingFile().getTextRange();
633 if (range.equals(fileRange)) return false;
635 LOG.assertTrue(range.getStartOffset() >= 0 && range.getEndOffset() <= fileRange.getEndOffset());
636 // PSI element text ranges may be invalid because of reparse exception (see, for example, IDEA-10617)
637 if (range.getStartOffset() < 0 || range.getEndOffset() > fileRange.getEndOffset()) {
638 return false;
640 if (!allowOneLiners) {
641 int startLine = document.getLineNumber(range.getStartOffset());
642 int endLine = document.getLineNumber(range.getEndOffset() - 1);
643 if (startLine < endLine && range.getLength() > 1) {
644 list.add(new FoldingDescriptor(elementToFold, range));
645 return true;
647 return false;
649 else {
650 if (range.getLength() > getPlaceholderText(elementToFold).length()) {
651 list.add(new FoldingDescriptor(elementToFold, range));
652 return true;
654 return false;