package cc.unitmesh.idea.provider import cc.unitmesh.devti.provider.devins.DevInsSymbolProvider import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.ide.highlighter.JavaFileType import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager import com.intellij.psi.PsiPackageStatement import com.intellij.psi.impl.file.impl.JavaFileManagerImpl import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.ProjectScope import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.SmartList class JavaCustomDevInsSymbolProvider : DevInsSymbolProvider { override val language: String = JavaLanguage.INSTANCE.displayName override fun lookupSymbol( project: Project, parameters: CompletionParameters, result: CompletionResultSet, ): List { val lookupElements: MutableList = SmartList() val searchScope = ProjectScope.getProjectScope(project) val javaFiles = FileTypeIndex.getFiles(JavaFileType.INSTANCE, searchScope) if (javaFiles.isEmpty()) return lookupElements val prefixMatcher = CompletionUtil.findReferenceOrAlphanumericPrefix(parameters) result.withPrefixMatcher(prefixMatcher) val text = parameters.position.text.removePrefix(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED) val packageStatements = javaFiles.mapNotNull { val psi = PsiManager.getInstance(project).findFile(it) ?: return@mapNotNull null PsiTreeUtil.getChildrenOfTypeAsList(psi, PsiPackageStatement::class.java).firstOrNull() } packageStatements.forEach { if (it.packageName.startsWith(text)) { val element = LookupElementBuilder.create(it.packageName) .withIcon(JavaFileType.INSTANCE.icon) .withTypeText("package") lookupElements.add(element) } } val psiShortNamesCache = PsiShortNamesCache.getInstance(project) val classNames = psiShortNamesCache.allClassNames classNames.forEach { className -> val psiClasses = psiShortNamesCache.getClassesByName(className, searchScope) psiClasses.forEach { psiClass -> val qualifiedName = psiClass.qualifiedName ?: return@forEach val element = LookupElementBuilder.create(qualifiedName) .withIcon(JavaFileType.INSTANCE.icon) .withTypeText("class") lookupElements.add(element) } } return lookupElements } override fun resolveSymbol(project: Project, symbol: String): List { val scope = ProjectScope.getProjectScope(project) if (symbol.isEmpty()) return emptyList() // className only, like `String` not Dot if (symbol.contains(".").not()) { val psiClasses = PsiShortNamesCache.getInstance(project).getClassesByName(symbol, scope) if (psiClasses.isNotEmpty()) { return psiClasses.map { it.qualifiedName!! } } } // for package name only, like `cc.unitmesh` JavaFileManagerImpl(project).findPackage(symbol)?.let { pkg -> return pkg.classes.map { it.qualifiedName!! } } // for single class, with function name, like `cc.unitmesh.idea.provider.JavaCustomDevInsSymbolProvider` val clazz = JavaFileManagerImpl(project).findClass(symbol, scope) if (clazz != null) { return clazz.methods.map { "${clazz.qualifiedName}#${it.name}" } } // for lookup for method val method = symbol.split("#") if (method.size == 2) { val clazzName = method[0] val methodName = method[1] return lookupWithMethodName(project, clazzName, scope, methodName) } // may by not our format, like .. split last val lastDotIndex = symbol.lastIndexOf(".") if (lastDotIndex != -1) { val clazzName = symbol.substring(0, lastDotIndex) val methodName = symbol.substring(lastDotIndex + 1) return lookupWithMethodName(project, clazzName, scope, methodName) } return emptyList() } override fun resolveElement(project: Project, symbol: String): List { val scope = ProjectScope.getProjectScope(project) if (symbol.isEmpty()) return emptyList() // className only, like `String` not Dot if (symbol.contains(".").not()) { val psiClasses = runReadAction { PsiShortNamesCache.getInstance(project).getClassesByName(symbol, scope) } if (psiClasses.isNotEmpty()) { return psiClasses.toList() } } // for package name only, like `cc.unitmesh` runReadAction { JavaFileManagerImpl(project).findPackage(symbol) }?.let { pkg -> return runReadAction { pkg.classes.toList() } } // for single class, with function name, like `cc.unitmesh.idea.provider.JavaCustomDevInsSymbolProvider` val clazz = runReadAction { JavaFileManagerImpl(project).findClass(symbol, scope) } if (clazz != null) { return listOf(clazz) } // for lookup for method val split = symbol.split("#") if (split.size == 2) { val clazzName = split[0] val methodName = split[1] return lookupElementWithMethodName(project, clazzName, scope, methodName) } // may by not our format, like .. split last val lastDotIndex = symbol.lastIndexOf(".") if (lastDotIndex != -1) { val clazzName = symbol.substring(0, lastDotIndex) val methodName = symbol.substring(lastDotIndex + 1) return lookupElementWithMethodName(project, clazzName, scope, methodName) } return emptyList() } private fun lookupWithMethodName( project: Project, clazzName: String, scope: GlobalSearchScope, methodName: String ): List { val psiClass = runReadAction { JavaFileManagerImpl(project).findClass(clazzName, scope) } if (psiClass != null) { val psiMethod = runReadAction { psiClass.findMethodsByName(methodName, true).firstOrNull() } if (psiMethod != null) { return listOf(psiMethod.text) } } return emptyList() } private fun lookupElementWithMethodName( project: Project, clazzName: String, scope: GlobalSearchScope, methodName: String ): List { val psiClass = runReadAction { JavaFileManagerImpl(project).findClass(clazzName, scope) } if (psiClass != null) { val psiMethod = runReadAction { psiClass.findMethodsByName(methodName, true).firstOrNull() } if (psiMethod != null) { return listOf(psiMethod) } } return emptyList() } }