Skip to content

Commit 33f86e2

Browse files
committed
feat: Implement caching for variable lookup and text folding
- Add caching for findAccessibleVariables to improve performance - Implement caching mechanism for foldTextOfLevel function - Refactor code to use new caching systems and improve efficiency
1 parent b9c1087 commit 33f86e2

File tree

1 file changed

+104
-24
lines changed

1 file changed

+104
-24
lines changed

src/main/kotlin/ai/devchat/common/IDEUtils.kt

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,36 @@ import kotlin.system.measureTimeMillis
2121
import com.intellij.psi.util.PsiTreeUtil
2222
import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction
2323
import com.intellij.openapi.fileEditor.FileEditorManager
24+
import java.lang.ref.SoftReference
25+
import java.util.concurrent.ConcurrentHashMap
26+
import java.util.concurrent.locks.ReentrantReadWriteLock
27+
import kotlin.concurrent.read
28+
import kotlin.concurrent.write
29+
import com.intellij.psi.SmartPointerManager
30+
import com.intellij.psi.SmartPsiElementPointer
31+
2432

2533
object IDEUtils {
34+
private const val MAX_CACHE_SIZE = 1000
35+
private data class CacheEntry(val filePath: String, val offset: Int, val element: SoftReference<SymbolTypeDeclaration>)
36+
37+
private val variableCache = object : LinkedHashMap<String, CacheEntry>(MAX_CACHE_SIZE, 0.75f, true) {
38+
override fun removeEldestEntry(eldest: Map.Entry<String, CacheEntry>): Boolean {
39+
return size > MAX_CACHE_SIZE
40+
}
41+
}
42+
private val cacheLock = ReentrantReadWriteLock()
43+
44+
private data class FoldCacheEntry(
45+
val foldedText: String,
46+
val elementPointer: SmartPsiElementPointer<PsiElement>,
47+
val elementLength: Int,
48+
val elementHash: Int
49+
)
50+
51+
private val foldCache = ConcurrentHashMap<String, SoftReference<FoldCacheEntry>>()
52+
53+
2654
fun <T> runInEdtAndGet(block: () -> T): T {
2755
val app = ApplicationManager.getApplication()
2856
if (app.isDispatchThread) {
@@ -131,11 +159,10 @@ object IDEUtils {
131159
fun PsiElement.findAccessibleVariables(): Sequence<SymbolTypeDeclaration> {
132160
val projectFileIndex = ProjectFileIndex.getInstance(project)
133161

134-
return sequence {
135-
// 从当前元素开始,向上遍历所有父元素
162+
// 首先收集所有可能的变量
163+
val allVariables = sequence {
136164
var currentScope: PsiElement? = this@findAccessibleVariables
137165
while (currentScope != null && currentScope !is PsiFile) {
138-
// 在当前作用域中搜索变量声明
139166
val variablesInScope = PsiTreeUtil.findChildrenOfAnyType(
140167
currentScope,
141168
false,
@@ -144,35 +171,56 @@ object IDEUtils {
144171

145172
for (variable in variablesInScope) {
146173
if (isLikelyVariable(variable) && !variable.name.isNullOrEmpty() && variable.nameIdentifier != null) {
147-
val typeDeclaration = getTypeElement(variable)
148-
if (typeDeclaration != null) {
149-
val virtualFile = variable.containingFile?.virtualFile
150-
if (virtualFile != null) {
151-
val isProjectContent = projectFileIndex.isInContent(virtualFile)
152-
yield(SymbolTypeDeclaration(variable, CodeNode(typeDeclaration, isProjectContent)))
153-
}
154-
}
174+
yield(variable)
155175
}
156176
}
157177

158-
// 移动到父作用域
159178
currentScope = currentScope.parent
160179
}
161180

162-
// 最后检查文件级别的变量
163181
yieldAll(this@findAccessibleVariables.containingFile.children
164182
.asSequence()
165183
.filterIsInstance<PsiNameIdentifierOwner>()
166-
.filter { isLikelyVariable(it) && !it.name.isNullOrEmpty() && it.nameIdentifier != null }
167-
.mapNotNull { variable ->
168-
val typeElement = getTypeElement(variable) ?: return@mapNotNull null
169-
val virtualFile = variable.containingFile?.virtualFile ?: return@mapNotNull null
170-
val isProjectContent = projectFileIndex.isInContent(virtualFile)
171-
SymbolTypeDeclaration(variable, CodeNode(typeElement, isProjectContent))
172-
})
184+
.filter { isLikelyVariable(it) && !it.name.isNullOrEmpty() && it.nameIdentifier != null })
185+
}.distinct()
186+
187+
// 处理这些变量的类型,使用缓存
188+
return allVariables.mapNotNull { variable ->
189+
val cacheKey = "${variable.containingFile?.virtualFile?.path}:${variable.textRange.startOffset}"
190+
191+
getCachedOrCompute(cacheKey, variable)
173192
}
174193
}
175194

195+
private fun getCachedOrCompute(cacheKey: String, variable: PsiElement): SymbolTypeDeclaration? {
196+
cacheLock.read {
197+
variableCache[cacheKey]?.let { entry ->
198+
entry.element.get()?.let { cached ->
199+
if (cached.symbol.isValid) return cached
200+
}
201+
}
202+
}
203+
204+
val computed = computeSymbolTypeDeclaration(variable) ?: return null
205+
206+
cacheLock.write {
207+
variableCache[cacheKey] = CacheEntry(
208+
variable.containingFile?.virtualFile?.path ?: return null,
209+
variable.textRange.startOffset,
210+
SoftReference(computed)
211+
)
212+
}
213+
214+
return computed
215+
}
216+
217+
private fun computeSymbolTypeDeclaration(variable: PsiElement): SymbolTypeDeclaration? {
218+
val typeDeclaration = getTypeElement(variable) ?: return null
219+
val virtualFile = variable.containingFile?.virtualFile ?: return null
220+
val isProjectContent = ProjectFileIndex.getInstance(variable.project).isInContent(virtualFile)
221+
return SymbolTypeDeclaration(variable as PsiNameIdentifierOwner, CodeNode(typeDeclaration, isProjectContent))
222+
}
223+
176224
// 辅助函数,用于判断一个元素是否可能是变量
177225
private fun isLikelyVariable(element: PsiElement): Boolean {
178226
val elementClass = element.javaClass.simpleName
@@ -183,14 +231,46 @@ object IDEUtils {
183231

184232
// 辅助函数,用于获取变量的类型元素
185233
private fun getTypeElement(element: PsiElement): PsiElement? {
186-
val project = element.project
187-
val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return null
188-
val offset = element.textOffset
234+
return ReadAction.compute<PsiElement?, Throwable> {
235+
val project = element.project
236+
val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return@compute null
237+
val offset = element.textOffset
189238

190-
return GotoTypeDeclarationAction.findSymbolType(editor, offset)
239+
GotoTypeDeclarationAction.findSymbolType(editor, offset)
240+
}
191241
}
192242

193243
fun PsiElement.foldTextOfLevel(foldingLevel: Int = 1): String {
244+
var result: String
245+
val executionTime = measureTimeMillis {
246+
val cacheKey = "${containingFile.virtualFile.path}:${textRange.startOffset}:$foldingLevel"
247+
248+
// 检查缓存
249+
result = foldCache[cacheKey]?.get()?.let { cachedEntry ->
250+
val cachedElement = cachedEntry.elementPointer.element
251+
if (cachedElement != null && cachedElement.isValid &&
252+
text.length == cachedEntry.elementLength &&
253+
text.hashCode() == cachedEntry.elementHash) {
254+
cachedEntry.foldedText
255+
} else null
256+
} ?: run {
257+
// 如果缓存无效或不存在,重新计算
258+
val foldedText = computeFoldedText(foldingLevel)
259+
// 更新缓存
260+
val elementPointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(this)
261+
foldCache[cacheKey] = SoftReference(FoldCacheEntry(foldedText, elementPointer, text.length, text.hashCode()))
262+
foldedText
263+
}
264+
}
265+
266+
// 记录执行时间
267+
Log.info("foldTextOfLevel execution time: $executionTime ms")
268+
269+
// 返回计算结果
270+
return result
271+
}
272+
273+
private fun PsiElement.computeFoldedText(foldingLevel: Int): String {
194274
val file = this.containingFile
195275
val document = file.viewProvider.document ?: return text
196276
val fileNode = file.node ?: return text

0 commit comments

Comments
 (0)