3 * Copyright 2000-2009 JetBrains s.r.o.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package com
.intellij
.codeInsight
.daemon
.impl
;
20 import com
.intellij
.codeHighlighting
.DirtyScopeTrackingHighlightingPassFactory
;
21 import com
.intellij
.codeHighlighting
.Pass
;
22 import com
.intellij
.codeHighlighting
.TextEditorHighlightingPassRegistrar
;
23 import com
.intellij
.codeInsight
.daemon
.DaemonCodeAnalyzer
;
24 import com
.intellij
.openapi
.Disposable
;
25 import com
.intellij
.openapi
.diagnostic
.Logger
;
26 import com
.intellij
.openapi
.editor
.Document
;
27 import com
.intellij
.openapi
.editor
.Editor
;
28 import com
.intellij
.openapi
.editor
.RangeMarker
;
29 import com
.intellij
.openapi
.editor
.ex
.DocumentEx
;
30 import com
.intellij
.openapi
.editor
.ex
.RangeMarkerEx
;
31 import com
.intellij
.openapi
.project
.Project
;
32 import com
.intellij
.openapi
.util
.TextRange
;
33 import com
.intellij
.psi
.PsiDocumentManager
;
34 import com
.intellij
.psi
.PsiFile
;
35 import com
.intellij
.util
.containers
.WeakHashMap
;
36 import gnu
.trove
.TIntObjectHashMap
;
37 import gnu
.trove
.TIntObjectProcedure
;
38 import org
.jetbrains
.annotations
.NotNull
;
39 import org
.jetbrains
.annotations
.Nullable
;
40 import org
.jetbrains
.annotations
.TestOnly
;
43 import java
.util
.concurrent
.atomic
.AtomicInteger
;
45 public class FileStatusMap
implements Disposable
{
46 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.daemon.impl.FileStatusMap");
47 private final Project myProject
;
48 private final Map
<Document
,FileStatus
> myDocumentToStatusMap
= new WeakHashMap
<Document
, FileStatus
>(); // all dirty if absent
49 private final AtomicInteger myClearModificationCount
= new AtomicInteger();
51 public FileStatusMap(@NotNull Project project
) {
55 public void dispose() {
60 public static TextRange
getDirtyTextRange(@NotNull Editor editor
, int passId
) {
61 Document document
= editor
.getDocument();
63 FileStatusMap me
= ((DaemonCodeAnalyzerImpl
)DaemonCodeAnalyzer
.getInstance(editor
.getProject())).getFileStatusMap();
64 TextRange dirtyScope
= me
.getFileDirtyScope(document
, passId
);
65 if (dirtyScope
== null) return null;
66 TextRange documentRange
= TextRange
.from(0, document
.getTextLength());
67 return documentRange
.intersection(dirtyScope
);
70 public void setErrorFoundFlag(@NotNull Document document
, boolean errorFound
) {
71 //GHP has found error. Flag is used by ExternalToolPass to decide whether to run or not
72 synchronized(myDocumentToStatusMap
) {
73 FileStatus status
= myDocumentToStatusMap
.get(document
);
75 if (!errorFound
) return;
76 PsiFile file
= PsiDocumentManager
.getInstance(myProject
).getPsiFile(document
);
77 assert file
!= null : document
;
78 status
= new FileStatus(file
,document
);
79 myDocumentToStatusMap
.put(document
, status
);
81 status
.errorFound
= errorFound
;
85 public boolean wasErrorFound(@NotNull Document document
) {
86 synchronized(myDocumentToStatusMap
) {
87 FileStatus status
= myDocumentToStatusMap
.get(document
);
88 return status
!= null && status
.errorFound
;
92 private static class FileStatus
{
93 public boolean defensivelyMarked
; // file marked dirty without knowledge of specific dirty region. Subsequent markScopeDirty can refine dirty scope, not extend it
94 private boolean wolfPassFinfished
;
95 private final TIntObjectHashMap
<RangeMarker
> dirtyScopes
= new TIntObjectHashMap
<RangeMarker
>();
96 private boolean errorFound
;
98 private FileStatus(@NotNull PsiFile file
, @NotNull Document document
) {
99 markWholeFile(file
, document
, file
.getProject());
102 private void markWholeFile(PsiFile file
, Document document
, Project project
) {
103 dirtyScopes
.put(Pass
.UPDATE_ALL
, createWholeFileMarker(file
, document
));
104 dirtyScopes
.put(Pass
.EXTERNAL_TOOLS
, createWholeFileMarker(file
, document
));
105 dirtyScopes
.put(Pass
.LOCAL_INSPECTIONS
, createWholeFileMarker(file
, document
));
106 TextEditorHighlightingPassRegistrarImpl registrar
= (TextEditorHighlightingPassRegistrarImpl
) TextEditorHighlightingPassRegistrar
.getInstance(project
);
107 for(DirtyScopeTrackingHighlightingPassFactory factory
: registrar
.getDirtyScopeTrackingFactories()) {
108 dirtyScopes
.put(factory
.getPassId(), createWholeFileMarker(file
, document
));
112 private static RangeMarker
createWholeFileMarker(PsiFile file
, Document document
) {
113 int length
= file
== null ?
-1 : Math
.min(file
.getTextLength(), document
.getTextLength());
114 return length
== -1 ?
null : document
.createRangeMarker(0, length
);
117 public boolean allDirtyScopesAreNull() {
118 for (Object o
: dirtyScopes
.getValues()) {
119 if (o
!= null) return false;
124 public void combineScopesWith(final TextRange scope
, final int fileLength
, final Document document
) {
125 dirtyScopes
.forEachEntry(new TIntObjectProcedure
<RangeMarker
>() {
126 public boolean execute(int id
, RangeMarker oldScope
) {
127 RangeMarker newScope
= combineScopes(oldScope
, scope
, fileLength
, document
);
128 if (newScope
!= oldScope
) {
129 dirtyScopes
.put(id
, newScope
);
137 public String
toString() {
138 final StringBuilder s
= new StringBuilder();
139 s
.append("defensivelyMarked = " + defensivelyMarked
);
140 s
.append("; wolfPassFinfished = " + wolfPassFinfished
);
141 s
.append("; errorFound = " + errorFound
);
142 s
.append("; dirtyScopes: (");
143 dirtyScopes
.forEachEntry(new TIntObjectProcedure
<RangeMarker
>() {
144 public boolean execute(int passId
, RangeMarker rangeMarker
) {
145 s
.append(" pass: " + passId
+ " -> " + rangeMarker
+ ";");
154 public void markAllFilesDirty() {
155 LOG
.debug("********************************* Mark all dirty");
156 synchronized(myDocumentToStatusMap
){
157 myDocumentToStatusMap
.clear();
159 myClearModificationCount
.incrementAndGet();
162 public void markFileUpToDate(@NotNull Document document
, @NotNull PsiFile file
, int passId
) {
163 synchronized(myDocumentToStatusMap
){
164 FileStatus status
= myDocumentToStatusMap
.get(document
);
166 status
= new FileStatus(file
,document
);
167 myDocumentToStatusMap
.put(document
, status
);
169 status
.defensivelyMarked
=false;
170 if (passId
== Pass
.WOLF
) {
171 status
.wolfPassFinfished
= true;
173 else if (status
.dirtyScopes
.containsKey(passId
)) {
174 RangeMarker marker
= status
.dirtyScopes
.get(passId
);
175 if (marker
!= null) {
176 ((DocumentEx
)document
).removeRangeMarker((RangeMarkerEx
)marker
);
177 status
.dirtyScopes
.put(passId
, null);
186 * @return null for processed file, whole file for untouched or entirely dirty file, range(usually code block) for dirty region (optimization)
189 public TextRange
getFileDirtyScope(@NotNull Document document
, int passId
) {
190 synchronized(myDocumentToStatusMap
){
191 PsiFile file
= PsiDocumentManager
.getInstance(myProject
).getPsiFile(document
);
192 if (CollectHighlightsUtil
.isOutsideSourceRootJavaFile(file
)) return null;
193 FileStatus status
= myDocumentToStatusMap
.get(document
);
195 return file
== null ?
null : file
.getTextRange();
197 if (status
.defensivelyMarked
) {
198 //PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
199 status
.markWholeFile(file
, document
, myProject
);
200 status
.defensivelyMarked
= false;
202 LOG
.assertTrue(status
.dirtyScopes
.containsKey(passId
), "Unknown pass " + passId
);
203 RangeMarker marker
= status
.dirtyScopes
.get(passId
);
204 return marker
== null ?
null : marker
.isValid() ?
new TextRange(marker
.getStartOffset(), marker
.getEndOffset()) : new TextRange(0, document
.getTextLength());
208 public void markFileScopeDirty(@NotNull Document document
, int passId
) {
209 synchronized(myDocumentToStatusMap
){
210 FileStatus status
= myDocumentToStatusMap
.get(document
);
214 if (passId
== Pass
.WOLF
) {
215 status
.wolfPassFinfished
= false;
218 LOG
.assertTrue(status
.dirtyScopes
.containsKey(passId
));
219 RangeMarker marker
= status
.dirtyScopes
.get(passId
);
220 if (marker
!= null) {
221 ((DocumentEx
)document
).removeRangeMarker((RangeMarkerEx
)marker
);
223 marker
= document
.createRangeMarker(0, document
.getTextLength());
224 status
.dirtyScopes
.put(passId
, marker
);
229 public void markFileScopeDirtyDefensively(@NotNull PsiFile file
) {
230 if (LOG
.isDebugEnabled()) {
231 LOG
.debug("********************************* Mark dirty file defensively: "+file
.getName());
233 // mark whole file dirty in case no subsequent PSI events will come, but file requires rehighlighting nevertheless
234 // e.g. in the case of quick typing/backspacing char
235 synchronized(myDocumentToStatusMap
){
236 Document document
= PsiDocumentManager
.getInstance(myProject
).getCachedDocument(file
);
237 if (document
== null) return;
238 FileStatus status
= myDocumentToStatusMap
.get(document
);
239 if (status
== null) return; // all dirty already
240 status
.defensivelyMarked
= true;
244 public void markFileScopeDirty(@NotNull Document document
, @NotNull TextRange scope
, int fileLength
) {
245 if (LOG
.isDebugEnabled()) {
246 LOG
.debug("********************************* Mark dirty: "+scope
);
248 synchronized(myDocumentToStatusMap
) {
249 FileStatus status
= myDocumentToStatusMap
.get(document
);
250 if (status
== null) return; // all dirty already
251 if (status
.defensivelyMarked
) {
252 status
.defensivelyMarked
= false;
254 status
.combineScopesWith(scope
, fileLength
,document
);
258 private static RangeMarker
combineScopes(RangeMarker old
, TextRange scope
, int textLength
, @NotNull Document document
) {
259 if (scope
== null) return old
;
261 return document
.createRangeMarker(scope
);
263 TextRange oldRange
= !old
.isValid() || scope
.getEndOffset() >= textLength ?
new TextRange(0, textLength
) : new TextRange(old
.getStartOffset(), old
.getEndOffset());
264 TextRange union
= scope
.union(oldRange
);
265 if (old
.isValid() && union
.equals(oldRange
)) {
269 ((DocumentEx
)document
).removeRangeMarker((RangeMarkerEx
)old
);
270 return document
.createRangeMarker(union
);
274 public boolean allDirtyScopesAreNull(@NotNull Document document
) {
275 synchronized (myDocumentToStatusMap
) {
276 PsiFile file
= PsiDocumentManager
.getInstance(myProject
).getPsiFile(document
);
277 if (CollectHighlightsUtil
.isOutsideSourceRootJavaFile(file
)) return true;
279 FileStatus status
= myDocumentToStatusMap
.get(document
);
280 return status
!= null && !status
.defensivelyMarked
&& status
.wolfPassFinfished
&& status
.allDirtyScopesAreNull();
285 public void assertAllDirtyScopesAreNull(@NotNull Document document
) {
286 synchronized (myDocumentToStatusMap
) {
287 FileStatus status
= myDocumentToStatusMap
.get(document
);
288 assert status
!= null && !status
.defensivelyMarked
&& status
.wolfPassFinfished
&& status
.allDirtyScopesAreNull() : status
;