From 6c909ad13ca99092d352997aea419b4142f6c5b7 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 15 Feb 2017 16:46:42 +0100 Subject: [PATCH] update editor highlighters on VFS changes asynchronously (IDEA-167482) --- .../impl/text/AsyncHighlighterUpdater.java | 95 +++++++++++++++++++ .../impl/text/TextEditorComponent.java | 5 +- .../progress/util/ProgressIndicatorUtils.java | 13 ++- .../testFramework/PlatformTestCase.java | 3 + 4 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java new file mode 100644 index 000000000000..5c51ebf99ab1 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java @@ -0,0 +1,95 @@ +/* + * 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.fileEditor.impl.text; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.util.ProgressIndicatorUtils; +import com.intellij.openapi.progress.util.ReadTask; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.concurrency.AppExecutorUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +/** + * @author peter + */ +public class AsyncHighlighterUpdater extends ReadTask { + private static final ExecutorService ourExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("AsyncEditorLoader pool", 2); + private static final Set> ourHighlighterFutures = ContainerUtil.newConcurrentSet(); + private final Project myProject; + private final Editor myEditor; + private final VirtualFile myFile; + + private AsyncHighlighterUpdater(Project project, Editor editor, VirtualFile file) { + myProject = project; + myEditor = editor; + myFile = file; + } + + @Override + public Continuation performInReadAction(@NotNull ProgressIndicator indicator) throws ProcessCanceledException { + if (!isEverythingValid()) return null; + + EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, myFile); + return new Continuation(() -> ((EditorEx)myEditor).setHighlighter(highlighter)); + } + + @Override + public void onCanceled(@NotNull ProgressIndicator indicator) { + updateHighlighters(myProject, myEditor, myFile); + } + + private boolean isEverythingValid() { + return !myProject.isDisposed() && !myEditor.isDisposed() && myFile.isValid(); + } + + public static void updateHighlighters(@NotNull Project project, @NotNull Editor editor, @NotNull VirtualFile file) { + AsyncHighlighterUpdater task = new AsyncHighlighterUpdater(project, editor, file); + if (task.isEverythingValid()) { + CompletableFuture future = ProgressIndicatorUtils.scheduleWithWriteActionPriority(ourExecutor, task); + ourHighlighterFutures.add(future); + future.whenComplete((a, b) -> ourHighlighterFutures.remove(future)); + } + } + + @TestOnly + public static void completeAsyncTasks() { + assert !ApplicationManager.getApplication().isWriteAccessAllowed(); + for (Future future : ourHighlighterFutures) { + try { + future.get(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + UIUtil.dispatchAllInvocationEvents(); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorComponent.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorComponent.java index 8b72201801f5..fe52087ddc48 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorComponent.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorComponent.java @@ -27,8 +27,6 @@ import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.EditorMarkupModel; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditor; @@ -239,8 +237,7 @@ class TextEditorComponent extends JBLoadingPanel implements DataProvider { */ private void updateHighlighters(){ if (!myProject.isDisposed() && !myEditor.isDisposed()) { - final EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, myFile); - ((EditorEx)myEditor).setHighlighter(highlighter); + AsyncHighlighterUpdater.updateHighlighters(myProject, myEditor, myFile); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java index c2b0a5994c47..b028ed6b6a8b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressIndicatorUtils.java @@ -34,7 +34,6 @@ import org.jetbrains.ide.PooledThreadExecutor; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.concurrent.Future; import java.util.function.BiConsumer; /** @@ -64,7 +63,7 @@ public class ProgressIndicatorUtils { } @NotNull - public static Future submitWithWriteActionPriority(@NotNull ReadTask task) { + public static CompletableFuture submitWithWriteActionPriority(@NotNull ReadTask task) { return scheduleWithWriteActionPriority(new ProgressIndicatorBase(), task); } @@ -73,12 +72,12 @@ public class ProgressIndicatorUtils { } @NotNull - public static Future scheduleWithWriteActionPriority(@NotNull ProgressIndicator progressIndicator, @NotNull ReadTask readTask) { + public static CompletableFuture scheduleWithWriteActionPriority(@NotNull ProgressIndicator progressIndicator, @NotNull ReadTask readTask) { return scheduleWithWriteActionPriority(progressIndicator, PooledThreadExecutor.INSTANCE, readTask); } @NotNull - public static Future scheduleWithWriteActionPriority(@NotNull Executor executor, @NotNull ReadTask task) { + public static CompletableFuture scheduleWithWriteActionPriority(@NotNull Executor executor, @NotNull ReadTask task) { return scheduleWithWriteActionPriority(new ProgressIndicatorBase(), executor, task); } @@ -159,9 +158,9 @@ public class ProgressIndicatorUtils { } @NotNull - public static Future scheduleWithWriteActionPriority(@NotNull final ProgressIndicator progressIndicator, - @NotNull final Executor executor, - @NotNull final ReadTask readTask) { + public static CompletableFuture scheduleWithWriteActionPriority(@NotNull final ProgressIndicator progressIndicator, + @NotNull final Executor executor, + @NotNull final ReadTask readTask) { final Application application = ApplicationManager.getApplication(); // invoke later even if on EDT // to avoid tasks eagerly restarting immediately, allocating many pooled threads diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java index bce6baff7c9d..1daebaa0dcfa 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestCase.java @@ -34,6 +34,7 @@ import com.intellij.openapi.command.undo.DocumentReferenceManager; import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.impl.text.AsyncHighlighterUpdater; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl; import com.intellij.openapi.module.EmptyModuleType; @@ -393,6 +394,8 @@ public abstract class PlatformTestCase extends UsefulTestCase implements DataPro ((PsiManagerImpl)PsiManager.getInstance(defaultProject)).cleanupForNextTest(); } + AsyncHighlighterUpdater.completeAsyncTasks(); + ((FileBasedIndexImpl) FileBasedIndex.getInstance()).cleanupForNextTest(); LocalFileSystemImpl localFileSystem = (LocalFileSystemImpl)LocalFileSystem.getInstance();