From 6ea7271ce7249b600f934891a0df6e36f00d1f8e Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 29 Nov 2025 09:58:13 +0100 Subject: [PATCH 1/4] feat: autocomplete brackets --- .../TemplateBracketBackspaceHandler.kt | 21 +++ .../completion/TemplateBracketTypedHandler.kt | 137 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 4 + 3 files changed, 162 insertions(+) create mode 100644 src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt create mode 100644 src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt diff --git a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt new file mode 100644 index 0000000..1050e91 --- /dev/null +++ b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt @@ -0,0 +1,21 @@ +package com.github.tempest.framework.views.completion + +import com.github.tempest.framework.TempestFrameworkUtil +import com.intellij.codeInsight.editorActions.BackspaceHandlerDelegate +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiFile + +class TemplateBracketBackspaceHandler : BackspaceHandlerDelegate() { + + override fun beforeCharDeleted(c: Char, file: PsiFile, editor: Editor) {} + + override fun charDeleted(c: Char, file: PsiFile, editor: Editor): Boolean { + if (!file.name.endsWith(TempestFrameworkUtil.TEMPLATE_SUFFIX)) return false + + if (c != '!' && c != '-') return false + + INSTANCE.synchronizeBracketsAfterDeletion(file.project, editor) + + return false + } +} diff --git a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt new file mode 100644 index 0000000..9603873 --- /dev/null +++ b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt @@ -0,0 +1,137 @@ +package com.github.tempest.framework.views.completion + +import com.github.tempest.framework.TempestFrameworkUtil +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile + +class TemplateBracketTypedHandler : TypedHandlerDelegate() { + + data class BracketPair(val opening: String, val closing: String) + + override fun charTyped(c: Char, project: Project, editor: Editor, file: PsiFile): Result { + if (!file.name.endsWith(TempestFrameworkUtil.TEMPLATE_SUFFIX)) return Result.CONTINUE + + val offset = editor.caretModel.offset + val text = editor.document.charsSequence + + if (c == ' ') { + return handleAutoComplete(project, editor, text, offset) + } + + if (c == '!' || c == '-') { + synchronizeBrackets(project, editor, text, offset) + } + + return Result.CONTINUE + } + + private fun handleAutoComplete(project: Project, editor: Editor, text: CharSequence, offset: Int): Result { + if (offset < 2) return Result.CONTINUE + + val textBefore = text.subSequence(0, offset).toString() + + for (pair in AUTO_COMPLETE_PAIRS) { + if (textBefore.endsWith(pair.opening + " ")) { + if (!hasClosingBracketAhead(text, offset, pair.closing.trim())) { + insertClosingBracket(project, editor, offset, pair.closing) + return Result.STOP + } + } + } + + return Result.CONTINUE + } + + private fun synchronizeBrackets(project: Project, editor: Editor, text: CharSequence, offset: Int) { + val textBefore = text.subSequence(0, offset).toString() + + val currentPair = BRACKET_PAIRS.find { textBefore.endsWith(it.opening) } ?: return + + if (currentPair.opening == "{{") return + + val textAfter = text.subSequence(offset, text.length).toString() + + for (otherPair in BRACKET_PAIRS) { + if (otherPair.closing == currentPair.closing) continue + + val closingIndex = findFirstUnnestedClosing(textAfter, otherPair.closing, currentPair.opening) + if (closingIndex != -1) { + replaceText(project, editor, offset + closingIndex, otherPair.closing.length, currentPair.closing) + return + } + } + } + + fun synchronizeBracketsAfterDeletion(project: Project, editor: Editor) { + val offset = editor.caretModel.offset + val text = editor.document.charsSequence + val textBefore = text.subSequence(0, offset).toString() + + if (!textBefore.endsWith("{{")) return + + if (textBefore.endsWith("{{!!") || textBefore.endsWith("{{--")) return + + val textAfter = text.subSequence(offset, text.length).toString() + + for (pair in BRACKET_PAIRS) { + if (pair.closing == "}}") continue + + val closingIndex = findFirstUnnestedClosing(textAfter, pair.closing, "{{") + if (closingIndex != -1) { + replaceText(project, editor, offset + closingIndex, pair.closing.length, "}}") + return + } + } + } + + private fun findFirstUnnestedClosing(textAfter: String, closing: String, opening: String): Int { + val closingIndex = textAfter.indexOf(closing) + if (closingIndex == -1) return -1 + + val textBeforeClosing = textAfter.take(closingIndex) + val nextOpenIndex = textBeforeClosing.indexOf(opening) + + return if (nextOpenIndex == -1) closingIndex else -1 + } + + private fun hasClosingBracketAhead(text: CharSequence, offset: Int, closing: String): Boolean { + val textAfter = text.subSequence(offset, text.length).toString() + + val nextClose = textAfter.indexOf(closing) + if (nextClose == -1) return false + + val opening = BRACKET_PAIRS.find { it.closing == closing }?.opening ?: return false + val nextOpen = textAfter.indexOf(opening) + + return nextOpen == -1 || nextClose < nextOpen + } + + private fun insertClosingBracket(project: Project, editor: Editor, offset: Int, closing: String) { + WriteCommandAction.runWriteCommandAction(project) { + editor.document.insertString(offset, closing) + } + } + + private fun replaceText(project: Project, editor: Editor, start: Int, length: Int, newText: String) { + WriteCommandAction.runWriteCommandAction(project) { + editor.document.replaceString(start, start + length, newText) + } + } +} + +val BRACKET_PAIRS = listOf( + TemplateBracketTypedHandler.BracketPair("{{--", "--}}"), + TemplateBracketTypedHandler.BracketPair("{{!!", "!!}}"), + TemplateBracketTypedHandler.BracketPair("{{", "}}"), +) + +private val AUTO_COMPLETE_PAIRS = listOf( + TemplateBracketTypedHandler.BracketPair("{{--", " --}}"), + TemplateBracketTypedHandler.BracketPair("{{!!", " !!}}"), + TemplateBracketTypedHandler.BracketPair("{{", " }}"), +) + +val INSTANCE = TemplateBracketTypedHandler() \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index d61f9c2..9c3d605 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -67,6 +67,10 @@ + + From e50dc7c7f691ae75c90d17c09d454047645e34ba Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 29 Nov 2025 18:45:54 +0100 Subject: [PATCH 2/4] refactor: better completion handling --- .../completion/TemplateBracketTypedHandler.kt | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt index 9603873..908c3cc 100644 --- a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt +++ b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt @@ -17,25 +17,44 @@ class TemplateBracketTypedHandler : TypedHandlerDelegate() { val offset = editor.caretModel.offset val text = editor.document.charsSequence - if (c == ' ') { - return handleAutoComplete(project, editor, text, offset) + if (c == '{' || c == '!' || c == '-') { + val result = handleAutoComplete(project, editor, text, offset) + if (result == Result.STOP) return result } if (c == '!' || c == '-') { synchronizeBrackets(project, editor, text, offset) } + if (c == ' ') { + handleSpaceInBrackets(project, editor, text, offset) + } + return Result.CONTINUE } + private fun handleSpaceInBrackets(project: Project, editor: Editor, text: CharSequence, offset: Int) { + val textBefore = text.subSequence(0, offset).toString() + val textAfter = text.subSequence(offset, text.length).toString() + + for (pair in BRACKET_PAIRS) { + if (textBefore.endsWith(pair.opening + " ") && textAfter.startsWith(pair.closing)) { + WriteCommandAction.runWriteCommandAction(project) { + editor.document.insertString(offset, " ") + } + return + } + } + } + private fun handleAutoComplete(project: Project, editor: Editor, text: CharSequence, offset: Int): Result { if (offset < 2) return Result.CONTINUE val textBefore = text.subSequence(0, offset).toString() for (pair in AUTO_COMPLETE_PAIRS) { - if (textBefore.endsWith(pair.opening + " ")) { - if (!hasClosingBracketAhead(text, offset, pair.closing.trim())) { + if (textBefore.endsWith(pair.opening)) { + if (!hasAnyClosingBracketAhead(text, offset)) { insertClosingBracket(project, editor, offset, pair.closing) return Result.STOP } @@ -97,16 +116,20 @@ class TemplateBracketTypedHandler : TypedHandlerDelegate() { return if (nextOpenIndex == -1) closingIndex else -1 } - private fun hasClosingBracketAhead(text: CharSequence, offset: Int, closing: String): Boolean { + private fun hasAnyClosingBracketAhead(text: CharSequence, offset: Int): Boolean { val textAfter = text.subSequence(offset, text.length).toString() - val nextClose = textAfter.indexOf(closing) - if (nextClose == -1) return false - - val opening = BRACKET_PAIRS.find { it.closing == closing }?.opening ?: return false - val nextOpen = textAfter.indexOf(opening) + for (pair in BRACKET_PAIRS) { + val nextClose = textAfter.indexOf(pair.closing) + if (nextClose != -1) { + val nextOpen = textAfter.indexOf(pair.opening) + if (nextOpen == -1 || nextClose < nextOpen) { + return true + } + } + } - return nextOpen == -1 || nextClose < nextOpen + return false } private fun insertClosingBracket(project: Project, editor: Editor, offset: Int, closing: String) { @@ -129,9 +152,9 @@ val BRACKET_PAIRS = listOf( ) private val AUTO_COMPLETE_PAIRS = listOf( - TemplateBracketTypedHandler.BracketPair("{{--", " --}}"), - TemplateBracketTypedHandler.BracketPair("{{!!", " !!}}"), - TemplateBracketTypedHandler.BracketPair("{{", " }}"), + TemplateBracketTypedHandler.BracketPair("{{--", "--}}"), + TemplateBracketTypedHandler.BracketPair("{{!!", "!!}}"), + TemplateBracketTypedHandler.BracketPair("{{", "}}"), ) val INSTANCE = TemplateBracketTypedHandler() \ No newline at end of file From 3ddc34481f773342e999c8fed5483a6c2032b756 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 2 Dec 2025 13:40:50 +0100 Subject: [PATCH 3/4] refactor: move instance to companion object --- .../views/completion/TemplateBracketBackspaceHandler.kt | 2 +- .../views/completion/TemplateBracketTypedHandler.kt | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt index 1050e91..4dfc870 100644 --- a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt +++ b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt @@ -14,7 +14,7 @@ class TemplateBracketBackspaceHandler : BackspaceHandlerDelegate() { if (c != '!' && c != '-') return false - INSTANCE.synchronizeBracketsAfterDeletion(file.project, editor) + TemplateBracketTypedHandler.INSTANCE.synchronizeBracketsAfterDeletion(file.project, editor) return false } diff --git a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt index 908c3cc..5457c20 100644 --- a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt +++ b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt @@ -143,6 +143,10 @@ class TemplateBracketTypedHandler : TypedHandlerDelegate() { editor.document.replaceString(start, start + length, newText) } } + + companion object { + val INSTANCE = TemplateBracketTypedHandler() + } } val BRACKET_PAIRS = listOf( @@ -155,6 +159,4 @@ private val AUTO_COMPLETE_PAIRS = listOf( TemplateBracketTypedHandler.BracketPair("{{--", "--}}"), TemplateBracketTypedHandler.BracketPair("{{!!", "!!}}"), TemplateBracketTypedHandler.BracketPair("{{", "}}"), -) - -val INSTANCE = TemplateBracketTypedHandler() \ No newline at end of file +) \ No newline at end of file From 0b0f0e52e3deeff86d5e7cecbe84daa34504b81f Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 2 Dec 2025 14:02:29 +0100 Subject: [PATCH 4/4] refactor: use offsets instead of text search --- .../TemplateBracketBackspaceHandler.kt | 15 +- .../completion/TemplateBracketTypedHandler.kt | 174 ++++++++---------- 2 files changed, 84 insertions(+), 105 deletions(-) diff --git a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt index 4dfc870..0c0b591 100644 --- a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt +++ b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketBackspaceHandler.kt @@ -7,15 +7,18 @@ import com.intellij.psi.PsiFile class TemplateBracketBackspaceHandler : BackspaceHandlerDelegate() { - override fun beforeCharDeleted(c: Char, file: PsiFile, editor: Editor) {} + override fun beforeCharDeleted(c: Char, file: PsiFile, editor: Editor) = Unit override fun charDeleted(c: Char, file: PsiFile, editor: Editor): Boolean { - if (!file.name.endsWith(TempestFrameworkUtil.TEMPLATE_SUFFIX)) return false - - if (c != '!' && c != '-') return false - - TemplateBracketTypedHandler.INSTANCE.synchronizeBracketsAfterDeletion(file.project, editor) + if (!file.name.endsWith(TempestFrameworkUtil.TEMPLATE_SUFFIX) || c !in SPECIAL_CHARS) { + return false + } + TemplateBracketTypedHandler.INSTANCE.synchronizeBracketsAfterDeletion(file.project, editor, c) return false } + + private companion object { + val SPECIAL_CHARS = setOf('!', '-') + } } diff --git a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt index 5457c20..556aab5 100644 --- a/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt +++ b/src/main/kotlin/com/github/tempest/framework/views/completion/TemplateBracketTypedHandler.kt @@ -17,119 +17,81 @@ class TemplateBracketTypedHandler : TypedHandlerDelegate() { val offset = editor.caretModel.offset val text = editor.document.charsSequence - if (c == '{' || c == '!' || c == '-') { - val result = handleAutoComplete(project, editor, text, offset) - if (result == Result.STOP) return result - } - - if (c == '!' || c == '-') { - synchronizeBrackets(project, editor, text, offset) - } - - if (c == ' ') { - handleSpaceInBrackets(project, editor, text, offset) - } - - return Result.CONTINUE - } - - private fun handleSpaceInBrackets(project: Project, editor: Editor, text: CharSequence, offset: Int) { - val textBefore = text.subSequence(0, offset).toString() - val textAfter = text.subSequence(offset, text.length).toString() - - for (pair in BRACKET_PAIRS) { - if (textBefore.endsWith(pair.opening + " ") && textAfter.startsWith(pair.closing)) { - WriteCommandAction.runWriteCommandAction(project) { - editor.document.insertString(offset, " ") + return when (c) { + '{' -> { + insertClosingBracket(project, editor, offset, "}") + Result.STOP + } + '!', '-' -> { + if (text.matchesAt(offset - 3, DOUBLE_BRACE_OPEN)) { + handleDoubledSpecialChar(project, editor, offset, c) + Result.STOP + } else { + Result.CONTINUE } - return } + ' ' -> { + handleSpaceInBrackets(project, editor, text, offset) + Result.CONTINUE + } + else -> Result.CONTINUE } } - private fun handleAutoComplete(project: Project, editor: Editor, text: CharSequence, offset: Int): Result { - if (offset < 2) return Result.CONTINUE - - val textBefore = text.subSequence(0, offset).toString() + private fun handleDoubledSpecialChar(project: Project, editor: Editor, offset: Int, char: Char) { + WriteCommandAction.runWriteCommandAction(project) { + editor.document.insertString(offset, char.toString()) + editor.caretModel.moveToOffset(offset + 1) + transformClosingBracket(editor, offset + 1, char) + } + } - for (pair in AUTO_COMPLETE_PAIRS) { - if (textBefore.endsWith(pair.opening)) { - if (!hasAnyClosingBracketAhead(text, offset)) { - insertClosingBracket(project, editor, offset, pair.closing) - return Result.STOP - } + private fun handleSpaceInBrackets(project: Project, editor: Editor, text: CharSequence, offset: Int) { + BRACKET_PAIRS.firstOrNull { pair -> + text.matchesAt(offset - pair.opening.length - 1, pair.opening) && + text[offset - 1] == ' ' && + text.matchesAt(offset, pair.closing) + }?.let { + WriteCommandAction.runWriteCommandAction(project) { + editor.document.insertString(offset, " ") } } - - return Result.CONTINUE } - private fun synchronizeBrackets(project: Project, editor: Editor, text: CharSequence, offset: Int) { - val textBefore = text.subSequence(0, offset).toString() - - val currentPair = BRACKET_PAIRS.find { textBefore.endsWith(it.opening) } ?: return - - if (currentPair.opening == "{{") return - - val textAfter = text.subSequence(offset, text.length).toString() - - for (otherPair in BRACKET_PAIRS) { - if (otherPair.closing == currentPair.closing) continue + private fun transformClosingBracket(editor: Editor, offset: Int, char: Char) { + val text = editor.document.charsSequence + val newClosing = "$char$char$DOUBLE_BRACE_CLOSE" - val closingIndex = findFirstUnnestedClosing(textAfter, otherPair.closing, currentPair.opening) - if (closingIndex != -1) { - replaceText(project, editor, offset + closingIndex, otherPair.closing.length, currentPair.closing) - return - } + findNextUnnestedClosing(text, offset, DOUBLE_BRACE_CLOSE)?.let { closingIndex -> + editor.document.replaceString(closingIndex, closingIndex + 2, newClosing) } } - fun synchronizeBracketsAfterDeletion(project: Project, editor: Editor) { + fun synchronizeBracketsAfterDeletion(project: Project, editor: Editor, deletedChar: Char) { val offset = editor.caretModel.offset val text = editor.document.charsSequence - val textBefore = text.subSequence(0, offset).toString() - - if (!textBefore.endsWith("{{")) return - - if (textBefore.endsWith("{{!!") || textBefore.endsWith("{{--")) return - val textAfter = text.subSequence(offset, text.length).toString() - - for (pair in BRACKET_PAIRS) { - if (pair.closing == "}}") continue - - val closingIndex = findFirstUnnestedClosing(textAfter, pair.closing, "{{") - if (closingIndex != -1) { - replaceText(project, editor, offset + closingIndex, pair.closing.length, "}}") - return + WriteCommandAction.runWriteCommandAction(project) { + when { + text.matchesAt(offset - 3, DOUBLE_BRACE_OPEN) && text.getOrNull(offset - 1) == deletedChar -> { + editor.document.deleteString(offset - 1, offset) + transformClosingAfterDeletion(editor, offset - 1, deletedChar) + } + text.matchesAt(offset - 2, DOUBLE_BRACE_OPEN) && text.getOrNull(offset) == deletedChar -> { + editor.document.deleteString(offset, offset + 1) + transformClosingAfterDeletion(editor, offset, deletedChar) + } } } } - private fun findFirstUnnestedClosing(textAfter: String, closing: String, opening: String): Int { - val closingIndex = textAfter.indexOf(closing) - if (closingIndex == -1) return -1 - - val textBeforeClosing = textAfter.take(closingIndex) - val nextOpenIndex = textBeforeClosing.indexOf(opening) - - return if (nextOpenIndex == -1) closingIndex else -1 - } - - private fun hasAnyClosingBracketAhead(text: CharSequence, offset: Int): Boolean { - val textAfter = text.subSequence(offset, text.length).toString() + private fun transformClosingAfterDeletion(editor: Editor, offset: Int, char: Char) { + val text = editor.document.charsSequence + val doubledClosing = "$char$char$DOUBLE_BRACE_CLOSE" - for (pair in BRACKET_PAIRS) { - val nextClose = textAfter.indexOf(pair.closing) - if (nextClose != -1) { - val nextOpen = textAfter.indexOf(pair.opening) - if (nextOpen == -1 || nextClose < nextOpen) { - return true - } - } + findNextUnnestedClosing(text, offset, doubledClosing)?.let { closingIndex -> + editor.document.replaceString(closingIndex, closingIndex + 4, DOUBLE_BRACE_CLOSE) } - - return false } private fun insertClosingBracket(project: Project, editor: Editor, offset: Int, closing: String) { @@ -138,24 +100,38 @@ class TemplateBracketTypedHandler : TypedHandlerDelegate() { } } - private fun replaceText(project: Project, editor: Editor, start: Int, length: Int, newText: String) { - WriteCommandAction.runWriteCommandAction(project) { - editor.document.replaceString(start, start + length, newText) + private fun findNextUnnestedClosing(text: CharSequence, startOffset: Int, pattern: String): Int? { + val patternLength = pattern.length + val searchRange = startOffset..(text.length - patternLength) + + for (i in searchRange) { + if (text.matchesAt(i, pattern) && !hasOpeningBetween(text, startOffset, i)) { + return i + } + } + return null + } + + private fun hasOpeningBetween(text: CharSequence, start: Int, end: Int): Boolean { + return (start until end - 1).any { i -> + text.matchesAt(i, DOUBLE_BRACE_OPEN) } } companion object { val INSTANCE = TemplateBracketTypedHandler() + + private const val DOUBLE_BRACE_OPEN = "{{" + private const val DOUBLE_BRACE_CLOSE = "}}" } } -val BRACKET_PAIRS = listOf( - TemplateBracketTypedHandler.BracketPair("{{--", "--}}"), - TemplateBracketTypedHandler.BracketPair("{{!!", "!!}}"), - TemplateBracketTypedHandler.BracketPair("{{", "}}"), -) +private fun CharSequence.matchesAt(index: Int, pattern: String): Boolean { + if (index < 0 || index + pattern.length > length) return false + return (pattern.indices).all { i -> this[index + i] == pattern[i] } +} -private val AUTO_COMPLETE_PAIRS = listOf( +val BRACKET_PAIRS = listOf( TemplateBracketTypedHandler.BracketPair("{{--", "--}}"), TemplateBracketTypedHandler.BracketPair("{{!!", "!!}}"), TemplateBracketTypedHandler.BracketPair("{{", "}}"),