2 * Copyright 2000-2016 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.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;
60 import java.util.List;
62 public class HighlightInfo implements Segment {
63 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.HighlightInfo");
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;
72 public final HighlightInfoType type;
74 public final int startOffset;
75 public final int endOffset;
77 private int fixStartOffset;
78 private int fixEndOffset;
79 RangeMarker fixMarker; // null means it the same as highlighter
81 private final String description;
82 private final String toolTip;
84 private final HighlightSeverity severity;
86 final int navigationShift;
88 volatile RangeHighlighterEx highlighter; // modified in EDT only
90 public List<Pair<IntentionActionDescriptor, TextRange>> quickFixActionRanges;
91 public List<Pair<IntentionActionDescriptor, RangeMarker>> quickFixActionMarkers;
93 private final GutterMark gutterIconRenderer;
94 private final ProblemGroup myProblemGroup;
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;
106 ProperTextRange getFixTextRange() {
107 return new ProperTextRange(fixStartOffset, fixEndOffset);
110 void setFromInjection(boolean fromInjection) {
111 setFlag(FROM_INJECTION_MASK, fromInjection);
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);
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));
126 String encoded = description.isEmpty() ? unescaped : StringUtil.replace(unescaped, description, DESCRIPTION_PLACEHOLDER);
127 //noinspection StringEquality
128 if (encoded == unescaped) {
131 if (encoded.equals(DESCRIPTION_PLACEHOLDER)) encoded = DESCRIPTION_PLACEHOLDER;
135 public String getDescription() {
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 {}
142 private boolean isFlagSet(@FlagConstant byte mask) {
143 return BitUtil.isSet(myFlags, mask);
146 private void setFlag(@FlagConstant byte mask, boolean value) {
147 myFlags = BitUtil.set(myFlags, mask, value);
150 boolean isFileLevelAnnotation() {
151 return isFlagSet(FILE_LEVEL_ANNOTATION_MASK);
154 boolean isBijective() {
155 return isFlagSet(BIJECTIVE_MASK);
158 void setBijective(boolean bijective) {
159 setFlag(BIJECTIVE_MASK, bijective);
163 public HighlightSeverity getSeverity() {
167 public boolean isAfterEndOfLine() {
168 return isFlagSet(AFTER_END_OF_LINE_MASK);
172 public TextAttributes getTextAttributes(@Nullable final PsiElement element, @Nullable final EditorColorsScheme editorColorsScheme) {
173 if (forcedTextAttributes != null) {
174 return forcedTextAttributes;
177 EditorColorsScheme colorsScheme = getColorsScheme(editorColorsScheme);
179 if (forcedTextAttributesKey != null) {
180 return colorsScheme.getAttributes(forcedTextAttributesKey);
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);
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);
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;
214 TextAttributesKey key = type.getAttributesKey();
215 return colorsScheme.getAttributes(key);
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();
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;
236 if (getSeverity() == HighlightSeverity.ERROR) {
237 return scheme.getAttributes(CodeInsightColors.ERRORS_ATTRIBUTES).getErrorStripeColor();
239 if (getSeverity() == HighlightSeverity.WARNING) {
240 return scheme.getAttributes(CodeInsightColors.WARNINGS_ATTRIBUTES).getErrorStripeColor();
242 if (getSeverity() == HighlightSeverity.INFO){
243 return scheme.getAttributes(CodeInsightColors.INFO_ATTRIBUTES).getErrorStripeColor();
245 if (getSeverity() == HighlightSeverity.WEAK_WARNING){
246 return scheme.getAttributes(CodeInsightColors.WEAK_WARNING_ATTRIBUTES).getErrorStripeColor();
248 if (getSeverity() == HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING) {
249 return scheme.getAttributes(CodeInsightColors.GENERIC_SERVER_ERROR_OR_WARNING).getErrorStripeColor();
252 TextAttributes attributes = getAttributesByType(element, type, scheme);
253 return attributes == null ? null : attributes.getErrorStripeColor();
258 private static EditorColorsScheme getColorsScheme(@Nullable final EditorColorsScheme customScheme) {
259 if (customScheme != null) {
262 return EditorColorsManager.getInstance().getGlobalScheme();
267 private static String htmlEscapeToolTip(@Nullable String unescapedTooltip) {
268 return unescapedTooltip == null ? null : XmlStringUtil.wrapInHtml(XmlStringUtil.escapeString(unescapedTooltip));
272 private static final HighlightInfoFilter[] FILTERS = HighlightInfoFilter.EXTENSION_POINT_NAME.getExtensions();
274 boolean needUpdateOnTyping() {
275 return isFlagSet(NEEDS_UPDATE_ON_TYPING_MASK);
278 HighlightInfo(@Nullable TextAttributes forcedTextAttributes,
279 @Nullable TextAttributesKey forcedTextAttributesKey,
280 @NotNull HighlightInfoType type,
283 @Nullable String escapedDescription,
284 @Nullable String escapedToolTip,
285 @NotNull HighlightSeverity severity,
286 boolean afterEndOfLine,
287 @Nullable Boolean needsUpdateOnTyping,
288 boolean isFileLevelAnnotation,
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);
295 this.forcedTextAttributes = forcedTextAttributes;
296 this.forcedTextAttributesKey = forcedTextAttributesKey;
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;
314 private static boolean calcNeedUpdateOnTyping(@Nullable Boolean needsUpdateOnTyping, HighlightInfoType type) {
315 if (needsUpdateOnTyping != null) return needsUpdateOnTyping.booleanValue();
317 if (type instanceof HighlightInfoType.UpdateOnTypingSuppressible) {
318 return ((HighlightInfoType.UpdateOnTypingSuppressible)type).needsUpdateOnTyping();
324 public boolean equals(Object obj) {
325 if (obj == this) return true;
326 if (!(obj instanceof HighlightInfo)) return false;
327 HighlightInfo info = (HighlightInfo)obj;
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());
339 boolean equalsByActualOffset(@NotNull HighlightInfo info) {
340 if (info == this) return true;
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());
353 public int hashCode() {
359 public String toString() {
360 @NonNls String s = "HighlightInfo(" + startOffset + "," + endOffset+")";
361 if (getActualStartOffset() != startOffset || getActualEndOffset() != endOffset) {
362 s += "; actual: (" + getActualStartOffset() + "," + getActualEndOffset() + ")";
364 if (highlighter != null) s += " text='" + getText() + "'";
365 if (getDescription() != null) s+= ", description='" + getDescription() + "'";
366 s += " severity=" + getSeverity();
367 s += " group=" + getGroup();
369 if (quickFixActionRanges != null) {
370 s+= "; quickFixes: "+quickFixActionRanges;
372 if (gutterIconRenderer != null) {
373 s += "; gutter: " + gutterIconRenderer;
379 public static Builder newHighlightInfo(@NotNull HighlightInfoType type) {
383 void setGroup(int group) {
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);
395 @NotNull Builder gutterIconRenderer(@NotNull GutterIconRenderer gutterIconRenderer);
396 @NotNull Builder problemGroup(@NotNull ProblemGroup problemGroup);
399 @NotNull Builder description(@NotNull String description);
400 @NotNull Builder descriptionAndTooltip(@NotNull String description);
403 @NotNull Builder textAttributes(@NotNull TextAttributes attributes);
404 @NotNull Builder textAttributes(@NotNull TextAttributesKey attributesKey);
407 @NotNull Builder unescapedToolTip(@NotNull String unescapedToolTip);
408 @NotNull Builder escapedToolTip(@NotNull String escapedToolTip);
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);
416 @Nullable("null means filtered out")
417 HighlightInfo create();
420 HighlightInfo createUnconditionally();
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)) {
430 info.psiElement = psiElement;
434 private static class B implements Builder {
435 private Boolean myNeedsUpdateOnTyping;
436 private TextAttributes forcedTextAttributes;
437 private TextAttributesKey forcedTextAttributesKey;
439 private final HighlightInfoType type;
440 private int startOffset = -1;
441 private int endOffset = -1;
443 private String escapedDescription;
444 private String escapedToolTip;
445 private HighlightSeverity severity;
447 private boolean isAfterEndOfLine;
448 private boolean isFileLevelAnnotation;
449 private int navigationShift;
451 private GutterIconRenderer gutterIconRenderer;
452 private ProblemGroup problemGroup;
453 private PsiElement psiElement;
455 private B(@NotNull HighlightInfoType type) {
461 public Builder gutterIconRenderer(@NotNull GutterIconRenderer gutterIconRenderer) {
462 assert this.gutterIconRenderer == null : "gutterIconRenderer already set";
463 this.gutterIconRenderer = gutterIconRenderer;
469 public Builder problemGroup(@NotNull ProblemGroup problemGroup) {
470 assert this.problemGroup == null : "problemGroup already set";
471 this.problemGroup = problemGroup;
477 public Builder description(@NotNull String description) {
478 assert escapedDescription == null : "description already set";
479 escapedDescription = description;
485 public Builder descriptionAndTooltip(@NotNull String description) {
486 return description(description).unescapedToolTip(description);
491 public Builder textAttributes(@NotNull TextAttributes attributes) {
492 assert forcedTextAttributes == null : "textattributes already set";
493 forcedTextAttributes = attributes;
499 public Builder textAttributes(@NotNull TextAttributesKey attributesKey) {
500 assert forcedTextAttributesKey == null : "textattributesKey already set";
501 forcedTextAttributesKey = attributesKey;
507 public Builder unescapedToolTip(@NotNull String unescapedToolTip) {
508 assert escapedToolTip == null : "Tooltip was already set";
509 escapedToolTip = htmlEscapeToolTip(unescapedToolTip);
515 public Builder escapedToolTip(@NotNull String escapedToolTip) {
516 assert this.escapedToolTip == null : "Tooltip was already set";
517 this.escapedToolTip = escapedToolTip;
523 public Builder range(int start, int end) {
524 assert startOffset == -1 && endOffset == -1 : "Offsets already set";
533 public Builder range(@NotNull TextRange textRange) {
534 assert startOffset == -1 && endOffset == -1 : "Offsets already set";
535 startOffset = textRange.getStartOffset();
536 endOffset = textRange.getEndOffset();
542 public Builder range(@NotNull ASTNode node) {
543 return range(node.getPsi());
548 public Builder range(@NotNull PsiElement element) {
549 assert psiElement == null : " psiElement already set";
550 psiElement = element;
551 return range(element.getTextRange());
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);
564 public Builder endOfLine() {
565 isAfterEndOfLine = true;
571 public Builder needsUpdateOnTyping(boolean update) {
572 assert myNeedsUpdateOnTyping == null : " needsUpdateOnTyping already set";
573 myNeedsUpdateOnTyping = update;
579 public Builder severity(@NotNull HighlightSeverity severity) {
580 assert this.severity == null : " severity already set";
581 this.severity = severity;
587 public Builder fileLevelAnnotation() {
588 isFileLevelAnnotation = true;
594 public Builder navigationShift(int navigationShift) {
595 this.navigationShift = navigationShift;
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");
606 if (!isAcceptedByFilters(info, psiElement)) return null;
613 public HighlightInfo createUnconditionally() {
614 if (severity == null) {
615 severity = type.getSeverity(psiElement);
618 return new HighlightInfo(forcedTextAttributes, forcedTextAttributesKey, type, startOffset, endOffset, escapedDescription,
619 escapedToolTip, severity, isAfterEndOfLine, myNeedsUpdateOnTyping, isFileLevelAnnotation, navigationShift,
620 problemGroup, gutterIconRenderer);
624 public GutterMark getGutterIconRenderer() {
625 return gutterIconRenderer;
629 public ProblemGroup getProblemGroup() {
630 return myProblemGroup;
634 public static HighlightInfo fromAnnotation(@NotNull Annotation annotation) {
635 return fromAnnotation(annotation, null, false);
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;
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());
654 private static final String ANNOTATOR_INSPECTION_SHORT_NAME = "Annotator";
656 private static void appendFixes(@Nullable TextRange fixedRange, @NotNull HighlightInfo info, @Nullable List<Annotation.QuickFixInfo> fixes) {
658 for (final Annotation.QuickFixInfo quickFixInfo : fixes) {
659 TextRange range = fixedRange != null ? fixedRange : quickFixInfo.textRange;
660 HighlightDisplayKey key = quickFixInfo.key != null
662 : HighlightDisplayKey.find(ANNOTATOR_INSPECTION_SHORT_NAME);
663 info.registerFix(quickFixInfo.quickFix, null, HighlightDisplayKey.getDisplayNameByKey(key), range, key);
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());
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;
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;
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;
704 public boolean hasHint() {
705 return isFlagSet(HAS_HINT_MASK);
708 void setHint(final boolean hasHint) {
709 setFlag(HAS_HINT_MASK, hasHint);
712 public int getActualStartOffset() {
713 RangeHighlighterEx h = highlighter;
714 return h == null || !h.isValid() ? startOffset : h.getStartOffset();
716 public int getActualEndOffset() {
717 RangeHighlighterEx h = highlighter;
718 return h == null || !h.isValid() ? endOffset : h.getEndOffset();
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;
731 IntentionActionDescriptor(@NotNull IntentionAction action, final List<IntentionAction> options, final String displayName) {
732 this(action, options, displayName, null);
735 public IntentionActionDescriptor(@NotNull IntentionAction action, final Icon icon) {
736 this(action, null, null, icon);
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);
746 public IntentionActionDescriptor(@NotNull IntentionAction action,
747 @Nullable final List<IntentionAction> options,
748 @Nullable final String displayName,
750 @Nullable HighlightDisplayKey key,
751 @Nullable ProblemGroup problemGroup,
752 @Nullable HighlightSeverity severity) {
755 myDisplayName = displayName;
758 myProblemGroup = problemGroup;
759 mySeverity = severity;
763 public IntentionAction getAction() {
768 return mySeverity == null || mySeverity.compareTo(HighlightSeverity.ERROR) >= 0;
771 boolean canCleanup(@NotNull PsiElement element) {
772 if (myCanCleanup == null) {
773 InspectionProfile profile = InspectionProjectProfileManager.getInstance(element.getProject()).getCurrentProfile();
774 final HighlightDisplayKey key = myKey;
776 myCanCleanup = false;
778 InspectionToolWrapper toolWrapper = profile.getInspectionTool(key.toString(), element);
779 myCanCleanup = toolWrapper != null && toolWrapper.isCleanupTool();
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))) {
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;
799 if (options != null || key == null) {
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());
809 toolWrapper = profile.getInspectionTool(idkey.toString(), element);
812 if (toolWrapper != null) {
814 myCanCleanup = toolWrapper.isCleanupTool();
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));
824 if (fixAllIntention != null) {
825 if (actions.isEmpty()) {
826 return Collections.singletonList(fixAllIntention);
829 actions = new ArrayList<>(actions);
830 actions.add(fixAllIntention);
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);
843 SuppressQuickFix[] suppressFixes = wrappedTool.getBatchSuppressActions(element);
844 if (suppressFixes.length > 0) {
845 ContainerUtil.addAll(newOptions, ContainerUtil.map(suppressFixes, SuppressIntentionActionFromFix::convertBatchToSuppressIntentionAction));
850 if (myProblemGroup instanceof SuppressableProblemGroup) {
851 final IntentionAction[] suppressActions = ((SuppressableProblemGroup)myProblemGroup).getSuppressActions(element);
852 ContainerUtil.addAll(newOptions, suppressActions);
855 synchronized (this) {
857 if (options == null) {
858 myOptions = options = newOptions;
866 public String getDisplayName() {
867 return myDisplayName;
872 public String toString() {
873 String text = getAction().getText();
874 return "descriptor: " + (text.isEmpty() ? getAction().getClass() : text);
878 public Icon getIcon() {
883 public boolean equals(Object obj) {
884 return obj instanceof IntentionActionDescriptor && myAction.equals(((IntentionActionDescriptor)obj).myAction);
889 public int getStartOffset() {
890 return getActualStartOffset();
894 public int getEndOffset() {
895 return getActualEndOffset();
902 boolean isFromInjection() {
903 return isFlagSet(FROM_INJECTION_MASK);
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");
913 if (!highlighter.isValid()) return "";
914 return highlighter.getDocument().getText(TextRange.create(highlighter));
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();
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) {
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())) {