mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-02 12:50:55 +00:00
idea: enhance symbol support
This commit is contained in:
parent
87623505c0
commit
97283636bc
@ -0,0 +1,39 @@
|
||||
package gay.pizza.pork.idea
|
||||
|
||||
import com.intellij.model.Pointer
|
||||
import com.intellij.model.Symbol
|
||||
import com.intellij.navigation.ItemPresentation
|
||||
import com.intellij.navigation.NavigatableSymbol
|
||||
import com.intellij.navigation.PsiElementNavigationItem
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.backend.navigation.NavigationRequest
|
||||
import com.intellij.platform.backend.navigation.NavigationRequests
|
||||
import com.intellij.platform.backend.navigation.NavigationTarget
|
||||
import com.intellij.platform.backend.presentation.TargetPresentation
|
||||
import com.intellij.psi.PsiElement
|
||||
import gay.pizza.pork.idea.psi.PorkElementHelpers
|
||||
import gay.pizza.pork.idea.psi.PorkReferencable
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
data class PorkDeclarationSymbol(val module: String, val name: String) : Symbol, NavigatableSymbol {
|
||||
override fun createPointer(): Pointer<out Symbol> = Pointer { this }
|
||||
override fun getNavigationTargets(project: Project): MutableCollection<out NavigationTarget> {
|
||||
return PorkReferenceResolution.getAllProjectPorkFiles(project)
|
||||
.flatMap { PorkReferenceResolution.findAnyDefinitions(it) }
|
||||
.map { PorkNavigationTarget(it) }
|
||||
.toMutableList()
|
||||
}
|
||||
|
||||
class PorkNavigationTarget(val internalPorkElement: PorkElement) : NavigationTarget {
|
||||
override fun createPointer(): Pointer<out NavigationTarget> = Pointer { this }
|
||||
|
||||
override fun computePresentation(): TargetPresentation = TargetPresentation
|
||||
.builder(internalPorkElement.name!!)
|
||||
.presentation()
|
||||
|
||||
override fun navigationRequest(): NavigationRequest? {
|
||||
return NavigationRequest.sourceNavigationRequest(internalPorkElement.containingFile, internalPorkElement.textRange)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package gay.pizza.pork.idea
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.FilenameIndex
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.childrenOfType
|
||||
import gay.pizza.pork.idea.psi.gen.*
|
||||
|
||||
object PorkReferenceResolution {
|
||||
fun getRelevantFiles(containingFile: PsiFile): List<PsiFile> {
|
||||
if (containingFile.virtualFile == null) {
|
||||
return getAllProjectPorkFiles(containingFile.project)
|
||||
}
|
||||
val importDeclarationElements = PsiTreeUtil.collectElementsOfType(containingFile, ImportDeclarationElement::class.java)
|
||||
val files = mutableListOf(containingFile)
|
||||
for (importDeclaration in importDeclarationElements) {
|
||||
val symbolElements = importDeclaration.childrenOfType<SymbolElement>()
|
||||
val importType = importDeclaration.childrenOfType<SymbolElement>().first().text
|
||||
if (importType != "local") {
|
||||
continue
|
||||
}
|
||||
|
||||
val basicImportPath = symbolElements.drop(1).joinToString("/") { it.text.trim() }
|
||||
val actualImportPath = "../${basicImportPath}.pork"
|
||||
val virtualFile = containingFile.virtualFile?.findFileByRelativePath(actualImportPath) ?: continue
|
||||
val psiFile = PsiManager.getInstance(containingFile.project).findFile(virtualFile) ?: continue
|
||||
files.add(psiFile)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
fun getAllProjectPorkFiles(project: Project): List<PsiFile> {
|
||||
val porkVirtualFiles = FilenameIndex.getAllFilesByExt(project, "pork")
|
||||
return porkVirtualFiles.mapNotNull { virtualFile ->
|
||||
PsiManager.getInstance(project).findFile(virtualFile)
|
||||
}
|
||||
}
|
||||
|
||||
fun findAllCandidates(internalPorkElement: PorkElement, name: String? = null): List<PorkElement> =
|
||||
listOf(findAnyLocals(internalPorkElement, name), findAnyDefinitions(internalPorkElement.containingFile, name)).flatten()
|
||||
|
||||
fun findAnyLocals(internalPorkElement: PorkElement, name: String? = null): List<PorkElement> {
|
||||
val functionDefinitionElement = PsiTreeUtil.getParentOfType(internalPorkElement, FunctionDefinitionElement::class.java)
|
||||
?: return emptyList()
|
||||
val locals = mutableListOf<PorkElement>()
|
||||
|
||||
fun check(localCandidate: PsiElement, upward: Boolean) {
|
||||
if (localCandidate is BlockElement && !upward) {
|
||||
return
|
||||
}
|
||||
|
||||
if (localCandidate is ArgumentSpecElement ||
|
||||
localCandidate is LetAssignmentElement ||
|
||||
localCandidate is VarAssignmentElement) {
|
||||
locals.add(localCandidate as PorkElement)
|
||||
}
|
||||
|
||||
if (localCandidate is ForInElement) {
|
||||
val forInItem = localCandidate.childrenOfType<ForInItemElement>().firstOrNull()
|
||||
if (forInItem != null) {
|
||||
locals.add(forInItem)
|
||||
}
|
||||
}
|
||||
|
||||
localCandidate.children.forEach { check(it, false) }
|
||||
}
|
||||
|
||||
PsiTreeUtil.treeWalkUp(internalPorkElement, functionDefinitionElement) { _, localCandidate ->
|
||||
if (localCandidate != null) {
|
||||
if (internalPorkElement == functionDefinitionElement) {
|
||||
return@treeWalkUp true
|
||||
}
|
||||
check(localCandidate, true)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val argumentSpecElements = functionDefinitionElement.childrenOfType<ArgumentSpecElement>()
|
||||
locals.addAll(argumentSpecElements)
|
||||
val finalLocals = locals.distinctBy { it.textRange }
|
||||
return finalLocals.filter { if (name != null) it.name == name else true }
|
||||
}
|
||||
|
||||
fun findAnyDefinitions(containingFile: PsiFile, name: String? = null): List<PorkElement> {
|
||||
val foundDefinitions = mutableListOf<PorkNamedElement>()
|
||||
for (file in getRelevantFiles(containingFile)) {
|
||||
val definitions = PsiTreeUtil.collectElements(file) { element ->
|
||||
element is FunctionDefinitionElement ||
|
||||
element is LetDefinitionElement
|
||||
}.filterIsInstance<PorkNamedElement>()
|
||||
if (name != null) {
|
||||
val fileFoundDefinition = definitions.firstOrNull {
|
||||
it.name == name
|
||||
}
|
||||
|
||||
if (fileFoundDefinition != null) {
|
||||
foundDefinitions.add(fileFoundDefinition)
|
||||
return foundDefinitions
|
||||
}
|
||||
} else {
|
||||
foundDefinitions.addAll(definitions)
|
||||
}
|
||||
}
|
||||
return foundDefinitions
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package gay.pizza.pork.idea
|
||||
|
||||
import com.intellij.model.Symbol
|
||||
import com.intellij.model.psi.PsiSymbolDeclaration
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import gay.pizza.pork.idea.psi.PorkElementHelpers
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class PorkSymbolDeclaration(val element: PorkElement) : PsiSymbolDeclaration {
|
||||
override fun getDeclaringElement(): PsiElement = element
|
||||
override fun getRangeInDeclaringElement(): TextRange {
|
||||
val textRangeOfSymbol = PorkElementHelpers.symbolElementOf(element)?.psi?.textRangeInParent
|
||||
?: throw RuntimeException("Unable to get symbol of element: $element")
|
||||
return textRangeOfSymbol
|
||||
}
|
||||
|
||||
override fun getSymbol(): Symbol = PorkElementHelpers.psiSymbolFor(element) ?:
|
||||
throw RuntimeException("Unable to get symbol of element: $element")
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package gay.pizza.pork.idea
|
||||
|
||||
import com.intellij.model.psi.PsiSymbolDeclaration
|
||||
import com.intellij.model.psi.PsiSymbolDeclarationProvider
|
||||
import com.intellij.psi.PsiElement
|
||||
import gay.pizza.pork.idea.psi.gen.FunctionDefinitionElement
|
||||
import gay.pizza.pork.idea.psi.gen.LetDefinitionElement
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class PorkSymbolDeclarationProvider : PsiSymbolDeclarationProvider {
|
||||
override fun getDeclarations(element: PsiElement, offsetInElement: Int): MutableCollection<out PsiSymbolDeclaration> {
|
||||
if (element !is PorkElement) return mutableListOf()
|
||||
if (element is FunctionDefinitionElement || element is LetDefinitionElement) {
|
||||
return mutableListOf(PorkSymbolDeclaration(element))
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package gay.pizza.pork.idea
|
||||
|
||||
import com.intellij.model.Symbol
|
||||
import com.intellij.model.psi.PsiSymbolReference
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import gay.pizza.pork.idea.psi.PorkElementHelpers
|
||||
import gay.pizza.pork.idea.psi.PorkReferencable
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class PorkSymbolReference(override val internalPorkElement: PorkElement, val range: TextRange) : PsiSymbolReference, PorkReferencable {
|
||||
override fun getElement(): PsiElement = internalPorkElement
|
||||
override fun getRangeInElement(): TextRange = range
|
||||
override fun resolveReference(): MutableCollection<out Symbol> {
|
||||
return findAllCandidates().mapNotNull { PorkElementHelpers.psiSymbolFor(it) }.toMutableList()
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package gay.pizza.pork.idea.psi
|
||||
|
||||
import com.intellij.lang.ASTNode
|
||||
import com.intellij.model.Symbol
|
||||
import com.intellij.navigation.ItemPresentation
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFileFactory
|
||||
@ -11,28 +12,30 @@ import com.intellij.psi.util.childrenOfType
|
||||
import com.intellij.util.PlatformIcons
|
||||
import gay.pizza.pork.ast.NodeType
|
||||
import gay.pizza.pork.common.unused
|
||||
import gay.pizza.pork.idea.PorkDeclarationSymbol
|
||||
import gay.pizza.pork.idea.PorkElementTypes
|
||||
import gay.pizza.pork.idea.PorkLanguage
|
||||
import gay.pizza.pork.idea.psi.gen.*
|
||||
import javax.swing.Icon
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
object PorkElementHelpers {
|
||||
private val symbolElementType = PorkElementTypes.elementTypeFor(NodeType.Symbol)
|
||||
|
||||
fun nameOfNamedElement(element: PorkNamedElement): String? {
|
||||
val child = symbolOf(element) ?: return null
|
||||
val child = symbolElementOf(element) ?: return null
|
||||
return child.text
|
||||
}
|
||||
|
||||
fun setNameOfNamedElement(element: PorkNamedElement, name: String): PsiElement {
|
||||
val child = symbolOf(element) ?: return element
|
||||
val child = symbolElementOf(element) ?: return element
|
||||
val factory = PsiFileFactory.getInstance(element.project) as PsiFileFactoryImpl
|
||||
val created = factory.createElementFromText(name, PorkLanguage, child.elementType, element.context) as PorkElement
|
||||
element.node.replaceChild(child, created.node)
|
||||
return element
|
||||
}
|
||||
|
||||
fun symbolOf(element: PorkElement): ASTNode? {
|
||||
fun symbolElementOf(element: PorkElement): ASTNode? {
|
||||
var child = element.node.findChildByType(symbolElementType)
|
||||
if (child == null) {
|
||||
child = PsiTreeUtil.collectElementsOfType(element, SymbolElement::class.java).firstOrNull()?.node
|
||||
@ -41,7 +44,7 @@ object PorkElementHelpers {
|
||||
}
|
||||
|
||||
fun nameIdentifierOfNamedElement(element: PorkNamedElement): PsiElement? {
|
||||
return symbolOf(element)?.psi
|
||||
return symbolElementOf(element)?.psi
|
||||
}
|
||||
|
||||
fun referenceOfElement(element: PorkElement, type: NodeType): PsiReference? {
|
||||
@ -73,4 +76,13 @@ object PorkElementHelpers {
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun psiSymbolFor(element: PorkElement): Symbol? {
|
||||
val symbolElement = symbolElementOf(element) ?: return null
|
||||
val module = element.containingFile?.virtualFile?.path ?: element.containingFile?.name ?: return null
|
||||
if (element is FunctionDefinitionElement || element is LetDefinitionElement) {
|
||||
return PorkDeclarationSymbol(module, symbolElement.text)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
package gay.pizza.pork.idea.psi
|
||||
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.childrenOfType
|
||||
import gay.pizza.pork.idea.psi.gen.*
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
|
||||
class PorkIdentifierReference(element: PorkElement, textRange: TextRange) : PorkReference(element, textRange) {
|
||||
override fun resolve(): PorkElement? {
|
||||
@ -20,72 +17,4 @@ class PorkIdentifierReference(element: PorkElement, textRange: TextRange) : Pork
|
||||
val candidates = findAllCandidates()
|
||||
return candidates.toTypedArray()
|
||||
}
|
||||
|
||||
fun findAllCandidates(name: String? = null): List<PorkElement> =
|
||||
listOf(findAnyLocals(name), findAnyDefinitions(name)).flatten()
|
||||
|
||||
fun findAnyLocals(name: String? = null): List<PorkElement> {
|
||||
val functionDefinitionElement = PsiTreeUtil.getParentOfType(element, FunctionDefinitionElement::class.java)
|
||||
?: return emptyList()
|
||||
val locals = mutableListOf<PorkElement>()
|
||||
|
||||
fun check(localCandidate: PsiElement, upward: Boolean) {
|
||||
if (localCandidate is BlockElement && !upward) {
|
||||
return
|
||||
}
|
||||
|
||||
if (localCandidate is ArgumentSpecElement ||
|
||||
localCandidate is LetAssignmentElement ||
|
||||
localCandidate is VarAssignmentElement) {
|
||||
locals.add(localCandidate as PorkElement)
|
||||
}
|
||||
|
||||
if (localCandidate is ForInElement) {
|
||||
val forInItem = localCandidate.childrenOfType<ForInItemElement>().firstOrNull()
|
||||
if (forInItem != null) {
|
||||
locals.add(forInItem)
|
||||
}
|
||||
}
|
||||
|
||||
localCandidate.children.forEach { check(it, false) }
|
||||
}
|
||||
|
||||
PsiTreeUtil.treeWalkUp(element, functionDefinitionElement) { _, localCandidate ->
|
||||
if (localCandidate != null) {
|
||||
if (element == functionDefinitionElement) {
|
||||
return@treeWalkUp true
|
||||
}
|
||||
check(localCandidate, true)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val argumentSpecElements = functionDefinitionElement.childrenOfType<ArgumentSpecElement>()
|
||||
locals.addAll(argumentSpecElements)
|
||||
val finalLocals = locals.distinctBy { it.textRange }
|
||||
return finalLocals.filter { if (name != null) it.name == name else true }
|
||||
}
|
||||
|
||||
fun findAnyDefinitions(name: String? = null): List<PorkElement> {
|
||||
val foundDefinitions = mutableListOf<PorkNamedElement>()
|
||||
for (file in getRelevantFiles()) {
|
||||
val definitions = PsiTreeUtil.collectElements(file) { element ->
|
||||
element is FunctionDefinitionElement ||
|
||||
element is LetDefinitionElement
|
||||
}.filterIsInstance<PorkNamedElement>()
|
||||
if (name != null) {
|
||||
val fileFoundDefinition = definitions.firstOrNull {
|
||||
it.name == name
|
||||
}
|
||||
|
||||
if (fileFoundDefinition != null) {
|
||||
foundDefinitions.add(fileFoundDefinition)
|
||||
return foundDefinitions
|
||||
}
|
||||
} else {
|
||||
foundDefinitions.addAll(definitions)
|
||||
}
|
||||
}
|
||||
return foundDefinitions
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package gay.pizza.pork.idea.psi
|
||||
|
||||
import com.intellij.psi.PsiFile
|
||||
import gay.pizza.pork.idea.PorkReferenceResolution
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
|
||||
interface PorkReferencable {
|
||||
val internalPorkElement: PorkElement
|
||||
|
||||
fun getRelevantFiles(): List<PsiFile> = PorkReferenceResolution.getRelevantFiles(internalPorkElement.containingFile)
|
||||
fun findAllCandidates(name: String? = null): List<PorkElement> =
|
||||
listOf(findAnyLocals(name), findAnyDefinitions(name)).flatten()
|
||||
|
||||
fun findAnyLocals(name: String? = null): List<PorkElement> =
|
||||
PorkReferenceResolution.findAnyLocals(internalPorkElement, name)
|
||||
|
||||
fun findAnyDefinitions(name: String? = null): List<PorkElement> =
|
||||
PorkReferenceResolution.findAnyDefinitions(internalPorkElement.containingFile, name)
|
||||
}
|
@ -12,34 +12,6 @@ import gay.pizza.pork.idea.psi.gen.ImportDeclarationElement
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
import gay.pizza.pork.idea.psi.gen.SymbolElement
|
||||
|
||||
abstract class PorkReference(element: PorkElement, textRange: TextRange) : PsiReferenceBase<PsiElement>(element, textRange) {
|
||||
fun getRelevantFiles(): List<PsiFile> {
|
||||
val containingFile = element.containingFile ?: return emptyList()
|
||||
if (containingFile.virtualFile == null) {
|
||||
return getAllProjectPorkFiles()
|
||||
}
|
||||
val importDeclarationElements = PsiTreeUtil.collectElementsOfType(containingFile, ImportDeclarationElement::class.java)
|
||||
val files = mutableListOf(containingFile)
|
||||
for (importDeclaration in importDeclarationElements) {
|
||||
val symbolElements = importDeclaration.childrenOfType<SymbolElement>()
|
||||
val importType = importDeclaration.childrenOfType<SymbolElement>().first().text
|
||||
if (importType != "local") {
|
||||
continue
|
||||
}
|
||||
|
||||
val basicImportPath = symbolElements.drop(1).joinToString("/") { it.text.trim() }
|
||||
val actualImportPath = "../${basicImportPath}.pork"
|
||||
val virtualFile = containingFile.virtualFile?.findFileByRelativePath(actualImportPath) ?: continue
|
||||
val psiFile = PsiManager.getInstance(element.project).findFile(virtualFile) ?: continue
|
||||
files.add(psiFile)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
fun getAllProjectPorkFiles(): List<PsiFile> {
|
||||
val porkVirtualFiles = FilenameIndex.getAllFilesByExt(element.project, "pork")
|
||||
return porkVirtualFiles.mapNotNull { virtualFile ->
|
||||
PsiManager.getInstance(element.project).findFile(virtualFile)
|
||||
}
|
||||
}
|
||||
abstract class PorkReference(element: PorkElement, textRange: TextRange) : PsiReferenceBase<PsiElement>(element, textRange), PorkReferencable {
|
||||
override val internalPorkElement: PorkElement = element
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
<lang.elementManipulator
|
||||
implementationClass="gay.pizza.pork.idea.PorkElementManipulator"
|
||||
forClass="gay.pizza.pork.idea.psi.gen.PorkElement"/>
|
||||
<psi.declarationProvider implementation="gay.pizza.pork.idea.PorkSymbolDeclarationProvider"/>
|
||||
</extensions>
|
||||
|
||||
<applicationListeners>
|
||||
|
Loading…
Reference in New Issue
Block a user