OC-9609 broken based on AST-tree folding at project opening +review CR-OC-1561
authorAlexey Utkin <alexey.utkin@jetbrains.com>
Sun, 1 Jun 2014 18:31:42 +0000 (22:31 +0400)
committerAlexey Utkin <alexey.utkin@jetbrains.com>
Sun, 1 Jun 2014 18:38:13 +0000 (22:38 +0400)
platform/core-api/src/com/intellij/lang/folding/CustomFoldingBuilder.java
platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/CodeFoldingPass.java
platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CodeFoldingManagerImpl.java
platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java
platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java

index f9f3148b8c9747aa6edd4634e011d5f04906dbbb..5d628464bb6fddc79ff5873edd397cb8410e929c 100644 (file)
@@ -1,8 +1,23 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.intellij.lang.folding;
 
 import com.intellij.lang.ASTNode;
 import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.PossiblyDumbAware;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.psi.PsiComment;
 import com.intellij.psi.PsiElement;
@@ -18,7 +33,7 @@ import java.util.List;
  * 
  * @author Rustam Vishnyakov
  */
-public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements DumbAware {
+public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements PossiblyDumbAware {
 
   private CustomFoldingProvider myDefaultProvider;
   private static final int MAX_LOOKUP_DEPTH = 10;
@@ -199,4 +214,16 @@ public abstract class CustomFoldingBuilder extends FoldingBuilderEx implements D
       return owner;
     }
   }
+
+  /**
+   * Checks if the folding ranges could be created in the Dumb Mode. In the most of
+   * language implementations the method returns true, but for strong context-dependant
+   * languages (like ObjC/C++) overridden method returns false.
+   *
+   * @return True if the folding ranges could be created in the Dumb Mode
+   */
+  @Override
+  public boolean isDumbAware() {
+    return true;
+  }
 }
index c888de7ef95d46ae622b06b82b50b8b90a76e86c..6e1c9a461dbe2e95129b6c34f7bae27daa4b8dae 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2014 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,15 +20,16 @@ import com.intellij.codeHighlighting.TextEditorHighlightingPass;
 import com.intellij.codeInsight.folding.CodeFoldingManager;
 import com.intellij.lang.injection.InjectedLanguageManager;
 import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
 import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.project.DumbAware;
 import com.intellij.openapi.project.IndexNotReadyException;
+import com.intellij.openapi.project.PossiblyDumbAware;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Key;
 import com.intellij.psi.PsiFile;
 import org.jetbrains.annotations.NotNull;
 
-class CodeFoldingPass extends TextEditorHighlightingPass implements DumbAware {
+class CodeFoldingPass extends TextEditorHighlightingPass implements PossiblyDumbAware {
   private static final Key<Boolean> THE_FIRST_TIME = Key.create("FirstFoldingPass");
   private Runnable myRunnable;
   private final Editor myEditor;
@@ -76,4 +77,13 @@ class CodeFoldingPass extends TextEditorHighlightingPass implements DumbAware {
       clearFirstTimeFlag(myFile, myEditor, THE_FIRST_TIME);
     }
   }
+
+  /**
+   * Checks the ability to update folding in the Dumb Mode. True by default.
+   * @return true if the language implementation can update folding ranges
+   */
+  @Override
+  public boolean isDumbAware() {
+    return EditorUtil.supportsDumbModeFolding(myEditor);
+  }
 }
index 2171b236533e71a7f2400ba37e27bfceee899533..c6c5f7a44cdde87ab57afe18d528b936e778d3a0 100644 (file)
@@ -29,8 +29,10 @@ import com.intellij.openapi.editor.event.EditorMouseMotionAdapter;
 import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener;
 import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.editor.ex.FoldingModelEx;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
 import com.intellij.openapi.fileEditor.impl.text.CodeFoldingState;
 import com.intellij.openapi.project.DumbAwareRunnable;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.startup.StartupManager;
 import com.intellij.openapi.util.*;
@@ -96,7 +98,7 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
         HintManager hintManager = HintManager.getInstance();
         if (hintManager != null && hintManager.hasShownHintsThatWillHideByOtherHint(false)) {
           return;
-        } 
+        }
 
         if (e.getArea() != EditorMouseEventArea.FOLDING_OUTLINE_AREA) return;
         LightweightHint hint = null;
@@ -127,8 +129,8 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
               myCurrentHint.hide();
               myCurrentHint = null;
             }
-            
-            
+
+
             // We want to show a hint with the top fold region content that is above the current viewport position.
             // However, there is a possible case that complete region has a big height and only a little bottom part
             // is shown at the moment. We can't just show hint with the whole top content because it would hide actual
