1+ package com.github.tempest.framework.views.completion
2+
3+ import com.github.tempest.framework.TempestFrameworkUtil
4+ import com.intellij.codeInsight.editorActions.TypedHandlerDelegate
5+ import com.intellij.openapi.command.WriteCommandAction
6+ import com.intellij.openapi.editor.Editor
7+ import com.intellij.openapi.project.Project
8+ import com.intellij.psi.PsiFile
9+
10+ class TemplateBracketTypedHandler : TypedHandlerDelegate () {
11+
12+ data class BracketPair (val opening : String , val closing : String )
13+
14+ override fun charTyped (c : Char , project : Project , editor : Editor , file : PsiFile ): Result {
15+ if (! file.name.endsWith(TempestFrameworkUtil .TEMPLATE_SUFFIX )) return Result .CONTINUE
16+
17+ val offset = editor.caretModel.offset
18+ val text = editor.document.charsSequence
19+
20+ return when (c) {
21+ ' {' -> {
22+ insertClosingBracket(project, editor, offset, " }" )
23+ Result .STOP
24+ }
25+ ' !' , ' -' -> {
26+ if (text.matchesAt(offset - 3 , DOUBLE_BRACE_OPEN )) {
27+ handleDoubledSpecialChar(project, editor, offset, c)
28+ Result .STOP
29+ } else {
30+ Result .CONTINUE
31+ }
32+ }
33+ ' ' -> {
34+ handleSpaceInBrackets(project, editor, text, offset)
35+ Result .CONTINUE
36+ }
37+ else -> Result .CONTINUE
38+ }
39+ }
40+
41+ private fun handleDoubledSpecialChar (project : Project , editor : Editor , offset : Int , char : Char ) {
42+ WriteCommandAction .runWriteCommandAction(project) {
43+ editor.document.insertString(offset, char.toString())
44+ editor.caretModel.moveToOffset(offset + 1 )
45+ transformClosingBracket(editor, offset + 1 , char)
46+ }
47+ }
48+
49+ private fun handleSpaceInBrackets (project : Project , editor : Editor , text : CharSequence , offset : Int ) {
50+ BRACKET_PAIRS .firstOrNull { pair ->
51+ text.matchesAt(offset - pair.opening.length - 1 , pair.opening) &&
52+ text[offset - 1 ] == ' ' &&
53+ text.matchesAt(offset, pair.closing)
54+ }?.let {
55+ WriteCommandAction .runWriteCommandAction(project) {
56+ editor.document.insertString(offset, " " )
57+ }
58+ }
59+ }
60+
61+ private fun transformClosingBracket (editor : Editor , offset : Int , char : Char ) {
62+ val text = editor.document.charsSequence
63+ val newClosing = " $char$char$DOUBLE_BRACE_CLOSE "
64+
65+ findNextUnnestedClosing(text, offset, DOUBLE_BRACE_CLOSE )?.let { closingIndex ->
66+ editor.document.replaceString(closingIndex, closingIndex + 2 , newClosing)
67+ }
68+ }
69+
70+ fun synchronizeBracketsAfterDeletion (project : Project , editor : Editor , deletedChar : Char ) {
71+ val offset = editor.caretModel.offset
72+ val text = editor.document.charsSequence
73+
74+ WriteCommandAction .runWriteCommandAction(project) {
75+ when {
76+ text.matchesAt(offset - 3 , DOUBLE_BRACE_OPEN ) && text.getOrNull(offset - 1 ) == deletedChar -> {
77+ editor.document.deleteString(offset - 1 , offset)
78+ transformClosingAfterDeletion(editor, offset - 1 , deletedChar)
79+ }
80+ text.matchesAt(offset - 2 , DOUBLE_BRACE_OPEN ) && text.getOrNull(offset) == deletedChar -> {
81+ editor.document.deleteString(offset, offset + 1 )
82+ transformClosingAfterDeletion(editor, offset, deletedChar)
83+ }
84+ }
85+ }
86+ }
87+
88+ private fun transformClosingAfterDeletion (editor : Editor , offset : Int , char : Char ) {
89+ val text = editor.document.charsSequence
90+ val doubledClosing = " $char$char$DOUBLE_BRACE_CLOSE "
91+
92+ findNextUnnestedClosing(text, offset, doubledClosing)?.let { closingIndex ->
93+ editor.document.replaceString(closingIndex, closingIndex + 4 , DOUBLE_BRACE_CLOSE )
94+ }
95+ }
96+
97+ private fun insertClosingBracket (project : Project , editor : Editor , offset : Int , closing : String ) {
98+ WriteCommandAction .runWriteCommandAction(project) {
99+ editor.document.insertString(offset, closing)
100+ }
101+ }
102+
103+ private fun findNextUnnestedClosing (text : CharSequence , startOffset : Int , pattern : String ): Int? {
104+ val patternLength = pattern.length
105+ val searchRange = startOffset.. (text.length - patternLength)
106+
107+ for (i in searchRange) {
108+ if (text.matchesAt(i, pattern) && ! hasOpeningBetween(text, startOffset, i)) {
109+ return i
110+ }
111+ }
112+ return null
113+ }
114+
115+ private fun hasOpeningBetween (text : CharSequence , start : Int , end : Int ): Boolean {
116+ return (start until end - 1 ).any { i ->
117+ text.matchesAt(i, DOUBLE_BRACE_OPEN )
118+ }
119+ }
120+
121+ companion object {
122+ val INSTANCE = TemplateBracketTypedHandler ()
123+
124+ private const val DOUBLE_BRACE_OPEN = " {{"
125+ private const val DOUBLE_BRACE_CLOSE = " }}"
126+ }
127+ }
128+
129+ private fun CharSequence.matchesAt (index : Int , pattern : String ): Boolean {
130+ if (index < 0 || index + pattern.length > length) return false
131+ return (pattern.indices).all { i -> this [index + i] == pattern[i] }
132+ }
133+
134+ val BRACKET_PAIRS = listOf (
135+ TemplateBracketTypedHandler .BracketPair (" {{--" , " --}}" ),
136+ TemplateBracketTypedHandler .BracketPair (" {{!!" , " !!}}" ),
137+ TemplateBracketTypedHandler .BracketPair (" {{" , " }}" ),
138+ )
0 commit comments