From 97283636bc3b8f6ddf8e00ee8c62bc1a5985dcd7 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 23 Sep 2023 18:51:02 -0700 Subject: [PATCH] idea: enhance symbol support --- .../pizza/pork/idea/PorkDeclarationSymbol.kt | 39 +++++++ .../pork/idea/PorkReferenceResolution.kt | 109 ++++++++++++++++++ .../pizza/pork/idea/PorkSymbolDeclaration.kt | 21 ++++ .../idea/PorkSymbolDeclarationProvider.kt | 19 +++ .../pizza/pork/idea/PorkSymbolReference.kt | 18 +++ .../pizza/pork/idea/psi/PorkElementHelpers.kt | 20 +++- .../pork/idea/psi/PorkIdentifierReference.kt | 73 +----------- .../pizza/pork/idea/psi/PorkReferencable.kt | 19 +++ .../gay/pizza/pork/idea/psi/PorkReference.kt | 32 +---- .../src/main/resources/META-INF/plugin.xml | 1 + 10 files changed, 245 insertions(+), 106 deletions(-) create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclaration.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclarationProvider.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolReference.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt new file mode 100644 index 0000000..758e481 --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt @@ -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 = Pointer { this } + override fun getNavigationTargets(project: Project): MutableCollection { + return PorkReferenceResolution.getAllProjectPorkFiles(project) + .flatMap { PorkReferenceResolution.findAnyDefinitions(it) } + .map { PorkNavigationTarget(it) } + .toMutableList() + } + + class PorkNavigationTarget(val internalPorkElement: PorkElement) : NavigationTarget { + override fun createPointer(): Pointer = Pointer { this } + + override fun computePresentation(): TargetPresentation = TargetPresentation + .builder(internalPorkElement.name!!) + .presentation() + + override fun navigationRequest(): NavigationRequest? { + return NavigationRequest.sourceNavigationRequest(internalPorkElement.containingFile, internalPorkElement.textRange) + } + } +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt new file mode 100644 index 0000000..8f389a6 --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt @@ -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 { + 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() + val importType = importDeclaration.childrenOfType().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 { + val porkVirtualFiles = FilenameIndex.getAllFilesByExt(project, "pork") + return porkVirtualFiles.mapNotNull { virtualFile -> + PsiManager.getInstance(project).findFile(virtualFile) + } + } + + fun findAllCandidates(internalPorkElement: PorkElement, name: String? = null): List = + listOf(findAnyLocals(internalPorkElement, name), findAnyDefinitions(internalPorkElement.containingFile, name)).flatten() + + fun findAnyLocals(internalPorkElement: PorkElement, name: String? = null): List { + val functionDefinitionElement = PsiTreeUtil.getParentOfType(internalPorkElement, FunctionDefinitionElement::class.java) + ?: return emptyList() + val locals = mutableListOf() + + 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().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() + 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 { + val foundDefinitions = mutableListOf() + for (file in getRelevantFiles(containingFile)) { + val definitions = PsiTreeUtil.collectElements(file) { element -> + element is FunctionDefinitionElement || + element is LetDefinitionElement + }.filterIsInstance() + if (name != null) { + val fileFoundDefinition = definitions.firstOrNull { + it.name == name + } + + if (fileFoundDefinition != null) { + foundDefinitions.add(fileFoundDefinition) + return foundDefinitions + } + } else { + foundDefinitions.addAll(definitions) + } + } + return foundDefinitions + } +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclaration.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclaration.kt new file mode 100644 index 0000000..731cc8a --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclaration.kt @@ -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") +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclarationProvider.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclarationProvider.kt new file mode 100644 index 0000000..3fe8e91 --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolDeclarationProvider.kt @@ -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 { + if (element !is PorkElement) return mutableListOf() + if (element is FunctionDefinitionElement || element is LetDefinitionElement) { + return mutableListOf(PorkSymbolDeclaration(element)) + } + return mutableListOf() + } +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolReference.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolReference.kt new file mode 100644 index 0000000..3b1b31a --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkSymbolReference.kt @@ -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 { + return findAllCandidates().mapNotNull { PorkElementHelpers.psiSymbolFor(it) }.toMutableList() + } +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt index 500cad5..895b051 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt @@ -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 + } } diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkIdentifierReference.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkIdentifierReference.kt index bb78afc..f6d171b 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkIdentifierReference.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkIdentifierReference.kt @@ -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 = - listOf(findAnyLocals(name), findAnyDefinitions(name)).flatten() - - fun findAnyLocals(name: String? = null): List { - val functionDefinitionElement = PsiTreeUtil.getParentOfType(element, FunctionDefinitionElement::class.java) - ?: return emptyList() - val locals = mutableListOf() - - 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().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() - 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 { - val foundDefinitions = mutableListOf() - for (file in getRelevantFiles()) { - val definitions = PsiTreeUtil.collectElements(file) { element -> - element is FunctionDefinitionElement || - element is LetDefinitionElement - }.filterIsInstance() - if (name != null) { - val fileFoundDefinition = definitions.firstOrNull { - it.name == name - } - - if (fileFoundDefinition != null) { - foundDefinitions.add(fileFoundDefinition) - return foundDefinitions - } - } else { - foundDefinitions.addAll(definitions) - } - } - return foundDefinitions - } } diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt new file mode 100644 index 0000000..48de33a --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt @@ -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 = PorkReferenceResolution.getRelevantFiles(internalPorkElement.containingFile) + fun findAllCandidates(name: String? = null): List = + listOf(findAnyLocals(name), findAnyDefinitions(name)).flatten() + + fun findAnyLocals(name: String? = null): List = + PorkReferenceResolution.findAnyLocals(internalPorkElement, name) + + fun findAnyDefinitions(name: String? = null): List = + PorkReferenceResolution.findAnyDefinitions(internalPorkElement.containingFile, name) +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReference.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReference.kt index 5936122..130b242 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReference.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReference.kt @@ -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(element, textRange) { - fun getRelevantFiles(): List { - 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() - val importType = importDeclaration.childrenOfType().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 { - 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(element, textRange), PorkReferencable { + override val internalPorkElement: PorkElement = element } diff --git a/support/pork-idea/src/main/resources/META-INF/plugin.xml b/support/pork-idea/src/main/resources/META-INF/plugin.xml index 36066ae..9885899 100644 --- a/support/pork-idea/src/main/resources/META-INF/plugin.xml +++ b/support/pork-idea/src/main/resources/META-INF/plugin.xml @@ -26,6 +26,7 @@ +