f2c83228205ce7fc52f23147cdf907a67006b273
[idea/community.git] / platform / analysis-impl / src / com / intellij / codeInsight / daemon / impl / HighlightInfo.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.codeInsight.daemon.impl;
18
19 import com.intellij.codeHighlighting.RainbowHighlighter;
20 import com.intellij.codeInsight.daemon.GutterMark;
21 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
22 import com.intellij.codeInsight.daemon.RainbowVisitor;
23 import com.intellij.codeInsight.intention.IntentionAction;
24 import com.intellij.codeInsight.intention.IntentionManager;
25 import com.intellij.codeInspection.*;
26 import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
27 import com.intellij.codeInspection.ex.InspectionToolWrapper;
28 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
29 import com.intellij.lang.ASTNode;
30 import com.intellij.lang.annotation.Annotation;
31 import com.intellij.lang.annotation.HighlightSeverity;
32 import com.intellij.lang.annotation.ProblemGroup;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
35 import com.intellij.openapi.editor.Editor;
36 import com.intellij.openapi.editor.HighlighterColors;
37 import com.intellij.openapi.editor.RangeMarker;
38 import com.intellij.openapi.editor.colors.*;
39 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
40 import com.intellij.openapi.editor.markup.GutterIconRenderer;
41 import com.intellij.openapi.editor.markup.TextAttributes;
42 import com.intellij.openapi.util.*;
43 import com.intellij.openapi.util.text.StringUtil;
44 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
45 import com.intellij.psi.PsiElement;
46 import com.intellij.psi.PsiFile;
47 import com.intellij.util.ArrayUtilRt;
48 import com.intellij.util.BitUtil;
49 import com.intellij.util.containers.ContainerUtil;
50 import com.intellij.xml.util.XmlStringUtil;
51 import org.intellij.lang.annotations.MagicConstant;
52 import org.jetbrains.annotations.Contract;
53 import org.jetbrains.annotations.NonNls;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56
57 import javax.swing.*;
58 import java.awt.*;
59 import java.util.*;
60 import java.util.List;
61
62 public class HighlightInfo implements Segment {
63   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.HighlightInfo");
64
65   // optimisation: if tooltip contains this marker object, then it replaced with description field in getTooltip()
66   private static final String DESCRIPTION_PLACEHOLDER = "\u0000";
67   JComponent fileLevelComponent;
68   public final TextAttributes forcedTextAttributes;
69   public final TextAttributesKey forcedTextAttributesKey;
70
71   @NotNull
72   public final HighlightInfoType type;
73   private int group;
74   public final int startOffset;
75   public final int endOffset;
76
77   private int fixStartOffset;
78   private int fixEndOffset;
79   RangeMarker fixMarker; // null means it the same as highlighter
80
81   private final String description;
82   private final String toolTip;
83   @NotNull
84   private final HighlightSeverity severity;
85
86   final int navigationShift;
87
88   volatile RangeHighlighterEx highlighter; // modified in EDT only
89
90   public List<Pair<IntentionActionDescriptor, TextRange>> quickFixActionRanges;
91   public List<Pair<IntentionActionDescriptor, RangeMarker>> quickFixActionMarkers;
92
93   private final GutterMark gutterIconRenderer;
94   private final ProblemGroup myProblemGroup;
95
96   private volatile byte myFlags; // bit packed flags below:
97   private static final byte BIJECTIVE_MASK = 1;
98   private static final byte HAS_HINT_MASK = 2;
99   private static final byte FROM_INJECTION_MASK = 4;
100   private static final byte AFTER_END_OF_LINE_MASK = 8;
101   private static final byte FILE_LEVEL_ANNOTATION_MASK = 16;
102   private static final byte NEEDS_UPDATE_ON_TYPING_MASK = 32;
103   PsiElement psiElement;
104
105   @NotNull
106   ProperTextRange getFixTextRange() {
107     return new ProperTextRange(fixStartOffset, fixEndOffset);
108   }
109
110   void setFromInjection(boolean fromInjection) {
111     setFlag(FROM_INJECTION_MASK, fromInjection);
112   }
113
114   public String getToolTip() {
115     String toolTip = this.toolTip;
116     String description = this.description;
117     if (toolTip == null || description == null || !toolTip.contains(DESCRIPTION_PLACEHOLDER)) return toolTip;
118     String decoded = StringUtil.replace(toolTip, DESCRIPTION_PLACEHOLDER, XmlStringUtil.escapeString(description));
119     return XmlStringUtil.wrapInHtml(decoded);
120   }
121
122   private static String encodeTooltip(String toolTip, String description) {
123     if (toolTip == null || description == null) return toolTip;
124     String unescaped = StringUtil.unescapeXml(XmlStringUtil.stripHtml(toolTip));
125
126     String encoded = description.isEmpty() ? unescaped : StringUtil.replace(unescaped, description, DESCRIPTION_PLACEHOLDER);
127     //noinspection StringEquality
128     if (encoded == unescaped) {
129       return toolTip;
130     }
131     if (encoded.equals(DESCRIPTION_PLACEHOLDER)) encoded = DESCRIPTION_PLACEHOLDER;
132     return encoded;
133   }
134
135   public String getDescription() {
136     return description;
137   }
138
139   @MagicConstant(intValues = {BIJECTIVE_MASK, HAS_HINT_MASK, FROM_INJECTION_MASK, AFTER_END_OF_LINE_MASK, FILE_LEVEL_ANNOTATION_MASK, NEEDS_UPDATE_ON_TYPING_MASK})
140   private @interface FlagConstant {}
141
142   private boolean isFlagSet(@FlagConstant byte mask) {
143     return BitUtil.isSet(myFlags, mask);
144   }
145
146   private void setFlag(@FlagConstant byte mask, boolean value) {
147     myFlags = BitUtil.set(myFlags, mask, value);
148   }
149
150   boolean isFileLevelAnnotation() {
151     return isFlagSet(FILE_LEVEL_ANNOTATION_MASK);
152   }
153
154   boolean isBijective() {
155     return isFlagSet(BIJECTIVE_MASK);
156   }
157
158   void setBijective(boolean bijective) {
159     setFlag(BIJECTIVE_MASK, bijective);
160   }
161
162   @NotNull
163   public HighlightSeverity getSeverity() {
164     return severity;
165   }
166
167   public boolean isAfterEndOfLine() {
168     return isFlagSet(AFTER_END_OF_LINE_MASK);
169   }
170
171   @Nullable
172   public TextAttributes getTextAttributes(@Nullable final PsiElement element, @Nullable final EditorColorsScheme editorColorsScheme) {
173     if (forcedTextAttributes != null) {
174       return forcedTextAttributes;
175     }
176
177     EditorColorsScheme colorsScheme = getColorsScheme(editorColorsScheme);
178
179     if (forcedTextAttributesKey != null) {
180       return colorsScheme.getAttributes(forcedTextAttributesKey);
181     }
182
183     TextAttributes attributes = getAttributesByType(element, type, colorsScheme);
184     if (element != null &&
185         RainbowHighlighter.isRainbowEnabled() &&
186         isLikeVariable(type.getAttributesKey())) {
187       PsiFile containingFile = element.getContainingFile();
188       if (!RainbowVisitor.existsPassSuitableForFile(containingFile)) {
189         CharSequence text = containingFile.getViewProvider().getContents().subSequence(startOffset, endOffset);
190         attributes = new RainbowHighlighter(colorsScheme).getAttributes(text.toString(), attributes);
191       }
192     }
193     return attributes;
194   }
195
196   @Contract("null -> false")
197   private static boolean isLikeVariable(TextAttributesKey key) {
198     if (key == null) return false;
199     TextAttributesKey fallbackAttributeKey = key.getFallbackAttributeKey();
200     if (fallbackAttributeKey == null) return false;
201     if (fallbackAttributeKey == DefaultLanguageHighlighterColors.LOCAL_VARIABLE || fallbackAttributeKey == DefaultLanguageHighlighterColors.PARAMETER) return true;
202     return isLikeVariable(fallbackAttributeKey);
203   }
204
205   public static TextAttributes getAttributesByType(@Nullable final PsiElement element,
206                                                    @NotNull HighlightInfoType type,
207                                                    @NotNull TextAttributesScheme colorsScheme) {
208     final SeverityRegistrar severityRegistrar = SeverityRegistrar
209       .getSeverityRegistrar(element != null ? element.getProject() : null);
210     final TextAttributes textAttributes = severityRegistrar.getTextAttributesBySeverity(type.getSeverity(element));
211     if (textAttributes != null) {
212       return textAttributes;
213     }
214     TextAttributesKey key = type.getAttributesKey();
215     return colorsScheme.getAttributes(key);
216   }
217
218   @Nullable
219   Color getErrorStripeMarkColor(@NotNull PsiElement element,
220                                 @Nullable final EditorColorsScheme colorsScheme) { // if null global scheme will be used
221     if (forcedTextAttributes != null) {
222       return forcedTextAttributes.getErrorStripeColor();
223     }
224     EditorColorsScheme scheme = getColorsScheme(colorsScheme);
225     if (forcedTextAttributesKey != null) {
226       TextAttributes forcedTextAttributes = scheme.getAttributes(forcedTextAttributesKey);
227       if (forcedTextAttributes != null) {
228         final Color errorStripeColor = forcedTextAttributes.getErrorStripeColor();
229         // let's copy above behaviour of forcedTextAttributes stripe color, but I'm not sure that the behaviour is correct in general
230         if (errorStripeColor != null) {
231           return errorStripeColor;
232         }
233       }
234     }
235
236     if (getSeverity() == HighlightSeverity.ERROR) {
237       return scheme.getAttributes(CodeInsightColors.ERRORS_ATTRIBUTES).getErrorStripeColor();
238     }
239     if (getSeverity() == HighlightSeverity.WARNING) {
240       return scheme.getAttributes(CodeInsightColors.WARNINGS_ATTRIBUTES).getErrorStripeColor();
241     }
242     if (getSeverity() == HighlightSeverity.INFO){
243       return scheme.getAttributes(CodeInsightColors.INFO_ATTRIBUTES).getErrorStripeColor();
244     }
245     if (getSeverity() == HighlightSeverity.WEAK_WARNING){
246       return scheme.getAttributes(CodeInsightColors.WEAK_WARNING_ATTRIBUTES).getErrorStripeColor();
247     }
248     if (getSeverity() == HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING) {
249       return scheme.getAttributes(CodeInsightColors.GENERIC_SERVER_ERROR_OR_WARNING).getErrorStripeColor();
250     }
251
252     TextAttributes attributes = getAttributesByType(element, type, scheme);
253     return attributes == null ? null : attributes.getErrorStripeColor();
254
255   }
256
257   @NotNull
258   private static EditorColorsScheme getColorsScheme(@Nullable final EditorColorsScheme customScheme) {
259     if (customScheme != null) {
260       return customScheme;
261     }
262     return EditorColorsManager.getInstance().getGlobalScheme();
263   }
264
265   @Nullable
266   @NonNls
267   private static String htmlEscapeToolTip(@Nullable String unescapedTooltip) {
268     return unescapedTooltip == null ? null : XmlStringUtil.wrapInHtml(XmlStringUtil.escapeString(unescapedTooltip));
269   }
270
271   @NotNull
272   private static final HighlightInfoFilter[] FILTERS = HighlightInfoFilter.EXTENSION_POINT_NAME.getExtensions();
273
274   boolean needUpdateOnTyping() {
275     return isFlagSet(NEEDS_UPDATE_ON_TYPING_MASK);
276   }
277
278   HighlightInfo(@Nullable TextAttributes forcedTextAttributes,
279                 @Nullable TextAttributesKey forcedTextAttributesKey,
280                 @NotNull HighlightInfoType type,
281                 int startOffset,
282                 int endOffset,
283                 @Nullable String escapedDescription,
284                 @Nullable String escapedToolTip,
285                 @NotNull HighlightSeverity severity,
286                 boolean afterEndOfLine,
287                 @Nullable Boolean needsUpdateOnTyping,
288                 boolean isFileLevelAnnotation,
289                 int navigationShift,
290                 ProblemGroup problemGroup,
291                 GutterMark gutterIconRenderer) {
292     if (startOffset < 0 || startOffset > endOffset) {
293       LOG.error("Incorrect highlightInfo bounds. description="+escapedDescription+"; startOffset="+startOffset+"; endOffset="+endOffset+";type="+type);
294     }
295     this.forcedTextAttributes = forcedTextAttributes;
296     this.forcedTextAttributesKey = forcedTextAttributesKey;
297     this.type = type;
298     this.startOffset = startOffset;
299     this.endOffset = endOffset;
300     fixStartOffset = startOffset;
301     fixEndOffset = endOffset;
302     description = escapedDescription;
303     // optimisation: do not retain extra memory if can recompute
304     toolTip = encodeTooltip(escapedToolTip, escapedDescription);
305     this.severity = severity;
306     setFlag(AFTER_END_OF_LINE_MASK, afterEndOfLine);
307     setFlag(NEEDS_UPDATE_ON_TYPING_MASK, calcNeedUpdateOnTyping(needsUpdateOnTyping, type));
308     setFlag(FILE_LEVEL_ANNOTATION_MASK, isFileLevelAnnotation);
309     this.navigationShift = navigationShift;
310     myProblemGroup = problemGroup;
311     this.gutterIconRenderer = gutterIconRenderer;
312   }
313
314   private static boolean calcNeedUpdateOnTyping(@Nullable Boolean needsUpdateOnTyping, HighlightInfoType type) {
315     if (needsUpdateOnTyping != null) return needsUpdateOnTyping.booleanValue();
316
317     if (type instanceof HighlightInfoType.UpdateOnTypingSuppressible) {
318       return ((HighlightInfoType.UpdateOnTypingSuppressible)type).needsUpdateOnTyping();
319     }
320     return true;
321   }
322
323   @Override
324   public boolean equals(Object obj) {
325     if (obj == this) return true;
326     if (!(obj instanceof HighlightInfo)) return false;
327     HighlightInfo info = (HighlightInfo)obj;
328
329     return info.getSeverity() == getSeverity() &&
330            info.startOffset == startOffset &&
331            info.endOffset == endOffset &&
332            Comparing.equal(info.type, type) &&
333            Comparing.equal(info.gutterIconRenderer, gutterIconRenderer) &&
334            Comparing.equal(info.forcedTextAttributes, forcedTextAttributes) &&
335            Comparing.equal(info.forcedTextAttributesKey, forcedTextAttributesKey) &&
336            Comparing.strEqual(info.getDescription(), getDescription());
337   }
338
339   boolean equalsByActualOffset(@NotNull HighlightInfo info) {
340     if (info == this) return true;
341
342     return info.getSeverity() == getSeverity() &&
343            info.getActualStartOffset() == getActualStartOffset() &&
344            info.getActualEndOffset() == getActualEndOffset() &&
345            Comparing.equal(info.type, type) &&
346            Comparing.equal(info.gutterIconRenderer, gutterIconRenderer) &&
347            Comparing.equal(info.forcedTextAttributes, forcedTextAttributes) &&
348            Comparing.equal(info.forcedTextAttributesKey, forcedTextAttributesKey) &&
349            Comparing.strEqual(info.getDescription(), getDescription());
350   }
351
352   @Override
353   public int hashCode() {
354     return startOffset;
355   }
356
357   @Override
358   @NonNls
359   public String toString() {
360     @NonNls String s = "HighlightInfo(" + startOffset + "," + endOffset+")";
361     if (getActualStartOffset() != startOffset || getActualEndOffset() != endOffset) {
362       s += "; actual: (" + getActualStartOffset() + "," + getActualEndOffset() + ")";
363     }
364     if (highlighter != null) s += " text='" + getText() + "'";
365     if (getDescription() != null) s+= ", description='" + getDescription() + "'";
366     s += " severity=" + getSeverity();
367     s += " group=" + getGroup();
368
369     if (quickFixActionRanges != null) {
370       s+= "; quickFixes: "+quickFixActionRanges;
371     }
372     if (gutterIconRenderer != null) {
373       s += "; gutter: " + gutterIconRenderer;
374     }
375     return s;
376   }
377
378   @NotNull
379   public static Builder newHighlightInfo(@NotNull HighlightInfoType type) {
380     return new B(type);
381   }
382
383   void setGroup(int group) {
384     this.group = group;
385   }
386
387   public interface Builder {
388     // only one 'range' call allowed
389     @NotNull Builder range(@NotNull TextRange textRange);
390     @NotNull Builder range(@NotNull ASTNode node);
391     @NotNull Builder range(@NotNull PsiElement element);
392     @NotNull Builder range(@NotNull PsiElement element, int start, int end);
393     @NotNull Builder range(int start, int end);
394
395     @NotNull Builder gutterIconRenderer(@NotNull GutterIconRenderer gutterIconRenderer);
396     @NotNull Builder problemGroup(@NotNull ProblemGroup problemGroup);
397
398     // only one allowed
399     @NotNull Builder description(@NotNull String description);
400     @NotNull Builder descriptionAndTooltip(@NotNull String description);
401
402     // only one allowed
403     @NotNull Builder textAttributes(@NotNull TextAttributes attributes);
404     @NotNull Builder textAttributes(@NotNull TextAttributesKey attributesKey);
405
406     // only one allowed
407     @NotNull Builder unescapedToolTip(@NotNull String unescapedToolTip);
408     @NotNull Builder escapedToolTip(@NotNull String escapedToolTip);
409
410     @NotNull Builder endOfLine();
411     @NotNull Builder needsUpdateOnTyping(boolean update);
412     @NotNull Builder severity(@NotNull HighlightSeverity severity);
413     @NotNull Builder fileLevelAnnotation();
414     @NotNull Builder navigationShift(int navigationShift);
415
416     @Nullable("null means filtered out")
417     HighlightInfo create();
418
419     @NotNull
420     HighlightInfo createUnconditionally();
421   }
422
423   private static boolean isAcceptedByFilters(@NotNull HighlightInfo info, @Nullable PsiElement psiElement) {
424     PsiFile file = psiElement == null ? null : psiElement.getContainingFile();
425     for (HighlightInfoFilter filter : FILTERS) {
426       if (!filter.accept(info, file)) {
427         return false;
428       }
429     }
430     info.psiElement = psiElement;
431     return true;
432   }
433
434   private static class B implements Builder {
435     private Boolean myNeedsUpdateOnTyping;
436     private TextAttributes forcedTextAttributes;
437     private TextAttributesKey forcedTextAttributesKey;
438
439     private final HighlightInfoType type;
440     private int startOffset = -1;
441     private int endOffset = -1;
442
443     private String escapedDescription;
444     private String escapedToolTip;
445     private HighlightSeverity severity;
446
447     private boolean isAfterEndOfLine;
448     private boolean isFileLevelAnnotation;
449     private int navigationShift;
450
451     private GutterIconRenderer gutterIconRenderer;
452     private ProblemGroup problemGroup;
453     private PsiElement psiElement;
454
455     private B(@NotNull HighlightInfoType type) {
456       this.type = type;
457     }
458
459     @NotNull
460     @Override
461     public Builder gutterIconRenderer(@NotNull GutterIconRenderer gutterIconRenderer) {
462       assert this.gutterIconRenderer == null : "gutterIconRenderer already set";
463       this.gutterIconRenderer = gutterIconRenderer;
464       return this;
465     }
466
467     @NotNull
468     @Override
469     public Builder problemGroup(@NotNull ProblemGroup problemGroup) {
470       assert this.problemGroup == null : "problemGroup already set";
471       this.problemGroup = problemGroup;
472       return this;
473     }
474
475     @NotNull
476     @Override
477     public Builder description(@NotNull String description) {
478       assert escapedDescription == null : "description already set";
479       escapedDescription = description;
480       return this;
481     }
482
483     @NotNull
484     @Override
485     public Builder descriptionAndTooltip(@NotNull String description) {
486       return description(description).unescapedToolTip(description);
487     }
488
489     @NotNull
490     @Override
491     public Builder textAttributes(@NotNull TextAttributes attributes) {
492       assert forcedTextAttributes == null : "textattributes already set";
493       forcedTextAttributes = attributes;
494       return this;
495     }
496
497     @NotNull
498     @Override
499     public Builder textAttributes(@NotNull TextAttributesKey attributesKey) {
500       assert forcedTextAttributesKey == null : "textattributesKey already set";
501       forcedTextAttributesKey = attributesKey;
502       return this;
503     }
504
505     @NotNull
506     @Override
507     public Builder unescapedToolTip(@NotNull String unescapedToolTip) {
508       assert escapedToolTip == null : "Tooltip was already set";
509       escapedToolTip = htmlEscapeToolTip(unescapedToolTip);
510       return this;
511     }
512
513     @NotNull
514     @Override
515     public Builder escapedToolTip(@NotNull String escapedToolTip) {
516       assert this.escapedToolTip == null : "Tooltip was already set";
517       this.escapedToolTip = escapedToolTip;
518       return this;
519     }
520
521     @NotNull
522     @Override
523     public Builder range(int start, int end) {
524       assert startOffset == -1 && endOffset == -1 : "Offsets already set";
525
526       startOffset = start;
527       endOffset = end;
528       return this;
529     }
530
531     @NotNull
532     @Override
533     public Builder range(@NotNull TextRange textRange) {
534       assert startOffset == -1 && endOffset == -1 : "Offsets already set";
535       startOffset = textRange.getStartOffset();
536       endOffset = textRange.getEndOffset();
537       return this;
538     }
539
540     @NotNull
541     @Override
542     public Builder range(@NotNull ASTNode node) {
543       return range(node.getPsi());
544     }
545
546     @NotNull
547     @Override
548     public Builder range(@NotNull PsiElement element) {
549       assert psiElement == null : " psiElement already set";
550       psiElement = element;
551       return range(element.getTextRange());
552     }
553
554     @NotNull
555     @Override
556     public Builder range(@NotNull PsiElement element, int start, int end) {
557       assert psiElement == null : " psiElement already set";
558       psiElement = element;
559       return range(start, end);
560     }
561
562     @NotNull
563     @Override
564     public Builder endOfLine() {
565       isAfterEndOfLine = true;
566       return this;
567     }
568
569     @NotNull
570     @Override
571     public Builder needsUpdateOnTyping(boolean update) {
572       assert myNeedsUpdateOnTyping == null : " needsUpdateOnTyping already set";
573       myNeedsUpdateOnTyping = update;
574       return this;
575     }
576
577     @NotNull
578     @Override
579     public Builder severity(@NotNull HighlightSeverity severity) {
580       assert this.severity == null : " severity already set";
581       this.severity = severity;
582       return this;
583     }
584
585     @NotNull
586     @Override
587     public Builder fileLevelAnnotation() {
588       isFileLevelAnnotation = true;
589       return this;
590     }
591
592     @NotNull
593     @Override
594     public Builder navigationShift(int navigationShift) {
595       this.navigationShift = navigationShift;
596       return this;
597     }
598
599     @Nullable
600     @Override
601     public HighlightInfo create() {
602       HighlightInfo info = createUnconditionally();
603       LOG.assertTrue(psiElement != null || severity == HighlightInfoType.SYMBOL_TYPE_SEVERITY || severity == HighlightInfoType.INJECTED_FRAGMENT_SEVERITY || ArrayUtilRt.find(HighlightSeverity.DEFAULT_SEVERITIES, severity) != -1,
604                      "Custom type requires not-null element to detect its text attributes");
605
606       if (!isAcceptedByFilters(info, psiElement)) return null;
607
608       return info;
609     }
610
611     @NotNull
612     @Override
613     public HighlightInfo createUnconditionally() {
614       if (severity == null) {
615         severity = type.getSeverity(psiElement);
616       }
617
618       return new HighlightInfo(forcedTextAttributes, forcedTextAttributesKey, type, startOffset, endOffset, escapedDescription,
619                                escapedToolTip, severity, isAfterEndOfLine, myNeedsUpdateOnTyping, isFileLevelAnnotation, navigationShift,
620                                problemGroup, gutterIconRenderer);
621     }
622   }
623
624   public GutterMark getGutterIconRenderer() {
625     return gutterIconRenderer;
626   }
627
628   @Nullable
629   public ProblemGroup getProblemGroup() {
630     return myProblemGroup;
631   }
632
633   @NotNull
634   public static HighlightInfo fromAnnotation(@NotNull Annotation annotation) {
635     return fromAnnotation(annotation, null, false);
636   }
637
638   @NotNull
639   static HighlightInfo fromAnnotation(@NotNull Annotation annotation, @Nullable TextRange fixedRange, boolean batchMode) {
640     final TextAttributes forcedAttributes = annotation.getEnforcedTextAttributes();
641     TextAttributesKey key = annotation.getTextAttributes();
642     final TextAttributesKey forcedAttributesKey = forcedAttributes == null ? (key == HighlighterColors.NO_HIGHLIGHTING ? null : key) : null;
643
644     HighlightInfo info = new HighlightInfo(forcedAttributes, forcedAttributesKey, convertType(annotation),
645                                            fixedRange != null? fixedRange.getStartOffset() : annotation.getStartOffset(),
646                                            fixedRange != null? fixedRange.getEndOffset() : annotation.getEndOffset(),
647                                            annotation.getMessage(), annotation.getTooltip(),
648                                            annotation.getSeverity(), annotation.isAfterEndOfLine(), annotation.needsUpdateOnTyping(), annotation.isFileLevelAnnotation(),
649                                            0, annotation.getProblemGroup(), annotation.getGutterIconRenderer());
650     appendFixes(fixedRange, info, batchMode ? annotation.getBatchFixes() : annotation.getQuickFixes());
651     return info;
652   }
653
654   private static final String ANNOTATOR_INSPECTION_SHORT_NAME = "Annotator";
655
656   private static void appendFixes(@Nullable TextRange fixedRange, @NotNull HighlightInfo info, @Nullable List<Annotation.QuickFixInfo> fixes) {
657     if (fixes != null) {
658       for (final Annotation.QuickFixInfo quickFixInfo : fixes) {
659         TextRange range = fixedRange != null ? fixedRange : quickFixInfo.textRange;
660         HighlightDisplayKey key = quickFixInfo.key != null
661                                   ? quickFixInfo.key
662                                   : HighlightDisplayKey.find(ANNOTATOR_INSPECTION_SHORT_NAME);
663         info.registerFix(quickFixInfo.quickFix, null, HighlightDisplayKey.getDisplayNameByKey(key), range, key);
664       }
665     }
666   }
667
668   @NotNull
669   private static HighlightInfoType convertType(@NotNull Annotation annotation) {
670     ProblemHighlightType type = annotation.getHighlightType();
671     if (type == ProblemHighlightType.LIKE_UNUSED_SYMBOL) return HighlightInfoType.UNUSED_SYMBOL;
672     if (type == ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) return HighlightInfoType.WRONG_REF;
673     if (type == ProblemHighlightType.LIKE_DEPRECATED) return HighlightInfoType.DEPRECATED;
674     return convertSeverity(annotation.getSeverity());
675   }
676
677   @NotNull
678   public static HighlightInfoType convertSeverity(@NotNull HighlightSeverity severity) {
679     return severity == HighlightSeverity.ERROR? HighlightInfoType.ERROR :
680            severity == HighlightSeverity.WARNING ? HighlightInfoType.WARNING :
681            severity == HighlightSeverity.INFO ? HighlightInfoType.INFO :
682            severity == HighlightSeverity.WEAK_WARNING ? HighlightInfoType.WEAK_WARNING :
683            severity ==HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING ? HighlightInfoType.GENERIC_WARNINGS_OR_ERRORS_FROM_SERVER :
684            HighlightInfoType.INFORMATION;
685   }
686
687   @NotNull
688   public static ProblemHighlightType convertType(HighlightInfoType infoType) {
689     if (infoType == HighlightInfoType.ERROR || infoType == HighlightInfoType.WRONG_REF) return ProblemHighlightType.ERROR;
690     if (infoType == HighlightInfoType.WARNING) return ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
691     if (infoType == HighlightInfoType.INFORMATION) return ProblemHighlightType.INFORMATION;
692     return ProblemHighlightType.WEAK_WARNING;
693   }
694
695   @NotNull
696   public static ProblemHighlightType convertSeverityToProblemHighlight(HighlightSeverity severity) {
697     return severity == HighlightSeverity.ERROR ? ProblemHighlightType.ERROR :
698            severity == HighlightSeverity.WARNING ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING :
699            severity == HighlightSeverity.INFO ? ProblemHighlightType.INFO :
700            severity == HighlightSeverity.WEAK_WARNING ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION;
701   }
702
703
704   public boolean hasHint() {
705     return isFlagSet(HAS_HINT_MASK);
706   }
707
708   void setHint(final boolean hasHint) {
709     setFlag(HAS_HINT_MASK, hasHint);
710   }
711
712   public int getActualStartOffset() {
713     RangeHighlighterEx h = highlighter;
714     return h == null || !h.isValid() ? startOffset : h.getStartOffset();
715   }
716   public int getActualEndOffset() {
717     RangeHighlighterEx h = highlighter;
718     return h == null || !h.isValid() ? endOffset : h.getEndOffset();
719   }
720
721   public static class IntentionActionDescriptor {
722     private final IntentionAction myAction;
723     private volatile List<IntentionAction> myOptions;
724     private volatile HighlightDisplayKey myKey;
725     private final ProblemGroup myProblemGroup;
726     private final HighlightSeverity mySeverity;
727     private final String myDisplayName;
728     private final Icon myIcon;
729     private Boolean myCanCleanup;
730
731     IntentionActionDescriptor(@NotNull IntentionAction action, final List<IntentionAction> options, final String displayName) {
732       this(action, options, displayName, null);
733     }
734
735     public IntentionActionDescriptor(@NotNull IntentionAction action, final Icon icon) {
736       this(action, null, null, icon);
737     }
738
739     IntentionActionDescriptor(@NotNull IntentionAction action,
740                               @Nullable final List<IntentionAction> options,
741                               @Nullable final String displayName,
742                               @Nullable Icon icon) {
743       this(action, options, displayName, icon, null, null, null);
744     }
745
746     public IntentionActionDescriptor(@NotNull IntentionAction action,
747                                      @Nullable final List<IntentionAction> options,
748                                      @Nullable final String displayName,
749                                      @Nullable Icon icon,
750                                      @Nullable HighlightDisplayKey key,
751                                      @Nullable ProblemGroup problemGroup,
752                                      @Nullable HighlightSeverity severity) {
753       myAction = action;
754       myOptions = options;
755       myDisplayName = displayName;
756       myIcon = icon;
757       myKey = key;
758       myProblemGroup = problemGroup;
759       mySeverity = severity;
760     }
761
762     @NotNull
763     public IntentionAction getAction() {
764       return myAction;
765     }
766
767     boolean isError() {
768       return mySeverity == null || mySeverity.compareTo(HighlightSeverity.ERROR) >= 0;
769     }
770     
771     boolean canCleanup(@NotNull PsiElement element) {
772       if (myCanCleanup == null) {
773         InspectionProfile profile = InspectionProjectProfileManager.getInstance(element.getProject()).getCurrentProfile();
774         final HighlightDisplayKey key = myKey;
775         if (key == null) {
776           myCanCleanup = false;
777         } else {
778           InspectionToolWrapper toolWrapper = profile.getInspectionTool(key.toString(), element);
779           myCanCleanup = toolWrapper != null && toolWrapper.isCleanupTool();
780         }
781       }
782       return myCanCleanup;
783     }
784
785     @Nullable
786     public List<IntentionAction> getOptions(@NotNull PsiElement element, @Nullable Editor editor) {
787       if (editor != null && Boolean.FALSE.equals(editor.getUserData(IntentionManager.SHOW_INTENTION_OPTIONS_KEY))) {
788         return null;
789       }
790       List<IntentionAction> options = myOptions;
791       HighlightDisplayKey key = myKey;
792       if (myProblemGroup != null) {
793         String problemName = myProblemGroup.getProblemName();
794         HighlightDisplayKey problemGroupKey = problemName != null ? HighlightDisplayKey.findById(problemName) : null;
795         if (problemGroupKey != null) {
796           key = problemGroupKey;
797         }
798       }
799       if (options != null || key == null) {
800         return options;
801       }
802       IntentionManager intentionManager = IntentionManager.getInstance();
803       List<IntentionAction> newOptions = intentionManager.getStandardIntentionOptions(key, element);
804       InspectionProfile profile = InspectionProjectProfileManager.getInstance(element.getProject()).getCurrentProfile();
805       InspectionToolWrapper toolWrapper = profile.getInspectionTool(key.toString(), element);
806       if (!(toolWrapper instanceof LocalInspectionToolWrapper)) {
807         HighlightDisplayKey idkey = HighlightDisplayKey.findById(key.toString());
808         if (idkey != null) {
809           toolWrapper = profile.getInspectionTool(idkey.toString(), element);
810         }
811       }
812       if (toolWrapper != null) {
813
814         myCanCleanup = toolWrapper.isCleanupTool();
815
816         final IntentionAction fixAllIntention = intentionManager.createFixAllIntention(toolWrapper, myAction);
817         InspectionProfileEntry wrappedTool = toolWrapper instanceof LocalInspectionToolWrapper ? ((LocalInspectionToolWrapper)toolWrapper).getTool()
818                                                                                                : ((GlobalInspectionToolWrapper)toolWrapper).getTool();
819         if (wrappedTool instanceof DefaultHighlightVisitorBasedInspection.AnnotatorBasedInspection) {
820           List<IntentionAction> actions = Collections.emptyList();
821           if (myProblemGroup instanceof SuppressableProblemGroup) {
822             actions = Arrays.asList(((SuppressableProblemGroup)myProblemGroup).getSuppressActions(element));
823           }
824           if (fixAllIntention != null) {
825             if (actions.isEmpty()) {
826               return Collections.singletonList(fixAllIntention);
827             }
828             else {
829               actions = new ArrayList<>(actions);
830               actions.add(fixAllIntention);
831             }
832           }
833           return actions;
834         }
835         ContainerUtil.addIfNotNull(newOptions, fixAllIntention);
836         if (wrappedTool instanceof CustomSuppressableInspectionTool) {
837           final IntentionAction[] suppressActions = ((CustomSuppressableInspectionTool)wrappedTool).getSuppressActions(element);
838           if (suppressActions != null) {
839             ContainerUtil.addAll(newOptions, suppressActions);
840           }
841         }
842         else {
843           SuppressQuickFix[] suppressFixes = wrappedTool.getBatchSuppressActions(element);
844           if (suppressFixes.length > 0) {
845             ContainerUtil.addAll(newOptions, ContainerUtil.map(suppressFixes, SuppressIntentionActionFromFix::convertBatchToSuppressIntentionAction));
846           }
847         }
848
849       }
850       if (myProblemGroup instanceof SuppressableProblemGroup) {
851         final IntentionAction[] suppressActions = ((SuppressableProblemGroup)myProblemGroup).getSuppressActions(element);
852         ContainerUtil.addAll(newOptions, suppressActions);
853       }
854
855       synchronized (this) {
856         options = myOptions;
857         if (options == null) {
858           myOptions = options = newOptions;
859         }
860         myKey = null;
861       }
862       return options;
863     }
864
865     @Nullable
866     public String getDisplayName() {
867       return myDisplayName;
868     }
869
870     @Override
871     @NonNls
872     public String toString() {
873       String text = getAction().getText();
874       return "descriptor: " + (text.isEmpty() ? getAction().getClass() : text);
875     }
876
877     @Nullable
878     public Icon getIcon() {
879       return myIcon;
880     }
881
882     @Override
883     public boolean equals(Object obj) {
884       return obj instanceof IntentionActionDescriptor && myAction.equals(((IntentionActionDescriptor)obj).myAction);
885     }
886   }
887
888   @Override
889   public int getStartOffset() {
890     return getActualStartOffset();
891   }
892
893   @Override
894   public int getEndOffset() {
895     return getActualEndOffset();
896   }
897
898   int getGroup() {
899     return group;
900   }
901
902   boolean isFromInjection() {
903     return isFlagSet(FROM_INJECTION_MASK);
904   }
905
906   @NotNull
907   public String getText() {
908     if (isFileLevelAnnotation()) return "";
909     RangeHighlighterEx highlighter = this.highlighter;
910     if (highlighter == null) {
911       throw new RuntimeException("info not applied yet");
912     }
913     if (!highlighter.isValid()) return "";
914     return highlighter.getDocument().getText(TextRange.create(highlighter));
915   }
916
917   public void registerFix(@Nullable IntentionAction action,
918                           @Nullable List<IntentionAction> options,
919                           @Nullable String displayName,
920                           @Nullable TextRange fixRange,
921                           @Nullable HighlightDisplayKey key) {
922     if (action == null) return;
923     if (fixRange == null) fixRange = new TextRange(startOffset, endOffset);
924     if (quickFixActionRanges == null) {
925       quickFixActionRanges = ContainerUtil.createLockFreeCopyOnWriteList();
926     }
927     IntentionActionDescriptor desc = new IntentionActionDescriptor(action, options, displayName, null, key, getProblemGroup(), getSeverity());
928     quickFixActionRanges.add(Pair.create(desc, fixRange));
929     fixStartOffset = Math.min (fixStartOffset, fixRange.getStartOffset());
930     fixEndOffset = Math.max (fixEndOffset, fixRange.getEndOffset());
931     if (action instanceof HintAction) {
932       setHint(true);
933     }
934   }
935
936   public void unregisterQuickFix(@NotNull Condition<IntentionAction> condition) {
937     for (Iterator<Pair<IntentionActionDescriptor, TextRange>> it = quickFixActionRanges.iterator(); it.hasNext();) {
938       Pair<IntentionActionDescriptor, TextRange> pair = it.next();
939       if (condition.value(pair.first.getAction())) {
940         it.remove();
941       }
942     }
943   }
944 }