From b8bfcd38dbd9379308a3545e8fd97859af9744ff Mon Sep 17 00:00:00 2001 From: Alexey Utkin Date: Fri, 13 Oct 2017 10:19:12 +0300 Subject: [PATCH] CPP-10639 Saving folding state triggers lazy blocks reparsing from EDT Cancels storing of folding state by timeout on EDT. The problem comes from smart pointers, that initiate lazy block parsing. It's is hard in C++. Here is temporal solution util the platform start collecting the folding state in background. (cherry picked from commit edf15a5) --- .../folding/impl/DocumentFoldingInfo.java | 69 +++++++++-------- .../util/ProgressWithTimeoutInDispatch.java | 74 +++++++++++++++++++ .../util/resources/misc/registry.properties | 2 + 3 files changed, 113 insertions(+), 32 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressWithTimeoutInDispatch.java 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