blob: 1517a6a9d6bc7b7c32ad270db4ee2217fd9d0150 [file] [log] [blame]
/*
* Copyright 2002-2005 Sascha Weinreuter
*
* 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 org.intellij.plugins.xpathView.util;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.plugins.xpathView.Config;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HighlighterUtil {
private static final Key<List<RangeHighlighter>> HIGHLIGHTERS_KEY = Key.create("XPATH_HIGHLIGHTERS");
private HighlighterUtil() {
}
/**
* Clear all highlighters in an editor that are set up by this class
*
* @param editor the editor
*/
public static void clearHighlighters(final Editor editor) {
final List<RangeHighlighter> hl = editor.getUserData(HIGHLIGHTERS_KEY);
if (hl != null) {
if (purgeInvalidHighlighters(editor, hl)) {
final HighlightManager mgr = HighlightManager.getInstance(editor.getProject());
for (Iterator<RangeHighlighter> iterator = hl.iterator(); iterator.hasNext();) {
RangeHighlighter highlighter = iterator.next();
mgr.removeSegmentHighlighter(editor, highlighter);
iterator.remove();
}
}
}
}
/**
* Add a new highlighter to an editor
*
* @param editor the editor
* @param highlighter the highlighter
*/
public static void addHighlighter(Editor editor, RangeHighlighter highlighter) {
List<RangeHighlighter> hl = editor.getUserData(HIGHLIGHTERS_KEY);
if (hl == null) {
hl = new LinkedList<RangeHighlighter>();
editor.putUserData(HIGHLIGHTERS_KEY, hl);
} else {
purgeInvalidHighlighters(editor, hl);
}
hl.add(highlighter);
}
public static void removeHighlighter(Editor editor, RangeHighlighter h) {
final HighlightManager mgr = HighlightManager.getInstance(editor.getProject());
mgr.removeSegmentHighlighter(editor, h);
}
public static boolean hasHighlighters(Editor editor) {
final List<RangeHighlighter> hl = editor.getUserData(HIGHLIGHTERS_KEY);
if (hl != null) {
if (hl.isEmpty()) {
return false;
}
return purgeInvalidHighlighters(editor, hl);
}
return false;
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
private static boolean purgeInvalidHighlighters(Editor editor, List<RangeHighlighter> hl) {
final Set set = ContainerUtil.newIdentityTroveSet(Arrays.asList(editor.getMarkupModel().getAllHighlighters()));
boolean hasHighlighter = false;
for (Iterator<RangeHighlighter> iterator = hl.iterator(); iterator.hasNext();) {
final RangeHighlighter h = iterator.next();
if (!h.isValid() || !set.contains(h)) {
iterator.remove();
} else {
hasHighlighter = true;
}
}
return hasHighlighter;
}
public static List<RangeHighlighter> getHighlighters(Editor editor) {
if (!hasHighlighters(editor)) {
//noinspection unchecked
return Collections.emptyList();
} else {
return editor.getUserData(HIGHLIGHTERS_KEY);
}
}
/**
* Highlight a node in the editor.
* @param editor the editor
* @param node the node to be highlighted
* @param attrs the attributes for the highlighter
* @param cfg the plugin configuration
* @return The created highlighter object
*/
public static RangeHighlighter highlightNode(Editor editor, final PsiElement node, TextAttributes attrs, Config cfg) {
TextRange range;
final PsiElement realElement;
if ((node instanceof XmlTag) && cfg.isHighlightStartTagOnly()) {
XmlTag tag = (XmlTag)node;
realElement = MyPsiUtil.getNameElement(tag);
range = realElement.getTextRange();
} else {
range = node.getTextRange();
realElement = node;
}
// TODO: break at line boundaries
final ArrayList<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>(1);
final HighlightManager mgr = HighlightManager.getInstance(editor.getProject());
mgr.addRangeHighlight(editor, range.getStartOffset(), range.getEndOffset(), attrs, false, highlighters);
final RangeHighlighter rangeHighlighter = highlighters.get(0);
if (cfg.isAddErrorStripe()) {
rangeHighlighter.setErrorStripeMarkColor(attrs.getBackgroundColor());
rangeHighlighter.setErrorStripeTooltip(formatTooltip(editor, realElement));
} else {
rangeHighlighter.setErrorStripeMarkColor(null);
}
return rangeHighlighter;
}
private static Object formatTooltip(Editor e, PsiElement element) {
if (!(element instanceof XmlTag)) {
final String text = element.getText();
if ((text == null || text.length() == 0) && MyPsiUtil.isNameElement(element)) {
final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class, true);
if (tag != null) {
return tag.getName();
}
}
return text;
}
// have to use html/preformatted or else the tooltip gets formatted totally weird.
final CodeStyleSettingsManager instance = CodeStyleSettingsManager.getInstance(element.getProject());
final int tabSize = instance.getCurrentSettings().getTabSize(FileTypeManager.getInstance().getFileTypeByExtension("xml"));
final char[] spaces = new char[tabSize];
for (int i = 0; i < spaces.length; i++) {
spaces[i] = ' ';
}
final int textOffset = element.getTextOffset();
final int lineStartOffset = e.logicalPositionToOffset(new LogicalPosition(e.offsetToLogicalPosition(textOffset).line, 0));
final CharSequence chars = e.getDocument().getCharsSequence();
int indent = 0;
for (int i=lineStartOffset; i<textOffset; i++) {
if (chars.charAt(i) == ' ') {
indent++;
} else if (chars.charAt(i) == '\t') {
indent += ((indent + tabSize) / tabSize) * tabSize - indent;
} else {
break;
}
}
final String text = element.getText().replaceAll("\\t", new String(spaces)).replaceAll("&", "&amp;").replaceAll("<", "&lt;");
final Pattern indentPattern = Pattern.compile("^(\\s*).+");
final StringBuilder sb = new StringBuilder("<html><pre>");
final String[] lines = text.split("\\n");
for (String line : lines) {
final Matcher matcher = indentPattern.matcher(line);
if (matcher.matches()) {
// strip off the amount of spaces the top-level element is indented with
line = line.substring(Math.min(matcher.group(1).length(), indent));
}
sb.append(line).append("\n");
}
return sb.append("</pre></html>").toString();
}
}