@@ -206,7 +208,7 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
     if (project == null || !project.equals(myProject)) return;
 
     final Document document = editor.getDocument();
-    //Do not save/restore folding for code fragments
+    // Do not save/restore folding for code fragments
     final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
     if (file == null || !file.getViewProvider().isPhysical() && !ApplicationManager.getApplication().isUnitTestMode()) return;
 
@@ -214,34 +216,58 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
     if (!foldingModel.isFoldingEnabled()) return;
     if (project.isDisposed() || editor.isDisposed() || !file.isValid()) return;
 
-    PsiDocumentManager.getInstance(myProject).commitDocument(document);
-
-    Runnable runnable = updateFoldRegions(editor, true, true);
-    if (runnable != null) {
-      runnable.run();
+    if (EditorUtil.supportsDumbModeFolding(editor)) {
+      // Else: Postpone operation until first call of #updateFoldRegionsAsync with the [firstTime] param [true]
+      PsiDocumentManager.getInstance(myProject).commitDocument(document);
+      createInitFoldingAction(updateFoldRegions(editor, true, true), editor).run();
     }
-    if (myProject.isDisposed() || editor.isDisposed()) return;
-    foldingModel.runBatchFoldingOperation(new Runnable() {
+  }
+
+  @NotNull
+  private Runnable createInitFoldingAction(@Nullable final Runnable updateFoldingRegionAction,
+                                           @NotNull final Editor editor) {
+    assert !DumbService.getInstance(myProject).isDumb() || EditorUtil.supportsDumbModeFolding(editor) : "Forbidden state for folding initialization";
+    return new Runnable() {
       @Override
       public void run() {
-        DocumentFoldingInfo documentFoldingInfo = getDocumentFoldingInfo(document);
-        Editor[] editors = EditorFactory.getInstance().getEditors(document, myProject);
-        for (Editor otherEditor : editors) {
-          if (otherEditor == editor) continue;
-          documentFoldingInfo.loadFromEditor(otherEditor);
-          break;
+        if (updateFoldingRegionAction != null) {
+          updateFoldingRegionAction.run();
         }
-        documentFoldingInfo.setToEditor(editor);
+        if (myProject.isDisposed() || editor.isDisposed()) return;
+        // Restore folding state if need (it could be done concurrently)
+        if (!isFoldingsInitializedInEditor(editor)) {
+          editor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
+            @Override
+            public void run() {
+              Document document = editor.getDocument();
+              DocumentFoldingInfo documentFoldingInfo = getDocumentFoldingInfo(document);
+
+              // Folding state could be changed in another editor
+              Editor[] editors = EditorFactory.getInstance().getEditors(document, editor.getProject());
+              for (Editor otherEditor : editors) {
+                if (otherEditor == editor || !isFoldingsInitializedInEditor(otherEditor)) continue;
+                // Any active editor overwrites folding from saved state (document info is empty for the case)
+                documentFoldingInfo.loadFromEditor(otherEditor);
+                break;
+              }
+              documentFoldingInfo.setToEditor(editor);
 
-        documentFoldingInfo.clear();
+              // Drop fording info for document. Next editor will load it from active editor.
+              // Then the last editor is closed, folding info saves into the document.
+              documentFoldingInfo.clear();
+              editor.getDocument().putUserData(FOLDING_STATE_INFO_IN_DOCUMENT_KEY, Boolean.TRUE);
+              editor.putUserData(FOLDING_STATE_INFO_IN_DOCUMENT_KEY, Boolean.TRUE);
+            }
+          });
+        }
       }
-    });
+    };
   }
 
   @Override
   public void projectClosed() {
   }
