From 8e4130ff2fa45f38d80ce7124fca9df7c40cedef Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Sat, 20 Jan 2024 18:36:31 -0700 Subject: [PATCH] Add IPC line markers This has C++ -> IPC and IPC -> C++ markers --- .../kotlin/me/mattco/serenityos/idl/utils.kt | 4 +- .../kotlin/me/mattco/serenityos/ipc/IPC.kt | 1 + .../serenityos/ipc/IPCLineMarkerProvider.kt | 84 +++++++++++++++++++ .../ipc/project/IPCProjectService.kt | 9 ++ .../ipc/project/IPCProjectServiceImpl.kt | 52 +++++++++++- .../ipc/psi/mixins/IPCEndpointMixin.kt | 13 +-- 6 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/me/mattco/serenityos/ipc/IPCLineMarkerProvider.kt diff --git a/src/main/kotlin/me/mattco/serenityos/idl/utils.kt b/src/main/kotlin/me/mattco/serenityos/idl/utils.kt index 526ffc5..2320c01 100644 --- a/src/main/kotlin/me/mattco/serenityos/idl/utils.kt +++ b/src/main/kotlin/me/mattco/serenityos/idl/utils.kt @@ -9,8 +9,8 @@ import com.intellij.psi.util.elementType fun PsiElement.prevSiblings() = generateSequence(this.prevSibling) { it.prevSibling }.filter { it !is PsiWhiteSpace } fun PsiElement.nextSiblings() = generateSequence(this.nextSibling) { it.nextSibling }.filter { it !is PsiWhiteSpace } -inline fun PsiElement.prevSiblingOfType() = prevSiblings().find { it is T } as T? -inline fun PsiElement.nextSiblingOfType() = nextSiblings().find { it is T } as T +inline fun PsiElement.prevSiblingOfType() = prevSiblings().find { it is T } as? T? +inline fun PsiElement.nextSiblingOfType() = nextSiblings().find { it is T } as? T inline fun PsiElement.prevSiblingsOfType() = prevSiblings().filterIsInstance() inline fun PsiElement.nextSiblingsOfType() = nextSiblings().filterIsInstance() diff --git a/src/main/kotlin/me/mattco/serenityos/ipc/IPC.kt b/src/main/kotlin/me/mattco/serenityos/ipc/IPC.kt index 3c60494..0e72e75 100644 --- a/src/main/kotlin/me/mattco/serenityos/ipc/IPC.kt +++ b/src/main/kotlin/me/mattco/serenityos/ipc/IPC.kt @@ -8,6 +8,7 @@ import com.intellij.psi.FileViewProvider object IPCLanguage : Language("SerenityOS IPC") { val FILE_ICON = IconLoader.getIcon("/META-INF/ipc.png", IPCLanguage::class.java) + val FILE_ICON_FOR_MARKER = IconLoader.getIcon("/META-INF/ipc_without_border.png", IPCLanguage::class.java) } object IPCFileType : LanguageFileType(IPCLanguage) { diff --git a/src/main/kotlin/me/mattco/serenityos/ipc/IPCLineMarkerProvider.kt b/src/main/kotlin/me/mattco/serenityos/ipc/IPCLineMarkerProvider.kt new file mode 100644 index 0000000..f96e755 --- /dev/null +++ b/src/main/kotlin/me/mattco/serenityos/ipc/IPCLineMarkerProvider.kt @@ -0,0 +1,84 @@ +package me.mattco.serenityos.ipc + +import com.intellij.codeInsight.daemon.LineMarkerInfo +import com.intellij.codeInsight.daemon.LineMarkerProvider +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder +import com.intellij.icons.AllIcons +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiManager +import com.intellij.psi.search.FileTypeIndex +import com.intellij.psi.search.GlobalSearchScopes +import com.intellij.psi.util.PsiTreeUtil +import com.jetbrains.cidr.lang.OCFileType +import com.jetbrains.cidr.lang.OCIcons +import com.jetbrains.cidr.lang.psi.OCFile +import com.jetbrains.cidr.lang.psi.OCStructLike +import com.jetbrains.cidr.lang.search.OCSearchEverywhereClassifier +import com.jetbrains.cidr.lang.search.OCSearchHelper +import com.jetbrains.cidr.lang.search.OCSearchUtil +import com.jetbrains.cidr.lang.search.scopes.OCSearchScopeService +import com.jetbrains.cidr.lang.symbols.OCSymbolContext +import me.mattco.serenityos.ipc.project.ipcProject +import me.mattco.serenityos.ipc.psi.api.IPCEndpoint + +class IPCLineMarkerProvider : LineMarkerProvider { + override fun collectSlowLineMarkers( + elements: MutableList, + result: MutableCollection> + ) { + // C++ is considered "slow" as opposed to IPC lookup, which is considered "fast", + // because there are thousands of C++ files and only a handful (~35) of IPC files + + val ipcElements = elements.filterIsInstance() + val project = ipcElements.firstOrNull()?.project ?: return + val ipcProject = project.ipcProject + val psiManager = PsiManager.getInstance(project) + val roots = ipcProject.getProjectRootDirectories() + val scope = GlobalSearchScopes.directoriesScope(project, true, *roots.toTypedArray()) + + val remainingEndpoints = ipcElements.associateByTo(mutableMapOf()) { it.identifier.text } + FileTypeIndex.processFiles(OCFileType.INSTANCE, { + // Only look in header files, this speeds it up a bit + if (it.extension != "h") return@processFiles true + + val ocFile = psiManager.findFile(it) as? OCFile ?: return@processFiles true + val structs = PsiTreeUtil.findChildrenOfType(ocFile, OCStructLike::class.java) + + for (struct in structs) { + val endpointNames = ipcProject.findEndpointNameFromStruct(struct) ?: continue + for (name in endpointNames) { + val removed = remainingEndpoints.remove(name) + if (removed != null) { + NavigationGutterIconBuilder.create(AllIcons.Gutter.ImplementedMethod) + .setTarget(struct) + .setTooltipText("Jump to C++ endpoint implementation") + .createLineMarkerInfo(removed.identifier) + .let(result::add) + } + } + } + + remainingEndpoints.isNotEmpty() + }, scope) + } + + override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { + val ipcProject = element.ipcProject + + if (element is OCStructLike) { + val endpoints = ipcProject.findEndpointNameFromStruct(element)?.map(ipcProject::findIPCEndpoint) + ?: return null + + if (endpoints.isNotEmpty()) { + return NavigationGutterIconBuilder.create(IPCLanguage.FILE_ICON_FOR_MARKER) + .setTargets(endpoints) + .setTooltipText("Jump to IPC endpoint declaration") + .createLineMarkerInfo(element.nameIdentifier ?: return null) + } + } + + return null + } +} diff --git a/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectService.kt b/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectService.kt index 7c49a38..0f3c3ef 100644 --- a/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectService.kt +++ b/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectService.kt @@ -2,12 +2,21 @@ package me.mattco.serenityos.ipc.project import com.intellij.openapi.components.service import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.jetbrains.cidr.lang.psi.OCFile +import com.jetbrains.cidr.lang.psi.OCStructLike +import me.mattco.serenityos.ipc.psi.api.IPCEndpoint import java.nio.file.Path interface IPCProjectService { fun resolveImportedFile(path: Path): OCFile? + + fun findIPCEndpoint(name: String): IPCEndpoint? + + fun findEndpointNameFromStruct(struct: OCStructLike): List? + + fun getProjectRootDirectories(): List } val Project.ipcProject: IPCProjectService diff --git a/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectServiceImpl.kt b/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectServiceImpl.kt index 9236849..d70034f 100644 --- a/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectServiceImpl.kt +++ b/src/main/kotlin/me/mattco/serenityos/ipc/project/IPCProjectServiceImpl.kt @@ -5,7 +5,14 @@ import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager +import com.intellij.psi.search.FileTypeIndex +import com.intellij.psi.search.GlobalSearchScopes import com.jetbrains.cidr.lang.psi.OCFile +import com.jetbrains.cidr.lang.psi.OCStructLike +import me.mattco.serenityos.idl.findChildOfType +import me.mattco.serenityos.ipc.IPCFile +import me.mattco.serenityos.ipc.IPCFileType +import me.mattco.serenityos.ipc.psi.api.IPCEndpoint import java.nio.file.Path class IPCProjectServiceImpl(private val project: Project) : IPCProjectService { @@ -15,7 +22,7 @@ class IPCProjectServiceImpl(private val project: Project) : IPCProjectService { if (DumbService.isDumb(project)) return null - for (root in getRoots()) { + for (root in getProjectRootDirectories()) { val file = path.fold(root as VirtualFile?) { f, name -> f?.findChild(name.toString()) } ?: continue return PsiManager.getInstance(project).findFile(file) as? OCFile ?: continue } @@ -23,7 +30,48 @@ class IPCProjectServiceImpl(private val project: Project) : IPCProjectService { return null } - private fun getRoots(): List { + override fun findIPCEndpoint(name: String): IPCEndpoint? { + if (DumbService.isDumb(project)) + return null + + val psiManager = PsiManager.getInstance(project) + val scope = GlobalSearchScopes.directoriesScope(project, true, *getProjectRootDirectories().toTypedArray()) + var matchingEndpoint: IPCEndpoint? = null + + FileTypeIndex.processFiles(IPCFileType, { + val ipcFile = psiManager.findFile(it) as? IPCFile ?: return@processFiles true + val endpoint = ipcFile.findChildOfType() ?: return@processFiles true + + if (endpoint.identifier.text == name) { + matchingEndpoint = endpoint + return@processFiles false + } + + true + }, scope) + + return matchingEndpoint + } + + override fun findEndpointNameFromStruct(struct: OCStructLike): List? { + for (base in struct.baseClausesList?.baseClauses.orEmpty()) { + val baseText = base.text + if (!baseText.startsWith("public IPC::ConnectionFromClient") + && !baseText.startsWith("public IPC::ConnectionToServer") + ) { + continue + } + + val templateArgs = baseText.substringAfter('<').substringBefore('>').split(',').map(String::trim) + return templateArgs.map { + it.removeSuffix("Endpoint") + } + } + + return null + } + + override fun getProjectRootDirectories(): List { if (includeRoots == null) { val base = project.getBaseDirectories().singleOrNull() ?: return emptyList() val userland = base.findChild("Userland") diff --git a/src/main/kotlin/me/mattco/serenityos/ipc/psi/mixins/IPCEndpointMixin.kt b/src/main/kotlin/me/mattco/serenityos/ipc/psi/mixins/IPCEndpointMixin.kt index f5c4191..f27ab03 100644 --- a/src/main/kotlin/me/mattco/serenityos/ipc/psi/mixins/IPCEndpointMixin.kt +++ b/src/main/kotlin/me/mattco/serenityos/ipc/psi/mixins/IPCEndpointMixin.kt @@ -9,6 +9,7 @@ import com.jetbrains.cidr.lang.psi.OCElement import com.jetbrains.cidr.lang.psi.OCFile import com.jetbrains.cidr.lang.psi.OCStructLike import com.jetbrains.cidr.lang.search.scopes.OCSearchScope +import me.mattco.serenityos.ipc.project.ipcProject import me.mattco.serenityos.ipc.psi.IPCNamedElement import me.mattco.serenityos.ipc.psi.api.IPCEndpoint import me.mattco.serenityos.ipc.psi.multiRef @@ -19,6 +20,7 @@ abstract class IPCEndpointMixin(node: ASTNode) : IPCNamedElement(node), IPCEndpo private fun resolveCppDecl(): List { val scope = OCSearchScope.getProjectSourcesScope(project) val psiManager = PsiManager.getInstance(project) + val ipcProject = ipcProject // We will only ever resolve to header files. Additionally, IPC files have some naming // conventions that we can use to avoid searching thousands of files @@ -34,15 +36,8 @@ abstract class IPCEndpointMixin(node: ASTNode) : IPCNamedElement(node), IPCEndpo val cppFile = psiManager.findFile(file) as OCFile val structs = PsiTreeUtil.findChildrenOfType(cppFile, OCStructLike::class.java) for (struct in structs) { - for (base in struct.baseClausesList?.baseClauses.orEmpty()) { - val baseText = base.text - if (endpointName !in baseText) continue - if (baseText.startsWith("public IPC::ConnectionFromClient") - || baseText.startsWith("public IPC::ConnectionToServer") - ) { - matchingEndpoints.add(struct) - } - } + if (ipcProject.findEndpointNameFromStruct(struct) != null) + matchingEndpoints.add(struct) } }