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
;
58 private final boolean runIntentionsPassAfter
;
60 private PassConfig(@NotNull TextEditorHighlightingPassFactory passFactory
,
61 boolean runIntentionsPassAfter
,
62 @NotNull int[] completionPredecessorIds
,
63 @NotNull int[] startingPredecessorIds
) {
64 this.runIntentionsPassAfter
= runIntentionsPassAfter
;
65 this.completionPredecessorIds
= completionPredecessorIds
;
66 this.startingPredecessorIds
= startingPredecessorIds
;
67 this.passFactory
= passFactory
;
71 public void registerTextEditorHighlightingPass(TextEditorHighlightingPassFactory factory
, int anchor
, int anchorPass
) {
72 Anchor anc
= Anchor
.FIRST
;
74 case FIRST
: anc
= Anchor
.FIRST
;
76 case LAST
: anc
= Anchor
.LAST
;
78 case BEFORE
: anc
= Anchor
.BEFORE
;
80 case AFTER
: anc
= Anchor
.AFTER
;
83 registerTextEditorHighlightingPass(factory
, anc
, anchorPass
, true, true);
86 public synchronized int registerTextEditorHighlightingPass(@NotNull TextEditorHighlightingPassFactory factory
,
87 @Nullable int[] runAfterCompletionOf
,
88 @Nullable int[] runAfterOfStartingOf
,
89 boolean runIntentionsPassAfter
,
91 assert !checkedForCycles
;
92 PassConfig info
= new PassConfig(factory
, runIntentionsPassAfter
,
93 runAfterCompletionOf
== null || runAfterCompletionOf
.length
== 0 ? ArrayUtil
.EMPTY_INT_ARRAY
: runAfterCompletionOf
,
94 runAfterOfStartingOf
== null || runAfterOfStartingOf
.length
== 0 ? ArrayUtil
.EMPTY_INT_ARRAY
: runAfterOfStartingOf
);
95 int passId
= forcedPassId
== -1 ? nextAvailableId
++ : forcedPassId
;
96 PassConfig registered
= myRegisteredPassFactories
.get(passId
);
97 assert registered
== null: "Pass id "+passId
+" has already been registered: "+ registered
.passFactory
;
98 myRegisteredPassFactories
.put(passId
, info
);
99 if (factory
instanceof DirtyScopeTrackingHighlightingPassFactory
) {
100 myDirtyScopeTrackingFactories
.add((DirtyScopeTrackingHighlightingPassFactory
) factory
);
106 public List
<TextEditorHighlightingPass
> instantiatePasses(@NotNull final PsiFile psiFile
, @NotNull final Editor editor
, @NotNull final int[] passesToIgnore
) {
107 final int[] nextId
= new int[1];
108 synchronized (this) {
109 nextId
[0] = nextAvailableId
;
110 if (!checkedForCycles
) {
111 checkedForCycles
= true;
115 PsiDocumentManager documentManager
= PsiDocumentManager
.getInstance(myProject
);
116 PsiFile fileFromDoc
= documentManager
.getPsiFile(editor
.getDocument());
117 if (!(fileFromDoc
instanceof PsiCompiledElement
)) {
118 assert fileFromDoc
== psiFile
: "Files are different: " + psiFile
+ ";" + fileFromDoc
;
119 Document documentFromFile
= documentManager
.getDocument(psiFile
);
120 assert documentFromFile
== editor
.getDocument() : "Documents are different: " + editor
.getDocument() + ";" + documentFromFile
;
122 final TIntObjectHashMap
<TextEditorHighlightingPass
> id2Pass
= new TIntObjectHashMap
<TextEditorHighlightingPass
>();
123 final TIntArrayList ignoredPasses
= new TIntArrayList();
124 myRegisteredPassFactories
.forEachKey(new TIntProcedure() {
125 public boolean execute(int passId
) {
126 if (ArrayUtil
.find(passesToIgnore
, passId
) != -1) return true;
127 PassConfig passConfig
= myRegisteredPassFactories
.get(passId
);
128 TextEditorHighlightingPassFactory factory
= passConfig
.passFactory
;
129 final TextEditorHighlightingPass pass
= factory
.createHighlightingPass(psiFile
, editor
);
132 ignoredPasses
.add(passId
);
135 TIntArrayList ids
= new TIntArrayList(passConfig
.completionPredecessorIds
.length
);
136 for (int id
: passConfig
.completionPredecessorIds
) {
137 if (myRegisteredPassFactories
.containsKey(id
)) ids
.add(id
);
139 pass
.setCompletionPredecessorIds(ids
.isEmpty() ? ArrayUtil
.EMPTY_INT_ARRAY
: ids
.toNativeArray());
140 ids
= new TIntArrayList(passConfig
.startingPredecessorIds
.length
);
141 for (int id
: passConfig
.startingPredecessorIds
) {
142 if (myRegisteredPassFactories
.containsKey(id
)) ids
.add(id
);
144 pass
.setStartingPredecessorIds(ids
.isEmpty() ? ArrayUtil
.EMPTY_INT_ARRAY
: ids
.toNativeArray());
146 id2Pass
.put(passId
, pass
);
152 final FileStatusMap statusMap
= ((DaemonCodeAnalyzerImpl
)DaemonCodeAnalyzer
.getInstance(myProject
)).getFileStatusMap();
153 ignoredPasses
.forEach(new TIntProcedure() {
154 public boolean execute(int passId
) {
155 statusMap
.markFileUpToDate(editor
.getDocument(), psiFile
, passId
);
160 //sort is mainly for tests which expect passes to be run sequentially and produce correct results
161 return topoSort(id2Pass
);
164 private static List
<TextEditorHighlightingPass
> topoSort(final TIntObjectHashMap
<TextEditorHighlightingPass
> id2Pass
) {
165 final Set
<TextEditorHighlightingPass
> topPasses
= new THashSet
<TextEditorHighlightingPass
>(id2Pass
.size());
166 id2Pass
.forEachValue(new TObjectProcedure
<TextEditorHighlightingPass
>() {
167 public boolean execute(TextEditorHighlightingPass object
) {
168 topPasses
.add(object
);
172 id2Pass
.forEachValue(new TObjectProcedure
<TextEditorHighlightingPass
>() {
173 public boolean execute(TextEditorHighlightingPass pass
) {
174 for (int id
: pass
.getCompletionPredecessorIds()) {
175 TextEditorHighlightingPass pred
= id2Pass
.get(id
);
176 if (pred
!= null) { //can be null if filtered out by passesToIgnore
177 topPasses
.remove(pred
);
180 for (int id
: pass
.getStartingPredecessorIds()) {
181 TextEditorHighlightingPass pred
= id2Pass
.get(id
);
182 if (pred
!= null) { //can be null if filtered out by passesToIgnore
183 topPasses
.remove(pred
);
189 List
<TextEditorHighlightingPass
> result
= new ArrayList
<TextEditorHighlightingPass
>();
190 for (TextEditorHighlightingPass topPass
: topPasses
) {
191 layout(topPass
, result
, id2Pass
);
196 private static void layout(@NotNull final TextEditorHighlightingPass pass
,
197 @NotNull final List
<TextEditorHighlightingPass
> result
,
198 @NotNull final TIntObjectHashMap
<TextEditorHighlightingPass
> id2Pass
) {
199 if (result
.contains(pass
)) return;
200 for (int id
: pass
.getCompletionPredecessorIds()) {
201 TextEditorHighlightingPass pred
= id2Pass
.get(id
);
203 layout(pred
, result
, id2Pass
);
206 for (int id
: pass
.getStartingPredecessorIds()) {
207 TextEditorHighlightingPass pred
= id2Pass
.get(id
);
209 layout(pred
, result
, id2Pass
);
215 private void checkForCycles() {
216 final TIntObjectHashMap
<TIntHashSet
> transitivePredecessors
= new TIntObjectHashMap
<TIntHashSet
>();
218 myRegisteredPassFactories
.forEachEntry(new TIntObjectProcedure
<PassConfig
>() {
219 public boolean execute(int passId
, PassConfig config
) {
220 TIntHashSet allPredecessors
= new TIntHashSet(config
.completionPredecessorIds
);
221 allPredecessors
.addAll(config
.startingPredecessorIds
);
222 transitivePredecessors
.put(passId
, allPredecessors
);
223 allPredecessors
.forEach(new TIntProcedure() {
224 public boolean execute(int predecessorId
) {
225 PassConfig predecessor
= myRegisteredPassFactories
.get(predecessorId
);
226 if (predecessor
== null) return true;
227 TIntHashSet transitives
= transitivePredecessors
.get(predecessorId
);
228 if (transitives
== null) {
229 transitives
= new TIntHashSet();
230 transitivePredecessors
.put(predecessorId
, transitives
);
232 transitives
.addAll(predecessor
.completionPredecessorIds
);
233 transitives
.addAll(predecessor
.startingPredecessorIds
);
240 transitivePredecessors
.forEachKey(new TIntProcedure() {
241 public boolean execute(int passId
) {
242 if (transitivePredecessors
.get(passId
).contains(passId
)) {
243 throw new IllegalArgumentException("There is a cycle introduced involving pass " + myRegisteredPassFactories
.get(passId
).passFactory
);
250 public List
<DirtyScopeTrackingHighlightingPassFactory
> getDirtyScopeTrackingFactories() {
251 return myDirtyScopeTrackingFactories
;