Skip to content

Commit

Permalink
idea: significant enhancements to ide experience
Browse files Browse the repository at this point in the history
  • Loading branch information
azenla committed Sep 23, 2023
1 parent 83c2484 commit c340cfb
Show file tree
Hide file tree
Showing 46 changed files with 450 additions and 131 deletions.
1 change: 1 addition & 0 deletions ast/src/main/ast/pork.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ types:
parent: Expression
referencedElementValue: symbol
referencedElementType: Node
namedElementValue: symbol
values:
- name: symbol
type: Symbol
Expand Down
2 changes: 1 addition & 1 deletion ast/src/main/kotlin/gay/pizza/pork/ast/Node.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast

import kotlinx.serialization.Transient
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient

@Serializable
@SerialName("node")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ class AstPorkIdeaCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
),
inherits = mutableListOf("${baseType}(node)"),
imports = mutableListOf(
"com.intellij.lang.ASTNode"
"com.intellij.lang.ASTNode",
"gay.pizza.pork.idea.psi.PorkElementHelpers",
"javax.swing.Icon",
"com.intellij.navigation.ItemPresentation"
)
)

if (baseType == "PorkNamedElement") {
kotlinClass.imports.add(0, "com.intellij.psi.PsiElement")
kotlinClass.imports.add("gay.pizza.pork.idea.psi.PorkElementHelpers")
val getNameFunction = KotlinFunction(
"getName",
overridden = true,
Expand Down Expand Up @@ -111,7 +113,6 @@ class AstPorkIdeaCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
if (type.referencedElementValue != null && type.referencedElementType != null) {
kotlinClass.imports.add(0, "com.intellij.psi.PsiReference")
kotlinClass.imports.add("gay.pizza.pork.ast.NodeType")
kotlinClass.imports.add("gay.pizza.pork.idea.psi.PorkElementHelpers")

val getReferenceFunction = KotlinFunction(
"getReference",
Expand All @@ -123,6 +124,27 @@ class AstPorkIdeaCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
kotlinClass.functions.add(getReferenceFunction)
}

val getIconFunction = KotlinFunction(
"getIcon",
overridden = true,
returnType = "Icon?",
parameters = mutableListOf(
KotlinParameter("flags", "Int")
),
isImmediateExpression = true
)
getIconFunction.body.add("PorkElementHelpers.iconOf(this)")
kotlinClass.functions.add(getIconFunction)

val getPresentationFunction = KotlinFunction(
"getPresentation",
overridden = true,
returnType = "ItemPresentation?",
isImmediateExpression = true
)
getPresentationFunction.body.add("PorkElementHelpers.presentationOf(this)")
kotlinClass.functions.add(getPresentationFunction)

write("${type.name}Element.kt", KotlinWriter(kotlinClass))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ class KotlinWriter() {
appendLine("package $pkg")
appendLine()

for (import in imports) {
for (import in imports.toSortedSet()) {
appendLine("import $import")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gay.pizza.pork.idea

import com.intellij.openapi.util.TextRange
import com.intellij.psi.AbstractElementManipulator
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.util.elementType
import gay.pizza.pork.idea.psi.gen.PorkElement

class PorkElementManipulator : AbstractElementManipulator<PorkElement>() {
override fun handleContentChange(element: PorkElement, range: TextRange, newContent: String): PorkElement? {
val sourceText = element.text
val beforeText = sourceText.substring(0, range.startOffset)
val afterText = sourceText.substring(range.endOffset)
val changedText = beforeText + newContent + afterText
return element.replace(produce(element, changedText)) as PorkElement?
}

fun produce(element: PorkElement, changed: String): PorkElement {
val factory = PsiFileFactory.getInstance(element.project) as PsiFileFactoryImpl
return factory.createElementFromText(changed, PorkLanguage, element.elementType!!, element.context) as PorkElement
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ package gay.pizza.pork.idea
import com.intellij.lang.ASTNode
import com.intellij.lang.PsiBuilder
import com.intellij.lang.PsiParser
import com.intellij.psi.impl.PsiElementBase
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.IFileElementType
import gay.pizza.pork.ast.NodeType
import gay.pizza.pork.ast.parse
import gay.pizza.pork.parser.Parser

class PorkParser : PsiParser {
override fun parse(root: IElementType, builder: PsiBuilder): ASTNode {
val nodeTypeForParse = if (root is IFileElementType) {
NodeType.CompilationUnit
} else {
PorkElementTypes.nodeTypeFor(root) ?:
throw RuntimeException("Unable to parse element type: $root")
}
val marker = builder.mark()
val psiBuilderMarkAttribution = PsiBuilderMarkAttribution(builder)
val source = PsiBuilderTokenSource(builder)
val parser = Parser(source, psiBuilderMarkAttribution)
try {
parser.parseCompilationUnit()
parser.parse(nodeTypeForParse)
} catch (_: ExitParser) {}
marker.done(root)
return builder.treeBuilt
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ import gay.pizza.pork.ast.Node
import gay.pizza.pork.ast.NodeType
import gay.pizza.pork.parser.ParseError
import gay.pizza.pork.parser.ParserNodeAttribution
import java.util.IdentityHashMap

class PsiBuilderMarkAttribution(val builder: PsiBuilder) : ParserNodeAttribution() {
private val map = IdentityHashMap<Node, Node>()

override fun <T : Node> guarded(type: NodeType?, block: () -> T): T {
val marker = builder.mark()
val result = try {
Expand All @@ -32,10 +29,6 @@ class PsiBuilderMarkAttribution(val builder: PsiBuilder) : ParserNodeAttribution
marker.done(PorkElementTypes.FailedToParse)
throw e
}
if (map[result] != null) {
marker.drop()
}
map[result] = result
return result
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,74 @@
package gay.pizza.pork.idea.psi

import com.intellij.lang.ASTNode
import com.intellij.navigation.ItemPresentation
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiReference
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.childrenOfType
import com.intellij.util.PlatformIcons
import gay.pizza.pork.ast.NodeType
import gay.pizza.pork.idea.PorkElementTypes
import gay.pizza.pork.idea.psi.gen.PorkElement
import gay.pizza.pork.idea.psi.gen.PorkNamedElement
import gay.pizza.pork.idea.psi.gen.SymbolElement
import gay.pizza.pork.idea.PorkLanguage
import gay.pizza.pork.idea.psi.gen.*
import javax.swing.Icon

object PorkElementHelpers {
private val symbolElementType = PorkElementTypes.elementTypeFor(NodeType.Symbol)

fun nameOfNamedElement(element: PorkNamedElement): String? {
val child = element.node.findChildByType(symbolElementType)
return child?.text
val child = symbolOf(element) ?: return null
return child.text
}

fun setNameOfNamedElement(element: PorkNamedElement, name: String): PsiElement = element
fun setNameOfNamedElement(element: PorkNamedElement, name: String): PsiElement {
val child = symbolOf(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? {
var child = element.node.findChildByType(symbolElementType)
if (child == null) {
child = PsiTreeUtil.collectElementsOfType(element, SymbolElement::class.java).firstOrNull()?.node
}
return child
}

fun nameIdentifierOfNamedElement(element: PorkNamedElement): PsiElement? {
val child = element.node.findChildByType(symbolElementType)
return child?.psi
return symbolOf(element)?.psi
}

fun referenceOfElement(element: PorkElement, type: NodeType): PsiReference? {
val textRangeOfSymbolInElement = element.childrenOfType<SymbolElement>().firstOrNull()?.textRangeInParent ?: return null
return PorkIdentifierReference(element, textRangeOfSymbolInElement)
}

fun iconOf(element: PorkElement): Icon? {
return when (element) {
is LetDefinitionElement -> PlatformIcons.FIELD_ICON
is FunctionDefinitionElement -> PlatformIcons.FUNCTION_ICON
is LetAssignmentElement -> PlatformIcons.VARIABLE_READ_ACCESS
is VarAssignmentElement -> PlatformIcons.VARIABLE_RW_ACCESS
is ArgumentSpecElement -> PlatformIcons.VARIABLE_READ_ACCESS
else -> null
}
}

fun presentationOf(element: PorkElement): ItemPresentation? {
val icon = iconOf(element)
if (element is FunctionDefinitionElement || element is LetDefinitionElement) {
return PorkPresentable(element.name, icon, element.containingFile.virtualFile?.name)
}

if (element is LetAssignmentElement || element is VarAssignmentElement) {
return PorkPresentable(element.name, icon)
}

return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,53 @@ class PorkIdentifierReference(element: PorkElement, textRange: TextRange) : Pork
}

override fun getVariants(): Array<Any> {
return findAllCandidates().toTypedArray()
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)
if (functionDefinitionElement == null) {
return emptyList()
}
?: return emptyList()
val locals = mutableListOf<PorkElement>()

fun check(localCandidate: PsiElement, upward: Boolean) {
if (localCandidate is BlockElement && !upward) {
return
}

if (localCandidate == element) {
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>().first()
locals.add(forInItem)
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)
return locals.filter { it.name == name }
val finalLocals = locals.distinctBy { it.textRange }
return finalLocals.filter { if (name != null) it.name == name else true }
}

fun findAnyDefinitions(name: String? = null): List<PorkElement> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gay.pizza.pork.idea.psi

import com.intellij.navigation.ItemPresentation
import javax.swing.Icon

class PorkPresentable(val porkText: String?, val porkIcon: Icon? = null, val porkLocation: String? = null) : ItemPresentation {
override fun getPresentableText(): String? = porkText
override fun getIcon(unused: Boolean): Icon? = porkIcon
override fun getLocationString(): String? = porkLocation
}
Loading

0 comments on commit c340cfb

Please sign in to comment.