Skip to content

Commit 2bb53af

Browse files
committed
refactor: improve safety and logging in TestoIconProvider icon resolution
1 parent b320de1 commit 2bb53af

1 file changed

Lines changed: 35 additions & 13 deletions

File tree

src/main/kotlin/com/github/xepozz/testo/ui/TestoIconProvider.kt

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import com.github.xepozz.testo.TestoIcons
44
import com.github.xepozz.testo.TestoUtil
55
import com.github.xepozz.testo.isTestoFile
66
import 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
710
import com.intellij.openapi.util.Iconable
811
import com.intellij.psi.PsiElement
912
import com.intellij.psi.search.GlobalSearchScopesCore
@@ -13,23 +16,42 @@ import com.jetbrains.php.lang.psi.elements.PhpClass
1316
import javax.swing.Icon
1417

1518
class 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

Comments
 (0)