Auto-closing braces in a custom language, BraceMatcher not working

 I'm working on a custom language plugin, and am trying to get matching braces to be automatically inserted. Aka. if I type "(", I want intellij to automatically add a matching ")". From the documentation, I should implement a PairedBraceMatcher:


public class DBraceMatcher implements PairedBraceMatcher {
private static final BracePair[] PAIRS = new BracePair[]{
new BracePair(DLanguageTypes.OP_PAR_LEFT, DLanguageTypes.OP_PAR_RIGHT, false),
new BracePair(DLanguageTypes.OP_BRACES_LEFT, DLanguageTypes.OP_BRACES_RIGHT, true),
new BracePair(DLanguageTypes.OP_BRACKET_LEFT, DLanguageTypes.OP_BRACKET_RIGHT, false),
};


@Override
public BracePair[] getPairs() {
return PAIRS;
}

@Override
public boolean isPairedBracesAllowedBeforeType(@NotNull IElementType lbraceType, @Nullable IElementType contextType) {
return true;
}

@Override
public int getCodeConstructStart(PsiFile file, int openingBraceOffset) {
return openingBraceOffset;
}
}

And then register the brace matcher in plugin.xml:

<lang.braceMatcher language="D" implementationClass="net.masterthought.dlanguage.features.DBraceMatcher"/>

For some reason I am not getting autoclosing braces, and when running under debugger the method "isPairedBracesAllowedBeforeType", seems to never be called.

Thanks in advance for any help.

2
4 comments

Try to debug com.intellij.codeInsight.editorActions.TypedHandler#handleAfterLParen method.

0

I had this same problem because I was trying to match angle brackets ("<>"), and after doing some I discovered that in com.intellij.codeInsight.editorActions.TypedHandler#handleAfterLParen(), it checks to see if an angle bracket was inputted:

if (!matched) {
String text;
if (lparenChar == '(') {
text = ")";
}
else if (lparenChar == '[') {
text = "]";
}
else if (lparenChar == '<') {
text = ">";
}
else if (lparenChar == '{') {
text = "}";
}
else {
throw new AssertionError("Unknown char "+lparenChar);
}
editor.getDocument().insertString(offset, text);
TabOutScopesTracker.getInstance().registerEmptyScope(editor, offset);
}

BUT! It only ever gets called in com.intellij.codeInsight.editorActions.TypedHandler#execute() like this:

if (('(' == charTyped || '[' == charTyped || '{' == charTyped) &&
CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
fileType != FileTypes.PLAIN_TEXT) {
handleAfterLParen(editor, fileType, charTyped);
}

Notice that lparenChar can never be "<" because handleAfterParen() only gets called if the character typed is "(", "[", or "{". So this seems to me to be a bug, because "<" can be matched pretty often. Although, it would make sense to me to allow ANY brace pairs specified in the PairedBraceMatcher to be auto-paired instead of just the 3 or 4 specified here. For example, if you a brace pair for ("BEGIN", "END") and you wrote "Here's my story BEGIN", intellij would automatically add "END" after your caret.

But if they never fix this, the way I solved it was by implementing a TypedHandlerDelegate (which can be added the plugin with the com.intellij.lang.typedHandler extension) then basically copied the code from handleAfterLParen and adjusted it to my needs into the charTyped() method.

0

I'm facing the same issue, basically the same code as yours but none of the braces (i.e {, (, [) get completion and isPairedBracesAllowedBeforeType doesn't get invoked at all. Does it have any prerequisites? Do I have to add anything other than braceMatcher in plugin.xml or anywhere else in the codebase?

Have you found a solution to this problem?

0

I faced the same issue and looked at the java plugin for reference. They add a custom TypedHandlerDelegate to handle this case. An implementation might look like this

// Extension point name: typedHandler
class MyLanguageBraceMatchingTypedHandler : TypedHandlerDelegate() {

    private var ltTyped = false

    override fun beforeCharTyped(c: Char, project: Project, editor: Editor, file: PsiFile, fileType: FileType): Result {
        if (file !is MyLanguagePsiFile) return Result.CONTINUE

        ltTyped = c == '<' && TypedHandlerUtil.isAfterClassLikeIdentifierOrDot(
            editor.caretModel.offset,
            editor,
            MyLanguageTokenTypes.IDENTIFIER,
            MyLanguageTokenTypes.IDENTIFIER,
            true
        ) && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET

        if (c == '>' && TypedHandlerUtil.handleGenericGT(
                editor,
                MyLanguageTokenTypes.LESS,
                MyLanguageTokenTypes.GREATER,
                INVALID_INSIDE_REFERENCE
            ) && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET
        ) return Result.STOP

        return Result.CONTINUE
    }

    override fun charTyped(c: Char, project: Project, editor: Editor, file: PsiFile): Result {
        if (file !is MyLanguagePsiFile || !ltTyped) return Result.CONTINUE

        ltTyped = false
        TypedHandlerUtil.handleAfterGenericLT(
            editor,
            MyLanguageTokenTypes.LESS,
            MyLanguageTokenTypes.GREATER,
            INVALID_INSIDE_REFERENCE
        )

        return Result.STOP
    }

    companion object {
        val INVALID_INSIDE_REFERENCE = TokenSet.create(
            MyLanguageTokenTypes.L_PAREN,
            MyLanguageTokenTypes.R_PAREN,
            MyLanguageTokenTypes.L_CURLY_PAREN,
            MyLanguageTokenTypes.R_CURLY_PAREN
        )
    }
}

I recommend looking at the docs and implementation of TypedHandlerUtil to get a better understanding of this code snippet.

References:

https://github.com/JetBrains/intellij-community/blob/master/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java#L109-L119 

https://github.com/JetBrains/intellij-community/blob/3a9d9eccaf61e4583d46d889698a09f681647208/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java#L327

 

Additionally, I can recommend adapting their brace matcher as well if you don't want highlighted braces for </> in normal expressions. You might only want this for generic parameter as an example. This can be achieved with a custom brace matcher like this (requires dependency on "com.intellij.java")

// Extension point name: lang.braceMatcher
class MyLanguagePairedBraceMatcher : PairedBraceAndAnglesMatcher(MyLanguageBraceMatcher(), MyLanguageLanguage.INSTANCE, MyLanguageLanguageFileType.INSTANCE, ALLOWED_INSIDE_ANGLES) {

    override fun lt(): IElementType = MyLanguageTokenTypes.LESS

    override fun gt(): IElementType = MyLanguageTokenTypes.GREATER

    companion object {
        val ALLOWED_INSIDE_ANGLES = TokenSet.create(MyLanguageTokenTypes.IDENTIFIER, MyLanguageTokenTypes.COMMA, TokenType.WHITE_SPACE)
    }
}

Reference:

https://github.com/JetBrains/intellij-community/blob/6dec3dca0e3675e048dc3665a99f65721c584e42/java/java-impl/src/com/intellij/codeInsight/highlighting/JavaPairedBraceMatcher.java 

0

Please sign in to leave a comment.