Skip to content

Commit b16afdc

Browse files
committed
find OpenAPI in more modules
1 parent f404dbc commit b16afdc

2 files changed

Lines changed: 108 additions & 12 deletions

File tree

src/main/kotlin/io/openapiprocessor/intellij/MappingAnnotationLineMarker.kt

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@ import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider
1010
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder
1111
import com.intellij.openapi.module.Module
1212
import com.intellij.openapi.project.Project
13-
import com.intellij.openapi.roots.ProjectRootManager
1413
import com.intellij.openapi.util.IconLoader
15-
import com.intellij.psi.*
14+
import com.intellij.psi.PsiAnnotation
15+
import com.intellij.psi.PsiElement
16+
import com.intellij.psi.PsiManager
1617
import com.intellij.psi.search.GlobalSearchScope
1718
import com.intellij.psi.util.firstLeaf
1819
import com.intellij.util.IconUtil
1920
import org.slf4j.Logger
2021
import org.slf4j.LoggerFactory
2122

23+
/**
24+
* line marker to navigate from mapping annotation (from interface) to the path in the OpenAPI document.
25+
*/
2226
class MappingAnnotationLineMarker: RelatedItemLineMarkerProvider() {
2327
private val log: Logger = LoggerFactory.getLogger(javaClass.name)
2428

@@ -41,11 +45,14 @@ class MappingAnnotationLineMarker: RelatedItemLineMarkerProvider() {
4145
}
4246

4347
val apiPath = match.path(element)
44-
val module = findModule(element) ?: return null
48+
val modules = findModules(element)
4549

46-
val scope = GlobalSearchScope.moduleScope(module)
47-
val targets = findPsiElementsOfPath(apiPath!!, scope, element.project)
50+
var searchScope = GlobalSearchScope.EMPTY_SCOPE
51+
for (module in modules) {
52+
searchScope = searchScope.uniteWith(GlobalSearchScope.moduleScope(module))
53+
}
4854

55+
val targets = findPsiElementsOfPath(apiPath!!, searchScope, element.project)
4956
if (targets.isEmpty()) {
5057
log.warn("found no targets!")
5158
return null
@@ -62,18 +69,20 @@ class MappingAnnotationLineMarker: RelatedItemLineMarkerProvider() {
6269
return builder.createLineMarkerInfo(id)
6370
}
6471

65-
private fun findModule(element: PsiElement): Module? {
66-
val found = ProjectRootManager.getInstance(element.project)
67-
.fileIndex
68-
.getModuleForFile(element.containingFile.virtualFile)
72+
private fun findModules(element: PsiElement): List<Module> {
73+
val finder = ModuleFinder(element.project)
74+
val modules = finder.findModules(element.containingFile.virtualFile.presentableUrl)
6975

70-
if (found != null) {
71-
log.debug("found module '{}' of file '{}'", found.name, element.containingFile.name)
76+
if (modules.isNotEmpty()) {
77+
modules.forEach {
78+
log.debug("related modules of file '{}'", element.containingFile.name)
79+
log.debug("found module '{}'", it.name)
80+
}
7281
} else {
7382
log.debug("could not find module of file '{}'", element.containingFile.name)
7483
}
7584

76-
return found
85+
return modules
7786
}
7887

7988
private fun findPsiElementsOfPath(path: String, searchScope: GlobalSearchScope, project: Project): List<PsiElement> {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2025 https://github.com/openapi-processor/openapi-processor-intellij
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.intellij
7+
8+
import com.intellij.openapi.module.Module
9+
import com.intellij.openapi.module.ModuleManager
10+
import com.intellij.openapi.project.Project
11+
import com.intellij.platform.backend.workspace.WorkspaceModel
12+
import com.intellij.platform.workspace.jps.entities.ModuleEntity
13+
import com.intellij.platform.workspace.jps.entities.sourceRoots
14+
import java.nio.file.Path
15+
import kotlin.io.path.name
16+
import kotlin.math.min
17+
18+
class ModuleFinder(private val project: Project) {
19+
20+
fun findModules(sourceUrl: String): List<Module> {
21+
val source = getRelativeUrl(sourceUrl)
22+
23+
val results = mutableMapOf<ModuleEntity, List<String>>()
24+
25+
val wm = WorkspaceModel.getInstance(project)
26+
27+
wm.currentSnapshot.entities(ModuleEntity::class.java)
28+
.filter { it.sourceRoots.isNotEmpty() }
29+
.forEach { moduleEntity ->
30+
val sourceRoots = mutableListOf<Path>()
31+
32+
moduleEntity.sourceRoots.forEach {
33+
sourceRoots.add(Path.of(getRelativeUrl(it.url.presentableUrl)))
34+
}
35+
36+
val matches = matchPaths(Path.of(source), sourceRoots)
37+
38+
if (matches.isNotEmpty()) {
39+
results.put(moduleEntity, matches)
40+
}
41+
}
42+
43+
val moduleManager = ModuleManager.getInstance(project)
44+
val modules = results.filter { it.value.isNotEmpty() }
45+
.keys
46+
.map { m -> moduleManager.findModuleByName(m.name)!! }
47+
48+
return modules
49+
}
50+
51+
private fun matchPaths(source: Path, candidates: List<Path>): List<String> {
52+
var matchingItems = listOf<String>()
53+
54+
val sourceItems = splitPath(source)
55+
56+
for (candidate in candidates) {
57+
val canSplit = splitPath(candidate)
58+
59+
val min = min(sourceItems.size, canSplit.size)
60+
val matching = mutableListOf<String>()
61+
62+
for (i in 0.. min - 1) {
63+
if (sourceItems[i] != canSplit[i]) {
64+
break
65+
}
66+
67+
matching.add(sourceItems[i])
68+
}
69+
70+
if (matching.size > matchingItems.size) {
71+
matchingItems = matching
72+
}
73+
}
74+
75+
return matchingItems
76+
}
77+
78+
private fun splitPath(path: Path): List<String> {
79+
val result = mutableListOf<String>()
80+
path.forEach { result.add(it.name) }
81+
return result
82+
}
83+
84+
private fun getRelativeUrl(sourceUrl: String): String {
85+
return sourceUrl.substring(project.presentableUrl!!.length)
86+
}
87+
}

0 commit comments

Comments
 (0)