Skip to content

Commit 19b6233

Browse files
committed
Enhance code completion for static property accesses and improve directive symbol handling
1 parent 5a55a7b commit 19b6233

File tree

8 files changed

+172
-57
lines changed

8 files changed

+172
-57
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import com.intellij.psi.util.elementType
2525
import com.intellij.psi.util.prevLeaf
2626
import com.intellij.psi.util.prevLeafs
2727
import com.intellij.util.ProcessingContext
28+
import org.domaframework.doma.intellij.common.sql.directive.DirectiveCompletion
29+
import org.domaframework.doma.intellij.psi.SqlCustomElCommentExpr
2830
import org.domaframework.doma.intellij.psi.SqlElClass
2931
import org.domaframework.doma.intellij.psi.SqlElIdExpr
3032
import org.domaframework.doma.intellij.psi.SqlTypes
@@ -43,7 +45,27 @@ object PsiPatternUtil {
4345
override fun accepts(
4446
element: PsiElement,
4547
context: ProcessingContext?,
46-
): Boolean = PsiTreeUtil.getParentOfType(element, parentClass, true) != null
48+
): Boolean {
49+
val inComment = PsiTreeUtil.getParentOfType(element, parentClass, true) != null
50+
if (inComment) return true
51+
52+
var prevElement = PsiTreeUtil.prevLeaf(element, true)
53+
while (prevElement != null &&
54+
!(
55+
prevElement.nextSibling is SqlCustomElCommentExpr &&
56+
!prevElement.nextSibling.text.endsWith("*/")
57+
)
58+
) {
59+
prevElement = PsiTreeUtil.prevLeaf(prevElement, true)
60+
}
61+
62+
var endBlock = PsiTreeUtil.nextLeaf(element, true)
63+
while (endBlock != null && endBlock.elementType != SqlTypes.BLOCK_COMMENT_END) {
64+
endBlock = PsiTreeUtil.nextLeaf(endBlock, true)
65+
}
66+
67+
return prevElement != null && endBlock != null
68+
}
4769
},
4870
)
4971

