/*
* Copyright 2013-2017 consulo.io
*
* 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 consulo.csharp.lang.doc;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.csharp.lang.doc.psi.CSharpDocTokenType;
import com.intellij.codeInsight.highlighting.XmlAwareBraceMatcher;
import com.intellij.lang.BracePair;
import com.intellij.lang.LanguageBraceMatching;
import com.intellij.lang.PairedBraceMatcher;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.psi.PsiFile;
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.BidirectionalMap;
/**
* @author Maxim.Mossienko
* @see com.intellij.xml.impl.XmlBraceMatcher
*/
public class CSharpDocBraceMatcher implements XmlAwareBraceMatcher, PairedBraceMatcher
{
private static final int XML_TAG_TOKEN_GROUP = 1;
private static final int XML_VALUE_DELIMITER_GROUP = 2;
private static final BidirectionalMap<IElementType, IElementType> PAIRING_TOKENS = new BidirectionalMap<IElementType, IElementType>();
static
{
PAIRING_TOKENS.put(CSharpDocTokenType.XML_TAG_END, CSharpDocTokenType.XML_START_TAG_START);
PAIRING_TOKENS.put(CSharpDocTokenType.XML_EMPTY_ELEMENT_END, CSharpDocTokenType.XML_START_TAG_START);
PAIRING_TOKENS.put(CSharpDocTokenType.XML_ATTRIBUTE_VALUE_START_DELIMITER, CSharpDocTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER);
}
@Override
public int getBraceTokenGroupId(IElementType tokenType)
{
return tokenType == CSharpDocTokenType.XML_ATTRIBUTE_VALUE_START_DELIMITER || tokenType == CSharpDocTokenType
.XML_ATTRIBUTE_VALUE_END_DELIMITER ? XML_VALUE_DELIMITER_GROUP : XML_TAG_TOKEN_GROUP;
}
@Override
public boolean isLBraceToken(HighlighterIterator iterator, CharSequence fileText, FileType fileType)
{
final IElementType tokenType = iterator.getTokenType();
return tokenType == CSharpDocTokenType.XML_START_TAG_START ||
tokenType == CSharpDocTokenType.XML_ATTRIBUTE_VALUE_START_DELIMITER;
}
@Override
public boolean isRBraceToken(HighlighterIterator iterator, CharSequence fileText, FileType fileType)
{
final IElementType tokenType = iterator.getTokenType();
if(tokenType == CSharpDocTokenType.XML_EMPTY_ELEMENT_END ||
tokenType == CSharpDocTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER)
{
return true;
}
else if(tokenType == CSharpDocTokenType.XML_TAG_END)
{
return findEndTagStart(iterator);
}
else
{
return false;
}
}
@Override
public boolean isPairBraces(IElementType tokenType1, IElementType tokenType2)
{
if(tokenType2.equals(PAIRING_TOKENS.get(tokenType1)))
{
return true;
}
List<IElementType> keys = PAIRING_TOKENS.getKeysByValue(tokenType1);
return keys != null && keys.contains(tokenType2);
}
@Override
public boolean isStructuralBrace(HighlighterIterator iterator, CharSequence text, FileType fileType)
{
IElementType tokenType = iterator.getTokenType();
return isXmlStructuralBrace(iterator, text, fileType, tokenType);
}
protected boolean isXmlStructuralBrace(HighlighterIterator iterator, CharSequence text, FileType fileType, IElementType tokenType)
{
return tokenType == CSharpDocTokenType.XML_START_TAG_START ||
tokenType == CSharpDocTokenType.XML_TAG_END ||
tokenType == CSharpDocTokenType.XML_EMPTY_ELEMENT_END;
}
@Override
public BracePair[] getPairs()
{
return new BracePair[0];
}
@Override
public boolean isPairedBracesAllowedBeforeType(@NotNull final IElementType lbraceType, @Nullable final IElementType contextType)
{
return true;
}
@Override
public boolean isStrictTagMatching(final FileType fileType, final int braceGroupId)
{
switch(braceGroupId)
{
case XML_TAG_TOKEN_GROUP:
// Other xml languages may have nonbalanced tag names
return isStrictTagMatchingForFileType(fileType);
default:
return false;
}
}
protected boolean isStrictTagMatchingForFileType(final FileType fileType)
{
return true; //fileType == XmlFileType.INSTANCE || fileType == XHtmlFileType.INSTANCE;
}
@Override
public boolean areTagsCaseSensitive(final FileType fileType, final int braceGroupId)
{
switch(braceGroupId)
{
case XML_TAG_TOKEN_GROUP:
return true;//fileType == XmlFileType.INSTANCE;
default:
return false;
}
}
private static boolean findEndTagStart(HighlighterIterator iterator)
{
IElementType tokenType = iterator.getTokenType();
int balance = 0;
int count = 0;
while(balance >= 0)
{
iterator.retreat();
count++;
if(iterator.atEnd())
{
break;
}
tokenType = iterator.getTokenType();
if(tokenType == CSharpDocTokenType.XML_TAG_END || tokenType == CSharpDocTokenType.XML_EMPTY_ELEMENT_END)
{
balance++;
}
else if(tokenType == CSharpDocTokenType.XML_END_TAG_START || tokenType == CSharpDocTokenType.XML_START_TAG_START)
{
balance--;
}
}
while(count-- > 0)
{
iterator.advance();
}
return tokenType == CSharpDocTokenType.XML_END_TAG_START;
}
@Override
public String getTagName(CharSequence fileText, HighlighterIterator iterator)
{
final IElementType tokenType = iterator.getTokenType();
String name = null;
if(tokenType == CSharpDocTokenType.XML_START_TAG_START)
{
iterator.advance();
IElementType tokenType1 = iterator.atEnd() ? null : iterator.getTokenType();
boolean wasWhiteSpace = false;
if(isWhitespace(tokenType1))
{
wasWhiteSpace = true;
iterator.advance();
tokenType1 = iterator.atEnd() ? null : iterator.getTokenType();
}
if(tokenType1 == CSharpDocTokenType.XML_TAG_NAME || tokenType1 == CSharpDocTokenType.XML_NAME)
{
name = fileText.subSequence(iterator.getStart(), iterator.getEnd()).toString();
}
if(wasWhiteSpace)
{
iterator.retreat();
}
iterator.retreat();
}
else if(tokenType == CSharpDocTokenType.XML_TAG_END || tokenType == CSharpDocTokenType.XML_EMPTY_ELEMENT_END)
{
int balance = 0;
int count = 0;
IElementType tokenType1 = iterator.getTokenType();
while(balance >= 0)
{
iterator.retreat();
count++;
if(iterator.atEnd())
{
break;
}
tokenType1 = iterator.getTokenType();
if(tokenType1 == CSharpDocTokenType.XML_TAG_END || tokenType1 == CSharpDocTokenType.XML_EMPTY_ELEMENT_END)
{
balance++;
}
else if(tokenType1 == CSharpDocTokenType.XML_TAG_NAME)
{
balance--;
}
}
if(tokenType1 == CSharpDocTokenType.XML_TAG_NAME)
{
name = fileText.subSequence(iterator.getStart(), iterator.getEnd()).toString();
}
while(count-- > 0)
{
iterator.advance();
}
}
return name;
}
protected boolean isWhitespace(final IElementType tokenType1)
{
return tokenType1 == TokenType.WHITE_SPACE;
}
@Override
public IElementType getOppositeBraceTokenType(@NotNull final IElementType type)
{
PairedBraceMatcher matcher = LanguageBraceMatching.INSTANCE.forLanguage(type.getLanguage());
if(matcher != null)
{
BracePair[] pairs = matcher.getPairs();
for(BracePair pair : pairs)
{
if(pair.getLeftBraceType() == type)
{
return pair.getRightBraceType();
}
if(pair.getRightBraceType() == type)
{
return pair.getLeftBraceType();
}
}
}
return null;
}
@Override
public int getCodeConstructStart(final PsiFile file, int openingBraceOffset)
{
return openingBraceOffset;
}
}