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