/* * Copyright 2000-2016 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.application.options.colors.highlighting; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; public class HighlightsExtractor { private final Map<String,TextAttributesKey> myTags; private final Map<String,TextAttributesKey> myInlineElements; private int myStartOffset; private int myEndOffset; private int mySkippedLen; private int myIndex; private boolean myIsOpeningTag; private List<TextRange> mySkipped = new ArrayList<>(); public HighlightsExtractor(@Nullable Map<String, TextAttributesKey> tags) { this(tags, null); } public HighlightsExtractor(@Nullable Map<String, TextAttributesKey> tags, @Nullable Map<String, TextAttributesKey> inlineElements) { myTags = tags; myInlineElements = inlineElements; } public String extractHighlights(String text, List<HighlightData> highlights) { mySkipped.clear(); if (ContainerUtil.isEmpty(myTags) && ContainerUtil.isEmpty(myInlineElements)) return text; resetIndices(); Stack<HighlightData> highlightsStack = new Stack<>(); while (true) { String tagName = findTagName(text); if (tagName == null || myIndex < 0) break; String tagNameWithoutParameters = StringUtil.substringBefore(tagName, " "); if (myInlineElements != null && tagNameWithoutParameters != null && myInlineElements.containsKey(tagNameWithoutParameters)) { mySkippedLen += tagName.length() + 2; String hintText = tagName.substring(tagNameWithoutParameters.length()).trim(); highlights.add(new InlineElementData(myStartOffset - mySkippedLen, myInlineElements.get(tagNameWithoutParameters), hintText)); } else if (myTags != null && myTags.containsKey(tagName)) { if (myIsOpeningTag) { mySkippedLen += tagName.length() + 2; HighlightData highlightData = new HighlightData(myStartOffset - mySkippedLen, myTags.get(tagName)); highlightsStack.push(highlightData); } else { HighlightData highlightData = highlightsStack.pop(); highlightData.setEndOffset(myEndOffset - mySkippedLen); mySkippedLen += tagName.length() + 3; highlights.add(highlightData); } } } return cutDefinedTags(text); } private String findTagName(String text) { myIsOpeningTag = true; int openTag = text.indexOf('<', myIndex); if (openTag == -1) { return null; } while (text.charAt(openTag + 1) == '<') { openTag++; } if (text.charAt(openTag + 1) == '/') { myIsOpeningTag = false; openTag++; } if (!isValidTagFirstChar(text.charAt(openTag + 1))) { myIndex = openTag + 1; return ""; } int closeTag = text.indexOf('>', openTag + 1); if (closeTag == -1) return null; int i = text.indexOf('<', openTag + 1); if (i != -1 && i < closeTag) { myIndex = i; return ""; } final String tagName = text.substring(openTag + 1, closeTag); if (myIsOpeningTag) { myStartOffset = openTag + tagName.length() + 2; if (myTags != null && myTags.containsKey(tagName) || myInlineElements != null && myInlineElements.containsKey(StringUtil.substringBefore(tagName, " "))) { mySkipped.add(TextRange.from(openTag, tagName.length() + 2)); } } else { myEndOffset = openTag - 1; if (myTags != null && myTags.containsKey(tagName)) { mySkipped.add(TextRange.from(openTag - 1, tagName.length() + 3)); } } myIndex = Math.max(myStartOffset, myEndOffset + 1); return tagName; } private static boolean isValidTagFirstChar(char c) { return Character.isLetter(c) || c == '_'; } private String cutDefinedTags(String text) { StringBuilder builder = new StringBuilder(text); for (int i = mySkipped.size() - 1; i >= 0; i--) { TextRange range = mySkipped.get(i); builder.delete(range.getStartOffset(), range.getEndOffset()); } return builder.toString(); } private void resetIndices() { myIndex = 0; myStartOffset = 0; myEndOffset = 0; mySkippedLen = 0; } }