2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.codeInsight.daemon.impl;
19 import com.intellij.codeHighlighting.DirtyScopeTrackingHighlightingPassFactory;
20 import com.intellij.codeHighlighting.Pass;
21 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
22 import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory;
23 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
24 import com.intellij.openapi.editor.Document;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.psi.PsiCompiledElement;
28 import com.intellij.psi.PsiDocumentManager;
29 import com.intellij.psi.PsiFile;
30 import com.intellij.util.ArrayUtil;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
35 import java.util.ArrayList;
36 import java.util.List;
43 public class TextEditorHighlightingPassRegistrarImpl extends TextEditorHighlightingPassRegistrarEx {
44 private final TIntObjectHashMap<PassConfig> myRegisteredPassFactories = new TIntObjectHashMap<PassConfig>();
45 private final List<DirtyScopeTrackingHighlightingPassFactory> myDirtyScopeTrackingFactories = new ArrayList<DirtyScopeTrackingHighlightingPassFactory>();
46 private int nextAvailableId = Pass.LAST_PASS + 1;
47 private boolean checkedForCycles;
48 private final Project myProject;
50 public TextEditorHighlightingPassRegistrarImpl(Project project) {
54 private static class PassConfig {
55 private final TextEditorHighlightingPassFactory passFactory;
56 private final int[] startingPredecessorIds;
57 private final int[] completionPredecessorIds;
59 private PassConfig(@NotNull TextEditorHighlightingPassFactory passFactory, @NotNull int[] completionPredecessorIds, @NotNull int[] startingPredecessorIds) {
60 this.completionPredecessorIds = completionPredecessorIds;
61 this.startingPredecessorIds = startingPredecessorIds;
62 this.passFactory = passFactory;
66 public void registerTextEditorHighlightingPass(TextEditorHighlightingPassFactory factory, int anchor, int anchorPass) {
67 Anchor anc = Anchor.FIRST;
69 case FIRST : anc = Anchor.FIRST;
71 case LAST : anc = Anchor.LAST;
73 case BEFORE : anc = Anchor.BEFORE;
75 case AFTER : anc = Anchor.AFTER;
78 registerTextEditorHighlightingPass(factory, anc, anchorPass, true, true);
81 public synchronized int registerTextEditorHighlightingPass(@NotNull TextEditorHighlightingPassFactory factory,
82 @Nullable int[] runAfterCompletionOf,
83 @Nullable int[] runAfterOfStartingOf,
84 boolean runIntentionsPassAfter,
86 assert !checkedForCycles;
87 PassConfig info = new PassConfig(factory,
88 runAfterCompletionOf == null || runAfterCompletionOf.length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : runAfterCompletionOf,
89 runAfterOfStartingOf == null || runAfterOfStartingOf.length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : runAfterOfStartingOf);
90 int passId = forcedPassId == -1 ? nextAvailableId++ : forcedPassId;
91 PassConfig registered = myRegisteredPassFactories.get(passId);
92 assert registered == null: "Pass id "+passId +" has already been registered in: "+ registered.passFactory;
93 myRegisteredPassFactories.put(passId, info);
94 if (factory instanceof DirtyScopeTrackingHighlightingPassFactory) {
95 myDirtyScopeTrackingFactories.add((DirtyScopeTrackingHighlightingPassFactory) factory);
101 public List<TextEditorHighlightingPass> instantiatePasses(@NotNull final PsiFile psiFile, @NotNull final Editor editor, @NotNull final int[] passesToIgnore) {
102 synchronized (this) {
103 if (!checkedForCycles) {
104 checkedForCycles = true;
108 PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
109 final Document document = editor.getDocument();
110 PsiFile fileFromDoc = documentManager.getPsiFile(document);
111 if (!(fileFromDoc instanceof PsiCompiledElement)) {
112 assert fileFromDoc == psiFile : "Files are different: " + psiFile + ";" + fileFromDoc;
113 Document documentFromFile = documentManager.getDocument(psiFile);
114 assert documentFromFile == document : "Documents are different: " + document + ";" + documentFromFile;
116 final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass = new TIntObjectHashMap<TextEditorHighlightingPass>();
117 final TIntArrayList passesRefusedToCreate = new TIntArrayList();
118 myRegisteredPassFactories.forEachKey(new TIntProcedure() {
119 public boolean execute(int passId) {
120 if (ArrayUtil.find(passesToIgnore, passId) != -1) {
123 PassConfig passConfig = myRegisteredPassFactories.get(passId);
124 TextEditorHighlightingPassFactory factory = passConfig.passFactory;
125 final TextEditorHighlightingPass pass = factory.createHighlightingPass(psiFile, editor);
128 passesRefusedToCreate.add(passId);
131 TIntArrayList ids = new TIntArrayList(passConfig.completionPredecessorIds.length);
132 for (int id : passConfig.completionPredecessorIds) {
133 if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
135 pass.setCompletionPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
136 ids = new TIntArrayList(passConfig.startingPredecessorIds.length);
137 for (int id : passConfig.startingPredecessorIds) {
138 if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
140 pass.setStartingPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
142 id2Pass.put(passId, pass);
148 final FileStatusMap statusMap = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject)).getFileStatusMap();
149 passesRefusedToCreate.forEach(new TIntProcedure() {
150 public boolean execute(int passId) {
151 statusMap.markFileUpToDate(document, psiFile, passId);
156 //sort is mainly for tests which expect passes to be run sequentially and produce correct results
157 return topoSort(id2Pass);
160 private static List<TextEditorHighlightingPass> topoSort(final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass) {
161 final Set<TextEditorHighlightingPass> topPasses = new THashSet<TextEditorHighlightingPass>(id2Pass.size());
162 id2Pass.forEachValue(new TObjectProcedure<TextEditorHighlightingPass>() {
163 public boolean execute(TextEditorHighlightingPass object) {
164 topPasses.add(object);
168 id2Pass.forEachValue(new TObjectProcedure<TextEditorHighlightingPass>() {
169 public boolean execute(TextEditorHighlightingPass pass) {
170 for (int id : pass.getCompletionPredecessorIds()) {
171 TextEditorHighlightingPass pred = id2Pass.get(id);
172 if (pred != null) { //can be null if filtered out by passesToIgnore
173 topPasses.remove(pred);
176 for (int id : pass.getStartingPredecessorIds()) {
177 TextEditorHighlightingPass pred = id2Pass.get(id);
178 if (pred != null) { //can be null if filtered out by passesToIgnore
179 topPasses.remove(pred);
185 List<TextEditorHighlightingPass> result = new ArrayList<TextEditorHighlightingPass>();
186 for (TextEditorHighlightingPass topPass : topPasses) {
187 layout(topPass, result, id2Pass);
192 private static void layout(@NotNull final TextEditorHighlightingPass pass,
193 @NotNull final List<TextEditorHighlightingPass> result,
194 @NotNull final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass) {
195 if (result.contains(pass)) return;
196 for (int id : pass.getCompletionPredecessorIds()) {
197 TextEditorHighlightingPass pred = id2Pass.get(id);
199 layout(pred, result, id2Pass);
202 for (int id : pass.getStartingPredecessorIds()) {
203 TextEditorHighlightingPass pred = id2Pass.get(id);
205 layout(pred, result, id2Pass);
211 private void checkForCycles() {
212 final TIntObjectHashMap<TIntHashSet> transitivePredecessors = new TIntObjectHashMap<TIntHashSet>();
214 myRegisteredPassFactories.forEachEntry(new TIntObjectProcedure<PassConfig>() {
215 public boolean execute(int passId, PassConfig config) {
216 TIntHashSet allPredecessors = new TIntHashSet(config.completionPredecessorIds);
217 allPredecessors.addAll(config.startingPredecessorIds);
218 transitivePredecessors.put(passId, allPredecessors);
219 allPredecessors.forEach(new TIntProcedure() {
220 public boolean execute(int predecessorId) {
221 PassConfig predecessor = myRegisteredPassFactories.get(predecessorId);
222 if (predecessor == null) return true;
223 TIntHashSet transitives = transitivePredecessors.get(predecessorId);
224 if (transitives == null) {
225 transitives = new TIntHashSet();
226 transitivePredecessors.put(predecessorId, transitives);
228 transitives.addAll(predecessor.completionPredecessorIds);
229 transitives.addAll(predecessor.startingPredecessorIds);
236 transitivePredecessors.forEachKey(new TIntProcedure() {
237 public boolean execute(int passId) {
238 if (transitivePredecessors.get(passId).contains(passId)) {
239 throw new IllegalArgumentException("There is a cycle introduced involving pass " + myRegisteredPassFactories.get(passId).passFactory);
246 public List<DirtyScopeTrackingHighlightingPassFactory> getDirtyScopeTrackingFactories() {
247 return myDirtyScopeTrackingFactories;