diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java index b80172259557..9cd429188f00 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java @@ -30,9 +30,11 @@ import com.intellij.openapi.editor.FoldRegion; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.impl.text.CodeFoldingState; +import com.intellij.openapi.progress.util.ProgressWithTimeoutInDispatch; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.util.containers.ContainerUtil; @@ -204,7 +206,7 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState { } @Override - public void writeExternal(Element element) throws WriteExternalException { + public void writeExternal(final Element element) throws WriteExternalException { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); if (myPsiElements.isEmpty() && myRangeMarkers.isEmpty() && mySerializedElements.isEmpty()){ @@ -212,39 +214,42 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState { } if (mySerializedElements.isEmpty()) { - for (SmartPsiElementPointer 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; - } + ProgressWithTimeoutInDispatch.execInDispatchWithTimeout(() -> { + for (SmartPsiElementPointer 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; + } - PsiFile containingFile = psiElement.getContainingFile(); - PsiElement restoredElement = FoldingPolicy.restoreBySignature(containingFile, signature); - if (!psiElement.equals(restoredElement)) { - StringBuilder trace = new StringBuilder(); - PsiElement restoredAgain = FoldingPolicy.restoreBySignature(containingFile, signature, trace); - LOG.error("element: " + psiElement + "(" + psiElement.getText() - + "); restoredElement: " + restoredElement - + "; signature: '" + signature - + "'; file: " + containingFile - + "; injected: " + InjectedLanguageManager.getInstance(myProject).isInjectedFragment(containingFile) - + "; languages: " + containingFile.getViewProvider().getLanguages() - + "; restored again: " + restoredAgain + - "; restore produces same results: " + (restoredAgain == restoredElement) - + "; trace:\n" + trace); - } + PsiFile containingFile = psiElement.getContainingFile(); + PsiElement restoredElement = FoldingPolicy.restoreBySignature(containingFile, signature); + if (!psiElement.equals(restoredElement)) { + StringBuilder trace = new StringBuilder(); + PsiElement restoredAgain = FoldingPolicy.restoreBySignature(containingFile, signature, trace); + LOG.error("element: " + psiElement + "(" + psiElement.getText() + + "); restoredElement: " + restoredElement + + "; signature: '" + signature + + "'; file: " + containingFile + + "; injected: " + InjectedLanguageManager.getInstance(myProject).isInjectedFragment(containingFile) + + "; languages: " + containingFile.getViewProvider().getLanguages() + + "; 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); + } + return Void.TYPE; + }, Registry.get("save.folding.state.timeout").asInteger()); } else { // get back postponed state (before folding initialization) diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWithTimeoutInDispatch.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWithTimeoutInDispatch.java new file mode 100644 index 000000000000..2be3e45b9087 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWithTimeoutInDispatch.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2017 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.openapi.progress.util; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Computable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ProgressWithTimeoutInDispatch extends AbstractProgressIndicatorExBase implements PingProgress { + private final long myTimeoutMs; + private final long myStartTimeMs; + + private ProgressWithTimeoutInDispatch(long timeoutMs) { + super(); + myTimeoutMs = timeoutMs; + myStartTimeMs = System.currentTimeMillis(); + setIndeterminate(true); + } + + @Override + public boolean isCanceled() { + if (System.currentTimeMillis() - myStartTimeMs >= myTimeoutMs) { + cancel(); + } + return super.isCanceled(); + } + + @Override + public void interact() {} + + + /** + * Cancels the action by the ProcessCanceledException exception + * after timeoutMs ms on event dispatch thread. + * + * The action is not limited by any timeout if it is called on non-dispatch + * thread, or timeoutMs <= 0, or the thread has progress indicator. + * + * @throws ProcessCanceledException + */ + @Nullable + public static T execInDispatchWithTimeout(@NotNull Computable action, long timeoutMs) throws ProcessCanceledException { + final Application application = ApplicationManager.getApplication(); + if (application.isDispatchThread() + && timeoutMs > 0 + && ProgressManager.getInstance().getProgressIndicator() == null) { + try { + return ProgressManager.getInstance().runProcess(action, new ProgressWithTimeoutInDispatch(timeoutMs)); + } + catch (ProcessCanceledException ex) { + return null; + } + } + return action.compute(); + } +} diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 3fb1323a3a43..98920934631e 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -1080,6 +1080,8 @@ welcome.screen.project.grouping.enabled.description=Allows to group recent proje custom.folding.max.lookup.depth=50 custom.folding.max.lookup.depth.description=Max lookup depth for custom folding comments in a PSI tree. +save.folding.state.timeout=0 +save.folding.state.timeout.description=Timeout after which folding state saving is forcibly cancelled (0 means no time limit) project.structure.add.tools.jar.to.new.jdk=false project.structure.add.tools.jar.to.new.jdk.description=Automatically add tools.jar to 'Classpath' when new JDK is created