/* * Copyright 2000-2013 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.coldFusion.UI.editorActions.matchers; import com.intellij.codeInsight.highlighting.BraceMatcher; import com.intellij.coldFusion.model.CfmlLanguage; import com.intellij.coldFusion.model.CfmlUtil; import com.intellij.coldFusion.model.files.CfmlFileType; import com.intellij.coldFusion.model.lexer.CfmlTokenTypes; import com.intellij.coldFusion.model.lexer.CfscriptTokenTypes; import com.intellij.ide.highlighter.XmlFileType; import com.intellij.lang.BracePair; import com.intellij.lang.Language; import com.intellij.lang.LanguageBraceMatching; import com.intellij.lang.PairedBraceMatcher; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeExtensionPoint; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Created by Lera Nikolaenko * Date: 09.10.2008 */ public class CfmlBraceMatcher implements BraceMatcher { private static final BracePair[] PAIRS = new BracePair[]{ // new BracePair(CfmlTokenTypes.OPENER, CfmlTokenTypes.CLOSER, true), new BracePair(CfscriptTokenTypes.L_BRACKET, CfscriptTokenTypes.R_BRACKET, false), new BracePair(CfscriptTokenTypes.L_SQUAREBRACKET, CfscriptTokenTypes.R_SQUAREBRACKET, false), new BracePair(CfscriptTokenTypes.L_CURLYBRACKET, CfscriptTokenTypes.R_CURLYBRACKET, false), new BracePair(CfscriptTokenTypes.OPENSHARP, CfscriptTokenTypes.CLOSESHARP, true),/* new BracePair(CfscriptTokenTypes.DOUBLE_QUOTE, CfscriptTokenTypes.DOUBLE_QUOTE_CLOSER, false), new BracePair(CfscriptTokenTypes.SINGLE_QUOTE, CfscriptTokenTypes.SINGLE_QUOTE_CLOSER, false),*/ new BracePair(CfmlTokenTypes.START_EXPRESSION, CfmlTokenTypes.END_EXPRESSION, true)/*, new BracePair(CfmlTokenTypes.DOUBLE_QUOTE, CfmlTokenTypes.DOUBLE_QUOTE_CLOSER, false), new BracePair(CfmlTokenTypes.SINGLE_QUOTE, CfmlTokenTypes.SINGLE_QUOTE_CLOSER, false)*/ }; public int getBraceTokenGroupId(IElementType tokenType) { final Language l = tokenType.getLanguage(); return l.hashCode(); /* PairedBraceMatcher matcher = LanguageBraceMatching.INSTANCE.forLanguage(l); if (matcher != null) { BracePair[] pairs = matcher.getPairs(); for (BracePair pair : pairs) { if (pair.getLeftBraceType() == tokenType || pair.getRightBraceType() == tokenType ) { return l.hashCode(); } } } FileType tokenFileType = tokenType.getLanguage().getAssociatedFileType(); if (tokenFileType != CfmlFileType.INSTANCE) { for(FileTypeExtensionPoint<BraceMatcher> ext : Extensions.getExtensions(BraceMatcher.EP_NAME)) { if (tokenFileType.getName().equals(ext.filetype)) { return ext.getInstance().getBraceTokenGroupId(tokenType); } } } return l.hashCode(); */ } public boolean isLBraceToken(HighlighterIterator iterator, CharSequence fileText, FileType fileType) { final IElementType tokenType = iterator.getTokenType(); PairedBraceMatcher matcher = LanguageBraceMatching.INSTANCE.forLanguage(tokenType.getLanguage()); if (matcher != null) { BracePair[] pairs = matcher.getPairs(); for (BracePair pair : pairs) { if (pair.getLeftBraceType() == tokenType) return true; } } if (!tokenType.getLanguage().equals(CfmlLanguage.INSTANCE)) { FileType tokenFileType = iterator.getTokenType().getLanguage().getAssociatedFileType(); if (tokenFileType != null && tokenFileType != CfmlFileType.INSTANCE) { for (FileTypeExtensionPoint<BraceMatcher> ext : Extensions.getExtensions(BraceMatcher.EP_NAME)) { if (ext.filetype != null && ext.filetype.equals(tokenFileType.getName())) { return ext.getInstance().isLBraceToken(iterator, fileText, tokenFileType instanceof XmlFileType ? StdFileTypes.HTML : tokenFileType); } } } } for (BracePair pair : PAIRS) { if (pair.getLeftBraceType() == tokenType) { return true; } } return tokenType.equals(CfmlTokenTypes.OPENER) && (!CfmlUtil.isEndTagRequired(getTagName(fileText, iterator), null) || findEndTag(fileText, iterator)); } public boolean isRBraceToken(HighlighterIterator iterator, CharSequence fileText, FileType fileType) { final IElementType tokenType = iterator.getTokenType(); PairedBraceMatcher matcher = LanguageBraceMatching.INSTANCE.forLanguage(tokenType.getLanguage()); if (matcher != null) { BracePair[] pairs = matcher.getPairs(); for (BracePair pair : pairs) { if (pair.getRightBraceType() == tokenType) return true; } } if (!tokenType.getLanguage().equals(CfmlLanguage.INSTANCE)) { FileType tokenFileType = iterator.getTokenType().getLanguage().getAssociatedFileType(); if (tokenFileType != null && tokenFileType != CfmlFileType.INSTANCE) { for (FileTypeExtensionPoint<BraceMatcher> ext : Extensions.getExtensions(BraceMatcher.EP_NAME)) { if (ext.filetype != null && ext.filetype.equals(tokenFileType.getName())) { return ext.getInstance().isRBraceToken(iterator, fileText, tokenFileType instanceof XmlFileType ? StdFileTypes.HTML : tokenFileType); } } } } for (BracePair pair : PAIRS) { if (pair.getRightBraceType() == tokenType) return true; } return ((tokenType.equals(CfmlTokenTypes.CLOSER)) && findBeginTag(fileText, iterator)) || (tokenType.equals(CfmlTokenTypes.R_ANGLEBRACKET) && !CfmlUtil.isEndTagRequired(getTagName(fileText, iterator), null) && !findEndTag(fileText, iterator)); } public boolean isPairBraces(IElementType tokenType1, IElementType tokenType2) { PairedBraceMatcher matcher = LanguageBraceMatching.INSTANCE.forLanguage(tokenType1.getLanguage()); if (matcher != null) { BracePair[] pairs = matcher.getPairs(); for (BracePair pair : pairs) { if (pair.getLeftBraceType() == tokenType1) return pair.getRightBraceType() == tokenType2; if (pair.getRightBraceType() == tokenType1) return pair.getLeftBraceType() == tokenType2; } } FileType tokenFileType1 = tokenType1.getLanguage().getAssociatedFileType(); FileType tokenFileType2 = tokenType2.getLanguage().getAssociatedFileType(); if (tokenFileType2 != tokenFileType1) { return false; } if (tokenFileType1 != CfmlFileType.INSTANCE && tokenFileType1 != null) { for (FileTypeExtensionPoint<BraceMatcher> ext : Extensions.getExtensions(BraceMatcher.EP_NAME)) { if (ext.filetype.equals(tokenFileType1.getName())) { return ext.getInstance().isPairBraces(tokenType1, tokenType2); } } } for (BracePair pair : PAIRS) { if (pair.getLeftBraceType() == tokenType1) return pair.getRightBraceType() == tokenType2; if (pair.getRightBraceType() == tokenType1) return pair.getLeftBraceType() == tokenType2; } return (tokenType1.equals(CfmlTokenTypes.OPENER) && tokenType2.equals(CfmlTokenTypes.CLOSER)) || (tokenType1.equals(CfmlTokenTypes.CLOSER) && tokenType2.equals(CfmlTokenTypes.OPENER)) || (tokenType1.equals(CfmlTokenTypes.R_ANGLEBRACKET) && tokenType2.equals(CfmlTokenTypes.OPENER)) || (tokenType1.equals(CfmlTokenTypes.OPENER) && tokenType2.equals(CfmlTokenTypes.R_ANGLEBRACKET)); } public boolean isStructuralBrace(HighlighterIterator iterator, CharSequence text, FileType fileType) { IElementType type = iterator.getTokenType(); if (type == CfscriptTokenTypes.L_BRACKET || type == CfscriptTokenTypes.R_BRACKET) { return false; } return true; /* for(FileTypeExtensionPoint<BraceMatcher> ext : Extensions.getExtensions(BraceMatcher.EP_NAME)) { if (StdFileTypes.HTML.getName().equals(ext.filetype)) { return ext.getInstance().isStructuralBrace(iterator, text, StdFileTypes.HTML); } } IElementType tokenType = iterator.getTokenType(); PairedBraceMatcher matcher = LanguageBraceMatching.INSTANCE.forLanguage(tokenType.getLanguage()); if (matcher != null) { BracePair[] pairs = matcher.getPairs(); for (BracePair pair : pairs) { if ((pair.getLeftBraceType() == tokenType || pair.getRightBraceType() == tokenType) && pair.isStructural()) return true; } } for (BracePair pair : PAIRS) { if ((pair.getLeftBraceType() == tokenType || pair.getRightBraceType() == tokenType) && pair.isStructural()) return true; } return tokenType.equals(CfmlTokenTypes.OPENER) || tokenType.equals(CfmlTokenTypes.CLOSER); */ } public boolean isPairedBracesAllowedBeforeType(@NotNull final IElementType lbraceType, @Nullable final IElementType contextType) { return true; } private static boolean findBeginTag(CharSequence fileText, HighlighterIterator iterator) { IElementType tokenType; final String name = getTagName(fileText, iterator); if (name == null) { return false; } int balance = 0; int count = 0; while (balance < 1) { iterator.retreat(); count++; if (iterator.atEnd()) break; tokenType = iterator.getTokenType(); String currentTagName = getTagName(fileText, iterator); if (tokenType == CfmlTokenTypes.CLOSER && name.equals(currentTagName)) { balance--; } else if (tokenType == CfmlTokenTypes.OPENER && name.equals(currentTagName)) { balance++; } } while (count-- > 0) iterator.advance(); return balance == 1; } private static boolean findEndTag(CharSequence fileText, HighlighterIterator iterator) { IElementType tokenType; final String name = getTagName(fileText, iterator); if (name == null) { return false; } int balance = 0; int count = 0; while (balance > -1 && !iterator.atEnd()) { iterator.advance(); count++; if (iterator.atEnd()) break; tokenType = iterator.getTokenType(); String currrentTagName = getTagName(fileText, iterator); if (tokenType == CfmlTokenTypes.OPENER && name.equals(currrentTagName)) { balance++; } else if (tokenType == CfmlTokenTypes.CLOSER && name.equals(currrentTagName)) { balance--; } } while (count-- > 0) iterator.retreat(); return balance == -1; } @Nullable public static String getTagName(CharSequence fileText, HighlighterIterator iterator) { final IElementType tokenType = iterator.getTokenType(); String name = null; if (tokenType == CfmlTokenTypes.CLOSER || tokenType == CfmlTokenTypes.R_ANGLEBRACKET) { iterator.retreat(); IElementType tokenType1 = (!iterator.atEnd() ? iterator.getTokenType() : null); if (tokenType1 == CfmlTokenTypes.CF_TAG_NAME) { name = fileText.subSequence(iterator.getStart(), iterator.getEnd()).toString(); } else { int counter = 0; while (!iterator.atEnd()) { if (iterator.getTokenType() == CfmlTokenTypes.CF_TAG_NAME) { name = fileText.subSequence(iterator.getStart(), iterator.getEnd()).toString(); break; } if (iterator.getTokenType() == CfmlTokenTypes.CLOSER || iterator.getTokenType() == CfmlTokenTypes.R_ANGLEBRACKET) { break; } iterator.retreat(); counter++; } while (counter-- > 0) iterator.advance(); } iterator.advance(); } else if (tokenType == CfmlTokenTypes.OPENER) { iterator.advance(); IElementType tokenType1 = (!iterator.atEnd() ? iterator.getTokenType() : null); if (tokenType1 == CfmlTokenTypes.CF_TAG_NAME) { name = fileText.subSequence(iterator.getStart(), iterator.getEnd()).toString(); } iterator.retreat(); } return name == null ? name : name.toLowerCase(); } public IElementType getOppositeBraceTokenType(@NotNull final IElementType type) { for (BracePair pair : PAIRS) { if (pair.getLeftBraceType() == type) return pair.getRightBraceType(); if (pair.getRightBraceType() == type) return pair.getLeftBraceType(); } if (type == CfmlTokenTypes.OPENER) return CfmlTokenTypes.R_ANGLEBRACKET; if (type == CfmlTokenTypes.CLOSER) return CfmlTokenTypes.OPENER; if (type == CfmlTokenTypes.R_ANGLEBRACKET) return CfmlTokenTypes.OPENER; return null; } public int getCodeConstructStart(final PsiFile file, int openingBraceOffset) { PsiElement element = file.findElementAt(openingBraceOffset); if (element == null || element instanceof PsiFile) return openingBraceOffset; PsiElement parent = element.getParent(); return parent.getTextRange().getStartOffset(); } }