-  
+
   @Override
   @Nullable
   public FoldRegion findFoldRegion(@NotNull Editor editor, int startOffset, int endOffset) {
@@ -292,6 +318,26 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
   @Override
   @Nullable
   public Runnable updateFoldRegionsAsync(@NotNull Editor editor, boolean firstTime) {
+    if (firstTime) {
+      final Document document = editor.getDocument();
+      if (!isFoldingsInitializedInDocument(document)) {
+        // all editors need to be initialized
+        return new Runnable() {
+          @Override
+          public void run() {
+            final Editor[] editors = EditorFactory.getInstance().getEditors(document);
+            for(Editor anyEditor:editors) if (!isFoldingsInitializedInEditor(anyEditor)) {
+              createInitFoldingAction(updateFoldRegions(anyEditor, true, false), anyEditor).run();
+            }
+          }
+        };
+      }
+      if (!isFoldingsInitializedInEditor(editor)) {
+        // Restores folding state after regions initialization.
+        // That is called after the first folding pass
+        return createInitFoldingAction(updateFoldRegions(editor, true, false), editor);
+      }
+    }
     return updateFoldRegions(editor, firstTime, false);
   }
 
@@ -299,7 +345,6 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
   private Runnable updateFoldRegions(@NotNull Editor editor, boolean applyDefaultState, boolean quick) {
     PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
     if (file != null) {
-      editor.getDocument().putUserData(FOLDING_STATE_INFO_IN_DOCUMENT_KEY, Boolean.TRUE);
       return FoldingUpdate.updateFoldRegions(editor, file, applyDefaultState, quick);
     }
     else {
@@ -311,14 +356,18 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
   public CodeFoldingState saveFoldingState(@NotNull Editor editor) {
     ApplicationManager.getApplication().assertIsDispatchThread();
     DocumentFoldingInfo info = getDocumentFoldingInfo(editor.getDocument());
-    info.loadFromEditor(editor);
+    if (isFoldingsInitializedInEditor(editor)) {
+      info.loadFromEditor(editor);
+    }
     return info;
   }
 
   @Override
   public void restoreFoldingState(@NotNull Editor editor, @NotNull CodeFoldingState state) {
     ApplicationManager.getApplication().assertIsDispatchThread();
-    ((DocumentFoldingInfo)state).setToEditor(editor);
+    if (isFoldingsInitializedInEditor(editor)) {
+      ((DocumentFoldingInfo)state).setToEditor(editor);
+    }
   }
 
   @Override
@@ -350,8 +399,7 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
   }
 
   private static void resetFoldingInfo(@NotNull final Document document) {
-    final Boolean foldingInfoStatus = document.getUserData(FOLDING_STATE_INFO_IN_DOCUMENT_KEY);
-    if (Boolean.TRUE.equals(foldingInfoStatus)) {
+    if (isFoldingsInitializedInDocument(document)) {
       final Editor[] editors = EditorFactory.getInstance().getEditors(document);
       for(Editor editor:editors) {
         EditorFoldingInfo.resetInfo(editor);
@@ -359,4 +407,12 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec
       document.putUserData(FOLDING_STATE_INFO_IN_DOCUMENT_KEY, null);
     }
   }
+
+  static boolean isFoldingsInitializedInDocument(@NotNull Document document) {
+    return Boolean.TRUE.equals(document.getUserData(FOLDING_STATE_INFO_IN_DOCUMENT_KEY));
+  }
+
+  static boolean isFoldingsInitializedInEditor(@NotNull Editor editor) {
+    return Boolean.TRUE.equals(editor.getUserData(FOLDING_STATE_INFO_IN_DOCUMENT_KEY));
+  }
 }
index 27ad0ba8118b295204b270c16e8330ea315e04db..3f1b9e4e9f08c37e6a820b956ea496b56d34cedb 100644 (file)
@@ -27,8 +27,10 @@ import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.FoldRegion;
 import com.intellij.openapi.editor.RangeMarker;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.fileEditor.impl.text.CodeFoldingState;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.*;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -48,7 +50,16 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
   @NotNull private final Project myProject;
   private final VirtualFile myFile;
 
+  private static class SerializedPsiElement {
+    private final String mySerializedElement;
+    private final FoldingInfo myFoldingInfo;
+    public SerializedPsiElement(@NotNull String serialized, @NotNull FoldingInfo foldingInfo) {
+      mySerializedElement = serialized;
+      myFoldingInfo = foldingInfo;
+    }
+  }
   @NotNull private final List<SmartPsiElementPointer<PsiElement>> myPsiElements = ContainerUtil.createLockFreeCopyOnWriteList();
+  @NotNull private final List<SerializedPsiElement> mySerializedElements = ContainerUtil.createLockFreeCopyOnWriteList();
   @NotNull private final List<RangeMarker> myRangeMarkers = ContainerUtil.createLockFreeCopyOnWriteList();
   private static final String DEFAULT_PLACEHOLDER = "...";
   @NonNls private static final String ELEMENT_TAG = "element";
@@ -108,6 +119,19 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
     final PsiFile psiFile = psiManager.findFile(myFile);
     if (psiFile == null) return;
 
+    if (!mySerializedElements.isEmpty()) {
+      // Restore postponed state
+      assert myPsiElements.isEmpty() : "Sequential deserialization";
+      for (SerializedPsiElement entry : mySerializedElements) {
+        PsiElement restoredElement = FoldingPolicy.restoreBySignature(psiFile, entry.mySerializedElement);
+        if (restoredElement != null && restoredElement.isValid()) {
+          myPsiElements.add(SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(restoredElement));
+          restoredElement.putUserData(FOLDING_INFO_KEY, entry.myFoldingInfo);
+        }
+      }
+      mySerializedElements.clear();
+    }
+
     Map<PsiElement, FoldingDescriptor> ranges = null;
     for (SmartPsiElementPointer<PsiElement> ptr: myPsiElements) {
       PsiElement element = ptr.getElement();
@@ -174,41 +198,53 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
       if (!(marker instanceof FoldRegion)) marker.dispose();
     }
     myRangeMarkers.clear();
+    mySerializedElements.clear();
   }
 
   @Override
   public void writeExternal(Element element) throws WriteExternalException {
     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
 
-    if (myPsiElements.isEmpty() && myRangeMarkers.isEmpty()){
+    if (myPsiElements.isEmpty() && myRangeMarkers.isEmpty() && mySerializedElements.isEmpty()){
       throw new WriteExternalException();
     }
 
-    for (SmartPsiElementPointer<PsiElement> ptr : myPsiElements) {
-      PsiElement psiElement = ptr.getElement();
-      if (psiElement == null || !psiElement.isValid()) {
-        continue;
-      }
-      FoldingInfo fi = psiElement.getUserData(FOLDING_INFO_KEY);
-      boolean state = fi != null && fi.expanded;
-      String signature = FoldingPolicy.getSignature(psiElement);
-      if (signature == null) {
-        continue;
-      }
+    if (mySerializedElements.isEmpty()) {
+      for (SmartPsiElementPointer<PsiElement> ptr : myPsiElements) {
+        PsiElement psiElement = ptr.getElement();
+        if (psiElement == null || !psiElement.isValid()) {
+          continue;
+        }
+        FoldingInfo fi = psiElement.getUserData(FOLDING_INFO_KEY);
+        boolean state = fi != null && fi.expanded;
+        String signature = FoldingPolicy.getSignature(psiElement);
+        if (signature == null) {
+          continue;
+        }
 
-      PsiElement restoredElement = FoldingPolicy.restoreBySignature(psiElement.getContainingFile(), signature);
-      if (!psiElement.equals(restoredElement)) {
-        StringBuilder trace = new StringBuilder();
-        PsiElement restoredAgain = FoldingPolicy.restoreBySignature(psiElement.getContainingFile(), signature, trace);
-        LOG.error("element: " + psiElement + "(" + psiElement.getText() + "); restoredElement: " + restoredElement
-                  + "; signature: '" + signature + "'; file: " + psiElement.getContainingFile() + "; restored again: "
-                  + restoredAgain + "; restore produces same results: " + (restoredAgain == restoredElement) + "; trace:\n" + trace);
-      }
+        PsiElement restoredElement = FoldingPolicy.restoreBySignature(psiElement.getContainingFile(), signature);
+        if (!psiElement.equals(restoredElement)) {
+          StringBuilder trace = new StringBuilder();
+          PsiElement restoredAgain = FoldingPolicy.restoreBySignature(psiElement.getContainingFile(), signature, trace);
+          LOG.error("element: " + psiElement + "(" + psiElement.getText() + "); restoredElement: " + restoredElement
+                    + "; signature: '" + signature + "'; file: " + psiElement.getContainingFile() + "; restored again: "
+                    + restoredAgain + "; restore produces same results: " + (restoredAgain == restoredElement) + "; trace:\n" + trace);
+        }
 
-      Element e = new Element(ELEMENT_TAG);
-      e.setAttribute(SIGNATURE_ATT, signature);
-      e.setAttribute(EXPANDED_ATT, Boolean.toString(state));
-      element.addContent(e);
+        Element e = new Element(ELEMENT_TAG);
+        e.setAttribute(SIGNATURE_ATT, signature);
+        e.setAttribute(EXPANDED_ATT, Boolean.toString(state));
+        element.addContent(e);
+      }
+    }
+    else {
+      // get back postponed state (before folding initialization)
+      for (SerializedPsiElement entry : mySerializedElements) {
+        Element e = new Element(ELEMENT_TAG);
+        e.setAttribute(SIGNATURE_ATT, entry.mySerializedElement);
+        e.setAttribute(EXPANDED_ATT, Boolean.toString(entry.myFoldingInfo.getExpanded()));
+        element.addContent(e);
+      }
     }
     String date = null;
     for (RangeMarker marker : myRangeMarkers) {
@@ -249,6 +285,7 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
         if (psiFile == null || !psiFile.getViewProvider().isPhysical()) return;
 
         String date = null;
+        boolean canRestoreElement = !DumbService.getInstance(myProject).isDumb() || EditorUtil.supportsDumbModeFolding(psiFile);
         for (final Object o : element.getChildren()) {
           Element e = (Element)o;
           Boolean expanded = Boolean.valueOf(e.getAttributeValue(EXPANDED_ATT));
@@ -257,11 +294,17 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
             if (signature == null) {
               continue;
             }
-            PsiElement restoredElement = FoldingPolicy.restoreBySignature(psiFile, signature);
-            if (restoredElement != null && restoredElement.isValid()) {
-              myPsiElements.add(SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(restoredElement));
-              FoldingInfo fi = new FoldingInfo(DEFAULT_PLACEHOLDER, expanded);
-              restoredElement.putUserData(FOLDING_INFO_KEY, fi);
+            FoldingInfo fi = new FoldingInfo(DEFAULT_PLACEHOLDER, expanded);
+            if (canRestoreElement) {
+              PsiElement restoredElement = FoldingPolicy.restoreBySignature(psiFile, signature);
+              if (restoredElement != null && restoredElement.isValid()) {
+                myPsiElements.add(SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(restoredElement));
+                restoredElement.putUserData(FOLDING_INFO_KEY, fi);
+              }
+            }
+            else {
+              // Postponed initialization
+              mySerializedElements.add(new SerializedPsiElement(signature, fi));
             }
           }
           else if (MARKER_TAG.equals(e.getName())) {
@@ -323,7 +366,9 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
     if (myFile != null ? !myFile.equals(info.myFile) : info.myFile != null) {
       return false;
     }
-    if (!myProject.equals(info.myProject) || !myPsiElements.equals(info.myPsiElements)) {
+    if (!myProject.equals(info.myProject)
+        || !myPsiElements.equals(info.myPsiElements)
+        || !mySerializedElements.equals(info.mySerializedElements)) {
       return false;
     }
 
@@ -372,5 +417,9 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState {
       result = 31 * result + (expanded ? 1 : 0);
       return result;
     }
+
+    public boolean getExpanded() {
+      return expanded;
+    }
   }
 }
index 8ca8556ebd49681058845fb322459eeb0802a428..514d63e580f3a50ecb18fbe40f2e7d5700c32e45 100644 (file)
@@ -17,6 +17,9 @@ package com.intellij.openapi.editor.ex.util;
 
 import com.intellij.diagnostic.Dumpable;
 import com.intellij.diagnostic.LogMessageEx;
+import com.intellij.lang.folding.FoldingBuilder;
+import com.intellij.lang.folding.LanguageFolding;
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.Result;
 import com.intellij.openapi.application.WriteAction;
 import com.intellij.openapi.diagnostic.Logger;
@@ -29,11 +32,18 @@ import com.intellij.openapi.editor.impl.FontInfo;
 import com.intellij.openapi.editor.impl.IterationState;
 import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl;
 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Couple;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
 import org.intellij.lang.annotations.JdkConstants;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -880,6 +890,41 @@ public final class EditorUtil {
     int line = document.getLineNumber(offset);
     return offset == document.getLineEndOffset(line);
   }
+
+  /**
+   * Checks the ability to initialize folding in the Dumb Mode. Due to language injections it may depend on
+   * edited file and active injections (not yet implemented).
+   *
+   * @param editor the editor that holds file view
+   * @return true  if folding initialization available in the Dumb Mode
+   */
+  public static boolean supportsDumbModeFolding(@NotNull Editor editor) {
+    Project project = editor.getProject();
+    if (project != null) {
+      PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
+      if (file != null) {
+        return supportsDumbModeFolding(file);
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Checks the ability to initialize folding in the Dumb Mode for file.
+   *
+   * @param file the file to test
+   * @return true  if folding initialization available in the Dumb Mode
+   */
+  public static boolean supportsDumbModeFolding(@NotNull PsiFile file) {
+    if (file.getVirtualFile() != null) {
+      FileType ft = FileTypeManager.getInstance().getFileTypeByFile(file.getVirtualFile());
+      if (ft instanceof LanguageFileType) {
+        final FoldingBuilder foldingBuilder = LanguageFolding.INSTANCE.forLanguage(((LanguageFileType)ft).getLanguage());
+        return DumbService.isDumbAware(foldingBuilder) || ApplicationManager.getApplication().isUnitTestMode();
+      }
+    }
+    return true;
+  }
 }