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.HighlightDisplayLevel;
20 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
21 import com.intellij.codeInsight.daemon.DaemonBundle;
22 import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil;
23 import com.intellij.ide.PowerSaveMode;
24 import com.intellij.lang.annotation.HighlightSeverity;
25 import com.intellij.openapi.editor.Document;
26 import com.intellij.openapi.editor.markup.ErrorStripeRenderer;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.IconLoader;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.psi.PsiFile;
31 import com.intellij.ui.LayeredIcon;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.Processor;
34 import com.intellij.util.ui.EmptyIcon;
35 import com.intellij.util.ui.UIUtil;
36 import gnu.trove.TIntArrayList;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.Nullable;
43 import java.util.ArrayList;
44 import java.util.List;
46 public class TrafficLightRenderer implements ErrorStripeRenderer {
47 private static final Icon IN_PROGRESS_ICON = IconLoader.getIcon("/general/errorsInProgress.png");
48 private static final Icon NO_ANALYSIS_ICON = IconLoader.getIcon("/general/noAnalysis.png");
49 private static final Icon NO_ICON = new EmptyIcon(IN_PROGRESS_ICON.getIconWidth(), IN_PROGRESS_ICON.getIconHeight());
50 private static final Icon STARING_EYE_ICON = IconLoader.getIcon("/general/inspectionInProgress.png");
52 private final Project myProject;
53 private final Document myDocument;
54 private final PsiFile myFile;
55 private final DaemonCodeAnalyzerImpl myDaemonCodeAnalyzer;
57 @NonNls private static final String HTML_HEADER = "<html><body>";
58 @NonNls private static final String HTML_FOOTER = "</body></html>";
59 @NonNls private static final String BR = "<br>";
60 @NonNls private static final String NO_PASS_FOR_MESSAGE_KEY_SUFFIX = ".for";
61 private final SeverityRegistrar mySeverityRegistrar;
63 public TrafficLightRenderer(Project project, DaemonCodeAnalyzerImpl highlighter, Document document, PsiFile file) {
65 myDaemonCodeAnalyzer = highlighter;
66 myDocument = document;
68 mySeverityRegistrar = SeverityRegistrar.getInstance(myProject);
71 public static class DaemonCodeAnalyzerStatus {
72 public boolean errorAnalyzingFinished; // all passes done
73 public final List<ProgressableTextEditorHighlightingPass> passStati = new ArrayList<ProgressableTextEditorHighlightingPass>();
74 public String[/*rootsNumber*/] noHighlightingRoots;
75 public String[/*rootsNumber*/] noInspectionRoots;
76 public int[] errorCount = ArrayUtil.EMPTY_INT_ARRAY;
78 public int rootsNumber;
79 public String toString() {
80 @NonNls String s = "DS: finished=" + errorAnalyzingFinished;
81 s += "; pass statuses: " + passStati.size() + "; ";
82 for (ProgressableTextEditorHighlightingPass passStatus : passStati) {
83 s += String.format("(%s %2.0f%% %b)", passStatus.getPresentableName(), passStatus.getProgress() *100, passStatus.isFinished());
85 s += "; error count: "+errorCount.length + ": "+new TIntArrayList(errorCount);
91 protected DaemonCodeAnalyzerStatus getDaemonCodeAnalyzerStatus(boolean fillErrorsCount, SeverityRegistrar severityRegistrar) {
92 if (myFile == null || myProject.isDisposed() || !myDaemonCodeAnalyzer.isHighlightingAvailable(myFile)) return null;
94 List<String> noInspectionRoots = new ArrayList<String>();
95 List<String> noHighlightingRoots = new ArrayList<String>();
96 final PsiFile[] roots = myFile.getPsiRoots();
97 for (PsiFile file : roots) {
98 if (!HighlightLevelUtil.shouldHighlight(file)) {
99 noHighlightingRoots.add(file.getLanguage().getID());
101 else if (!HighlightLevelUtil.shouldInspect(file)) {
102 noInspectionRoots.add(file.getLanguage().getID());
105 DaemonCodeAnalyzerStatus status = new DaemonCodeAnalyzerStatus();
106 status.noInspectionRoots = noInspectionRoots.isEmpty() ? null : ArrayUtil.toStringArray(noInspectionRoots);
107 status.noHighlightingRoots = noHighlightingRoots.isEmpty() ? null : ArrayUtil.toStringArray(noHighlightingRoots);
109 status.errorCount = new int[severityRegistrar.getSeveritiesCount()];
110 status.rootsNumber = roots.length;
111 fillDaemonCodeAnalyzerErrorsStatus(status, fillErrorsCount, severityRegistrar);
112 List<TextEditorHighlightingPass> passes = myDaemonCodeAnalyzer.getPassesToShowProgressFor(myDocument);
113 for (TextEditorHighlightingPass tepass : passes) {
114 if (!(tepass instanceof ProgressableTextEditorHighlightingPass)) continue;
115 ProgressableTextEditorHighlightingPass pass = (ProgressableTextEditorHighlightingPass)tepass;
117 if (pass.getProgress() < 0) continue;
118 status.passStati.add(pass);
120 status.errorAnalyzingFinished = myDaemonCodeAnalyzer.isAllAnalysisFinished(myFile);
125 protected void fillDaemonCodeAnalyzerErrorsStatus(final DaemonCodeAnalyzerStatus status,
126 final boolean fillErrorsCount,
127 final SeverityRegistrar severityRegistrar) {
128 final int count = severityRegistrar.getSeveritiesCount() - 1;
129 final HighlightSeverity maxPossibleSeverity = severityRegistrar.getSeverityByIndex(count);
130 final HighlightSeverity[] maxFoundSeverity = {null};
132 DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, null, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
133 public boolean process(HighlightInfo info) {
134 HighlightSeverity infoSeverity = info.getSeverity();
135 if (fillErrorsCount) {
136 final int severityIdx = severityRegistrar.getSeverityIdx(infoSeverity);
137 if (severityIdx != -1) {
138 status.errorCount[severityIdx] ++;
142 if (maxFoundSeverity[0] == null || severityRegistrar.compare(maxFoundSeverity[0], infoSeverity) < 0) {
143 maxFoundSeverity[0] = infoSeverity;
145 if (infoSeverity == maxPossibleSeverity) {
152 if (maxFoundSeverity[0] != null) {
153 final int severityIdx = severityRegistrar.getSeverityIdx(maxFoundSeverity[0]);
154 if (severityIdx != -1) {
155 status.errorCount[severityIdx] = 1;
160 public final Project getProject() {
164 public String getTooltipMessage() {
165 DaemonCodeAnalyzerStatus status = getDaemonCodeAnalyzerStatus(true, mySeverityRegistrar);
167 if (status == null) return null;
168 @NonNls String text = HTML_HEADER;
169 if (PowerSaveMode.isEnabled()) {
170 text += "Code analysis is disabled in power save mode.";
174 if (status.noHighlightingRoots != null && status.noHighlightingRoots.length == status.rootsNumber) {
175 text += DaemonBundle.message("analysis.hasnot.been.run");
180 if (status.errorAnalyzingFinished) {
181 text += DaemonBundle.message("analysis.completed");
184 text += DaemonBundle.message("performing.code.analysis");
186 for (ProgressableTextEditorHighlightingPass passStatus : status.passStati) {
187 if (passStatus.isFinished()) continue;
188 text += "<tr><td>" + passStatus.getPresentableName() + ":</td><td>" + renderProgressHtml(passStatus.getProgress()) + "</td></tr>";
193 int currentSeverityErrors = 0;
194 for (int i = status.errorCount.length - 1; i >= 0; i--) {
195 if (status.errorCount[i] > 0) {
196 final HighlightSeverity severity = mySeverityRegistrar.getSeverityByIndex(i);
198 String name = status.errorCount[i] > 1 ? StringUtil.pluralize(severity.toString().toLowerCase()) : severity.toString().toLowerCase();
199 text += status.errorAnalyzingFinished
200 ? DaemonBundle.message("errors.found", status.errorCount[i], name)
201 : DaemonBundle.message("errors.found.so.far", status.errorCount[i], name);
202 currentSeverityErrors += status.errorCount[i];
205 if (currentSeverityErrors == 0) {
207 text += status.errorAnalyzingFinished
208 ? DaemonBundle.message("no.errors.or.warnings.found")
209 : DaemonBundle.message("no.errors.or.warnings.found.so.far");
212 text += getMessageByRoots(status.noHighlightingRoots, status.rootsNumber, "no.syntax.highlighting.performed");
213 text += getMessageByRoots(status.noInspectionRoots, status.rootsNumber, "no.inspections.performed");
216 text = UIUtil.convertSpace2Nbsp(text);
221 private static final URL progressUrl = TrafficLightRenderer.class.getClassLoader().getResource("/general/progress.png");
222 private static final URL progressPlaceHolderUrl = TrafficLightRenderer.class.getClassLoader().getResource("/general/progressTransparentPlaceHolder.png");
224 private static String renderProgressHtml(double progress) {
225 @NonNls String text = "<table><tr><td>";
227 int nFilledBricks = (int)(nBricks * progress);
229 for (i = 0; i < nFilledBricks; i++) {
230 text += "<img src=\"" + progressUrl + "\">";
232 for (; i < nBricks; i++) {
233 text += "<img src=\"" + progressPlaceHolderUrl + "\">";
235 text += " "+String.format("%2.0f%%", progress * 100);
236 text += "</td></tr></table>";
240 private static String getMessageByRoots(String [] roots, int rootsNumber, @NonNls String prefix){
241 if (roots != null && roots.length > 0) {
242 return BR + (rootsNumber > 1
243 ? DaemonBundle.message(prefix + NO_PASS_FOR_MESSAGE_KEY_SUFFIX, StringUtil.join(roots, ", "))
244 : DaemonBundle.message(prefix));
249 public void paint(Component c, Graphics g, Rectangle r) {
250 Icon icon = getIcon();
252 int height = icon.getIconHeight();
253 int width = icon.getIconWidth();
254 icon.paintIcon(c, g, r.x + (r.width - width) / 2, r.y + (r.height - height) / 2);
257 private Icon getIcon() {
258 DaemonCodeAnalyzerStatus status = getDaemonCodeAnalyzerStatus(false, mySeverityRegistrar);
260 if (status == null) {
263 if (status.noHighlightingRoots != null && status.noHighlightingRoots.length == status.rootsNumber) {
264 return NO_ANALYSIS_ICON;
267 Icon icon = HighlightDisplayLevel.DO_NOT_SHOW.getIcon();
268 for (int i = status.errorCount.length - 1; i >= 0; i--) {
269 if (status.errorCount[i] != 0) {
270 icon = mySeverityRegistrar.getRendererIconByIndex(i);
275 if (status.errorAnalyzingFinished) return icon;
276 double progress = getOverallProgress(status);
277 TruncatingIcon trunc = new TruncatingIcon(icon, (int)(icon.getIconWidth() * progress), (int)(icon.getIconHeight() * progress));
278 return LayeredIcon.create(trunc, STARING_EYE_ICON);
281 private static double getOverallProgress(DaemonCodeAnalyzerStatus status) {
282 long advancement = 0;
284 for (ProgressableTextEditorHighlightingPass ps : status.passStati) {
285 advancement += ps.getProgressCount();
286 limit += ps.getProgressLimit();
288 return limit == 0 ? status.errorAnalyzingFinished ? 1 : 0 : advancement * 1.0 / limit;