private void highlightInjectedSyntax( @NotNull PsiFile injectedPsi, @NotNull HighlightInfoHolder holder) { List<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange>> tokens = InjectedLanguageUtil.getHighlightTokens(injectedPsi); if (tokens == null) return; final Language injectedLanguage = injectedPsi.getLanguage(); Project project = injectedPsi.getProject(); SyntaxHighlighter syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter( injectedLanguage, project, injectedPsi.getVirtualFile()); final TextAttributes defaultAttrs = myGlobalScheme.getAttributes(HighlighterColors.TEXT); for (Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange> token : tokens) { ProgressManager.checkCanceled(); IElementType tokenType = token.getFirst(); PsiLanguageInjectionHost injectionHost = token.getSecond().getElement(); if (injectionHost == null) continue; TextRange textRange = token.getThird(); TextAttributesKey[] keys = syntaxHighlighter.getTokenHighlights(tokenType); if (textRange.getLength() == 0) continue; TextRange annRange = textRange.shiftRight(injectionHost.getTextRange().getStartOffset()); // force attribute colors to override host' ones TextAttributes attributes = null; for (TextAttributesKey key : keys) { TextAttributes attrs2 = myGlobalScheme.getAttributes(key); if (attrs2 != null) { attributes = attributes == null ? attrs2 : TextAttributes.merge(attributes, attrs2); } } TextAttributes forcedAttributes; if (attributes == null || attributes.isEmpty() || attributes.equals(defaultAttrs)) { forcedAttributes = TextAttributes.ERASE_MARKER; } else { HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT) .range(annRange) .textAttributes(TextAttributes.ERASE_MARKER) .createUnconditionally(); holder.add(info); forcedAttributes = new TextAttributes( attributes.getForegroundColor(), attributes.getBackgroundColor(), attributes.getEffectColor(), attributes.getEffectType(), attributes.getFontType()); } HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT) .range(annRange) .textAttributes(forcedAttributes) .createUnconditionally(); holder.add(info); } }
private static void highlightTodos( @NotNull PsiFile file, @NotNull CharSequence text, int startOffset, int endOffset, @NotNull ProgressIndicator progress, @NotNull ProperTextRange priorityRange, @NotNull Collection<HighlightInfo> result, @NotNull Collection<HighlightInfo> outsideResult) { PsiSearchHelper helper = PsiSearchHelper.SERVICE.getInstance(file.getProject()); TodoItem[] todoItems = helper.findTodoItems(file, startOffset, endOffset); if (todoItems.length == 0) return; for (TodoItem todoItem : todoItems) { progress.checkCanceled(); TextRange range = todoItem.getTextRange(); String description = text.subSequence(range.getStartOffset(), range.getEndOffset()).toString(); TextAttributes attributes = todoItem.getPattern().getAttributes().getTextAttributes(); HighlightInfo info = HighlightInfo.createHighlightInfo( HighlightInfoType.TODO, range, description, description, attributes); assert info != null; if (priorityRange.containsRange(info.getStartOffset(), info.getEndOffset())) { result.add(info); } else { outsideResult.add(info); } } }
@Override public void checkFile( @NotNull PsiFile originalFile, @NotNull final InspectionManager manager, @NotNull ProblemsHolder problemsHolder, @NotNull final GlobalInspectionContext globalContext, @NotNull final ProblemDescriptionsProcessor problemDescriptionsProcessor) { for (Pair<PsiFile, HighlightInfo> pair : runGeneralHighlighting(originalFile, highlightErrorElements, runAnnotators)) { PsiFile file = pair.first; HighlightInfo info = pair.second; TextRange range = new TextRange(info.startOffset, info.endOffset); PsiElement element = file.findElementAt(info.startOffset); while (element != null && !element.getTextRange().contains(range)) { element = element.getParent(); } if (element == null) { element = file; } GlobalInspectionUtil.createProblem( element, info, range.shiftRight(-element.getNode().getStartOffset()), info.getProblemGroup(), manager, problemDescriptionsProcessor, globalContext); } }
@NotNull public static HighlightInfo fromAnnotation( @NotNull Annotation annotation, @Nullable TextRange fixedRange, boolean batchMode) { final TextAttributes forcedAttributes = annotation.getEnforcedTextAttributes(); final TextAttributesKey forcedAttributesKey = forcedAttributes == null ? annotation.getTextAttributes() : null; HighlightInfo info = new HighlightInfo( forcedAttributes, forcedAttributesKey, convertType(annotation), fixedRange != null ? fixedRange.getStartOffset() : annotation.getStartOffset(), fixedRange != null ? fixedRange.getEndOffset() : annotation.getEndOffset(), annotation.getMessage(), annotation.getTooltip(), annotation.getSeverity(), annotation.isAfterEndOfLine(), annotation.needsUpdateOnTyping(), annotation.isFileLevelAnnotation(), 0); info.setGutterIconRenderer(annotation.getGutterIconRenderer()); info.setProblemGroup(annotation.getProblemGroup()); appendFixes( fixedRange, info, batchMode ? annotation.getBatchFixes() : annotation.getQuickFixes()); return info; }
private void incErrorCount(RangeHighlighter highlighter, int delta) { Object o = highlighter.getErrorStripeTooltip(); if (!(o instanceof HighlightInfo)) return; HighlightInfo info = (HighlightInfo) o; HighlightSeverity infoSeverity = info.getSeverity(); final int severityIdx = mySeverityRegistrar.getSeverityIdx(infoSeverity); if (severityIdx != -1) { errorCount[severityIdx] += delta; } }
public boolean equalsByActualOffset(@NotNull HighlightInfo info) { if (info == this) return true; return info.getSeverity() == getSeverity() && info.getActualStartOffset() == getActualStartOffset() && info.getActualEndOffset() == getActualEndOffset() && Comparing.equal(info.type, type) && Comparing.equal(info.gutterIconRenderer, gutterIconRenderer) && Comparing.equal(info.forcedTextAttributes, forcedTextAttributes) && Comparing.equal(info.forcedTextAttributesKey, forcedTextAttributesKey) && Comparing.strEqual(info.getDescription(), getDescription()); }
private static List<Problem> convertToProblems( final Collection<HighlightInfo> infos, final VirtualFile file, final boolean hasErrorElement) { List<Problem> problems = new SmartList<Problem>(); for (HighlightInfo info : infos) { if (info.getSeverity() == HighlightSeverity.ERROR) { Problem problem = new ProblemImpl(file, info, hasErrorElement); problems.add(problem); } } return problems; }
public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof HighlightInfo)) return false; HighlightInfo info = (HighlightInfo) obj; return info.getSeverity() == getSeverity() && info.startOffset == startOffset && info.endOffset == endOffset && Comparing.equal(info.type, type) && Comparing.equal(info.gutterIconRenderer, gutterIconRenderer) && Comparing.equal(info.forcedTextAttributes, forcedTextAttributes) && Comparing.equal(info.forcedTextAttributesKey, forcedTextAttributesKey) && Comparing.strEqual(info.getDescription(), getDescription()); }
public static void showInfoTooltip( @NotNull final HighlightInfo info, final Editor editor, final int defaultOffset, final int currentWidth) { if (info.toolTip == null || info.getSeverity() == HighlightSeverity.INFORMATION) return; Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); int endOffset = info.highlighter.getEndOffset(); int startOffset = info.highlighter.getStartOffset(); Point top = editor.logicalPositionToXY(editor.offsetToLogicalPosition(startOffset)); Point bottom = editor.logicalPositionToXY(editor.offsetToLogicalPosition(endOffset)); Point bestPoint = new Point(top.x, bottom.y + editor.getLineHeight()); if (!visibleArea.contains(bestPoint)) { bestPoint = editor.logicalPositionToXY(editor.offsetToLogicalPosition(defaultOffset)); } Point p = SwingUtilities.convertPoint( editor.getContentComponent(), bestPoint, editor.getComponent().getRootPane().getLayeredPane()); TooltipController.getInstance() .showTooltip(editor, p, info.toolTip, currentWidth, false, DAEMON_INFO_GROUP); }
private static boolean isAcceptedByFilters( @NotNull HighlightInfo info, @Nullable PsiElement psiElement) { PsiFile file = psiElement == null ? null : psiElement.getContainingFile(); for (HighlightInfoFilter filter : FILTERS) { if (!filter.accept(info, file)) { return false; } } info.psiElement = psiElement; return true; }
private static void appendFixes( @Nullable TextRange fixedRange, @NotNull HighlightInfo info, @Nullable List<Annotation.QuickFixInfo> fixes) { if (fixes != null) { for (final Annotation.QuickFixInfo quickFixInfo : fixes) { TextRange range = fixedRange != null ? fixedRange : quickFixInfo.textRange; HighlightDisplayKey key = quickFixInfo.key != null ? quickFixInfo.key : HighlightDisplayKey.find(ANNOTATOR_INSPECTION_SHORT_NAME); info.registerFix( quickFixInfo.quickFix, null, HighlightDisplayKey.getDisplayNameByKey(key), range, key); } } }
private HighlightInfo createHighlightInfo( TextRange range, HighlightInfoType type, Set<Pair<Object, TextRange>> existingMarkupTooltips) { int start = range.getStartOffset(); String tooltip = start <= myDocument.getTextLength() ? HighlightHandlerBase.getLineTextErrorStripeTooltip(myDocument, start, false) : null; String unescapedTooltip = existingMarkupTooltips.contains(new Pair<Object, TextRange>(tooltip, range)) ? null : tooltip; HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(type).range(range); if (unescapedTooltip != null) { builder.unescapedToolTip(unescapedTooltip); } return builder.createUnconditionally(); }
@Override protected void collectInformationWithProgress(@NotNull final ProgressIndicator progress) { if (!Registry.is("editor.injected.highlighting.enabled")) return; final Set<HighlightInfo> gotHighlights = new THashSet<HighlightInfo>(100); final List<PsiElement> inside = new ArrayList<PsiElement>(); final List<PsiElement> outside = new ArrayList<PsiElement>(); List<ProperTextRange> insideRanges = new ArrayList<ProperTextRange>(); List<ProperTextRange> outsideRanges = new ArrayList<ProperTextRange>(); // TODO: this thing is just called TWICE with same arguments eating CPU on huge files :( Divider.divideInsideAndOutside( myFile, myRestrictRange.getStartOffset(), myRestrictRange.getEndOffset(), myPriorityRange, inside, insideRanges, outside, outsideRanges, false, SHOULD_HIGHIGHT_FILTER); // all infos for the "injected fragment for the host which is inside" are indeed inside // but some of the infos for the "injected fragment for the host which is outside" can be still // inside Set<HighlightInfo> injectedResult = new THashSet<HighlightInfo>(); Set<PsiFile> injected = getInjectedPsiFiles(inside, outside, progress); setProgressLimit(injected.size()); if (!addInjectedPsiHighlights( injected, progress, Collections.synchronizedSet(injectedResult))) { throw new ProcessCanceledException(); } final List<HighlightInfo> injectionsOutside = new ArrayList<HighlightInfo>(gotHighlights.size()); Set<HighlightInfo> result; synchronized (injectedResult) { // sync here because all writes happened in another thread result = injectedResult; } for (HighlightInfo info : result) { if (myRestrictRange.contains(info)) { gotHighlights.add(info); } else { // nonconditionally apply injected results regardless whether they are in // myStartOffset,myEndOffset injectionsOutside.add(info); } } if (!injectionsOutside.isEmpty()) { final ProperTextRange priorityIntersection = myPriorityRange.intersection(myRestrictRange); if ((!inside.isEmpty() || !gotHighlights.isEmpty()) && priorityIntersection != null) { // do not apply when there were no elements to highlight // clear infos found in visible area to avoid applying them twice final List<HighlightInfo> toApplyInside = new ArrayList<HighlightInfo>(gotHighlights); myHighlights.addAll(toApplyInside); gotHighlights.clear(); myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced( myHighlightingSession, toApplyInside, myPriorityRange, myRestrictRange, getId()); } List<HighlightInfo> toApply = new ArrayList<HighlightInfo>(); for (HighlightInfo info : gotHighlights) { if (!myRestrictRange.containsRange(info.getStartOffset(), info.getEndOffset())) continue; if (!myPriorityRange.containsRange(info.getStartOffset(), info.getEndOffset())) { toApply.add(info); } } toApply.addAll(injectionsOutside); myHighlightInfoProcessor.highlightsOutsideVisiblePartAreProduced( myHighlightingSession, toApply, myRestrictRange, new ProperTextRange(0, myDocument.getTextLength()), getId()); } else { // else apply only result (by default apply command) and only within inside myHighlights.addAll(gotHighlights); myHighlightInfoProcessor.highlightsInsideVisiblePartAreProduced( myHighlightingSession, myHighlights, myRestrictRange, myRestrictRange, getId()); } }
private static void addPatchedInfos( @NotNull HighlightInfo info, @NotNull PsiFile injectedPsi, @NotNull DocumentWindow documentWindow, @NotNull InjectedLanguageManager injectedLanguageManager, @Nullable TextRange fixedTextRange, @NotNull Collection<HighlightInfo> out) { ProperTextRange textRange = new ProperTextRange(info.startOffset, info.endOffset); List<TextRange> editables = injectedLanguageManager.intersectWithAllEditableFragments(injectedPsi, textRange); for (TextRange editable : editables) { TextRange hostRange = fixedTextRange == null ? documentWindow.injectedToHost(editable) : fixedTextRange; boolean isAfterEndOfLine = info.isAfterEndOfLine(); if (isAfterEndOfLine) { // convert injected afterEndOfLine to either host' afterEndOfLine or not-afterEndOfLine // highlight of the injected fragment boundary int hostEndOffset = hostRange.getEndOffset(); int lineNumber = documentWindow.getDelegate().getLineNumber(hostEndOffset); int hostLineEndOffset = documentWindow.getDelegate().getLineEndOffset(lineNumber); if (hostEndOffset < hostLineEndOffset) { // convert to non-afterEndOfLine isAfterEndOfLine = false; hostRange = new ProperTextRange(hostRange.getStartOffset(), hostEndOffset + 1); } } HighlightInfo patched = new HighlightInfo( info.forcedTextAttributes, info.forcedTextAttributesKey, info.type, hostRange.getStartOffset(), hostRange.getEndOffset(), info.getDescription(), info.getToolTip(), info.type.getSeverity(null), isAfterEndOfLine, null, false, 0, info.getProblemGroup(), info.getGutterIconRenderer()); patched.setHint(info.hasHint()); if (info.quickFixActionRanges != null) { for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> pair : info.quickFixActionRanges) { TextRange quickfixTextRange = pair.getSecond(); List<TextRange> editableQF = injectedLanguageManager.intersectWithAllEditableFragments( injectedPsi, quickfixTextRange); for (TextRange editableRange : editableQF) { HighlightInfo.IntentionActionDescriptor descriptor = pair.getFirst(); if (patched.quickFixActionRanges == null) patched.quickFixActionRanges = new ArrayList<Pair<HighlightInfo.IntentionActionDescriptor, TextRange>>(); TextRange hostEditableRange = documentWindow.injectedToHost(editableRange); patched.quickFixActionRanges.add(Pair.create(descriptor, hostEditableRange)); } } } patched.setFromInjection(true); out.add(patched); } }
private boolean addInjectedPsiHighlights( @NotNull PsiFile injectedPsi, TextAttributes injectedAttributes, @NotNull Collection<HighlightInfo> outInfos, @NotNull ProgressIndicator progress, @NotNull InjectedLanguageManager injectedLanguageManager) { DocumentWindow documentWindow = (DocumentWindow) PsiDocumentManager.getInstance(myProject).getCachedDocument(injectedPsi); if (documentWindow == null) return true; Place places = InjectedLanguageUtil.getShreds(injectedPsi); for (PsiLanguageInjectionHost.Shred place : places) { PsiLanguageInjectionHost host = place.getHost(); if (host == null) continue; TextRange textRange = place.getRangeInsideHost().shiftRight(host.getTextRange().getStartOffset()); if (textRange.isEmpty()) continue; String desc = injectedPsi.getLanguage().getDisplayName() + ": " + injectedPsi.getText(); HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_BACKGROUND) .range(textRange); if (injectedAttributes != null && InjectedLanguageUtil.isHighlightInjectionBackground(host)) { builder.textAttributes(injectedAttributes); } builder.unescapedToolTip(desc); HighlightInfo info = builder.createUnconditionally(); info.setFromInjection(true); outInfos.add(info); } HighlightInfoHolder holder = createInfoHolder(injectedPsi); runHighlightVisitorsForInjected(injectedPsi, holder, progress); for (int i = 0; i < holder.size(); i++) { HighlightInfo info = holder.get(i); final int startOffset = documentWindow.injectedToHost(info.startOffset); final TextRange fixedTextRange = getFixedTextRange(documentWindow, startOffset); addPatchedInfos( info, injectedPsi, documentWindow, injectedLanguageManager, fixedTextRange, outInfos); } int injectedStart = holder.size(); highlightInjectedSyntax(injectedPsi, holder); for (int i = injectedStart; i < holder.size(); i++) { HighlightInfo info = holder.get(i); final int startOffset = info.startOffset; final TextRange fixedTextRange = getFixedTextRange(documentWindow, startOffset); if (fixedTextRange == null) { info.setFromInjection(true); outInfos.add(info); } else { HighlightInfo patched = new HighlightInfo( info.forcedTextAttributes, info.forcedTextAttributesKey, info.type, fixedTextRange.getStartOffset(), fixedTextRange.getEndOffset(), info.getDescription(), info.getToolTip(), info.type.getSeverity(null), info.isAfterEndOfLine(), null, false, 0, info.getProblemGroup(), info.getGutterIconRenderer()); patched.setFromInjection(true); outInfos.add(patched); } } if (!isDumbMode()) { List<HighlightInfo> todos = new ArrayList<HighlightInfo>(); highlightTodos( injectedPsi, injectedPsi.getText(), 0, injectedPsi.getTextLength(), progress, myPriorityRange, todos, todos); for (HighlightInfo info : todos) { addPatchedInfos(info, injectedPsi, documentWindow, injectedLanguageManager, null, outInfos); } } advanceProgress(1); return true; }