@@ -4,6 +4,9 @@ import com.github.xepozz.testo.TestoIcons
44import com.github.xepozz.testo.TestoUtil
55import com.github.xepozz.testo.isTestoFile
66import com.intellij.ide.IconProvider
7+ import com.intellij.openapi.diagnostic.logger
8+ import com.intellij.openapi.progress.ProcessCanceledException
9+ import com.intellij.openapi.roots.ProjectFileIndex
710import com.intellij.openapi.util.Iconable
811import com.intellij.psi.PsiElement
912import com.intellij.psi.search.GlobalSearchScopesCore
@@ -13,23 +16,42 @@ import com.jetbrains.php.lang.psi.elements.PhpClass
1316import javax.swing.Icon
1417
1518class TestoIconProvider : IconProvider () {
19+ private val log = logger<TestoIconProvider >()
20+
1621 override fun getIcon (element : PsiElement , @Iconable.IconFlags flags : Int ): Icon ? {
17- val phpFile = element as ? PhpFile ? : return null
18- if (! TestoUtil .isEnabled(element.project)) return null
22+ return try {
23+ val phpFile = element as ? PhpFile ? : return null
24+ val project = element.project
25+ if (! TestoUtil .isEnabled(project)) return null
1926
20- if (! phpFile.isTestoFile()) return null
21- if (! GlobalSearchScopesCore .projectTestScope(element.project).contains(phpFile.virtualFile)) {
22- return null
23- }
27+ val virtualFile = phpFile.virtualFile ? : return null
28+ if (! virtualFile.isValid) return null
29+
30+ val fileIndex = ProjectFileIndex .getInstance(project)
31+ // Quick exit: file is not in project content, is excluded, or is ignored by the IDE.
32+ if (! fileIndex.isInContent(virtualFile)) return null
33+ if (fileIndex.isExcluded(virtualFile)) return null
34+ if (fileIndex.isUnderIgnored(virtualFile)) return null
35+
36+ if (! phpFile.isTestoFile()) return null
37+ if (! GlobalSearchScopesCore .projectTestScope(project).contains(virtualFile)) {
38+ return null
39+ }
2440
25- val phpClasses = PsiTreeUtil .findChildrenOfType(phpFile, PhpClass ::class .java)
41+ val phpClasses = PsiTreeUtil .findChildrenOfType(phpFile, PhpClass ::class .java)
2642
27- return when {
28- phpClasses.isEmpty() -> TestoIcons .Layered .FUNCTION
29- phpClasses.size > 1 -> TestoIcons .Layered .FILE
30- phpClasses.first().modifier.isAbstract -> TestoIcons .Layered .Class .CLASS_ABSTRACT
31- phpClasses.first().modifier.isFinal -> TestoIcons .Layered .Class .CLASS_FINAL
32- else -> TestoIcons .Layered .Class .CLASS
43+ when {
44+ phpClasses.isEmpty() -> TestoIcons .Layered .FUNCTION
45+ phpClasses.size > 1 -> TestoIcons .Layered .FILE
46+ phpClasses.first().modifier.isAbstract -> TestoIcons .Layered .Class .CLASS_ABSTRACT
47+ phpClasses.first().modifier.isFinal -> TestoIcons .Layered .Class .CLASS_FINAL
48+ else -> TestoIcons .Layered .Class .CLASS
49+ }
50+ } catch (e: ProcessCanceledException ) {
51+ throw e
52+ } catch (e: Throwable ) {
53+ log.warn(" Failed to compute Testo icon for element: ${element.javaClass.name} " , e)
54+ null
3355 }
3456 }
3557}
0 commit comments