run highlight visitors for injected fragments
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / problems / WolfTheProblemSolverImpl.java
blobff423b6dc5e1db52d846de254af6f17a2dda5923
1 /*
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;
57 import java.util.*;
59 /**
60 * @author cdr
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()) {
101 clearInvalidFiles();
103 else {
104 doRemove(file);
109 private void doRemove(VirtualFile problemFile) {
110 ProblemFileInfo old;
111 synchronized (myProblems) {
112 old = myProblems.remove(problemFile);
114 synchronized (myCheckingQueue) {
115 myCheckingQueue.remove(problemFile);
117 if (old != null) {
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;
138 return true;
141 public int hashCode() {
142 int result = problems.hashCode();
143 result = 31 * result + (hasSyntaxErrors ? 1 : 0);
144 return result;
148 public WolfTheProblemSolverImpl(Project project, PsiManager psiManager, VirtualFileManager virtualFileManager) {
149 myProject = project;
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() {
181 clearInvalidFiles();
184 public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
185 fileStatusesChanged();
191 private void clearInvalidFiles() {
192 VirtualFile[] files;
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() {
221 @NotNull
222 @NonNls
223 public String getComponentName() {
224 return "Problems";
227 public void initComponent() {
231 public void disposeComponent() {
235 public void startCheckingIfVincentSolvedProblemsYet(final ProgressIndicator progress, ProgressableTextEditorHighlightingPass pass) throws ProcessCanceledException{
236 if (!myProject.isOpen()) return;
238 int size;
239 List<VirtualFile> files;
240 synchronized (myCheckingQueue) {
241 files = new ArrayList<VirtualFile>(myCheckingQueue);
242 size = files.size();
244 pass.setProgressLimit(size);
245 final StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject);
246 String oldInfo = saveStatusBarInfo(statusBar);
247 try {
248 for (final VirtualFile virtualFile : files) {
249 progress.checkCanceled();
250 if (virtualFile == null) break;
251 ApplicationManager.getApplication().invokeLater(new Runnable() {
252 public void run() {
253 statusBar.setInfo("Checking '" + virtualFile.getPresentableUrl() + "'");
256 if (!virtualFile.isValid() || orderVincentToCleanTheCar(virtualFile, progress)) {
257 doRemove(virtualFile);
259 pass.advanceProgress(1);
262 finally {
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)) {
280 clearProblems(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
285 return false;
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;
294 try {
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));
313 return false;
315 clearProblems(file);
316 return true;
319 private static String saveStatusBarInfo(StatusBar statusBar) {
320 String oldInfo = null;
321 if (statusBar instanceof StatusBarEx) {
322 oldInfo = ((StatusBarEx)statusBar).getInfo();
324 return oldInfo;
327 private static void restoreStatusBarInfo(final StatusBar statusBar, final String oldInfo) {
328 if (statusBar instanceof StatusBarEx) {
329 LaterInvocator.invokeLater(new Runnable() {
330 public void run() {
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;
354 return false;
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;
366 return false;
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);
404 @Override
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)) {
430 return true;
434 return false;
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);
447 fireListener = true;
449 storedProblems.problems.addAll(problems);
451 synchronized (myCheckingQueue) {
452 myCheckingQueue.add(virtualFile);
454 if (fireListener) {
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()) {
488 clearProblems(file);
489 return;
491 if (!isToBeHighlighted(file)) return;
492 boolean hasProblemsBefore;
493 boolean fireChanged;
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);
516 @NotNull
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);