cleanup
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / fileEditor / impl / text / EditorHighlighterUpdater.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.fileEditor.impl.text;
3
4 import com.intellij.openapi.Disposable;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.application.ModalityState;
7 import com.intellij.openapi.application.ReadAction;
8 import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
9 import com.intellij.openapi.editor.HighlighterColors;
10 import com.intellij.openapi.editor.colors.EditorColorsManager;
11 import com.intellij.openapi.editor.ex.EditorEx;
12 import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
13 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
14 import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
15 import com.intellij.openapi.extensions.ExtensionPointListener;
16 import com.intellij.openapi.extensions.ExtensionPointName;
17 import com.intellij.openapi.extensions.KeyedFactoryEPBean;
18 import com.intellij.openapi.extensions.PluginDescriptor;
19 import com.intellij.openapi.fileTypes.*;
20 import com.intellij.openapi.fileTypes.impl.AbstractFileType;
21 import com.intellij.openapi.project.DumbService;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import com.intellij.util.KeyedLazyInstance;
25 import com.intellij.util.concurrency.NonUrgentExecutor;
26 import com.intellij.util.messages.MessageBusConnection;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29 import org.jetbrains.annotations.TestOnly;
30
31 /**
32  * @author peter
33  */
34 public class EditorHighlighterUpdater {
35   @NotNull protected final Project myProject;
36   @NotNull private final EditorEx myEditor;
37   @Nullable private final VirtualFile myFile;
38
39   public EditorHighlighterUpdater(@NotNull Project project, @NotNull Disposable parentDisposable, @NotNull EditorEx editor, @Nullable VirtualFile file) {
40     myProject = project;
41     myEditor = editor;
42     myFile = file;
43     MessageBusConnection connection = project.getMessageBus().connect(parentDisposable);
44     connection.subscribe(FileTypeManager.TOPIC, new MyFileTypeListener());
45     connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
46       @Override
47       public void enteredDumbMode() {
48         updateHighlighters();
49       }
50
51       @Override
52       public void exitDumbMode() {
53         updateHighlighters();
54       }
55     });
56
57     updateHighlightersOnExtensionsChange(parentDisposable, LanguageSyntaxHighlighters.EP_NAME);
58     updateHighlightersOnExtensionsChange(parentDisposable, SyntaxHighlighterLanguageFactory.EP_NAME);
59     updateHighlightersOnExtensionsChange(parentDisposable, FileTypeEditorHighlighterProviders.EP_NAME);
60
61     SyntaxHighlighter.EP_NAME.addExtensionPointListener(new ExtensionPointListener<KeyedFactoryEPBean>() {
62       @Override
63       public void extensionAdded(@NotNull KeyedFactoryEPBean extension, @NotNull PluginDescriptor pluginDescriptor) {
64         checkUpdateHighlighters(extension.key, false);
65       }
66
67       @Override
68       public void extensionRemoved(@NotNull KeyedFactoryEPBean extension, @NotNull PluginDescriptor pluginDescriptor) {
69         checkUpdateHighlighters(extension.key, true);
70       }
71     }, parentDisposable);
72   }
73
74   private <T> void updateHighlightersOnExtensionsChange(@NotNull Disposable parentDisposable, @NotNull ExtensionPointName<KeyedLazyInstance<T>> epName) {
75     epName.addExtensionPointListener(
76       new ExtensionPointListener<KeyedLazyInstance<T>>() {
77         @Override
78         public void extensionAdded(@NotNull KeyedLazyInstance<T> extension, @NotNull PluginDescriptor pluginDescriptor) {
79           checkUpdateHighlighters(extension.getKey(), false);
80         }
81
82         @Override
83         public void extensionRemoved(@NotNull KeyedLazyInstance<T> extension, @NotNull PluginDescriptor pluginDescriptor) {
84           checkUpdateHighlighters(extension.getKey(), true);
85         }
86       }, parentDisposable);
87   }
88
89   private void checkUpdateHighlighters(String key, boolean updateSynchronously) {
90     if (myFile != null) {
91       FileType fileType = myFile.getFileType();
92       boolean needUpdate = (fileType.getName().equals(key) ||
93                             (fileType instanceof LanguageFileType && ((LanguageFileType)fileType).getLanguage().getID().equals(key)));
94       if (!needUpdate) return;
95     }
96
97     if (ApplicationManager.getApplication().isDispatchThread() && updateSynchronously) {
98       updateHighlightersSynchronously();
99     }
100     else {
101       updateHighlighters();
102     }
103   }
104
105   public void updateHighlightersAsync() {
106     ReadAction
107       .nonBlocking(() -> createHighlighter())
108       .expireWith(myProject)
109       .expireWhen(() -> (myFile != null && !myFile.isValid()) || myEditor.isDisposed())
110       .coalesceBy(EditorHighlighterUpdater.class, myEditor)
111       .finishOnUiThread(ModalityState.any(), highlighter -> myEditor.setHighlighter(highlighter))
112       .submit(NonUrgentExecutor.getInstance());
113   }
114
115   @NotNull
116   protected EditorHighlighter createHighlighter() {
117     EditorHighlighter highlighter = myFile != null
118                                     ? EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, myFile)
119                                     : new EmptyEditorHighlighter(EditorColorsManager.getInstance().getGlobalScheme().getAttributes(HighlighterColors.TEXT));
120     highlighter.setText(myEditor.getDocument().getImmutableCharSequence());
121     return highlighter;
122   }
123
124   /**
125    * Updates editors' highlighters. This should be done when the opened file
126    * changes its file type.
127    */
128   public void updateHighlighters() {
129     if (!myProject.isDisposed() && !myEditor.isDisposed()) {
130       updateHighlightersAsync();
131     }
132   }
133
134   private void updateHighlightersSynchronously() {
135     if (!myProject.isDisposed() && !myEditor.isDisposed()) {
136       myEditor.setHighlighter(createHighlighter());
137     }
138   }
139
140   @TestOnly
141   public static void completeAsyncTasks() {
142     NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
143   }
144
145   /**
146    * Listen changes of file types. When type of the file changes we need
147    * to also change highlighter.
148    */
149   private final class MyFileTypeListener implements FileTypeListener {
150     @Override
151     public void fileTypesChanged(@NotNull final FileTypeEvent event) {
152       ApplicationManager.getApplication().assertIsDispatchThread();
153     // File can be invalid after file type changing. The editor should be removed
154       // by the FileEditorManager if it's invalid.
155       FileType type = event.getRemovedFileType();
156       if (type != null && !(type instanceof AbstractFileType)) {
157         // Plugin is being unloaded, so we need to release plugin classes immediately
158         updateHighlightersSynchronously();
159       }
160       else {
161         updateHighlighters();
162       }
163     }
164   }
165
166 }