@@ -55,7 +77,7 @@ object PsiPatternUtil {
5577
context: ProcessingContext?,
5678
): Boolean {
5779
val bindText = element.prevLeaf()?.text ?: ""
58-
val directiveSymbol = listOf("%", "@", "^", "#")
80+
val directiveSymbol = DirectiveCompletion.directiveSymbols
5981
return directiveSymbol.any {
6082
bindText.startsWith(it) ||
6183
(element.elementType == SqlTypes.EL_IDENTIFIER && element.prevLeaf()?.text == it) ||
@@ -104,7 +126,7 @@ object PsiPatternUtil {
104126

105127
/**
106128
* Get the string to search from the cursor position to the start of a block comment or a blank space
107-
* @return search Keyword
129+
* @return The string up to the specified character
108130
*/
109131
fun getBindSearchWord(
110132
originalFile: PsiElement,

src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiStaticElement.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.intellij.psi.JavaPsiFacade
1919
import com.intellij.psi.PsiClass
2020
import com.intellij.psi.PsiFile
2121
import com.intellij.psi.search.GlobalSearchScope
22+
import org.domaframework.doma.intellij.common.util.StringUtil
2223
import org.domaframework.doma.intellij.extension.getJavaClazz
2324
import org.domaframework.doma.intellij.psi.SqlElExpr
2425

@@ -32,10 +33,7 @@ class PsiStaticElement(
3233
private var fqdn = elExprList?.joinToString(".") { e -> e.text } ?: ""
3334

3435
constructor(elExprNames: String, file: PsiFile) : this(null, file) {
35-
fqdn =
36-
elExprNames
37-
.substringAfter("@")
38-
.substringBefore("@")
36+
fqdn = StringUtil.getSqlElClassText(elExprNames)
3937
}
4038

4139
fun getRefClazz(): PsiClass? {

src/main/kotlin/org/domaframework/doma/intellij/common/sql/CleanElementText.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@
1515
*/
1616
package org.domaframework.doma.intellij.common.sql
1717

18+
import org.domaframework.doma.intellij.common.util.StringUtil
19+
1820
/**
1921
* Exclude extra strings and block symbols added by IntelliJ operations a
2022
* nd format them into necessary elements
2123
*/
2224
fun cleanString(str: String): String {
2325
val intelliKIdeaRuleZzz = "IntellijIdeaRulezzz"
24-
return str
25-
.substringAfter("/*")
26-
.substringBefore("*/")
26+
return StringUtil
27+
.replaceBlockCommentStartEnd(str)
2728
.replace(intelliKIdeaRuleZzz, "")
2829
// TODO: Temporary support when using operators.
2930
// Remove the "== a" element because it is attached to the end.

src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/DirectiveCompletion.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ class DirectiveCompletion(
2626
private val caretNextText: String,
2727
private val result: CompletionResultSet,
2828
) {
29+
companion object {
30+
val directiveSymbols =
31+
listOf(
32+
"%",
33+
"#",
34+
"^",
35+
"@",
36+
)
37+
}
38+
2939
fun directiveHandle(symbol: String): Boolean {
3040
return when (symbol) {
3141
"%" ->

src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.intellij.openapi.module.Module
2020
import com.intellij.psi.PsiElement
2121
import com.intellij.psi.util.PsiTreeUtil
2222
import com.intellij.psi.util.elementType
23+
import org.domaframework.doma.intellij.common.psi.PsiPatternUtil
2324
import org.domaframework.doma.intellij.common.sql.directive.collector.FunctionCallCollector
2425
import org.domaframework.doma.intellij.common.sql.directive.collector.StaticClassPackageCollector
2526
import org.domaframework.doma.intellij.common.sql.directive.collector.StaticPropertyCollector
@@ -38,14 +39,12 @@ class StaticDirectiveHandler(
3839
override fun directiveHandle(): Boolean {
3940
var handleResult = false
4041

41-
if (isNextStaticFieldAccess(element)) {
42+
if (isStaticFieldAccessTopElement(element)) {
4243
handleResult = staticDirectiveHandler(element, result)
4344
}
4445
if (handleResult) return true
4546

46-
if (PsiTreeUtil.nextLeaf(element)?.elementType == SqlTypes.AT_SIGN ||
47-
element.elementType == SqlTypes.AT_SIGN
48-
) {
47+
if (isSqlElClassCompletion()) {
4948
val module = element.module ?: return false
5049
handleResult =
5150
collectionModulePackages(
@@ -55,32 +54,64 @@ class StaticDirectiveHandler(
5554
}
5655
if (handleResult) return true
5756

58-
if (PsiTreeUtil.prevLeaf(element)?.elementType == SqlTypes.AT_SIGN) {
57+
if (PsiTreeUtil.prevLeaf(element, true)?.elementType == SqlTypes.AT_SIGN) {
5958
// Built-in function completion
6059
handleResult = builtInDirectiveHandler(element, result)
6160
}
6261
return handleResult
6362
}
6463

65-
private fun isNextStaticFieldAccess(element: PsiElement): Boolean {
66-
val prev = PsiTreeUtil.prevLeaf(element)
67-
return element.prevSibling is SqlElStaticFieldAccessExpr ||
64+
/**
65+
* Determines whether code completion is needed for [SqlElClass] elements.
66+
*/
67+
private fun isSqlElClassCompletion(): Boolean {
68+
val elClassPattern = "^([a-zA-Z]*(\\.)+)*"
69+
val regex = Regex(elClassPattern)
70+
val prevWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, "@")
71+
return (
72+
(PsiTreeUtil.nextLeaf(element)?.elementType == SqlTypes.AT_SIGN || element.elementType == SqlTypes.AT_SIGN) &&
73+
regex.matches(prevWords)
74+
) ||
75+
(
76+
element.elementType == SqlTypes.AT_SIGN &&
77+
PsiTreeUtil.prevLeaf(element)?.elementType == SqlTypes.AT_SIGN
78+
)
79+
}
80+
81+
/**
82+
* Code completion for static properties after [SqlElClass].
83+
*/
84+
private fun isStaticFieldAccessTopElement(element: PsiElement): Boolean {
85+
val prev = PsiTreeUtil.prevLeaf(element, true)
86+
val staticFieldAccess =
87+
PsiTreeUtil.getParentOfType(prev, SqlElStaticFieldAccessExpr::class.java)
88+
val sqlElClassWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, " ")
89+
return (
90+
staticFieldAccess != null && staticFieldAccess.elIdExprList.isEmpty()
91+
) ||
6892
(
6993
prev?.elementType == SqlTypes.AT_SIGN &&
7094
prev.parent is SqlElStaticFieldAccessExpr
95+
) ||
96+
(
97+
sqlElClassWords.startsWith("@") &&
98+
sqlElClassWords.endsWith("@")
7199
)
72100
}
73101

74102
private fun staticDirectiveHandler(
75103
element: PsiElement,
76104
result: CompletionResultSet,
77105
): Boolean {
106+
val prev = PsiTreeUtil.prevLeaf(element, true)
78107
val clazzRef =
79108
PsiTreeUtil
80-
.getChildOfType(element.prevSibling, SqlElClass::class.java)
109+
.getChildOfType(prev, SqlElClass::class.java)
81110
?: PsiTreeUtil.getChildOfType(PsiTreeUtil.prevLeaf(element)?.parent, SqlElClass::class.java)
82-
val fqdn =
83-
PsiTreeUtil.getChildrenOfTypeAsList(clazzRef, PsiElement::class.java).joinToString("") { it.text }
111+
112+
val sqlElClassWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, " ")
113+
val sqlElClassName = PsiTreeUtil.getChildrenOfTypeAsList(clazzRef, PsiElement::class.java).joinToString("") { it.text }
114+
val fqdn = if (sqlElClassName.isNotEmpty()) sqlElClassName else sqlElClassWords.replace("@", "")
84115

85116
val collector = StaticPropertyCollector(element, caretNextText, bindText)
86117
val candidates = collector.collectCompletionSuggest(fqdn) ?: return false

src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class ForDirectiveUtil {
6767
private val cachedForDirectiveBlocks: MutableMap<PsiElement, CachedValue<List<BlockToken>>> =
6868
mutableMapOf()
6969

70+
const val HAS_NEXT_PREFIX = "_has_next"
71+
const val INDEX_PREFIX = "_index"
72+
7073
/**
7174
* Get the parent for directive list to which this directive belongs
7275
* @param targetElement Element to search for definer
@@ -162,7 +165,7 @@ class ForDirectiveUtil {
162165
skipSelf: Boolean = true,
163166
forDirectives: List<BlockToken> = getForDirectiveBlocks(targetElement, skipSelf),
164167
): PsiElement? {
165-
val searchText = targetElement.text.replace("_has_next", "").replace("_index", "")
168+
val searchText = targetElement.text.replace(HAS_NEXT_PREFIX, "").replace(INDEX_PREFIX, "")
166169
return forDirectives.firstOrNull { it.item.text == searchText }?.item
167170
}
168171

@@ -507,9 +510,9 @@ class ForDirectiveUtil {
507510
}
508511

509512
fun resolveForDirectiveItemClassTypeBySuffixElement(searchName: String): PsiType? =
510-
if (searchName.endsWith("_has_next")) {
513+
if (searchName.endsWith(HAS_NEXT_PREFIX)) {
511514
PsiTypes.booleanType()
512-
} else if (searchName.endsWith("_index")) {
515+
} else if (searchName.endsWith(INDEX_PREFIX)) {
513516
PsiTypes.intType()
514517
} else {
515518
null
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.common.util
17+
18+
object StringUtil {
19+
fun getSqlElClassText(text: String): String =
20+
text
21+
.substringAfter("@")
22+
.substringBefore("@")
23+
24+
fun replaceBlockCommentStartEnd(text: String): String = text.substringAfter("/*").substringBefore("*/")
25+
}

0 commit comments

Comments
 (0)