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
.problems
;
19 import com
.intellij
.codeInsight
.daemon
.impl
.*;
20 import com
.intellij
.codeInsight
.daemon
.impl
.analysis
.HighlightInfoHolder
;
21 import com
.intellij
.lang
.annotation
.HighlightSeverity
;
22 import com
.intellij
.openapi
.Disposable
;
23 import com
.intellij
.openapi
.application
.ApplicationManager
;
24 import com
.intellij
.openapi
.application
.impl
.LaterInvocator
;
25 import com
.intellij
.openapi
.editor
.Document
;
26 import com
.intellij
.openapi
.extensions
.Extensions
;
27 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
28 import com
.intellij
.openapi
.fileEditor
.FileEditor
;
29 import com
.intellij
.openapi
.fileEditor
.FileEditorManager
;
30 import com
.intellij
.openapi
.fileEditor
.TextEditor
;
31 import com
.intellij
.openapi
.module
.Module
;
32 import com
.intellij
.openapi
.module
.ModuleUtil
;
33 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
34 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
35 import com
.intellij
.openapi
.progress
.ProgressManager
;
36 import com
.intellij
.openapi
.project
.Project
;
37 import com
.intellij
.openapi
.util
.Computable
;
38 import com
.intellij
.openapi
.util
.Condition
;
39 import com
.intellij
.openapi
.util
.Disposer
;
40 import com
.intellij
.openapi
.util
.TextRange
;
41 import com
.intellij
.openapi
.util
.text
.StringUtil
;
42 import com
.intellij
.openapi
.vcs
.FileStatusListener
;
43 import com
.intellij
.openapi
.vcs
.FileStatusManager
;
44 import com
.intellij
.openapi
.vfs
.*;
45 import com
.intellij
.openapi
.wm
.StatusBar
;
46 import com
.intellij
.openapi
.wm
.WindowManager
;
47 import com
.intellij
.openapi
.wm
.ex
.StatusBarEx
;
48 import com
.intellij
.problems
.Problem
;
49 import com
.intellij
.problems
.WolfTheProblemSolver
;
50 import com
.intellij
.psi
.*;
51 import com
.intellij
.util
.containers
.ContainerUtil
;
52 import gnu
.trove
.THashMap
;
53 import gnu
.trove
.THashSet
;
54 import org
.jetbrains
.annotations
.NonNls
;
55 import org
.jetbrains
.annotations
.NotNull
;
62 public class WolfTheProblemSolverImpl
extends WolfTheProblemSolver
{
63 private final Map
<VirtualFile
, ProblemFileInfo
> myProblems
= new THashMap
<VirtualFile
, ProblemFileInfo
>();
64 private final Collection
<VirtualFile
> myCheckingQueue
= new THashSet
<VirtualFile
>(10);
66 private final Project myProject
;
67 private final List
<ProblemListener
> myProblemListeners
= ContainerUtil
.createEmptyCOWList();
68 private final List
<Condition
<VirtualFile
>> myFilters
= ContainerUtil
.createEmptyCOWList();
69 private boolean myFiltersLoaded
= false;
70 private final ProblemListener fireProblemListeners
= new ProblemListener() {
71 public void problemsAppeared(VirtualFile file
) {
72 for (final ProblemListener problemListener
: myProblemListeners
) {
73 problemListener
.problemsAppeared(file
);
77 public void problemsChanged(VirtualFile file
) {
78 for (final ProblemListener problemListener
: myProblemListeners
) {
79 problemListener
.problemsChanged(file
);
83 public void problemsDisappeared(VirtualFile file
) {
84 for (final ProblemListener problemListener
: myProblemListeners
) {
85 problemListener
.problemsDisappeared(file
);
90 private final VirtualFileListener myVirtualFileListener
= new VirtualFileAdapter() {
91 public void fileDeleted(final VirtualFileEvent event
) {
92 onDeleted(event
.getFile());
95 public void fileMoved(final VirtualFileMoveEvent event
) {
96 onDeleted(event
.getFile());
99 private void onDeleted(final VirtualFile file
) {
100 if (file
.isDirectory()) {
109 private void doRemove(VirtualFile problemFile
) {
111 synchronized (myProblems
) {
112 old
= myProblems
.remove(problemFile
);
114 synchronized (myCheckingQueue
) {
115 myCheckingQueue
.remove(problemFile
);
118 // firing outside lock
119 fireProblemListeners
.problemsDisappeared(problemFile
);
123 private final PsiTreeChangeListener myChangeListener
;
125 private static class ProblemFileInfo
{
126 private final Collection
<Problem
> problems
= new THashSet
<Problem
>();
127 private boolean hasSyntaxErrors
;
129 public boolean equals(final Object o
) {
130 if (this == o
) return true;
131 if (o
== null || getClass() != o
.getClass()) return false;
133 final ProblemFileInfo that
= (ProblemFileInfo
)o
;
135 if (hasSyntaxErrors
!= that
.hasSyntaxErrors
) return false;
136 if (!problems
.equals(that
.problems
)) return false;
141 public int hashCode() {
142 int result
= problems
.hashCode();
143 result
= 31 * result
+ (hasSyntaxErrors ?
1 : 0);
148 public WolfTheProblemSolverImpl(Project project
, PsiManager psiManager
, VirtualFileManager virtualFileManager
) {
150 myChangeListener
= new PsiTreeChangeAdapter() {
151 public void childAdded(PsiTreeChangeEvent event
) {
152 childrenChanged(event
);
155 public void childRemoved(PsiTreeChangeEvent event
) {
156 childrenChanged(event
);
159 public void childReplaced(PsiTreeChangeEvent event
) {
160 childrenChanged(event
);
163 public void childMoved(PsiTreeChangeEvent event
) {
164 childrenChanged(event
);
167 public void propertyChanged(PsiTreeChangeEvent event
) {
168 childrenChanged(event
);
171 public void childrenChanged(PsiTreeChangeEvent event
) {
172 clearSyntaxErrorFlag(event
);
175 psiManager
.addPsiTreeChangeListener(myChangeListener
);
176 virtualFileManager
.addVirtualFileListener(myVirtualFileListener
, myProject
);
177 FileStatusManager fileStatusManager
= FileStatusManager
.getInstance(myProject
);
178 if (fileStatusManager
!= null) { //tests?
179 fileStatusManager
.addFileStatusListener(new FileStatusListener() {
180 public void fileStatusesChanged() {
184 public void fileStatusChanged(@NotNull VirtualFile virtualFile
) {
185 fileStatusesChanged();
191 private void clearInvalidFiles() {
193 synchronized (myProblems
) {
194 files
= myProblems
.keySet().toArray(new VirtualFile
[myProblems
.size()]);
196 for (VirtualFile problemFile
: files
) {
197 if (!problemFile
.isValid() || !isToBeHighlighted(problemFile
)) {
198 doRemove(problemFile
);
203 private void clearSyntaxErrorFlag(final PsiTreeChangeEvent event
) {
204 PsiFile file
= event
.getFile();
205 if (file
== null) return;
206 VirtualFile virtualFile
= file
.getVirtualFile();
207 if (virtualFile
== null) return;
208 synchronized (myProblems
) {
209 ProblemFileInfo info
= myProblems
.get(virtualFile
);
210 if (info
== null) return;
211 info
.hasSyntaxErrors
= false;
215 public void projectOpened() {
218 public void projectClosed() {
223 public String
getComponentName() {
227 public void initComponent() {
231 public void disposeComponent() {
235 public void startCheckingIfVincentSolvedProblemsYet(final ProgressIndicator progress
, ProgressableTextEditorHighlightingPass pass
) throws ProcessCanceledException
{
236 if (!myProject
.isOpen()) return;
239 List
<VirtualFile
> files
;
240 synchronized (myCheckingQueue
) {
241 files
= new ArrayList
<VirtualFile
>(myCheckingQueue
);
244 pass
.setProgressLimit(size
);
245 final StatusBar statusBar
= WindowManager
.getInstance().getStatusBar(myProject
);
246 String oldInfo
= saveStatusBarInfo(statusBar
);
248 for (final VirtualFile virtualFile
: files
) {
249 progress
.checkCanceled();
250 if (virtualFile
== null) break;
251 ApplicationManager
.getApplication().invokeLater(new Runnable() {
253 statusBar
.setInfo("Checking '" + virtualFile
.getPresentableUrl() + "'");
256 if (!virtualFile
.isValid() || orderVincentToCleanTheCar(virtualFile
, progress
)) {
257 doRemove(virtualFile
);
259 pass
.advanceProgress(1);
263 restoreStatusBarInfo(statusBar
, oldInfo
);
267 private static class HaveGotErrorException
extends RuntimeException
{
268 private final HighlightInfo myHighlightInfo
;
269 private final boolean myHasErrorElement
;
271 private HaveGotErrorException(HighlightInfo info
, final boolean hasErrorElement
) {
272 myHighlightInfo
= info
;
273 myHasErrorElement
= hasErrorElement
;
277 // returns true if car has been cleaned
278 private boolean orderVincentToCleanTheCar(final VirtualFile file
, ProgressIndicator progressIndicator
) throws ProcessCanceledException
{
279 if (!isToBeHighlighted(file
)) {
281 return true; // file is going to be red waved no more
283 if (hasSyntaxErrors(file
)) {
284 // it's no use anyway to try clean the file with syntax errors, only changing the file itself can help
287 if (myProject
.isDisposed()) return false;
288 if (willBeHighlightedAnyway(file
)) return false;
289 final PsiFile psiFile
= PsiManager
.getInstance(myProject
).findFile(file
);
290 if (psiFile
== null) return false;
291 final Document document
= FileDocumentManager
.getInstance().getDocument(file
);
292 if (document
== null) return false;
295 GeneralHighlightingPass pass
= new GeneralHighlightingPass(myProject
, psiFile
, document
, 0, document
.getTextLength(), false) {
296 protected HighlightInfoHolder
createInfoHolder(final PsiFile file
) {
297 return new HighlightInfoHolder(file
, HighlightInfoFilter
.EMPTY_ARRAY
) {
298 public boolean add(HighlightInfo info
) {
299 if (info
!= null && info
.getSeverity() == HighlightSeverity
.ERROR
) {
300 throw new HaveGotErrorException(info
, myHasErrorElement
);
302 return super.add(info
);
308 pass
.collectInformation(progressIndicator
);
310 catch (HaveGotErrorException e
) {
311 ProblemImpl problem
= new ProblemImpl(file
, e
.myHighlightInfo
, e
.myHasErrorElement
);
312 reportProblems(file
, Collections
.<Problem
>singleton(problem
));
319 private static String
saveStatusBarInfo(StatusBar statusBar
) {
320 String oldInfo
= null;
321 if (statusBar
instanceof StatusBarEx
) {
322 oldInfo
= ((StatusBarEx
)statusBar
).getInfo();
327 private static void restoreStatusBarInfo(final StatusBar statusBar
, final String oldInfo
) {
328 if (statusBar
instanceof StatusBarEx
) {
329 LaterInvocator
.invokeLater(new Runnable() {
331 statusBar
.setInfo(oldInfo
);
337 public boolean hasSyntaxErrors(final VirtualFile file
) {
338 synchronized (myProblems
) {
339 ProblemFileInfo info
= myProblems
.get(file
);
340 return info
!= null && info
.hasSyntaxErrors
;
344 private boolean willBeHighlightedAnyway(final VirtualFile file
) {
345 // opened in some editor, and hence will be highlighted automatically sometime later
346 FileEditor
[] selectedEditors
= FileEditorManager
.getInstance(myProject
).getSelectedEditors();
347 for (FileEditor editor
: selectedEditors
) {
348 if (!(editor
instanceof TextEditor
)) continue;
349 Document document
= ((TextEditor
)editor
).getEditor().getDocument();
350 PsiFile psiFile
= PsiDocumentManager
.getInstance(myProject
).getCachedPsiFile(document
);
351 if (psiFile
== null) continue;
352 if (file
== psiFile
.getVirtualFile()) return true;
357 public boolean hasProblemFilesBeneath(Condition
<VirtualFile
> condition
) {
358 if (!myProject
.isOpen()) return false;
359 synchronized (myProblems
) {
360 if (!myProblems
.isEmpty()) {
361 Set
<VirtualFile
> problemFiles
= myProblems
.keySet();
362 for (VirtualFile problemFile
: problemFiles
) {
363 if (problemFile
.isValid() && condition
.value(problemFile
)) return true;
370 public boolean hasProblemFilesBeneath(final Module scope
) {
371 return hasProblemFilesBeneath(new Condition
<VirtualFile
>() {
372 public boolean value(final VirtualFile virtualFile
) {
373 return ModuleUtil
.moduleContainsFile(scope
, virtualFile
, false);
378 public void addProblemListener(ProblemListener listener
) {
379 myProblemListeners
.add(listener
);
382 public void addProblemListener(final ProblemListener listener
, Disposable parentDisposable
) {
383 addProblemListener(listener
);
384 Disposer
.register(parentDisposable
, new Disposable() {
385 public void dispose() {
386 removeProblemListener(listener
);
391 public void removeProblemListener(ProblemListener listener
) {
392 myProblemListeners
.remove(listener
);
395 public void registerFileHighlightFilter(final Condition
<VirtualFile
> filter
, Disposable parentDisposable
) {
396 myFilters
.add(filter
);
397 Disposer
.register(parentDisposable
, new Disposable() {
398 public void dispose() {
399 myFilters
.remove(filter
);
405 public void queue(VirtualFile suspiciousFile
) {
406 if (!isToBeHighlighted(suspiciousFile
)) return;
407 synchronized (myCheckingQueue
) {
408 myCheckingQueue
.add(suspiciousFile
);
412 public boolean isProblemFile(VirtualFile virtualFile
) {
413 synchronized (myProblems
) {
414 return myProblems
.containsKey(virtualFile
);
418 private boolean isToBeHighlighted(VirtualFile virtualFile
) {
419 if (virtualFile
== null) return false;
421 synchronized (myFilters
) {
422 if (!myFiltersLoaded
) {
423 myFiltersLoaded
= true;
424 Collections
.addAll(myFilters
, Extensions
.getExtensions(FILTER_EP_NAME
, myProject
));
427 for (final Condition
<VirtualFile
> filter
: myFilters
) {
428 ProgressManager
.checkCanceled();
429 if (filter
.value(virtualFile
)) {
437 public void weHaveGotProblems(@NotNull final VirtualFile virtualFile
, @NotNull List
<Problem
> problems
) {
438 if (problems
.isEmpty()) return;
439 if (!isToBeHighlighted(virtualFile
)) return;
440 boolean fireListener
= false;
441 synchronized (myProblems
) {
442 ProblemFileInfo storedProblems
= myProblems
.get(virtualFile
);
443 if (storedProblems
== null) {
444 storedProblems
= new ProblemFileInfo();
446 myProblems
.put(virtualFile
, storedProblems
);
449 storedProblems
.problems
.addAll(problems
);
451 synchronized (myCheckingQueue
) {
452 myCheckingQueue
.add(virtualFile
);
455 fireProblemListeners
.problemsAppeared(virtualFile
);
459 public void clearProblems(@NotNull VirtualFile virtualFile
) {
460 doRemove(virtualFile
);
463 public Problem
convertToProblem(final VirtualFile virtualFile
, final HighlightSeverity severity
,
464 final TextRange textRange
, final String messageText
) {
465 if (virtualFile
== null || textRange
.getStartOffset() < 0 || textRange
.getLength() < 0 ) return null;
466 HighlightInfo info
= ApplicationManager
.getApplication().runReadAction(new Computable
<HighlightInfo
>() {
467 public HighlightInfo
compute() {
468 return HighlightInfo
.createHighlightInfo(HighlightInfo
.convertSeverity(severity
), textRange
, messageText
);
471 return new ProblemImpl(virtualFile
, info
, false);
474 public Problem
convertToProblem(final VirtualFile virtualFile
, final int line
, final int column
, final String
[] message
) {
475 if (virtualFile
== null || virtualFile
.isDirectory() || virtualFile
.getFileType().isBinary()) return null;
476 HighlightInfo info
= ApplicationManager
.getApplication().runReadAction(new Computable
<HighlightInfo
>() {
477 public HighlightInfo
compute() {
478 TextRange textRange
= getTextRange(virtualFile
, line
, column
);
479 return HighlightInfo
.createHighlightInfo(HighlightInfoType
.ERROR
, textRange
, StringUtil
.join(message
, "\n"));
482 if (info
== null) return null;
483 return new ProblemImpl(virtualFile
, info
, false);
486 public void reportProblems(final VirtualFile file
, Collection
<Problem
> problems
) {
487 if (problems
.isEmpty()) {
491 if (!isToBeHighlighted(file
)) return;
492 boolean hasProblemsBefore
;
494 synchronized (myProblems
) {
495 final ProblemFileInfo oldInfo
= myProblems
.remove(file
);
496 hasProblemsBefore
= oldInfo
!= null;
497 ProblemFileInfo newInfo
= new ProblemFileInfo();
498 myProblems
.put(file
, newInfo
);
499 for (Problem problem
: problems
) {
500 newInfo
.problems
.add(problem
);
501 newInfo
.hasSyntaxErrors
|= ((ProblemImpl
)problem
).isSyntaxOnly();
503 fireChanged
= hasProblemsBefore
&& !oldInfo
.equals(newInfo
);
505 synchronized (myCheckingQueue
) {
506 myCheckingQueue
.add(file
);
508 if (!hasProblemsBefore
) {
509 fireProblemListeners
.problemsAppeared(file
);
511 else if (fireChanged
) {
512 fireProblemListeners
.problemsChanged(file
);
517 private static TextRange
getTextRange(final VirtualFile virtualFile
, int line
, final int column
) {
518 Document document
= FileDocumentManager
.getInstance().getDocument(virtualFile
);
519 if (line
> document
.getLineCount()) line
= document
.getLineCount();
520 line
= line
<= 0 ?
0 : line
- 1;
521 int offset
= document
.getLineStartOffset(line
) + (column
<= 0 ?
0 : column
- 1);
522 return new TextRange(offset
, offset
);