Skip to content

Commit

Permalink
Add IPC line markers
Browse files Browse the repository at this point in the history
This has C++ -> IPC and IPC -> C++ markers
  • Loading branch information
mattco98 committed Jan 21, 2024
1 parent 0f4bac1 commit 8e4130f
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/main/kotlin/me/mattco/serenityos/idl/utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <reified T> PsiElement.prevSiblingOfType() = prevSiblings().find { it is T } as T?
inline fun <reified T> PsiElement.nextSiblingOfType() = nextSiblings().find { it is T } as T
inline fun <reified T> PsiElement.prevSiblingOfType() = prevSiblings().find { it is T } as? T?
inline fun <reified T> PsiElement.nextSiblingOfType() = nextSiblings().find { it is T } as? T

inline fun <reified T> PsiElement.prevSiblingsOfType() = prevSiblings().filterIsInstance<T>()
inline fun <reified T> PsiElement.nextSiblingsOfType() = nextSiblings().filterIsInstance<T>()
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/me/mattco/serenityos/ipc/IPC.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
84 changes: 84 additions & 0 deletions src/main/kotlin/me/mattco/serenityos/ipc/IPCLineMarkerProvider.kt
Original file line number Diff line number Diff line change
@@ -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<out PsiElement>,
result: MutableCollection<in LineMarkerInfo<*>>
) {
// 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<IPCEndpoint>()
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>?

fun getProjectRootDirectories(): List<VirtualFile>
}

val Project.ipcProject: IPCProjectService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -15,15 +22,56 @@ 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
}

return null
}

private fun getRoots(): List<VirtualFile> {
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<IPCEndpoint>() ?: return@processFiles true

if (endpoint.identifier.text == name) {
matchingEndpoint = endpoint
return@processFiles false
}

true
}, scope)

return matchingEndpoint
}

override fun findEndpointNameFromStruct(struct: OCStructLike): List<String>? {
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<VirtualFile> {
if (includeRoots == null) {
val base = project.getBaseDirectories().singleOrNull() ?: return emptyList()
val userland = base.findChild("Userland")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,6 +20,7 @@ abstract class IPCEndpointMixin(node: ASTNode) : IPCNamedElement(node), IPCEndpo
private fun resolveCppDecl(): List<OCElement> {
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
Expand All @@ -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)
}
}

Expand Down

0 comments on commit 8e4130f

Please sign in to comment.