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+ if (c == ' ' ) {
21+ return handleAutoComplete(project, editor, text, offset)
22+ }
23+
24+ if (c == ' !' || c == ' -' ) {
25+ synchronizeBrackets(project, editor, text, offset)
26+ }
27+
28+ return Result .CONTINUE
29+ }
30+
31+ private fun handleAutoComplete (project : Project , editor : Editor , text : CharSequence , offset : Int ): Result {
32+ if (offset < 2 ) return Result .CONTINUE
33+
34+ val textBefore = text.subSequence(0 , offset).toString()
35+
36+ for (pair in AUTO_COMPLETE_PAIRS ) {
37+ if (textBefore.endsWith(pair.opening + " " )) {
38+ if (! hasClosingBracketAhead(text, offset, pair.closing.trim())) {
39+ insertClosingBracket(project, editor, offset, pair.closing)
40+ return Result .STOP
41+ }
42+ }
43+ }
44+
45+ return Result .CONTINUE
46+ }
47+
48+ private fun synchronizeBrackets (project : Project , editor : Editor , text : CharSequence , offset : Int ) {
49+ val textBefore = text.subSequence(0 , offset).toString()
50+
51+ val currentPair = BRACKET_PAIRS .find { textBefore.endsWith(it.opening) } ? : return
52+
53+ if (currentPair.opening == " {{" ) return
54+
55+ val textAfter = text.subSequence(offset, text.length).toString()
56+
57+ for (otherPair in BRACKET_PAIRS ) {
58+ if (otherPair.closing == currentPair.closing) continue
59+
60+ val closingIndex = findFirstUnnestedClosing(textAfter, otherPair.closing, currentPair.opening)
61+ if (closingIndex != - 1 ) {
62+ replaceText(project, editor, offset + closingIndex, otherPair.closing.length, currentPair.closing)
63+ return
64+ }
65+ }
66+ }
67+
68+ fun synchronizeBracketsAfterDeletion (project : Project , editor : Editor ) {
69+ val offset = editor.caretModel.offset
70+ val text = editor.document.charsSequence
71+ val textBefore = text.subSequence(0 , offset).toString()
72+
73+ if (! textBefore.endsWith(" {{" )) return
74+
75+ if (textBefore.endsWith(" {{!!" ) || textBefore.endsWith(" {{--" )) return
76+
77+ val textAfter = text.subSequence(offset, text.length).toString()
78+
79+ for (pair in BRACKET_PAIRS ) {
80+ if (pair.closing == " }}" ) continue
81+
82+ val closingIndex = findFirstUnnestedClosing(textAfter, pair.closing, " {{" )
83+ if (closingIndex != - 1 ) {
84+ replaceText(project, editor, offset + closingIndex, pair.closing.length, " }}" )
85+ return
86+ }
87+ }
88+ }
89+
90+ private fun findFirstUnnestedClosing (textAfter : String , closing : String , opening : String ): Int {
91+ val closingIndex = textAfter.indexOf(closing)
92+ if (closingIndex == - 1 ) return - 1
93+
94+ val textBeforeClosing = textAfter.take(closingIndex)
95+ val nextOpenIndex = textBeforeClosing.indexOf(opening)
96+
97+ return if (nextOpenIndex == - 1 ) closingIndex else - 1
98+ }
99+
100+ private fun hasClosingBracketAhead (text : CharSequence , offset : Int , closing : String ): Boolean {
101+ val textAfter = text.subSequence(offset, text.length).toString()
102+
103+ val nextClose = textAfter.indexOf(closing)
104+ if (nextClose == - 1 ) return false
105+
106+ val opening = BRACKET_PAIRS .find { it.closing == closing }?.opening ? : return false
107+ val nextOpen = textAfter.indexOf(opening)
108+
109+ return nextOpen == - 1 || nextClose < nextOpen
110+ }
111+
112+ private fun insertClosingBracket (project : Project , editor : Editor , offset : Int , closing : String ) {
113+ WriteCommandAction .runWriteCommandAction(project) {
114+ editor.document.insertString(offset, closing)
115+ }
116+ }
117+
118+ private fun replaceText (project : Project , editor : Editor , start : Int , length : Int , newText : String ) {
119+ WriteCommandAction .runWriteCommandAction(project) {
120+ editor.document.replaceString(start, start + length, newText)
121+ }
122+ }
123+ }
124+
125+ val BRACKET_PAIRS = listOf (
126+ TemplateBracketTypedHandler .BracketPair (" {{--" , " --}}" ),
127+ TemplateBracketTypedHandler .BracketPair (" {{!!" , " !!}}" ),
128+ TemplateBracketTypedHandler .BracketPair (" {{" , " }}" ),
129+ )
130+
131+ private val AUTO_COMPLETE_PAIRS = listOf (
132+ TemplateBracketTypedHandler .BracketPair (" {{--" , " --}}" ),
133+ TemplateBracketTypedHandler .BracketPair (" {{!!" , " !!}}" ),
134+ TemplateBracketTypedHandler .BracketPair (" {{" , " }}" ),
135+ )
136+
137+ val INSTANCE = TemplateBracketTypedHandler ()
0 commit comments