2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.intellij
.codeInsight
.highlighting
;
19 import com
.intellij
.lang
.Language
;
20 import com
.intellij
.lang
.LanguageBraceMatching
;
21 import com
.intellij
.lang
.PairedBraceMatcher
;
22 import com
.intellij
.openapi
.editor
.highlighter
.HighlighterIterator
;
23 import com
.intellij
.openapi
.extensions
.Extensions
;
24 import com
.intellij
.openapi
.fileTypes
.FileType
;
25 import com
.intellij
.openapi
.fileTypes
.FileTypeExtensionPoint
;
26 import com
.intellij
.openapi
.fileTypes
.LanguageFileType
;
27 import com
.intellij
.openapi
.util
.Comparing
;
28 import com
.intellij
.psi
.PsiFile
;
29 import com
.intellij
.psi
.tree
.IElementType
;
30 import org
.jetbrains
.annotations
.NotNull
;
31 import org
.jetbrains
.annotations
.Nullable
;
33 import java
.util
.HashMap
;
34 import java
.util
.Stack
;
36 public class BraceMatchingUtil
{
37 public static final int UNDEFINED_TOKEN_GROUP
= -1;
39 private BraceMatchingUtil() {}
41 public static boolean isPairedBracesAllowedBeforeTypeInFileType(final IElementType lbraceType
, final IElementType tokenType
, final FileType fileType
) {
43 return getBraceMatcher(fileType
, lbraceType
).isPairedBracesAllowedBeforeType(lbraceType
, tokenType
);
45 catch (AbstractMethodError incompatiblePluginThatWeDoNotCare
) {}
49 private static final HashMap
<FileType
,BraceMatcher
> BRACE_MATCHERS
= new HashMap
<FileType
, BraceMatcher
>();
51 public static void registerBraceMatcher(FileType fileType
,BraceMatcher braceMatcher
) {
52 BRACE_MATCHERS
.put(fileType
, braceMatcher
);
55 private static final Stack
<IElementType
> ourBraceStack
= new Stack
<IElementType
>();
56 private static final Stack
<String
> ourTagNameStack
= new Stack
<String
>();
58 private static class MatchBraceContext
{
59 CharSequence fileText
;
61 HighlighterIterator iterator
;
64 IElementType brace1Token
;
68 boolean isCaseSensitive
;
70 private BraceMatcher myMatcher
;
72 MatchBraceContext(CharSequence _fileText
, FileType _fileType
, HighlighterIterator _iterator
, boolean _forward
) {
78 myMatcher
= getBraceMatcher(_fileType
, _iterator
);
79 brace1Token
= iterator
.getTokenType();
80 group
= getTokenGroup(brace1Token
, fileType
);
81 brace1TagName
= myMatcher
== null ?
null : getTagName(myMatcher
,fileText
, iterator
);
83 isStrict
= myMatcher
!= null && isStrictTagMatching(myMatcher
,fileType
, group
);
84 isStructural
= !isStrict
&& myMatcher
!= null && myMatcher
.isStructuralBrace(iterator
, fileText
, fileType
);
85 isCaseSensitive
= myMatcher
!= null && areTagsCaseSensitive(myMatcher
,fileType
, group
);
88 MatchBraceContext(CharSequence _fileText
, FileType _fileType
, HighlighterIterator _iterator
, boolean _forward
,
90 this(_fileText
, _fileType
, _iterator
, _forward
);
94 boolean doBraceMatch() {
95 ourBraceStack
.clear();
96 ourTagNameStack
.clear();
97 ourBraceStack
.push(brace1Token
);
99 ourTagNameStack
.push(brace1TagName
);
101 boolean matched
= false;
109 if (iterator
.atEnd()) {
113 IElementType tokenType
= iterator
.getTokenType();
115 if (getTokenGroup(tokenType
, fileType
) != group
) {
118 String tagName
= myMatcher
== null ?
null : getTagName(myMatcher
,fileText
, iterator
);
119 if (!isStrict
&& !Comparing
.equal(brace1TagName
, tagName
, isCaseSensitive
)) continue;
121 if (isStructural
&& (forward ?
isRBraceToken(iterator
, fileText
, fileType
) && !isPairBraces(brace1Token
, tokenType
, fileType
)
122 : isLBraceToken(iterator
, fileText
, fileType
) && !isPairBraces(brace1Token
, tokenType
, fileType
))) {
123 if (!ourBraceStack
.isEmpty() && myMatcher
!= null && !ourBraceStack
.contains(myMatcher
.getOppositeBraceTokenType(tokenType
))) continue;
126 if (forward ?
isLBraceToken(iterator
, fileText
, fileType
) : isRBraceToken(iterator
, fileText
, fileType
)){
127 ourBraceStack
.push(tokenType
);
129 ourTagNameStack
.push(tagName
);
132 else if (forward ?
isRBraceToken(iterator
, fileText
,fileType
) : isLBraceToken(iterator
, fileText
, fileType
)){
133 IElementType topTokenType
= ourBraceStack
.pop();
134 String topTagName
= null;
136 topTagName
= ourTagNameStack
.pop();
139 if (!isStrict
&& myMatcher
!= null && ourBraceStack
.contains(myMatcher
.getOppositeBraceTokenType(tokenType
))) {
140 while(!isPairBraces(topTokenType
, tokenType
, fileType
)) {
141 topTokenType
= ourBraceStack
.pop();
145 if (!isPairBraces(topTokenType
, tokenType
, fileType
)
146 || isStrict
&& !Comparing
.equal(topTagName
, tagName
, isCaseSensitive
)
152 if (ourBraceStack
.isEmpty()){
162 public static synchronized boolean matchBrace(CharSequence fileText
, FileType fileType
, HighlighterIterator iterator
,
164 return new MatchBraceContext(fileText
, fileType
, iterator
, forward
).doBraceMatch();
168 public static synchronized boolean matchBrace(CharSequence fileText
, FileType fileType
, HighlighterIterator iterator
,
169 boolean forward
, boolean isStrict
) {
170 return new MatchBraceContext(fileText
, fileType
, iterator
, forward
, isStrict
).doBraceMatch();
173 public static boolean findStructuralLeftBrace(FileType fileType
, HighlighterIterator iterator
, CharSequence fileText
) {
174 ourBraceStack
.clear();
175 ourTagNameStack
.clear();
177 BraceMatcher matcher
= getBraceMatcher(fileType
, iterator
);
179 while (!iterator
.atEnd()) {
180 if (isStructuralBraceToken(fileType
, iterator
,fileText
)) {
181 if (isRBraceToken(iterator
, fileText
, fileType
)) {
182 ourBraceStack
.push(iterator
.getTokenType());
183 ourTagNameStack
.push(getTagName(matcher
,fileText
, iterator
));
185 if (isLBraceToken(iterator
, fileText
, fileType
)) {
186 if (ourBraceStack
.isEmpty()) return true;
188 final int group
= matcher
.getBraceTokenGroupId(iterator
.getTokenType());
190 final IElementType topTokenType
= ourBraceStack
.pop();
191 final IElementType tokenType
= iterator
.getTokenType();
193 boolean isStrict
= isStrictTagMatching(matcher
,fileType
, group
);
194 boolean isCaseSensitive
= areTagsCaseSensitive(matcher
,fileType
, group
);
196 String topTagName
= null;
197 String tagName
= null;
199 topTagName
= ourTagNameStack
.pop();
200 tagName
= getTagName(matcher
,fileText
, iterator
);
203 if (!isPairBraces(topTokenType
, tokenType
, fileType
)
204 || isStrict
&& !Comparing
.equal(topTagName
, tagName
, isCaseSensitive
)) {
216 public static boolean isStructuralBraceToken(FileType fileType
, HighlighterIterator iterator
,CharSequence text
) {
217 BraceMatcher matcher
= getBraceMatcher(fileType
, iterator
);
218 return matcher
.isStructuralBrace(iterator
, text
, fileType
);
221 public static boolean isLBraceToken(HighlighterIterator iterator
, CharSequence fileText
, FileType fileType
){
222 final BraceMatcher braceMatcher
= getBraceMatcher(fileType
, iterator
);
224 return braceMatcher
.isLBraceToken(iterator
, fileText
, fileType
);
227 public static boolean isRBraceToken(HighlighterIterator iterator
, CharSequence fileText
, FileType fileType
){
228 final BraceMatcher braceMatcher
= getBraceMatcher(fileType
, iterator
);
230 return braceMatcher
.isRBraceToken(iterator
, fileText
, fileType
);
233 public static boolean isPairBraces(IElementType tokenType1
, IElementType tokenType2
, FileType fileType
){
234 BraceMatcher matcher
= getBraceMatcher(fileType
, tokenType1
);
235 return matcher
.isPairBraces(tokenType1
, tokenType2
);
238 private static int getTokenGroup(IElementType tokenType
, FileType fileType
){
239 BraceMatcher matcher
= getBraceMatcher(fileType
, tokenType
);
240 return matcher
.getBraceTokenGroupId(tokenType
);
243 // TODO: better name for this method
244 public static int findLeftmostLParen(HighlighterIterator iterator
, IElementType lparenTokenType
, CharSequence fileText
, FileType fileType
){
245 int lastLbraceOffset
= -1;
247 Stack
<IElementType
> braceStack
= new Stack
<IElementType
>();
248 for( ; !iterator
.atEnd(); iterator
.retreat()){
249 final IElementType tokenType
= iterator
.getTokenType();
251 if (isLBraceToken(iterator
, fileText
, fileType
)){
252 if (!braceStack
.isEmpty()){
253 IElementType topToken
= braceStack
.pop();
254 if (!isPairBraces(tokenType
, topToken
, fileType
)) {
255 break; // unmatched braces
259 if (tokenType
== lparenTokenType
){
260 lastLbraceOffset
= iterator
.getStart();
267 else if (isRBraceToken(iterator
, fileText
, fileType
)){
268 braceStack
.push(iterator
.getTokenType());
272 return lastLbraceOffset
;
275 // TODO: better name for this method
276 public static int findRightmostRParen(HighlighterIterator iterator
, IElementType rparenTokenType
, CharSequence fileText
, FileType fileType
) {
277 int lastRbraceOffset
= -1;
279 Stack
<IElementType
> braceStack
= new Stack
<IElementType
>();
280 for(; !iterator
.atEnd(); iterator
.advance()){
281 final IElementType tokenType
= iterator
.getTokenType();
283 if (isRBraceToken(iterator
, fileText
, fileType
)){
284 if (!braceStack
.isEmpty()){
285 IElementType topToken
= braceStack
.pop();
286 if (!isPairBraces(tokenType
, topToken
, fileType
)) {
287 break; // unmatched braces
291 if (tokenType
== rparenTokenType
){
292 lastRbraceOffset
= iterator
.getStart();
299 else if (isLBraceToken(iterator
, fileText
, fileType
)){
300 braceStack
.push(iterator
.getTokenType());
304 return lastRbraceOffset
;
307 private static class BraceMatcherHolder
{
308 private static final BraceMatcher ourDefaultBraceMatcher
= new DefaultBraceMatcher();
312 public static BraceMatcher
getBraceMatcher(@NotNull FileType fileType
, @NotNull HighlighterIterator iterator
) {
313 return getBraceMatcher(fileType
, iterator
.getTokenType());
317 public static BraceMatcher
getBraceMatcher(@NotNull FileType fileType
, @NotNull IElementType type
) {
318 return getBraceMatcher(fileType
, type
.getLanguage());
322 public static BraceMatcher
getBraceMatcher(FileType fileType
, Language lang
) {
323 final BraceMatcher byFileType
= getBraceMatcherByFileType(fileType
);
324 if (byFileType
!= null) return byFileType
;
326 PairedBraceMatcher matcher
= LanguageBraceMatching
.INSTANCE
.forLanguage(lang
);
327 if (matcher
!= null) {
328 return new PairedBraceMatcherAdapter(matcher
, lang
);
330 if (fileType
instanceof LanguageFileType
) {
331 final Language language
= ((LanguageFileType
)fileType
).getLanguage();
332 if (lang
!= language
) {
333 final FileType type1
= lang
.getAssociatedFileType();
335 final BraceMatcher braceMatcher
= getBraceMatcherByFileType(type1
);
336 if (braceMatcher
!= null) {
341 matcher
= LanguageBraceMatching
.INSTANCE
.forLanguage(language
);
342 if (matcher
!= null) {
343 return new PairedBraceMatcherAdapter(matcher
,language
);
348 return BraceMatcherHolder
.ourDefaultBraceMatcher
;
352 private static BraceMatcher
getBraceMatcherByFileType(final FileType fileType
) {
353 BraceMatcher braceMatcher
= BRACE_MATCHERS
.get(fileType
);
354 if (braceMatcher
!= null) return braceMatcher
;
356 for(FileTypeExtensionPoint
<BraceMatcher
> ext
: Extensions
.getExtensions(BraceMatcher
.EP_NAME
)) {
357 if (fileType
.getName().equals(ext
.filetype
)) {
358 braceMatcher
= ext
.getInstance();
359 BRACE_MATCHERS
.put(fileType
, braceMatcher
);
366 private static boolean isStrictTagMatching(BraceMatcher matcher
,final FileType fileType
, final int group
) {
367 return matcher
instanceof XmlAwareBraceMatcher
&& ((XmlAwareBraceMatcher
)matcher
).isStrictTagMatching(fileType
, group
);
370 private static boolean areTagsCaseSensitive(BraceMatcher matcher
,final FileType fileType
, final int tokenGroup
) {
371 return matcher
instanceof XmlAwareBraceMatcher
&& ((XmlAwareBraceMatcher
)matcher
).areTagsCaseSensitive(fileType
, tokenGroup
);
375 private static String
getTagName(BraceMatcher matcher
,CharSequence fileText
, HighlighterIterator iterator
) {
376 if (matcher
instanceof XmlAwareBraceMatcher
) return ((XmlAwareBraceMatcher
)matcher
).getTagName(fileText
, iterator
);
380 private static class DefaultBraceMatcher
implements BraceMatcher
{
381 public int getBraceTokenGroupId(final IElementType tokenType
) {
382 return UNDEFINED_TOKEN_GROUP
;
385 public boolean isLBraceToken(final HighlighterIterator iterator
, final CharSequence fileText
, final FileType fileType
) {
389 public boolean isRBraceToken(final HighlighterIterator iterator
, final CharSequence fileText
, final FileType fileType
) {
393 public boolean isPairBraces(final IElementType tokenType
, final IElementType tokenType2
) {
397 public boolean isStructuralBrace(final HighlighterIterator iterator
, final CharSequence text
, final FileType fileType
) {
401 public IElementType
getOppositeBraceTokenType(@NotNull final IElementType type
) {
405 public boolean isPairedBracesAllowedBeforeType(@NotNull final IElementType lbraceType
, @Nullable final IElementType contextType
) {
409 public int getCodeConstructStart(final PsiFile file
, final int openingBraceOffset
) {
410 return openingBraceOffset
;