Added M68kGlobalLabelSymbolCompletionContributor to replace variants returned by M68kGlobalLabelSymbolReference, to have more control over resolution. Contributor also adds registers to code completion. Code tidying.

This commit is contained in:
Chris Hodges 2021-07-26 19:52:48 +02:00
parent fc5e1f6bf7
commit 5ffffb5680
14 changed files with 249 additions and 64 deletions

View File

@ -74,7 +74,8 @@ make it work with JUnit 5. Feel free to use the code (in package ```de.platon42.
- Cosmetics: Added (same) icon for plugin as for file type.
- Performance improvement: Use Word-Index for global labels and symbols instead of iterating over the file.
- Performance improvement: Use Stub-Index for global labels and symbols.
- Bugfix: No longer reports a syntax error when file lacks terminating End-Of-Line
- Bugfix: No longer reports a syntax error when file lacks terminating End-Of-Line.
- Enhancement: Registers are now offered for code completion, making editing less annoying.
### V0.1 (20-Jul-21)

View File

@ -51,7 +51,8 @@ patchPluginXml {
<li>Cosmetics: Added (same) icon for plugin as for file type.
<li>Performance improvement: Use Word-Index for global labels and symbols instead of iterating over the file.
<li>Performance improvement: Use Stub-Index for global labels and symbols.
<li>Bugfix: No longer reports a syntax error when file lacks terminating End-Of-Line
<li>Bugfix: No longer reports a syntax error when file lacks terminating End-Of-Line.
<li>Enhancement: Registers are now offered for code completion, making editing less annoying.
</ul>
<h4>V0.1 (20-Jul-21)</h4>
<ul>

View File

@ -39,6 +39,8 @@ object M68kLookupUtil {
return results
}
fun findAllGlobalLabelNames(project: Project): Collection<String> = StubIndex.getInstance().getAllKeys(M68kGlobalLabelStubIndex.KEY, project).toList()
fun findAllLocalLabels(globalLabel: M68kGlobalLabel): List<M68kLocalLabel> {
val statement = PsiTreeUtil.getStubOrPsiParentOfType(globalLabel, M68kStatement::class.java)!!
val results: MutableList<M68kLocalLabel> = ArrayList()
@ -89,4 +91,6 @@ object M68kLookupUtil {
)
return results
}
fun findAllSymbolDefinitionNames(project: Project): Collection<String> = StubIndex.getInstance().getAllKeys(M68kSymbolDefinitionStubIndex.KEY, project)
}

View File

@ -1,24 +1,39 @@
package de.platon42.intellij.plugins.m68k.refs
import com.intellij.navigation.ChooseByNameContributor
import com.intellij.navigation.ChooseByNameContributorEx2
import com.intellij.navigation.NavigationItem
import com.intellij.openapi.project.Project
import de.platon42.intellij.plugins.m68k.psi.M68kLookupUtil.findAllGlobalLabels
import de.platon42.intellij.plugins.m68k.psi.M68kLookupUtil.findAllSymbolDefinitions
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.stubs.StubIndex
import com.intellij.util.Processor
import com.intellij.util.indexing.FindSymbolParameters
import com.intellij.util.indexing.IdFilter
import de.platon42.intellij.plugins.m68k.psi.M68kGlobalLabel
import de.platon42.intellij.plugins.m68k.psi.M68kSymbolDefinition
import de.platon42.intellij.plugins.m68k.stubs.M68kGlobalLabelStubIndex
import de.platon42.intellij.plugins.m68k.stubs.M68kSymbolDefinitionStubIndex
class M68kChooseByNameContributor : ChooseByNameContributor {
override fun getNames(project: Project, includeNonProjectItems: Boolean): Array<String> {
return listOf(findAllGlobalLabels(project), findAllSymbolDefinitions(project))
.asSequence()
.flatten()
.mapNotNull { it.name }
.toList()
.toTypedArray()
class M68kChooseByNameContributor : ChooseByNameContributorEx2 {
override fun processNames(processor: Processor<in String>, parameters: FindSymbolParameters) {
processNames(processor, parameters.searchScope, parameters.idFilter)
}
override fun getItemsByName(name: String, pattern: String, project: Project, includeNonProjectItems: Boolean): Array<NavigationItem> {
return listOf(findAllGlobalLabels(project), findAllSymbolDefinitions(project))
.flatten()
.toTypedArray()
val result: MutableList<NavigationItem> = ArrayList()
processElementsWithName(name, result::add, FindSymbolParameters.wrap(pattern, project, includeNonProjectItems))
return result.toTypedArray()
}
override fun processNames(processor: Processor<in String>, scope: GlobalSearchScope, filter: IdFilter?) {
StubIndex.getInstance().processAllKeys(M68kGlobalLabelStubIndex.KEY, processor, scope, filter)
StubIndex.getInstance().processAllKeys(M68kSymbolDefinitionStubIndex.KEY, processor, scope, filter)
}
override fun processElementsWithName(name: String, processor: Processor<in NavigationItem>, parameters: FindSymbolParameters) {
StubIndex.getInstance()
.processElements(M68kGlobalLabelStubIndex.KEY, name, parameters.project, parameters.searchScope, M68kGlobalLabel::class.java, processor)
StubIndex.getInstance()
.processElements(M68kSymbolDefinitionStubIndex.KEY, name, parameters.project, parameters.searchScope, M68kSymbolDefinition::class.java, processor)
}
}

View File

@ -0,0 +1,33 @@
package de.platon42.intellij.plugins.m68k.refs
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.icons.AllIcons
import com.intellij.patterns.PlatformPatterns
import com.intellij.util.ProcessingContext
import de.platon42.intellij.plugins.m68k.psi.M68kLookupUtil
import de.platon42.intellij.plugins.m68k.psi.M68kTypes
class M68kGlobalLabelSymbolCompletionContributor : CompletionContributor() {
companion object {
val REGISTER_SUGGESTIONS: List<LookupElement> =
listOf(
"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "sp",
"pc"
)
.map { PrioritizedLookupElement.withPriority(LookupElementBuilder.create(it).withIcon(AllIcons.Nodes.Record).withBoldness(true), 2.0) }
}
init {
extend(CompletionType.BASIC, PlatformPatterns.psiElement(M68kTypes.SYMBOL), object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, resultSet: CompletionResultSet) {
resultSet.addAllElements(REGISTER_SUGGESTIONS)
resultSet.addAllElements(M68kLookupUtil.findAllGlobalLabels(parameters.originalFile.project).map(LookupElementBuilder::createWithIcon))
resultSet.addAllElements(M68kLookupUtil.findAllSymbolDefinitions(parameters.originalFile.project).map(LookupElementBuilder::createWithIcon))
}
})
}
}

View File

@ -1,6 +1,5 @@
package de.platon42.intellij.plugins.m68k.refs
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementResolveResult
@ -12,7 +11,6 @@ import com.intellij.psi.search.PsiSearchHelper
import com.intellij.psi.search.UsageSearchContext
import com.intellij.util.SmartList
import de.platon42.intellij.plugins.m68k.psi.M68kGlobalLabel
import de.platon42.intellij.plugins.m68k.psi.M68kLookupUtil
import de.platon42.intellij.plugins.m68k.psi.M68kSymbolDefinition
import de.platon42.intellij.plugins.m68k.psi.M68kSymbolReference
@ -21,12 +19,6 @@ class M68kGlobalLabelSymbolReference(element: M68kSymbolReference) :
companion object {
val INSTANCE = Resolver()
fun findGlobalLabels(element: M68kSymbolReference, predicate: (M68kGlobalLabel) -> Boolean): List<M68kGlobalLabel> =
M68kLookupUtil.findAllGlobalLabels(element.project).filter(predicate)
fun findSymbolDefinitions(element: M68kSymbolReference, predicate: (M68kSymbolDefinition) -> Boolean): List<M68kSymbolDefinition> =
M68kLookupUtil.findAllSymbolDefinitions(element.project).filter(predicate)
}
class Resolver : ResolveCache.PolyVariantResolver<M68kGlobalLabelSymbolReference> {
@ -44,32 +36,17 @@ class M68kGlobalLabelSymbolReference(element: M68kSymbolReference) :
}, GlobalSearchScope.allScope(project),
refName, UsageSearchContext.IN_CODE, true
)
val globalLabelMatches: Array<ResolveResult> = findGlobalLabels(ref.myElement) { it.name == refName }
.map { PsiElementResolveResult(it) }
.toTypedArray()
if (globalLabelMatches.isNotEmpty()) return globalLabelMatches
return targets
.map { PsiElementResolveResult(it) }
.map(::PsiElementResolveResult)
.toTypedArray()
}
}
override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
return ResolveCache.getInstance(element.project)
override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> =
ResolveCache.getInstance(element.project)
.resolveWithCaching(this, INSTANCE, false, incompleteCode)
}
override fun resolve(): PsiElement? {
val resolveResults = multiResolve(false)
return resolveResults.singleOrNull()?.element
}
override fun resolve(): PsiElement? = multiResolve(false).singleOrNull()?.element
override fun getVariants(): Array<Any> {
return listOf(findGlobalLabels(element) { true }, findSymbolDefinitions(element) { true }).asSequence()
.flatten()
.map { LookupElementBuilder.createWithIcon(it) }
.toList()
.toTypedArray()
}
override fun getVariants(): Array<Any> = emptyArray()
}

View File

@ -4,7 +4,6 @@ import com.intellij.lang.cacheBuilder.DefaultWordsScanner
import com.intellij.lang.cacheBuilder.WordsScanner
import com.intellij.lang.findUsages.FindUsagesProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.tree.TokenSet
import de.platon42.intellij.plugins.m68k.lexer.M68kLexer
import de.platon42.intellij.plugins.m68k.lexer.M68kLexerPrefs
@ -14,23 +13,18 @@ import org.jetbrains.annotations.NonNls
class M68kFindUsagesProvider : FindUsagesProvider {
override fun getWordsScanner(): WordsScanner {
return DefaultWordsScanner(
override fun getWordsScanner(): WordsScanner =
DefaultWordsScanner(
M68kLexer(M68kLexerPrefs()), // FIXME Oh no! More Prefs!
TokenSet.create(M68kTypes.SYMBOLDEF, M68kTypes.GLOBAL_LABEL_DEF, M68kTypes.LOCAL_LABEL_DEF, M68kTypes.SYMBOL),
TokenSet.create(M68kTypes.COMMENT),
TokenSet.create(M68kTypes.STRINGLIT, M68kTypes.DECIMAL, M68kTypes.HEXADECIMAL, M68kTypes.OCTAL, M68kTypes.BINARY),
TokenSet.EMPTY
)
}
override fun canFindUsagesFor(psiElement: PsiElement): Boolean {
return psiElement is PsiNamedElement
}
override fun canFindUsagesFor(psiElement: PsiElement): Boolean = psiElement is M68kNamedElement
override fun getHelpId(psiElement: PsiElement): @NonNls String? {
return null
}
override fun getHelpId(psiElement: PsiElement): @NonNls String? = null
override fun getType(element: PsiElement): @Nls String {
return when (element) {
@ -38,6 +32,8 @@ class M68kFindUsagesProvider : FindUsagesProvider {
is M68kLocalLabel -> "local label"
is M68kSymbolDefinition -> "symbol definition"
is M68kSymbolReference -> "symbol reference"
is M68kRegister -> "register"
is M68kRefExpr -> "reference expression"
else -> ""
}
}
@ -48,11 +44,11 @@ class M68kFindUsagesProvider : FindUsagesProvider {
is M68kLocalLabel -> element.name!!
is M68kSymbolDefinition -> element.parent.text
is M68kSymbolReference -> element.symbolName
is M68kRefExpr -> element.symbolReference?.symbolName ?: ""
is M68kRegister -> element.text
else -> ""
}
}
override fun getNodeText(element: PsiElement, useFullName: Boolean): @Nls String {
return getDescriptiveName(element)
}
override fun getNodeText(element: PsiElement, useFullName: Boolean): @Nls String = getDescriptiveName(element)
}

View File

@ -23,6 +23,7 @@
implementationClass="de.platon42.intellij.plugins.m68k.syntax.M68kSyntaxHighlighterFactory"/>
<colorSettingsPage implementation="de.platon42.intellij.plugins.m68k.syntax.M68kColorSettingsPage"/>
<completion.contributor language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.asm.M68kMnemonicCompletionContributor"/>
<completion.contributor language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.refs.M68kGlobalLabelSymbolCompletionContributor"/>
<lang.braceMatcher language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.syntax.M68kPairedBraceMatcher"/>
<lang.quoteHandler language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.M68kStringQuoteHandler"/>
<lang.findUsagesProvider language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.scanner.M68kFindUsagesProvider"/>

View File

@ -0,0 +1,86 @@
package de.platon42.intellij.plugins.m68k.refs
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import de.platon42.intellij.jupiter.LightCodeInsightExtension
import de.platon42.intellij.jupiter.MyFixture
import de.platon42.intellij.jupiter.TestDataPath
import de.platon42.intellij.jupiter.TestDataSubPath
import de.platon42.intellij.plugins.m68k.AbstractM68kTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@TestDataPath("src/test/resources/references")
@TestDataSubPath("completion")
@ExtendWith(LightCodeInsightExtension::class)
internal class M68kGlobalLabelSymbolCompletionContributorTest : AbstractM68kTest() {
@Test
internal fun completion_shows_fitting_symbols_and_labels_after_first_letter(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.configureByText(
"completeme.asm", """
PIC_WIDTH = 100
PIC_HEIGHT = 100
platon = 42
HURZ = 220
NP equ 10
Irrelevant_Label:
Problem_solver:
move.l P<caret>
"""
)
myFixture.completeBasic()
assertThat(myFixture.lookupElementStrings).containsExactlyInAnyOrder("PIC_HEIGHT", "PIC_WIDTH", "Problem_solver", "NP")
}
@Test
internal fun completion_shows_all_symbols_and_labels_inside_expr(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.configureByText(
"completeme.asm", """
PIC_WIDTH = 100
HURZ = 220
NP equ 10
Problem_solver:
move.l 145+<caret>
"""
)
myFixture.completeBasic()
assertThat(myFixture.lookupElementStrings).containsExactlyInAnyOrder(
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "sp",
"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
"pc",
"HURZ", "NP", "PIC_WIDTH", "Problem_solver"
)
}
@Test
internal fun completion_puts_register_on_top(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.configureByText(
"completeme.asm", """
PIC_WIDTH = 320
PIC_HEIGHT equ 256
DEBUG_LEVEL set 10
double_buffer_1 = 1
auto_complete_2 = 1
entry:
bsr init
bsr main
bsr exit
move.l d1<caret>
rts
"""
)
myFixture.completeBasic()
assertThat(myFixture.lookupElementStrings).containsExactly("d1", "double_buffer_1")
}
@Test
internal fun complete_several_basic_symbols_or_labels(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.configureByFile("basic_completion.asm")
myFixture.completeBasicAllCarets(null)
myFixture.checkResultByFile("basic_completion_after_op.asm")
}
}

View File

@ -17,11 +17,11 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@TestDataPath("src/test/resources/references")
@TestDataSubPath("labels")
@ExtendWith(LightCodeInsightExtension::class)
internal class M68kReferenceContributorTest : AbstractM68kTest() {
@Test
@TestDataSubPath("labels")
internal fun reference_to_dot_local_label_can_be_renamed(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.configureByFile("dot_local_label.asm")
assertThat(myFixture.elementAtCaret).isInstanceOf(M68kLocalLabel::class.java)
@ -31,6 +31,7 @@ internal class M68kReferenceContributorTest : AbstractM68kTest() {
}
@Test
@TestDataSubPath("labels")
internal fun reference_to_multiple_conditional_local_label_dollar_and_variants(@MyFixture myFixture: CodeInsightTestFixture) {
val reference = myFixture.getReferenceAtCaretPositionWithAssertion("multiple_conditional_local_label_dollar.asm")
assertThat(reference.element).isInstanceOf(M68kSymbolReference::class.java)
@ -40,6 +41,7 @@ internal class M68kReferenceContributorTest : AbstractM68kTest() {
}
@Test
@TestDataSubPath("labels")
internal fun reference_to_global_label_can_be_renamed(@MyFixture myFixture: CodeInsightTestFixture) {
val file = myFixture.configureByFile("global_labels.asm")
assertThat(myFixture.elementAtCaret).isInstanceOf(M68kGlobalLabel::class.java)
@ -47,9 +49,7 @@ internal class M68kReferenceContributorTest : AbstractM68kTest() {
val reference = file.findReferenceAt(myFixture.editor.caretModel.offset - 1)!!
assertThat(reference).isInstanceOf(M68kGlobalLabelSymbolReference::class.java)
assertThat(reference.variants).hasOnlyElementsOfType(LookupElementBuilder::class.java)
.extracting<String> { (it as LookupElementBuilder).lookupString }
.containsExactlyInAnyOrder("main", "init", "exit")
assertThat(reference.variants).isEmpty()
myFixture.renameElementAtCaret("intro_main")
@ -57,6 +57,7 @@ internal class M68kReferenceContributorTest : AbstractM68kTest() {
}
@Test
@TestDataSubPath("symbols")
internal fun reference_to_symbol_can_be_renamed(@MyFixture myFixture: CodeInsightTestFixture) {
val file = myFixture.configureByFile("symbol_assignment.asm")
assertThat(myFixture.elementAtCaret).isInstanceOf(M68kSymbolDefinition::class.java)
@ -66,9 +67,7 @@ internal class M68kReferenceContributorTest : AbstractM68kTest() {
val reference = file.findReferenceAt(myFixture.editor.caretModel.offset)!!
assertThat(reference).isInstanceOf(M68kGlobalLabelSymbolReference::class.java)
assertThat(reference.variants).hasOnlyElementsOfType(LookupElementBuilder::class.java)
.extracting<String> { (it as LookupElementBuilder).lookupString }
.containsExactlyInAnyOrder("main", "init", "exit", "PIC_WIDTH", "PIC_HEIGHT")
assertThat(reference.variants).isEmpty()
myFixture.checkResultByFile("symbol_assignment_after_rename.asm")
}

View File

@ -0,0 +1,36 @@
PIC_WIDTH = 320
PIC_HEIGHT equ 256
DEBUG_LEVEL set 10
DOUBLE_BUFFER_1 = 1
AUTO_COMPLETE_2 = 1
dood2 = 1
entry:
bsr init
bsr main
bsr exit
rts
init
move.w #PIC_HEIGHT,d1
.looph move.w #PIC_WIDTH,d0
.loopw clr.b (a0)+
subq.w #1,d0
bne.s .loopw
subq.w #1,d1
bne.s .looph
move.w a1,d2<caret>
move.w e<caret>
move.w ex<caret>
move.w PW<caret>
move.w A<caret>
move.w DEB<caret>
move.w A2<caret>
move.w D1<caret>
rts
main moveq.l #0,d0
rts
exit illegal
rts

View File

@ -0,0 +1,36 @@
PIC_WIDTH = 320
PIC_HEIGHT equ 256
DEBUG_LEVEL set 10
DOUBLE_BUFFER_1 = 1
AUTO_COMPLETE_2 = 1
dood2 = 1
entry:
bsr init
bsr main
bsr exit
rts
init
move.w #PIC_HEIGHT,d1
.looph move.w #PIC_WIDTH,d0
.loopw clr.b (a0)+
subq.w #1,d0
bne.s .loopw
subq.w #1,d1
bne.s .looph
move.w a1,d2
move.w e
move.w exit
move.w PIC_WIDTH
move.w AUTO_COMPLETE_2
move.w DEBUG_LEVEL
move.w AUTO_COMPLETE_2
move.w DOUBLE_BUFFER_1
rts
main moveq.l #0,d0
rts
exit illegal
rts