Skip to content

Commit adc4d85

Browse files
authored
Merge branch 'TabooLib:master' into master
2 parents c8516d2 + a46c4c2 commit adc4d85

File tree

10 files changed

+877
-4
lines changed

10 files changed

+877
-4
lines changed

src/main/kotlin/org/tabooproject/development/inlay/EditorDocumentListener.kt

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class EditorDocumentListener(private val project: Project) : Disposable {
3838
val document = event.document
3939
val file = FileDocumentManager.getInstance().getFile(document)
4040

41-
if (file != null && isLanguageFile(file)) {
41+
if (file != null && (isLanguageFile(file) || isKotlinFile(file))) {
4242
// 防抖:避免频繁刷新
4343
val currentTime = System.currentTimeMillis()
4444
if (currentTime - lastRefreshTime < refreshDebounceMs) {
@@ -49,7 +49,11 @@ class EditorDocumentListener(private val project: Project) : Disposable {
4949
// 延迟刷新以合并连续的修改
5050
ApplicationManager.getApplication().invokeLater({
5151
if (!project.isDisposed) {
52-
refreshEditorsForFile(file)
52+
if (isLanguageFile(file)) {
53+
refreshEditorsForFile(file)
54+
} else if (isKotlinFile(file)) {
55+
refreshUsageCache()
56+
}
5357
}
5458
}, project.disposed)
5559
}
@@ -77,7 +81,7 @@ class EditorDocumentListener(private val project: Project) : Disposable {
7781
val openFiles = fileEditorManager.openFiles
7882

7983
for (file in openFiles) {
80-
if (isLanguageFile(file)) {
84+
if (isLanguageFile(file) || isKotlinFile(file)) {
8185
val document = FileDocumentManager.getInstance().getDocument(file)
8286
if (document != null) {
8387
registerDocument(document)
@@ -188,6 +192,40 @@ class EditorDocumentListener(private val project: Project) : Disposable {
188192
return LangFiles.isLangFile(file)
189193
}
190194

195+
/**
196+
* 检查文件是否为Kotlin文件
197+
*
198+
* @param file 要检查的文件
199+
* @return 如果是Kotlin文件返回 true
200+
*/
201+
private fun isKotlinFile(file: VirtualFile): Boolean {
202+
return file.name.endsWith(".kt")
203+
}
204+
205+
/**
206+
* 刷新使用缓存并更新相关编辑器
207+
*/
208+
private fun refreshUsageCache() {
209+
// 清除语言使用分析缓存
210+
LangUsageAnalyzer.clearCache(project)
211+
212+
// 更新编辑器通知
213+
com.intellij.ui.EditorNotifications.updateAll()
214+
215+
// 重新分析所有语言文件以更新未使用标记
216+
val fileEditorManager = FileEditorManager.getInstance(project)
217+
val psiManager = PsiManager.getInstance(project)
218+
val daemonCodeAnalyzer = com.intellij.codeInsight.daemon.DaemonCodeAnalyzer.getInstance(project)
219+
220+
// 查找所有语言文件并触发重新分析
221+
LangIndex.findProjectLangFiles(project).forEach { langFile ->
222+
val psiFile = psiManager.findFile(langFile)
223+
if (psiFile != null) {
224+
daemonCodeAnalyzer.restart(psiFile)
225+
}
226+
}
227+
}
228+
191229
override fun dispose() {
192230
// 清理所有监听器
193231
registeredDocuments.forEach { document ->

src/main/kotlin/org/tabooproject/development/inlay/LangColorSettingsPage.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ class LangColorSettingsPage : ColorSettingsPage {
6565
}
6666
}
6767

68+
// 添加语言键验证相关的颜色设置
69+
attributes.add(AttributesDescriptor("TabooLib Lang: Missing Key", LangKeyValidationAnnotator.MISSING_LANG_KEY_ATTRIBUTES))
70+
attributes.add(AttributesDescriptor("TabooLib Lang: Valid Key", LangKeyValidationAnnotator.VALID_LANG_KEY_ATTRIBUTES))
71+
attributes.add(AttributesDescriptor("TabooLib Lang: Unused Key", LangFileUnusedAnnotator.UNUSED_LANG_KEY_ATTRIBUTES))
72+
6873
return attributes.toTypedArray()
6974
}
7075

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.tabooproject.development.inlay
2+
3+
import com.intellij.lang.annotation.AnnotationHolder
4+
import com.intellij.lang.annotation.Annotator
5+
import com.intellij.lang.annotation.HighlightSeverity
6+
import com.intellij.openapi.editor.colors.EditorColorsManager
7+
import com.intellij.openapi.editor.colors.TextAttributesKey
8+
import com.intellij.openapi.editor.markup.TextAttributes
9+
import com.intellij.psi.PsiElement
10+
import org.yaml.snakeyaml.Yaml
11+
import java.awt.Color
12+
import java.awt.Font
13+
14+
/**
15+
* TabooLib 语言文件未使用键注解器
16+
*
17+
* 为语言文件中未使用的键添加灰色显示
18+
*
19+
* @since 1.42
20+
*/
21+
class LangFileUnusedAnnotator : Annotator {
22+
23+
companion object {
24+
// 定义未使用语言键的高亮样式
25+
val UNUSED_LANG_KEY_ATTRIBUTES = TextAttributesKey.createTextAttributesKey(
26+
"TABOOLIB_UNUSED_LANG_KEY",
27+
TextAttributes().apply {
28+
foregroundColor = Color.GRAY
29+
fontType = Font.ITALIC
30+
}
31+
)
32+
}
33+
34+
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
35+
val file = element.containingFile?.virtualFile ?: return
36+
37+
// 只处理语言文件
38+
if (!LangFiles.isLangFile(file)) return
39+
40+
// 检查是否是YAML键值对的键部分
41+
val langKey = extractLangKeyFromElement(element) ?: return
42+
43+
// 检查这个语言键是否被使用
44+
val project = element.project
45+
val isUsed = LangUsageAnalyzer.isLangKeyUsed(project, langKey)
46+
47+
if (!isUsed) {
48+
// 为未使用的语言键添加灰色高亮
49+
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
50+
.range(element.textRange)
51+
.textAttributes(UNUSED_LANG_KEY_ATTRIBUTES)
52+
.tooltip("语言键 '$langKey' 未在项目中使用")
53+
.create()
54+
}
55+
}
56+
57+
/**
58+
* 从PSI元素中提取语言键
59+
* 这是一个简化的实现,实际需要根据YAML文件结构来解析
60+
*/
61+
private fun extractLangKeyFromElement(element: PsiElement): String? {
62+
val text = element.text.trim()
63+
64+
// 简单的YAML键值对匹配
65+
if (text.contains(":")) {
66+
val key = text.substringBefore(":").trim()
67+
if (key.isNotEmpty() && !key.startsWith("#")) {
68+
return key
69+
}
70+
}
71+
72+
return null
73+
}
74+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package org.tabooproject.development.inlay
2+
3+
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo
4+
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider
5+
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder
6+
import com.intellij.icons.AllIcons
7+
import com.intellij.openapi.editor.markup.GutterIconRenderer
8+
import com.intellij.psi.PsiElement
9+
import com.intellij.psi.PsiManager
10+
import org.jetbrains.kotlin.psi.KtCallExpression
11+
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
12+
import org.tabooproject.development.isSendLangCall
13+
import javax.swing.Icon
14+
15+
/**
16+
* TabooLib 语言文件使用情况行标记提供器
17+
*
18+
* 在语言文件中显示每个语言键的使用次数,并提供跳转到使用位置的功能
19+
*
20+
* @since 1.42
21+
*/
22+
class LangFileUsageLineMarker : RelatedItemLineMarkerProvider() {
23+
24+
override fun collectNavigationMarkers(
25+
element: PsiElement,
26+
result: MutableCollection<in RelatedItemLineMarkerInfo<*>>
27+
) {
28+
val file = element.containingFile?.virtualFile ?: return
29+
30+
// 只处理语言文件
31+
if (!LangFiles.isLangFile(file)) return
32+
33+
// 提取语言键
34+
val langKey = extractLangKeyFromElement(element) ?: return
35+
36+
// 查找使用位置
37+
val project = element.project
38+
val usages = LangUsageAnalyzer.findUsages(project, langKey)
39+
40+
if (usages.isEmpty()) {
41+
// 未使用的键,显示警告图标
42+
val builder = NavigationGutterIconBuilder
43+
.create(AllIcons.General.InspectionsEye)
44+
.setTargets(emptyList<PsiElement>())
45+
.setTooltipText("语言键 '$langKey' 未被使用")
46+
.setPopupTitle("未使用的语言键")
47+
.setEmptyPopupText("此语言键在项目中未被使用")
48+
49+
result.add(builder.createLineMarkerInfo(element))
50+
} else {
51+
// 有使用的键,显示使用次数和跳转选项
52+
val psiManager = PsiManager.getInstance(project)
53+
val targets = mutableListOf<PsiElement>()
54+
55+
usages.forEach { usage ->
56+
val psiFile = psiManager.findFile(usage.file)
57+
if (psiFile != null) {
58+
val targetElement = findElementAtOffset(psiFile, usage.offset)
59+
if (targetElement != null) {
60+
targets.add(targetElement)
61+
}
62+
}
63+
}
64+
65+
if (targets.isNotEmpty()) {
66+
val usageCount = usages.size
67+
val tooltip = "语言键 '$langKey' 被使用了 $usageCount"
68+
69+
val builder = NavigationGutterIconBuilder
70+
.create(getUsageIcon(usageCount))
71+
.setTargets(targets)
72+
.setTooltipText(tooltip)
73+
.setPopupTitle("语言键使用位置")
74+
.setEmptyPopupText("无法找到使用位置")
75+
76+
result.add(builder.createLineMarkerInfo(element))
77+
}
78+
}
79+
}
80+
81+
/**
82+
* 从元素中提取语言键
83+
*/
84+
private fun extractLangKeyFromElement(element: PsiElement): String? {
85+
val text = element.text.trim()
86+
87+
// 简单的YAML键值对匹配
88+
if (text.contains(":")) {
89+
val key = text.substringBefore(":").trim()
90+
if (key.isNotEmpty() && !key.startsWith("#") && !key.startsWith("\"") && !key.startsWith("'")) {
91+
return key
92+
}
93+
}
94+
95+
return null
96+
}
97+
98+
/**
99+
* 根据使用次数获取相应图标
100+
*/
101+
private fun getUsageIcon(usageCount: Int): Icon {
102+
return when {
103+
usageCount == 0 -> AllIcons.General.InspectionsEye
104+
usageCount == 1 -> AllIcons.Gutter.Unique
105+
usageCount <= 5 -> AllIcons.General.ArrowRight
106+
else -> AllIcons.General.BalloonInformation
107+
}
108+
}
109+
110+
/**
111+
* 在指定偏移位置查找PSI元素
112+
*/
113+
private fun findElementAtOffset(psiFile: com.intellij.psi.PsiFile, offset: Int): PsiElement? {
114+
val elementAtOffset = psiFile.findElementAt(offset) ?: return null
115+
116+
// 查找包含的sendLang调用
117+
val callExpression = com.intellij.psi.util.PsiTreeUtil.getParentOfType(elementAtOffset, KtCallExpression::class.java)
118+
if (callExpression != null && isSendLangCall(callExpression)) {
119+
// 返回语言键参数的字符串模板
120+
val arguments = callExpression.valueArgumentList?.arguments
121+
if (!arguments.isNullOrEmpty()) {
122+
val firstArg = arguments[0].getArgumentExpression()
123+
if (firstArg is KtStringTemplateExpression) {
124+
return firstArg
125+
}
126+
}
127+
}
128+
129+
return elementAtOffset
130+
}
131+
}

src/main/kotlin/org/tabooproject/development/inlay/LangFoldingSettings.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ class LangFoldingOptionsProvider :
3131
// 设置变更后刷新折叠
3232
LangFoldingSettingsListener.refreshAllEditors()
3333
}
34+
checkBox(
35+
"Highlight valid language keys",
36+
LangFoldingSettings.instance::showValidLangKeyHighlight,
37+
) {
38+
LangFoldingSettings.instance.showValidLangKeyHighlight = it
39+
}
3440
}
3541
}
3642

@@ -42,7 +48,8 @@ class LangFoldingSettings : PersistentStateComponent<LangFoldingSettings.State>
4248

4349
data class State(
4450
var shouldFoldTranslations: Boolean = true,
45-
var showColorCodes: Boolean = false
51+
var showColorCodes: Boolean = false,
52+
var showValidLangKeyHighlight: Boolean = false
4653
)
4754

4855
private var state = State()
@@ -68,6 +75,12 @@ class LangFoldingSettings : PersistentStateComponent<LangFoldingSettings.State>
6875
state.showColorCodes = value
6976
}
7077

78+
var showValidLangKeyHighlight: Boolean
79+
get() = state.showValidLangKeyHighlight
80+
set(value) {
81+
state.showValidLangKeyHighlight = value
82+
}
83+
7184
companion object {
7285
val instance: LangFoldingSettings
7386
get() = ApplicationManager.getApplication().getService(LangFoldingSettings::class.java)

0 commit comments

Comments
 (0)