Skip to content

Commit 6ea7271

Browse files
committed
feat: autocomplete brackets
1 parent ab02b52 commit 6ea7271

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.github.tempest.framework.views.completion
2+
3+
import com.github.tempest.framework.TempestFrameworkUtil
4+
import com.intellij.codeInsight.editorActions.BackspaceHandlerDelegate
5+
import com.intellij.openapi.editor.Editor
6+
import com.intellij.psi.PsiFile
7+
8+
class TemplateBracketBackspaceHandler : BackspaceHandlerDelegate() {
9+
10+
override fun beforeCharDeleted(c: Char, file: PsiFile, editor: Editor) {}
11+
12+
override fun charDeleted(c: Char, file: PsiFile, editor: Editor): Boolean {
13+
if (!file.name.endsWith(TempestFrameworkUtil.TEMPLATE_SUFFIX)) return false
14+
15+
if (c != '!' && c != '-') return false
16+
17+
INSTANCE.synchronizeBracketsAfterDeletion(file.project, editor)
18+
19+
return false
20+
}
21+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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()

src/main/resources/META-INF/plugin.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
<psi.referenceContributor
6868
language="PHP"
6969
implementation="com.github.tempest.framework.db.references.QueryBuilderReferenceContributor"/>
70+
<typedHandler
71+
implementation="com.github.tempest.framework.views.completion.TemplateBracketTypedHandler"/>
72+
<backspaceHandlerDelegate
73+
implementation="com.github.tempest.framework.views.completion.TemplateBracketBackspaceHandler"/>
7074
</extensions>
7175
<extensions defaultExtensionNs="com.jetbrains.php">
7276
<libraryRoot id="tempest.meta-storm" path="meta-storm" runtime="false" />

0 commit comments

Comments
 (0)