IDEA-38432 CSS: non-curved braces without pair brace are resolved as matching to...
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / highlighting / BraceMatchingUtil.java
blobc3bbed3225fa84dce1a7c2eca236c5ca888313ac
1 /*
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) {
42 try {
43 return getBraceMatcher(fileType, lbraceType).isPairedBracesAllowedBeforeType(lbraceType, tokenType);
45 catch (AbstractMethodError incompatiblePluginThatWeDoNotCare) {}
46 return true;
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;
60 FileType fileType;
61 HighlighterIterator iterator;
62 boolean forward;
64 IElementType brace1Token;
65 int group;
66 String brace1TagName;
67 boolean isStrict;
68 boolean isCaseSensitive;
69 boolean isStructural;
70 private BraceMatcher myMatcher;
72 MatchBraceContext(CharSequence _fileText, FileType _fileType, HighlighterIterator _iterator, boolean _forward) {
73 fileText = _fileText;
74 fileType = _fileType;
75 iterator = _iterator;
76 forward = _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,
89 boolean _strict) {
90 this(_fileText, _fileType, _iterator, _forward);
91 isStrict = _strict;
94 boolean doBraceMatch() {
95 ourBraceStack.clear();
96 ourTagNameStack.clear();
97 ourBraceStack.push(brace1Token);
98 if (isStrict){
99 ourTagNameStack.push(brace1TagName);
101 boolean matched = false;
102 while(true){
103 if (!forward){
104 iterator.retreat();
106 else{
107 iterator.advance();
109 if (iterator.atEnd()) {
110 break;
113 IElementType tokenType = iterator.getTokenType();
115 if (getTokenGroup(tokenType, fileType) != group) {
116 continue;
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);
128 if (isStrict){
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;
135 if (isStrict){
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)
148 matched = false;
149 break;
152 if (ourBraceStack.isEmpty()){
153 matched = true;
154 break;
158 return matched;
162 public static synchronized boolean matchBrace(CharSequence fileText, FileType fileType, HighlighterIterator iterator,
163 boolean forward) {
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;
198 if (isStrict){
199 topTagName = ourTagNameStack.pop();
200 tagName = getTagName(matcher,fileText, iterator);
203 if (!isPairBraces(topTokenType, tokenType, fileType)
204 || isStrict && !Comparing.equal(topTagName, tagName, isCaseSensitive)) {
205 return false;
210 iterator.retreat();
213 return false;
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
258 else{
259 if (tokenType == lparenTokenType){
260 lastLbraceOffset = iterator.getStart();
262 else{
263 break;
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
290 else{
291 if (tokenType == rparenTokenType){
292 lastRbraceOffset = iterator.getStart();
294 else{
295 break;
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();
311 @NotNull
312 public static BraceMatcher getBraceMatcher(@NotNull FileType fileType, @NotNull HighlighterIterator iterator) {
313 return getBraceMatcher(fileType, iterator.getTokenType());
316 @NotNull
317 public static BraceMatcher getBraceMatcher(@NotNull FileType fileType, @NotNull IElementType type) {
318 return getBraceMatcher(fileType, type.getLanguage());
321 @NotNull
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();
334 if (type1 != null) {
335 final BraceMatcher braceMatcher = getBraceMatcherByFileType(type1);
336 if (braceMatcher != null) {
337 return braceMatcher;
341 matcher = LanguageBraceMatching.INSTANCE.forLanguage(language);
342 if (matcher != null) {
343 return new PairedBraceMatcherAdapter(matcher,language);
348 return BraceMatcherHolder.ourDefaultBraceMatcher;
351 @Nullable
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);
360 return braceMatcher;
363 return null;
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);
374 @Nullable
375 private static String getTagName(BraceMatcher matcher,CharSequence fileText, HighlighterIterator iterator) {
376 if (matcher instanceof XmlAwareBraceMatcher) return ((XmlAwareBraceMatcher)matcher).getTagName(fileText, iterator);
377 return null;
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) {
386 return false;
389 public boolean isRBraceToken(final HighlighterIterator iterator, final CharSequence fileText, final FileType fileType) {
390 return false;
393 public boolean isPairBraces(final IElementType tokenType, final IElementType tokenType2) {
394 return false;
397 public boolean isStructuralBrace(final HighlighterIterator iterator, final CharSequence text, final FileType fileType) {
398 return false;
401 public IElementType getOppositeBraceTokenType(@NotNull final IElementType type) {
402 return null;
405 public boolean isPairedBracesAllowedBeforeType(@NotNull final IElementType lbraceType, @Nullable final IElementType contextType) {
406 return true;
409 public int getCodeConstructStart(final PsiFile file, final int openingBraceOffset) {
410 return openingBraceOffset;