Skip to content

Commit 512042b

Browse files
committed
feat: 优化代码可读性
增加补全的优先级 修复完成后立即索引PSI
1 parent 5235117 commit 512042b

10 files changed

Lines changed: 173 additions & 104 deletions

src/main/kotlin/com/example/ide/annotator/CssModulesClassAnnotator.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.example.ide.annotator
22

3-
import com.example.ide.psi.CssModulesUnknownClassPsiReference
3+
import com.example.ide.psi.CssModuleClassReference
44
import com.example.ide.psi.isStyleIndex
55
import com.intellij.lang.annotation.AnnotationHolder
66
import com.intellij.lang.annotation.Annotator
@@ -13,11 +13,14 @@ import org.jetbrains.annotations.NotNull
1313

1414
const val MESSAGE = "Selector declarations is Empty"
1515
const val UNKNOWN = "Unknown class name"
16+
1617
class CssModulesClassAnnotator : Annotator {
1718
private fun resolveUnknownClass(holder: AnnotationHolder, psiElement: JSLiteralExpression) {
1819
val cssSelectorName = psiElement.stringValue?.trim().orEmpty()
1920
val reference = psiElement.reference
20-
if (reference is CssModulesUnknownClassPsiReference) {
21+
22+
// Check if the reference is unresolved (CSS class doesn't exist)
23+
if (reference is CssModuleClassReference && reference.isUnresolved()) {
2124
val message = "$UNKNOWN \"$cssSelectorName\""
2225
holder.newAnnotation(HighlightSeverity.WARNING, message)
2326
.range(psiElement)

src/main/kotlin/com/example/ide/annotator/SimpleCssSelectorFix.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.ide.annotator
22

3+
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
34
import com.intellij.codeInsight.intention.impl.BaseIntentionAction
45
import com.intellij.openapi.editor.Editor
56
import com.intellij.openapi.fileEditor.FileEditorManager
@@ -23,17 +24,19 @@ class SimpleCssSelectorFix(private val key: String, private val stylesheetFile:
2324

2425
override fun invoke(@NotNull project: Project, editor: Editor?, file: PsiFile?) {
2526
if (editor == null || file == null) return
27+
val rulesetText = "\n.$key {\n \n}"
2628
val ruleset = CssElementFactory.getInstance(project).createRuleset(
27-
"\n.$key {\n\n}",
29+
rulesetText,
2830
stylesheetFile.language
2931
)
3032
val afterRuleSet = stylesheetFile.add(ruleset)!!
3133
stylesheetFile.navigate(true)
32-
val offset = afterRuleSet.textOffset + ruleset.text.indexOf("{") + 2
34+
val offset = afterRuleSet.textOffset + rulesetText.indexOf("{") + 4
3335
FileEditorManager.getInstance(project).getEditors(stylesheetFile.virtualFile).forEach {
3436
if (it is TextEditor) {
3537
it.editor.caretModel.moveToOffset(offset)
3638
}
3739
}
40+
DaemonCodeAnalyzer.getInstance(project).restart(file)
3841
}
3942
}

src/main/kotlin/com/example/ide/completion/CssModulesClassNameCompletionContributor.kt

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.example.ide.completion;
22

33
import com.intellij.codeInsight.completion.*
44
import com.intellij.codeInsight.lookup.LookupElement
5+
import com.intellij.codeInsight.lookup.LookupElementDecorator
56
import com.intellij.lang.ecmascript6.psi.ES6ImportedBinding
67
import com.intellij.lang.javascript.JavascriptLanguage
78
import com.intellij.lang.javascript.psi.JSIndexedPropertyAccessExpression
@@ -68,12 +69,14 @@ class CssModulesClassNameCompletionContributor : CompletionContributor() {
6869
) {
6970
val style = position.prevSibling.prevSibling
7071
style.reference?.resolve()?.let { stylesFileImportStatement ->
71-
if (stylesFileImportStatement !is ES6ImportedBinding || stylesFileImportStatement.findReferencedElements().isEmpty()) return
72+
if (stylesFileImportStatement !is ES6ImportedBinding || stylesFileImportStatement.findReferencedElements()
73+
.isEmpty()
74+
) return
7275
val first = stylesFileImportStatement.findReferencedElements().first()
7376
first.let {
74-
resultSet.addAllElements(generateLookupElementList(it as StylesheetFile, true).map {
77+
resultSet.addAllElements(generateLookupElementList(it as StylesheetFile, true).map { element ->
7578
// if choose completion with - , auto make to IndexedAccess
76-
it.withInsertHandler { context, item ->
79+
LookupElementDecorator.withInsertHandler(element) { context, item ->
7780
StylesInsertHandler(item.lookupString.contains(SplitChar)).handleInsert(context, item)
7881
}
7982
})
@@ -82,31 +85,22 @@ class CssModulesClassNameCompletionContributor : CompletionContributor() {
8285
}
8386
}
8487
}
85-
private class StylesInsertHandler: InsertHandler<LookupElement>{
86-
private val needsBracketSyntax: Boolean;
8788

88-
constructor(needsBracketSyntax: Boolean) {
89-
this.needsBracketSyntax = needsBracketSyntax
90-
91-
}
92-
93-
override fun handleInsert(
94-
context: InsertionContext,
95-
item: LookupElement
96-
) {
97-
val editor = context.editor
98-
val document = editor.document
99-
val startOffset = context.startOffset
100-
val dotPosOffset = startOffset - 1
101-
val tailOffset = context.tailOffset
102-
if (needsBracketSyntax) {
103-
val lookupString = item.lookupString
104-
document.replaceString(dotPosOffset, tailOffset, "[$lookupString]")
105-
// move cursor to the end of the inserted text
106-
// 2 = [ + ]
107-
editor.caretModel.moveToOffset(dotPosOffset + item.lookupString.length + 2)
89+
private class StylesInsertHandler(private val needsBracketSyntax: Boolean) : InsertHandler<LookupElement> {
90+
override fun handleInsert(context: InsertionContext, item: LookupElement) {
91+
if (needsBracketSyntax) {
92+
val editor = context.editor
93+
val document = editor.document
94+
val startOffset = context.startOffset
95+
val dotPosOffset = startOffset - 1
96+
val tailOffset = context.tailOffset
97+
val lookupString = item.lookupString
98+
document.replaceString(dotPosOffset, tailOffset, "[$lookupString]")
99+
// move cursor to the end of the inserted text
100+
// 2 = [ + ]
101+
editor.caretModel.moveToOffset(dotPosOffset + lookupString.length + 2)
102+
}
108103
}
109104
}
110-
}
111105

112106
}

src/main/kotlin/com/example/ide/completion/QCssModulesUtil.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.example.ide.completion
22

33
import com.intellij.codeInsight.completion.PrioritizedLookupElement
4+
import com.intellij.codeInsight.lookup.LookupElement
45
import com.intellij.codeInsight.lookup.LookupElementBuilder
56
import com.intellij.icons.AllIcons
67
import com.intellij.lang.ecmascript6.psi.ES6ImportedBinding
@@ -58,7 +59,7 @@ fun buildLookupElementHelper(
5859
css: PsiElement,
5960
location: String,
6061
isNeedWrapByChar: Boolean = false
61-
): LookupElementBuilder {
62+
): LookupElement {
6263
val lookupString = CssEscapeUtil.escapeSpecialCssChars(name)
6364
val lineNumber = (css as CssRuleset).selectors.first().lineNumber
6465
val lookup = if (isNeedWrapByChar) "'$lookupString'" else lookupString
@@ -71,8 +72,7 @@ fun buildLookupElementHelper(
7172
.withCaseSensitivity(true)
7273
.withTailText(" ".repeat(SpaceSize) + "($location:$lineNumber)", true)
7374

74-
PrioritizedLookupElement.withPriority(ele, CssCompletionUtil.CSS_SELECTOR_SUFFIX_PRIORITY.toDouble())
75-
return ele
75+
return PrioritizedLookupElement.withPriority(ele, CssCompletionUtil.CSS_SELECTOR_SUFFIX_PRIORITY.toDouble())
7676
}
7777

7878
/**
@@ -94,7 +94,7 @@ fun findReferenceStyleFile(innerStringIndexPsiElement: JSLiteralExpression?): St
9494
fun generateLookupElementList(
9595
stylesheetFile: StylesheetFile,
9696
isDotCompletion: Boolean = false
97-
): List<LookupElementBuilder> {
97+
): List<LookupElement> {
9898
val shortLocation =
9999
PathUtil.toSystemIndependentName(SymbolPresentationUtil.getFilePathPresentation(stylesheetFile))
100100
val allSelector = restoreAllSelector(stylesheetFile)

src/main/kotlin/com/example/ide/document/SimpleDocumentationProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.intellij.lang.javascript.psi.JSLiteralExpression
77
import com.intellij.psi.PsiElement
88
import com.intellij.psi.css.CssRuleset
99

10-
fun String.replaceLast(target: String, replacement: String): String {
10+
private fun String.replaceLast(target: String, replacement: String): String {
1111
val index = this.lastIndexOf(target)
1212
return if (index != -1) {
1313
this.substring(0, index) + replacement + this.substring(index + target.length)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.example.ide.psi
2+
3+
4+
import com.example.ide.completion.findReferenceStyleFile
5+
import com.intellij.lang.ecmascript6.psi.ES6ImportedBinding
6+
import com.intellij.lang.javascript.psi.JSLiteralExpression
7+
import com.intellij.lang.javascript.psi.JSReferenceExpression
8+
import com.intellij.patterns.PlatformPatterns
9+
import com.intellij.psi.*
10+
import com.intellij.psi.css.StylesheetFile
11+
import com.intellij.psi.filters.ElementFilter
12+
import com.intellij.psi.filters.position.FilterPattern
13+
import com.intellij.util.ProcessingContext
14+
import org.jetbrains.annotations.NotNull
15+
16+
17+
/**
18+
* Reference provider for styles["className"] syntax
19+
*/
20+
class CssModuleIndexedReferenceProvider : PsiReferenceProvider() {
21+
22+
override fun getReferencesByElement(
23+
element: PsiElement,
24+
context: ProcessingContext
25+
): Array<PsiReference> {
26+
if (element !is JSLiteralExpression) return PsiReference.EMPTY_ARRAY
27+
val name = element.stringValue?.trim().orEmpty()
28+
if (name.isBlank()) return PsiReference.EMPTY_ARRAY
29+
val styleFile = findReferenceStyleFile(element) ?: return PsiReference.EMPTY_ARRAY
30+
31+
// Always return a dynamic reference that resolves fresh each time
32+
return arrayOf(CssModuleClassReference(element, styleFile, name))
33+
}
34+
}
35+
36+
/**
37+
* Reference provider for styles.className syntax
38+
*/
39+
class CssModuleDotReferenceProvider : PsiReferenceProvider() {
40+
41+
override fun getReferencesByElement(
42+
element: PsiElement,
43+
context: ProcessingContext
44+
): Array<PsiReference> {
45+
if (element !is JSReferenceExpression) return PsiReference.EMPTY_ARRAY
46+
47+
// Get the qualifier (the part before the dot, e.g., "styles" in "styles.app")
48+
val qualifier = element.qualifier
49+
if (qualifier !is JSReferenceExpression) return PsiReference.EMPTY_ARRAY
50+
51+
// Resolve the qualifier to check if it's a CSS module import
52+
val resolved = qualifier.reference?.resolve()
53+
if (resolved !is ES6ImportedBinding) return PsiReference.EMPTY_ARRAY
54+
55+
val referencedElements = resolved.findReferencedElements()
56+
if (referencedElements.isEmpty()) return PsiReference.EMPTY_ARRAY
57+
58+
val styleFile = referencedElements.first() as? StylesheetFile ?: return PsiReference.EMPTY_ARRAY
59+
60+
// Get the property name (the part after the dot)
61+
val propertyName = element.referenceName ?: return PsiReference.EMPTY_ARRAY
62+
63+
return arrayOf(CssModuleClassReference(element, styleFile, propertyName))
64+
}
65+
}
66+
67+
68+
// Filter for styles.className syntax
69+
private val DOT_ACCESS_FILTER = PlatformPatterns.psiElement(JSReferenceExpression::class.java).and(
70+
FilterPattern(
71+
object : ElementFilter {
72+
override fun isAcceptable(element: Any?, context: PsiElement?): Boolean {
73+
74+
return element is JSReferenceExpression
75+
&& element.reference?.resolve() !== null
76+
&& (element.reference?.resolve() as ES6ImportedBinding).findReferencedElements()
77+
.isNotEmpty()
78+
&& (element.reference?.resolve() as ES6ImportedBinding).findReferencedElements()
79+
.first() is StylesheetFile
80+
}
81+
82+
override fun isClassAcceptable(hintClass: Class<*>?): Boolean {
83+
return JSReferenceExpression::class.java.isAssignableFrom(hintClass!!)
84+
}
85+
}
86+
))
87+
88+
class CssModulesIndexedStylesVarPsiReferenceContributor : PsiReferenceContributor() {
89+
override fun registerReferenceProviders(@NotNull registrar: PsiReferenceRegistrar) {
90+
// Register provider for styles.className syntax
91+
registrar.registerReferenceProvider(
92+
DOT_ACCESS_FILTER, CssModuleDotReferenceProvider()
93+
)
94+
}
95+
}
96+
97+
fun isStyleIndex(element: JSLiteralExpression): Boolean = findReferenceStyleFile(element) !== null

src/main/kotlin/com/example/ide/psi/CssModulesIndexedStylesVarPsiReferenceContributorKt.kt

Lines changed: 0 additions & 62 deletions
This file was deleted.
Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,44 @@
11
package com.example.ide.psi
22

3+
import com.example.ide.completion.restoreAllSelector
34
import com.intellij.psi.PsiElement
45
import com.intellij.psi.PsiReferenceBase
56
import com.intellij.psi.css.StylesheetFile
67

8+
/**
9+
* A dynamic PSI reference for CSS class names.
10+
* The resolve() method dynamically looks up the CSS selector each time it's called,
11+
* ensuring that changes to the CSS file are immediately reflected.
12+
*/
13+
class CssModuleClassReference(
14+
element: PsiElement,
15+
val stylesheetFile: StylesheetFile,
16+
private val className: String
17+
) : PsiReferenceBase<PsiElement>(element) {
18+
19+
/**
20+
* Dynamically resolves the CSS class name. This is called each time the reference
21+
* needs to be resolved, ensuring fresh results after CSS file modifications.
22+
*/
23+
override fun resolve(): PsiElement? {
24+
val map = restoreAllSelector(stylesheetFile)
25+
return map[className]
26+
}
27+
28+
/**
29+
* Check if this reference points to an unknown (non-existent) CSS class.
30+
* Used by the annotator to show warnings.
31+
*/
32+
fun isUnresolved(): Boolean = resolve() == null
33+
}
34+
35+
/**
36+
* Legacy reference class for backwards compatibility.
37+
* @deprecated Use CssModuleClassReference instead
38+
*/
739
class CssModulesUnknownClassPsiReference(
840
element: PsiElement,
941
val stylesheetFile: StylesheetFile
10-
) :
11-
PsiReferenceBase<PsiElement?>(element) {
42+
) : PsiReferenceBase<PsiElement?>(element) {
1243
override fun resolve(): PsiElement = element
1344
}

src/main/resources/META-INF/plugin.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
language="JavaScript"/>
2626
<!-- find reference -->
2727
<psi.referenceContributor
28-
implementation="com.example.ide.psi.CssModulesIndexedStylesVarPsiReferenceContributorKt"
28+
implementation="com.example.ide.psi.CssModulesIndexedStylesVarPsiReferenceContributor"
2929
language="JavaScript"/>
3030
<!-- supply completion -->
3131
<completion.contributor language="JavaScript"

0 commit comments

Comments
 (0)