New: Files in 'include' directives can be referenced and renamed/refactored.

This commit is contained in:
Chris Hodges 2021-08-06 20:03:47 +02:00
parent 8d7977927f
commit e0cdfef42b
14 changed files with 176 additions and 10 deletions

View File

@ -148,6 +148,7 @@ make it work with JUnit 5. Feel free to use the code (in package ```de.platon42.
- Enhancement: `opt` and several other directives (`printt`, `fail` etc.) no longer causes a syntax error when unquoted.
- Enhancement: `include`, `incdir` and `incbin` and `output` with `<pathname>` quotes no longer cause syntax error.
- New: Files in `include` directives can be referenced and renamed/refactored.
### V0.5 (06-Aug-21)

View File

@ -60,7 +60,8 @@ patchPluginXml {
<h4>V0.6 (unreleased)</h4>
<ul>
<li>Enhancement: 'opt' and several other directives ('printt', 'fail' etc.) no longer causes a syntax error when unquoted.
<li<Enhancement: 'include', 'incdir' and 'incbin' and 'output' with '&lt;pathname&gt;' quotes no longer cause syntax error.
<li>Enhancement: 'include', 'incdir' and 'incbin' and 'output' with '&lt;pathname&gt;' quotes no longer cause syntax error.
<li>New: Files in 'include' directives can be referenced and renamed/refactored.
</ul>
<h4>V0.5 (06-Aug-21)</h4>
<ul>

View File

@ -817,8 +817,7 @@ public class M68kParser implements PsiParser, LightPsiParser {
}
/* ********************************************************** */
// Label? PreprocessorKeyword
// PreprocessorOperands?
// Label? PreprocessorKeyword PreprocessorOperands?
public static boolean PreprocessorDirective(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "PreprocessorDirective")) return false;
boolean r;

View File

@ -1,7 +1,6 @@
// This is a generated file. Not intended for manual editing.
package de.platon42.intellij.plugins.m68k.psi.impl;
import com.intellij.extapi.psi.ASTWrapperPsiElement;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
@ -11,7 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
public class M68kPreprocessorDirectiveImpl extends ASTWrapperPsiElement implements M68kPreprocessorDirective {
public class M68kPreprocessorDirectiveImpl extends M68kPreprocessorDirectiveMixin implements M68kPreprocessorDirective {
public M68kPreprocessorDirectiveImpl(@NotNull ASTNode node) {
super(node);

View File

@ -175,8 +175,9 @@ AsmOp ::= MNEMONIC OperandSize? {
PreprocessorKeyword ::= (DATA_DIRECTIVE | OTHER_DIRECTIVE)
PreprocessorDirective ::= Label? PreprocessorKeyword
PreprocessorOperands?
PreprocessorDirective ::= Label? PreprocessorKeyword PreprocessorOperands? {
mixin = "de.platon42.intellij.plugins.m68k.psi.M68kPreprocessorDirectiveMixin"
}
MacroPlainLine ::= MACRO_LINE
MacroNameDefinition ::= MACRO_NAME

View File

@ -37,6 +37,11 @@ object M68kElementFactory {
return PsiTreeUtil.findChildOfType(file, M68kMacroCall::class.java)!!
}
fun createIncludeStatement(project: Project, path: String): M68kPreprocessorDirective {
val file = createFile(project, " include \"$path\"\n")
return PsiTreeUtil.findChildOfType(file, M68kPreprocessorDirective::class.java)!!
}
fun createFile(project: Project, content: String): M68kFile {
return PsiFileFactory.getInstance(project).createFileFromText("dummy.m68k", INSTANCE, content) as M68kFile
}

View File

@ -0,0 +1,13 @@
package de.platon42.intellij.plugins.m68k.psi
import com.intellij.extapi.psi.ASTWrapperPsiElement
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiReference
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry
abstract class M68kPreprocessorDirectiveMixin(node: ASTNode) : ASTWrapperPsiElement(node), M68kPreprocessorDirective {
override fun getReferences(): Array<PsiReference> {
return ReferenceProvidersRegistry.getReferencesFromProviders(this)
}
}

View File

@ -0,0 +1,46 @@
package de.platon42.intellij.plugins.m68k.refs
import com.intellij.psi.*
import com.intellij.psi.impl.source.resolve.ResolveCache
import com.intellij.psi.search.FileTypeIndex
import com.intellij.psi.search.GlobalSearchScope
import de.platon42.intellij.plugins.m68k.M68kFileType
import de.platon42.intellij.plugins.m68k.lexer.LexerUtil
import de.platon42.intellij.plugins.m68k.psi.M68kPreprocessorDirective
class M68kIncludeFileReference(element: M68kPreprocessorDirective) : PsiPolyVariantReferenceBase<M68kPreprocessorDirective>(element, true) {
companion object {
val INSTANCE = Resolver()
}
class Resolver : ResolveCache.PolyVariantResolver<M68kIncludeFileReference> {
override fun resolve(ref: M68kIncludeFileReference, incompleteCode: Boolean): Array<ResolveResult> {
val project = ref.element.project
val allFiles = FileTypeIndex.getFiles(M68kFileType.INSTANCE, GlobalSearchScope.allScope(project))
val pathName = LexerUtil.unquoteString(ref.element.exprList.first().text).replace('\\', '/')
val fileMatched = ((allFiles.firstOrNull { it.path.equals(pathName, ignoreCase = true) }
?: allFiles.firstOrNull { it.path.endsWith(pathName.removePrefix("../"), ignoreCase = true) })
?: allFiles.firstOrNull { it.path.endsWith(pathName.substringAfterLast('/'), ignoreCase = true) }) ?: return emptyArray()
return PsiElementResolveResult.createResults(PsiManager.getInstance(project).findFile(fileMatched))
}
}
override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> =
ResolveCache.getInstance(element.project)
.resolveWithCaching(this, INSTANCE, false, incompleteCode)
override fun resolve(): PsiElement? = multiResolve(false).singleOrNull()?.element
override fun getVariants(): Array<Any> = emptyArray()
// override fun getRangeInElement(): TextRange {
// val unquoted = LexerUtil.unquoteString(element.text)
//
// val path = element.exprList.first()
// return TextRange.from(
// path.textRange.startOffset + path.text.indexOf(unquoted), unquoted.length
// )
// }
}

View File

@ -0,0 +1,34 @@
package de.platon42.intellij.plugins.m68k.refs
import com.intellij.history.core.Paths
import com.intellij.openapi.util.TextRange
import com.intellij.psi.AbstractElementManipulator
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.IncorrectOperationException
import de.platon42.intellij.plugins.m68k.lexer.LexerUtil
import de.platon42.intellij.plugins.m68k.psi.M68kElementFactory
import de.platon42.intellij.plugins.m68k.psi.M68kLiteralExpr
import de.platon42.intellij.plugins.m68k.psi.M68kPreprocessorDirective
class M68kPreprocessorDirectiveElementManipulator : AbstractElementManipulator<M68kPreprocessorDirective>() {
@Throws(IncorrectOperationException::class)
override fun handleContentChange(element: M68kPreprocessorDirective, range: TextRange, newContent: String): M68kPreprocessorDirective {
val oldPath = LexerUtil.unquoteString(element.exprList.first().text)
.replace('\\', '/')
.substringBeforeLast('/', missingDelimiterValue = "")
val newIncludeStatement = M68kElementFactory.createIncludeStatement(element.project, Paths.appended(oldPath, newContent))
PsiTreeUtil.findChildOfType(element, M68kLiteralExpr::class.java)!!.replace(newIncludeStatement.exprList.first())
return element
}
override fun getRangeInElement(element: M68kPreprocessorDirective): TextRange {
val pathExpr = element.exprList.first()
val unquotedPath = LexerUtil.unquoteString(pathExpr.text)
return if (unquotedPath.length < pathExpr.textLength) {
TextRange.from(pathExpr.startOffsetInParent + 1, unquotedPath.length)
} else {
pathExpr.textRangeInParent
}
}
}

View File

@ -3,7 +3,9 @@ package de.platon42.intellij.plugins.m68k.refs
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.*
import com.intellij.util.ProcessingContext
import de.platon42.intellij.plugins.m68k.psi.M68kLiteralExpr
import de.platon42.intellij.plugins.m68k.psi.M68kMacroCall
import de.platon42.intellij.plugins.m68k.psi.M68kPreprocessorDirective
import de.platon42.intellij.plugins.m68k.psi.M68kSymbolReference
class M68kReferenceContributor : PsiReferenceContributor() {
@ -12,12 +14,14 @@ class M68kReferenceContributor : PsiReferenceContributor() {
val localLabelReferenceProvider = LocalLabelReferenceProvider()
val globalLabelReferenceProvider = GlobalLabelSymbolReferenceProvider()
val macroReferenceProvider = MacroReferenceProvider()
val includeFileReferenceProvider = IncludeFileReferenceProvider()
}
override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
registrar.registerReferenceProvider(PlatformPatterns.psiElement(M68kSymbolReference::class.java), localLabelReferenceProvider)
registrar.registerReferenceProvider(PlatformPatterns.psiElement(M68kSymbolReference::class.java), globalLabelReferenceProvider)
registrar.registerReferenceProvider(PlatformPatterns.psiElement(M68kMacroCall::class.java), macroReferenceProvider)
registrar.registerReferenceProvider(PlatformPatterns.psiElement(M68kPreprocessorDirective::class.java), includeFileReferenceProvider)
}
class LocalLabelReferenceProvider : PsiReferenceProvider() {
@ -40,4 +44,16 @@ class M68kReferenceContributor : PsiReferenceContributor() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> =
arrayOf(M68kMacroReference(element as M68kMacroCall))
}
class IncludeFileReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
val directive = element as M68kPreprocessorDirective
if (directive.exprList.firstOrNull() !is M68kLiteralExpr) return emptyArray()
return if (directive.preprocessorKeyword.text.equals("include", ignoreCase = true)) {
arrayOf(M68kIncludeFileReference(directive))
} else {
emptyArray()
}
}
}
}

View File

@ -1,5 +1,6 @@
package de.platon42.intellij.plugins.m68k.scanner
import com.intellij.lang.HelpID
import com.intellij.lang.cacheBuilder.DefaultWordsScanner
import com.intellij.lang.cacheBuilder.WordsScanner
import com.intellij.lang.findUsages.FindUsagesProvider
@ -22,14 +23,19 @@ class M68kFindUsagesProvider : FindUsagesProvider {
TokenSet.EMPTY
)
override fun canFindUsagesFor(psiElement: PsiElement): Boolean = psiElement is M68kNamedElement
override fun canFindUsagesFor(psiElement: PsiElement): Boolean =
when (psiElement) {
is M68kNamedElement, is M68kSymbolReference, is M68kMacroCall, is M68kFile, is M68kRegister -> true
else -> false
}
override fun getHelpId(psiElement: PsiElement): @NonNls String? = null
override fun getHelpId(psiElement: PsiElement): @NonNls String = HelpID.FIND_OTHER_USAGES
override fun getType(element: PsiElement): @Nls String {
return when (element) {
is M68kGlobalLabel -> "global label"
is M68kLocalLabel -> "local label"
is M68kPreprocessorDirective -> "preprocessor directive"
is M68kSymbolDefinition -> "symbol definition"
is M68kSymbolReference -> "symbol reference"
is M68kMacroDefinition -> "macro definition"
@ -44,6 +50,7 @@ class M68kFindUsagesProvider : FindUsagesProvider {
return when (element) {
is M68kGlobalLabel -> element.name!!
is M68kLocalLabel -> element.name!!
is M68kPreprocessorDirective -> element.text
is M68kSymbolDefinition -> element.parent.text
is M68kSymbolReference -> element.symbolName
is M68kMacroDefinition -> element.macroNameDefinition.text

View File

@ -45,7 +45,7 @@ class M68kIncludeFileProvider : FileIncludeProvider() {
}
.mapNotNull {
val pathNode = LightTreeUtil.firstChildOfType(tree, it.second, M68kTypes.LITERAL_EXPR) ?: return@mapNotNull null
val path = LexerUtil.unquoteString(LightTreeUtil.toFilteredString(tree, pathNode, null)).replace("\\", "/")
val path = LexerUtil.unquoteString(LightTreeUtil.toFilteredString(tree, pathNode, null)).replace('\\', '/')
if (it.first.equals("include", true)) {
FileIncludeInfo(path)
} else {

View File

@ -33,6 +33,8 @@
implementationClass="de.platon42.intellij.plugins.m68k.refs.M68kSymbolReferenceElementManipulator"/>
<lang.elementManipulator forClass="de.platon42.intellij.plugins.m68k.psi.M68kMacroCall"
implementationClass="de.platon42.intellij.plugins.m68k.refs.M68kMacroCallElementManipulator"/>
<lang.elementManipulator forClass="de.platon42.intellij.plugins.m68k.psi.M68kPreprocessorDirective"
implementationClass="de.platon42.intellij.plugins.m68k.refs.M68kPreprocessorDirectiveElementManipulator"/>
<lang.documentationProvider language="MC68000"
implementationClass="de.platon42.intellij.plugins.m68k.documentation.M68kSymbolDefinitionDocumentationProvider"/>
<lang.documentationProvider language="MC68000"

View File

@ -2,6 +2,8 @@ package de.platon42.intellij.plugins.m68k.refs
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.psi.PsiElement
import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import de.platon42.intellij.jupiter.LightCodeInsightExtension
import de.platon42.intellij.jupiter.MyFixture
@ -86,4 +88,44 @@ internal class M68kReferenceContributorTest : AbstractM68kTest() {
myFixture.checkResultByFile("macros_after_rename.asm")
}
@Test
internal fun reference_to_file_can_be_renamed(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.addFileToProject("otherfile.asm", "; oh no!")
val file = myFixture.configureByText(
"fileref.asm", """
include "otherfil<caret>e.asm"
"""
)
val reference = file.findReferenceAt(myFixture.editor.caretModel.offset)
assertThat(reference).isInstanceOf(M68kIncludeFileReference::class.java)
val otherfile = reference!!.resolve() as M68kFile
assertThat(otherfile.text).isEqualTo("; oh no!")
myFixture.renameElementAtCaret("foobar.asm")
val files = FilenameIndex.getFilesByName(myFixture.project, "foobar.asm", GlobalSearchScope.allScope(myFixture.project))
assertThat(files).hasSize(1)
assertThat(file.text).isEqualToIgnoringWhitespace("include \"foobar.asm\"")
}
@Test
internal fun reference_to_file_in_a_subdirectory_can_be_renamed(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.addFileToProject("foobar/otherfile.asm", "; oh no!")
val file = myFixture.configureByText(
"fileref.asm", """
include "foobar/otherfil<caret>e.asm"
"""
)
val reference = file.findReferenceAt(myFixture.editor.caretModel.offset)
assertThat(reference).isInstanceOf(M68kIncludeFileReference::class.java)
val otherfile = reference!!.resolve() as M68kFile
assertThat(otherfile.text).isEqualTo("; oh no!")
myFixture.renameElementAtCaret("foobar.asm")
val files = FilenameIndex.getFilesByName(myFixture.project, "foobar.asm", GlobalSearchScope.allScope(myFixture.project))
assertThat(files).hasSize(1)
assertThat(file.text).isEqualToIgnoringWhitespace("include \"foobar/foobar.asm\"")
}
}