blob: f35bebce37155f417a4f0d139a47ad485b78242e [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.impl.tagTreeHighlighting;
import com.intellij.application.options.editor.WebEditorOptions;
import com.intellij.codeHighlighting.TextEditorHighlightingPass;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.MarkupModelEx;
import com.intellij.openapi.editor.impl.DocumentMarkupModel;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlChildRole;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlTokenType;
import com.intellij.ui.Gray;
import com.intellij.xml.breadcrumbs.BreadcrumbsInfoProvider;
import com.intellij.xml.breadcrumbs.BreadcrumbsXmlWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class XmlTagTreeHighlightingPass extends TextEditorHighlightingPass {
private static final Key<List<RangeHighlighter>> TAG_TREE_HIGHLIGHTERS_IN_EDITOR_KEY = Key.create("TAG_TREE_HIGHLIGHTERS_IN_EDITOR_KEY");
private static final HighlightInfoType TYPE = new HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, TextAttributesKey
.createTextAttributesKey("TAG_TREE_HIGHLIGHTING_KEY"));
private final PsiFile myFile;
private final EditorEx myEditor;
private final BreadcrumbsInfoProvider myInfoProvider;
private final List<Pair<TextRange, TextRange>> myPairsToHighlight = new ArrayList<Pair<TextRange, TextRange>>();
public XmlTagTreeHighlightingPass(@NotNull PsiFile file, @NotNull EditorEx editor) {
super(file.getProject(), editor.getDocument(), true);
myFile = file;
myEditor = editor;
final FileViewProvider viewProvider = file.getManager().findViewProvider(file.getVirtualFile());
myInfoProvider = BreadcrumbsXmlWrapper.findInfoProvider(viewProvider);
}
@Override
public void doCollectInformation(@NotNull ProgressIndicator progress) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return;
}
if (!WebEditorOptions.getInstance().isTagTreeHighlightingEnabled()) {
return;
}
final PsiElement[] elements =
BreadcrumbsXmlWrapper.getLinePsiElements(myEditor.getCaretModel().getOffset(), myFile.getVirtualFile(), myProject, myInfoProvider);
if (elements == null || elements.length == 0) {
return;
}
if (!XmlTagTreeHighlightingUtil.containsTagsWithSameName(elements)) {
return;
}
for (int i = elements.length - 1; i >= 0; i--) {
if (elements[i] instanceof XmlTag) {
myPairsToHighlight.add(getTagRanges((XmlTag)elements[i]));
}
}
}
@Nullable
private static Pair<TextRange, TextRange> getTagRanges(XmlTag tag) {
final ASTNode tagNode = tag.getNode();
return Pair.create(getStartTagRange(tagNode), getEndTagRange(tagNode));
}
@Nullable
private static TextRange getStartTagRange(ASTNode tagNode) {
final ASTNode startTagStart = XmlChildRole.START_TAG_START_FINDER.findChild(tagNode);
if (startTagStart == null) {
return null;
}
ASTNode tagName = startTagStart.getTreeNext();
if (tagName == null || tagName.getElementType() != XmlTokenType.XML_NAME) {
return null;
}
ASTNode next = tagName.getTreeNext();
if (next != null && next.getElementType() == XmlTokenType.XML_TAG_END) {
tagName = next;
}
return new TextRange(startTagStart.getStartOffset(), tagName.getTextRange().getEndOffset());
}
@Nullable
private static TextRange getEndTagRange(ASTNode tagNode) {
final ASTNode endTagStart = XmlChildRole.CLOSING_TAG_START_FINDER.findChild(tagNode);
if (endTagStart == null) {
return null;
}
ASTNode endTagEnd = endTagStart;
while (endTagEnd != null && endTagEnd.getElementType() != XmlTokenType.XML_TAG_END) {
endTagEnd = endTagEnd.getTreeNext();
}
if (endTagEnd == null) {
return null;
}
return new TextRange(endTagStart.getStartOffset(), endTagEnd.getTextRange().getEndOffset());
}
@Override
public void doApplyInformationToEditor() {
final List<HighlightInfo> infos = getHighlights();
UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, 0, myFile.getTextLength(), infos, getColorsScheme(), getId());
}
public List<HighlightInfo> getHighlights() {
clearLineMarkers(myEditor);
final int count = myPairsToHighlight.size();
final List<HighlightInfo> highlightInfos = new ArrayList<HighlightInfo>(count * 2);
final MarkupModel markupModel = myEditor.getMarkupModel();
final Color[] baseColors = XmlTagTreeHighlightingUtil.getBaseColors();
final Color[] colorsForEditor = toColorsForEditor(baseColors);
final Color[] colorsForLineMarkers = toColorsForLineMarkers(baseColors);
final List<RangeHighlighter> newHighlighters = new ArrayList<RangeHighlighter>();
assert colorsForEditor.length > 0;
for (int i = 0; i < count && i < baseColors.length; i++) {
Pair<TextRange, TextRange> pair = myPairsToHighlight.get(i);
if (pair == null || pair.first == null && pair.second == null) {
continue;
}
Color color = colorsForEditor[i];
if (color == null) {
continue;
}
if (pair.first != null && !pair.first.isEmpty()) {
highlightInfos.add(createHighlightInfo(color, pair.first));
}
if (pair.second != null && !pair.second.isEmpty()) {
highlightInfos.add(createHighlightInfo(color, pair.second));
}
final int start = pair.first != null ? pair.first.getStartOffset() : pair.second.getStartOffset();
final int end = pair.second != null ? pair.second.getEndOffset() : pair.first.getEndOffset();
final Color lineMarkerColor = colorsForLineMarkers[i];
if (lineMarkerColor != null && start != end) {
final RangeHighlighter highlighter = createHighlighter(markupModel, new TextRange(start, end), lineMarkerColor);
newHighlighters.add(highlighter);
}
}
myEditor.putUserData(TAG_TREE_HIGHLIGHTERS_IN_EDITOR_KEY, newHighlighters);
return highlightInfos;
}
private static void clearLineMarkers(Editor editor) {
final List<RangeHighlighter> oldHighlighters = editor.getUserData(TAG_TREE_HIGHLIGHTERS_IN_EDITOR_KEY);
if (oldHighlighters != null) {
final MarkupModelEx markupModel = (MarkupModelEx)editor.getMarkupModel();
for (RangeHighlighter highlighter : oldHighlighters) {
if (markupModel.containsHighlighter(highlighter)) {
highlighter.dispose();
}
}
editor.putUserData(TAG_TREE_HIGHLIGHTERS_IN_EDITOR_KEY, null);
}
}
@NotNull
private static HighlightInfo createHighlightInfo(Color color, @NotNull TextRange range) {
TextAttributes attributes = new TextAttributes(null, color, null, null, Font.PLAIN);
return HighlightInfo.newHighlightInfo(TYPE).range(range).textAttributes(attributes).severity(HighlightSeverity.INFORMATION).createUnconditionally();
}
@NotNull
private static RangeHighlighter createHighlighter(final MarkupModel mm, @NotNull final TextRange range, final Color color) {
final RangeHighlighter highlighter =
mm.addRangeHighlighter(range.getStartOffset(), range.getEndOffset(), 0, null, HighlighterTargetArea.LINES_IN_RANGE);
highlighter.setLineMarkerRenderer(new LineMarkerRenderer() {
@Override
public void paint(Editor editor, Graphics g, Rectangle r) {
g.setColor(color);
g.fillRect(r.x, r.y, 2, r.height);
}
});
return highlighter;
}
private static Color[] toColorsForLineMarkers(Color[] baseColors) {
final Color[] colors = new Color[baseColors.length];
final Color tagBackground = Gray._239;
final double transparency = 0.4;
final double factor = 0.8;
for (int i = 0; i < colors.length; i++) {
final Color color = baseColors[i];
if (color == null) {
colors[i] = null;
continue;
}
int r = (int)(color.getRed() * factor);
int g = (int)(color.getGreen() * factor);
int b = (int)(color.getBlue() * factor);
r = (int)(tagBackground.getRed() * (1 - transparency) + r * transparency);
g = (int)(tagBackground.getGreen() * (1 - transparency) + g * transparency);
b = (int)(tagBackground.getBlue() * (1 - transparency) + b * transparency);
colors[i] = new Color(r, g, b);
}
return colors;
}
private Color[] toColorsForEditor(Color[] baseColors) {
final Color tagBackground = myEditor.getBackgroundColor();
if (tagBackground == null) {
return baseColors;
}
final Color[] resultColors = new Color[baseColors.length];
// todo: make configurable
final double transparency = WebEditorOptions.getInstance().getTagTreeHighlightingOpacity() * 0.01;
for (int i = 0; i < resultColors.length; i++) {
final Color color = baseColors[i];
final Color color1 = color != null
? XmlTagTreeHighlightingUtil.makeTransparent(color, tagBackground, transparency)
: null;
resultColors[i] = color1;
}
return resultColors;
}
public static void clearHighlightingAndLineMarkers(final Editor editor, @NotNull Project project) {
final MarkupModel markupModel = DocumentMarkupModel.forDocument(editor.getDocument(), project, true);
for (RangeHighlighter highlighter : markupModel.getAllHighlighters()) {
Object tooltip = highlighter.getErrorStripeTooltip();
if (!(tooltip instanceof HighlightInfo)) {
continue;
}
if (((HighlightInfo)tooltip).type == TYPE) {
highlighter.dispose();
}
}
clearLineMarkers(editor);
}
}