|
16 | 16 | package org.domaframework.doma.intellij.common.sql.directive |
17 | 17 |
|
18 | 18 | import com.intellij.codeInsight.completion.CompletionResultSet |
19 | | -import com.intellij.codeInsight.lookup.AutoCompletionPolicy |
20 | | -import com.intellij.codeInsight.lookup.LookupElement |
21 | | -import com.intellij.codeInsight.lookup.LookupElementBuilder |
22 | | -import com.intellij.codeInsight.lookup.VariableLookupItem |
| 19 | +import com.intellij.openapi.module.Module |
23 | 20 | import com.intellij.openapi.project.Project |
24 | | -import com.intellij.openapi.vfs.VirtualFile |
25 | 21 | import com.intellij.psi.PsiElement |
26 | | -import com.intellij.psi.PsiFile |
27 | | -import com.intellij.psi.PsiManager |
28 | | -import com.intellij.psi.PsiType |
29 | | -import com.intellij.psi.search.GlobalSearchScope |
30 | 22 | import com.intellij.psi.util.PsiTreeUtil |
31 | 23 | import com.intellij.psi.util.elementType |
32 | | -import org.domaframework.doma.intellij.common.isJavaOrKotlinFileType |
33 | | -import org.domaframework.doma.intellij.common.psi.PsiParentClass |
34 | | -import org.domaframework.doma.intellij.common.psi.PsiPatternUtil |
35 | | -import org.domaframework.doma.intellij.common.psi.PsiStaticElement |
36 | | -import org.domaframework.doma.intellij.common.sql.cleanString |
37 | | -import org.domaframework.doma.intellij.extension.getContentRoot |
38 | | -import org.domaframework.doma.intellij.extension.psi.psiClassType |
| 24 | +import org.domaframework.doma.intellij.common.sql.directive.collector.StaticBuildFunctionCollector |
| 25 | +import org.domaframework.doma.intellij.common.sql.directive.collector.StaticClassPackageCollector |
| 26 | +import org.domaframework.doma.intellij.common.sql.directive.collector.StaticPropertyCollector |
39 | 27 | import org.domaframework.doma.intellij.psi.SqlElClass |
40 | 28 | import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr |
41 | 29 | import org.domaframework.doma.intellij.psi.SqlTypes |
42 | | -import org.jetbrains.kotlin.idea.util.getSourceRoot |
| 30 | +import org.jetbrains.kotlin.idea.base.util.module |
43 | 31 |
|
44 | 32 | class StaticDirectiveHandler( |
45 | | - private val originalFile: PsiElement, |
| 33 | + originalFile: PsiElement, |
46 | 34 | private val element: PsiElement, |
47 | 35 | private val result: CompletionResultSet, |
48 | 36 | private val bindText: String, |
49 | 37 | private val project: Project, |
50 | 38 | ) : DirectiveHandler(originalFile) { |
51 | | - /** |
52 | | - * Function information displayed with code completion for built-in functions |
53 | | - */ |
54 | | - data class DomaFunction( |
55 | | - val name: String, |
56 | | - val returnType: PsiType, |
57 | | - val parameters: List<PsiType>, |
58 | | - ) |
59 | | - |
60 | | - /** |
61 | | - * Show parameters in code completion for fields and methods |
62 | | - */ |
63 | | - data class CompletionSuggest( |
64 | | - val field: List<VariableLookupItem>, |
65 | | - val methods: List<LookupElement>, |
66 | | - ) |
67 | | - |
68 | 39 | override fun directiveHandle(): Boolean { |
69 | 40 | var handleResult = false |
70 | 41 | if (element.prevSibling is SqlElStaticFieldAccessExpr) { |
71 | | - handleResult = |
72 | | - staticDirectiveHandler(element, result) { fqdn, bind -> |
73 | | - val psiStaticElement = PsiStaticElement(fqdn, originalFile.containingFile) |
74 | | - val javaClass = |
75 | | - psiStaticElement.getRefClazz() ?: return@staticDirectiveHandler null |
76 | | - val parentClazz = PsiParentClass(javaClass.psiClassType) |
77 | | - parentClazz.let { clazz -> |
78 | | - val fields = |
79 | | - clazz.searchStaticField(bind)?.map { f -> VariableLookupItem(f) } |
80 | | - val methods = |
81 | | - clazz.searchStaticMethod(bind)?.map { m -> |
82 | | - LookupElementBuilder |
83 | | - .create("${m.name}()") |
84 | | - .withPresentableText(m.name) |
85 | | - .withTailText(m.parameterList.text, true) |
86 | | - .withTypeText(m.returnType?.presentableText ?: "") |
87 | | - } |
88 | | - CompletionSuggest(fields ?: emptyList(), methods ?: emptyList()) |
89 | | - } |
90 | | - } |
| 42 | + handleResult = staticDirectiveHandler(element, result) |
91 | 43 | } |
92 | 44 | if (handleResult) return true |
93 | 45 |
|
94 | 46 | if (PsiTreeUtil.nextLeaf(element)?.elementType == SqlTypes.AT_SIGN || |
95 | 47 | element.elementType == SqlTypes.AT_SIGN |
96 | 48 | ) { |
| 49 | + val module = element.module ?: return false |
97 | 50 | handleResult = |
98 | | - staticClassPath( |
| 51 | + collectionModulePackages( |
| 52 | + module, |
99 | 53 | result, |
100 | | - ) { file, root -> |
101 | | - val rootChildren = root.children |
102 | | - if (PsiTreeUtil.prevLeaf(element)?.elementType == SqlTypes.AT_SIGN) { |
103 | | - return@staticClassPath rootChildren.map { |
104 | | - LookupElementBuilder |
105 | | - .create(it.name) |
106 | | - .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE) |
107 | | - } |
108 | | - } else { |
109 | | - val prevPackageNames = |
110 | | - PsiPatternUtil.getBindSearchWord(file, element, "@").split(".") |
111 | | - val topPackage = |
112 | | - rootChildren.firstOrNull { it.name == prevPackageNames.firstOrNull() } |
113 | | - ?: return@staticClassPath null |
114 | | - var nextPackage: VirtualFile? = |
115 | | - topPackage |
116 | | - if (prevPackageNames.size > 2) { |
117 | | - for (packageName in prevPackageNames.drop(1).dropLast(1)) { |
118 | | - if (nextPackage == null) break |
119 | | - nextPackage = |
120 | | - nextPackage.children.firstOrNull { |
121 | | - it.name == cleanString(packageName) |
122 | | - } |
123 | | - } |
124 | | - } |
125 | | - return@staticClassPath nextPackage |
126 | | - ?.children |
127 | | - ?.filter { |
128 | | - it.name.contains(cleanString(prevPackageNames.lastOrNull() ?: "")) |
129 | | - }?.map { |
130 | | - val packageName = prevPackageNames.joinToString(".").plus(it.nameWithoutExtension) |
131 | | - val suggestName = |
132 | | - it.nameWithoutExtension |
133 | | - if (!isJavaOrKotlinFileType(it)) { |
134 | | - suggestName.plus(".") |
135 | | - } |
136 | | - LookupElementBuilder |
137 | | - .create(packageName) |
138 | | - .withPresentableText(suggestName) |
139 | | - .withTailText("($packageName)", true) |
140 | | - .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE) |
141 | | - } |
142 | | - } |
143 | | - } |
| 54 | + ) |
144 | 55 | } |
145 | 56 | if (handleResult) return true |
146 | 57 |
|
147 | 58 | if (element.prevSibling?.elementType == SqlTypes.AT_SIGN) { |
148 | 59 | // Built-in function completion |
149 | | - handleResult = |
150 | | - builtInDirectiveHandler(element, result) { bind -> |
151 | | - listOf( |
152 | | - DomaFunction( |
153 | | - "escape", |
154 | | - getJavaLangString(), |
155 | | - listOf( |
156 | | - getPsiTypeByClassName("java.lang.CharSequence"), |
157 | | - getPsiTypeByClassName("java.lang.Char"), |
158 | | - ), |
159 | | - ), |
160 | | - DomaFunction( |
161 | | - "prefix", |
162 | | - getJavaLangString(), |
163 | | - listOf( |
164 | | - getPsiTypeByClassName("java.lang.CharSequence"), |
165 | | - getPsiTypeByClassName("java.lang.Char"), |
166 | | - ), |
167 | | - ), |
168 | | - DomaFunction( |
169 | | - "infix", |
170 | | - getJavaLangString(), |
171 | | - listOf( |
172 | | - getPsiTypeByClassName("java.lang.CharSequence"), |
173 | | - getPsiTypeByClassName("java.lang.Char"), |
174 | | - ), |
175 | | - ), |
176 | | - DomaFunction( |
177 | | - "suffix", |
178 | | - getJavaLangString(), |
179 | | - listOf( |
180 | | - getPsiTypeByClassName("java.lang.CharSequence"), |
181 | | - getPsiTypeByClassName("java.lang.Char"), |
182 | | - ), |
183 | | - ), |
184 | | - DomaFunction( |
185 | | - "roundDownTimePart", |
186 | | - getPsiTypeByClassName("java.util.Date"), |
187 | | - listOf(getPsiTypeByClassName("java.util.Date")), |
188 | | - ), |
189 | | - DomaFunction( |
190 | | - "roundDownTimePart", |
191 | | - getPsiTypeByClassName("java.sql.Date"), |
192 | | - listOf(getPsiTypeByClassName("java.util.Date")), |
193 | | - ), |
194 | | - DomaFunction( |
195 | | - "roundDownTimePart", |
196 | | - getPsiTypeByClassName("java.sql.Timestamp"), |
197 | | - listOf(getPsiTypeByClassName("java.sql.Timestamp")), |
198 | | - ), |
199 | | - DomaFunction( |
200 | | - "roundDownTimePart", |
201 | | - getPsiTypeByClassName("java.time.LocalDateTime"), |
202 | | - listOf(getPsiTypeByClassName("java.time.LocalDateTime")), |
203 | | - ), |
204 | | - DomaFunction( |
205 | | - "roundUpTimePart", |
206 | | - getPsiTypeByClassName("java.util.Date"), |
207 | | - listOf(getPsiTypeByClassName("java.sql.Date")), |
208 | | - ), |
209 | | - DomaFunction( |
210 | | - "roundUpTimePart", |
211 | | - getPsiTypeByClassName("java.sql.Timestamp"), |
212 | | - listOf(getPsiTypeByClassName("java.sql.Timestamp")), |
213 | | - ), |
214 | | - DomaFunction( |
215 | | - "roundUpTimePart", |
216 | | - getPsiTypeByClassName("java.time.LocalDate"), |
217 | | - listOf(getPsiTypeByClassName("java.time.LocalDate")), |
218 | | - ), |
219 | | - DomaFunction( |
220 | | - "isEmpty", |
221 | | - getPsiTypeByClassName("boolean"), |
222 | | - listOf(getPsiTypeByClassName("java.lang.CharSequence")), |
223 | | - ), |
224 | | - DomaFunction( |
225 | | - "isNotEmpty", |
226 | | - getPsiTypeByClassName("boolean"), |
227 | | - listOf(getPsiTypeByClassName("java.lang.CharSequence")), |
228 | | - ), |
229 | | - DomaFunction( |
230 | | - "isBlank", |
231 | | - getPsiTypeByClassName("boolean"), |
232 | | - listOf(getPsiTypeByClassName("java.lang.CharSequence")), |
233 | | - ), |
234 | | - DomaFunction( |
235 | | - "isNotBlank", |
236 | | - getPsiTypeByClassName("boolean"), |
237 | | - listOf(getPsiTypeByClassName("java.lang.CharSequence")), |
238 | | - ), |
239 | | - ).filter { |
240 | | - it.name.startsWith(bind.substringAfter("@")) |
241 | | - }.map { |
242 | | - LookupElementBuilder |
243 | | - .create("${it.name}()") |
244 | | - .withPresentableText(it.name) |
245 | | - .withTailText( |
246 | | - "(${ |
247 | | - it.parameters.joinToString(",") { param -> |
248 | | - param.toString().replace("PsiType:", "") |
249 | | - } |
250 | | - })", |
251 | | - true, |
252 | | - ).withTypeText(it.returnType.presentableText) |
253 | | - } |
254 | | - } |
| 60 | + handleResult = builtInDirectiveHandler(element, result) |
255 | 61 | } |
256 | 62 | return handleResult |
257 | 63 | } |
258 | 64 |
|
259 | 65 | private fun staticDirectiveHandler( |
260 | 66 | element: PsiElement, |
261 | 67 | result: CompletionResultSet, |
262 | | - processor: (String, String) -> CompletionSuggest?, |
263 | 68 | ): Boolean { |
264 | 69 | val clazzRef = |
265 | 70 | PsiTreeUtil |
266 | 71 | .getChildOfType(element.prevSibling, SqlElClass::class.java) |
267 | 72 | val fqdn = |
268 | 73 | PsiTreeUtil.getChildrenOfTypeAsList(clazzRef, PsiElement::class.java).joinToString("") { it.text } |
269 | | - val candidates = processor(fqdn, bindText) ?: return false |
| 74 | + |
| 75 | + val collector = StaticPropertyCollector(element, bindText) |
| 76 | + val candidates = collector.collectCompletionSuggest(fqdn) ?: return false |
270 | 77 | result.addAllElements(candidates.field) |
271 | 78 | candidates.methods.map { m -> result.addElement(m) } |
272 | 79 | return true |
273 | 80 | } |
274 | 81 |
|
275 | | - private fun staticClassPath( |
| 82 | + private fun collectionModulePackages( |
| 83 | + module: Module, |
276 | 84 | result: CompletionResultSet, |
277 | | - processor: (PsiFile, VirtualFile) -> List<LookupElement>?, |
278 | 85 | ): Boolean { |
279 | | - val file = originalFile.containingFile ?: return false |
280 | | - val virtualFile = file.virtualFile ?: return false |
281 | | - val root = |
282 | | - project |
283 | | - .getContentRoot(virtualFile) |
284 | | - ?.children |
285 | | - ?.firstOrNull() |
286 | | - ?.getSourceRoot(project) |
287 | | - ?: return false |
288 | | - val candidates = processor(file, root) ?: return false |
| 86 | + val collector = StaticClassPackageCollector(element, module) |
| 87 | + val candidates = collector.collect() ?: return false |
289 | 88 | result.addAllElements(candidates) |
290 | 89 | return true |
291 | 90 | } |
292 | 91 |
|
293 | 92 | private fun builtInDirectiveHandler( |
294 | 93 | element: PsiElement, |
295 | 94 | result: CompletionResultSet, |
296 | | - processor: (String) -> List<LookupElement>?, |
297 | 95 | ): Boolean { |
298 | 96 | if (BindDirectiveUtil.getDirectiveType(element) == DirectiveType.BUILT_IN) { |
299 | 97 | val prefix = getBindSearchWord(element, bindText) |
300 | | - val candidates = processor(prefix) |
| 98 | + val collector = StaticBuildFunctionCollector(project, prefix) |
| 99 | + val candidates = collector.collect() |
301 | 100 | candidates?.let { it1 -> result.addAllElements(it1) } |
302 | 101 | return true |
303 | 102 | } |
304 | 103 | return false |
305 | 104 | } |
306 | | - |
307 | | - private fun getJavaLangString(): PsiType = |
308 | | - PsiType.getJavaLangString( |
309 | | - PsiManager.getInstance(project), |
310 | | - GlobalSearchScope.allScope(project), |
311 | | - ) |
312 | | - |
313 | | - private fun getPsiTypeByClassName(className: String): PsiType = |
314 | | - PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project)) |
315 | 105 | } |
0 commit comments