package com.dmarcotte.handlebars.editor.braces; import com.dmarcotte.handlebars.parsing.HbTokenTypes; import com.intellij.codeInsight.highlighting.BraceMatcher; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.fileTypes.FileType; import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashSet; import java.util.Set; public class HbBraceMatcher implements BraceMatcher { private static final Set<IElementType> LEFT_BRACES = new HashSet<>(); private static final Set<IElementType> RIGHT_BRACES = new HashSet<>(); static { LEFT_BRACES.add(HbTokenTypes.OPEN); LEFT_BRACES.add(HbTokenTypes.OPEN_PARTIAL); LEFT_BRACES.add(HbTokenTypes.OPEN_UNESCAPED); LEFT_BRACES.add(HbTokenTypes.OPEN_BLOCK); LEFT_BRACES.add(HbTokenTypes.OPEN_INVERSE); RIGHT_BRACES.add(HbTokenTypes.CLOSE); RIGHT_BRACES.add(HbTokenTypes.CLOSE_UNESCAPED); } @Override public boolean isPairBraces(IElementType tokenType1, IElementType tokenType2) { return LEFT_BRACES.contains(tokenType1) && RIGHT_BRACES.contains(tokenType2) || RIGHT_BRACES.contains(tokenType1) && LEFT_BRACES.contains(tokenType2); } @Override public boolean isLBraceToken(HighlighterIterator iterator, CharSequence fileText, FileType fileType) { return LEFT_BRACES.contains(iterator.getTokenType()); } @Override public boolean isRBraceToken(HighlighterIterator iterator, CharSequence fileText, FileType fileType) { if (!RIGHT_BRACES.contains(iterator.getTokenType())) { // definitely not a right brace return false; } boolean isRBraceToken = false; int iteratorRetreatCount = 0; while (true) { iterator.retreat(); iteratorRetreatCount++; if (iterator.atEnd()) { break; } if (iterator.getTokenType() == HbTokenTypes.OPEN_BLOCK) { // the first open type token we encountered is a block opener, // so this is not a close brace (the paired close brace for these tokens // is at the end of the corresponding block close 'stache) break; } if (iterator.getTokenType() == HbTokenTypes.OPEN_INVERSE) { // this might be a simple inverse, so backtrack until we either see // and ID (which means we're in a situation like OPEN_BLOCK above) // or a CLOSE (which means we're a simple inverse, and this is the RBrace) while (iteratorRetreatCount-- > 0) { iterator.advance(); if (iterator.getTokenType() == HbTokenTypes.ID) { break; } if (iterator.getTokenType() == HbTokenTypes.CLOSE) { isRBraceToken = true; break; } } break; } if (iterator.getTokenType() == HbTokenTypes.OPEN || iterator.getTokenType() == HbTokenTypes.OPEN_PARTIAL || iterator.getTokenType() == HbTokenTypes.OPEN_UNESCAPED || iterator.getTokenType() == HbTokenTypes.OPEN_ENDBLOCK) { // the first open token we encountered was a simple opener (i.e. didn't start a block) // or the close brace of a close block 'stache for some open block. Definitely a right brace. isRBraceToken = true; } } // reset the given iterator before returning while (iteratorRetreatCount-- > 0) { iterator.advance(); } return isRBraceToken; } @Override public int getBraceTokenGroupId(IElementType tokenType) { return 1; } @Override public boolean isStructuralBrace(HighlighterIterator iterator, CharSequence text, FileType fileType) { return false; } @Nullable @Override public IElementType getOppositeBraceTokenType(@NotNull IElementType type) { return null; } @Override public boolean isPairedBracesAllowedBeforeType(@NotNull IElementType lbraceType, @Nullable IElementType contextType) { return true; } @Override public int getCodeConstructStart(PsiFile file, int openingBraceOffset) { return openingBraceOffset; } }