@@ -18,9 +18,39 @@ import kotlinx.coroutines.runBlocking
1818import java.util.concurrent.CompletableFuture
1919import java.util.concurrent.CountDownLatch
2020import kotlin.system.measureTimeMillis
21+ import com.intellij.psi.util.PsiTreeUtil
22+ import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction
23+ 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
2131
2232
2333object 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+
2454 fun <T > runInEdtAndGet (block : () -> T ): T {
2555 val app = ApplicationManager .getApplication()
2656 if (app.isDispatchThread) {
@@ -127,21 +157,120 @@ object IDEUtils {
127157 )
128158
129159 fun PsiElement.findAccessibleVariables (): Sequence <SymbolTypeDeclaration > {
130- val projectFileIndex = ProjectFileIndex .getInstance(this .project)
131- return generateSequence(this .parent) { it.parent }
132- .takeWhile { it !is PsiFile }
133- .flatMap { it.children.asSequence().filterIsInstance<PsiNameIdentifierOwner >() }
134- .plus(this .containingFile.children.asSequence().filterIsInstance<PsiNameIdentifierOwner >())
135- .filter { ! it.name.isNullOrEmpty() && it.nameIdentifier != null }
136- .mapNotNull {
137- val typeDeclaration = it.getTypeDeclaration() ? : return @mapNotNull null
138- val virtualFile = typeDeclaration.containingFile.virtualFile ? : return @mapNotNull null
139- val isProjectContent = projectFileIndex.isInContent(virtualFile)
140- SymbolTypeDeclaration (it, CodeNode (typeDeclaration, isProjectContent))
160+ val projectFileIndex = ProjectFileIndex .getInstance(project)
161+
162+ // 首先收集所有可能的变量
163+ val allVariables = sequence {
164+ var currentScope: PsiElement ? = this @findAccessibleVariables
165+ while (currentScope != null && currentScope !is PsiFile ) {
166+ val variablesInScope = PsiTreeUtil .findChildrenOfAnyType(
167+ currentScope,
168+ false ,
169+ PsiNameIdentifierOwner ::class .java
170+ )
171+
172+ for (variable in variablesInScope) {
173+ if (isLikelyVariable(variable) && ! variable.name.isNullOrEmpty() && variable.nameIdentifier != null ) {
174+ yield (variable)
175+ }
176+ }
177+
178+ currentScope = currentScope.parent
179+ }
180+
181+ yieldAll(this @findAccessibleVariables.containingFile.children
182+ .asSequence()
183+ .filterIsInstance<PsiNameIdentifierOwner >()
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)
192+ }
193+ }
194+
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+ }
141201 }
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+
224+ // 辅助函数,用于判断一个元素是否可能是变量
225+ private fun isLikelyVariable (element : PsiElement ): Boolean {
226+ val elementClass = element.javaClass.simpleName
227+ return elementClass.contains(" Variable" , ignoreCase = true ) ||
228+ elementClass.contains(" Parameter" , ignoreCase = true ) ||
229+ elementClass.contains(" Field" , ignoreCase = true )
230+ }
231+
232+ // 辅助函数,用于获取变量的类型元素
233+ private fun getTypeElement (element : PsiElement ): PsiElement ? {
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
238+
239+ GotoTypeDeclarationAction .findSymbolType(editor, offset)
240+ }
142241 }
143242
144243 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 {
145274 val file = this .containingFile
146275 val document = file.viewProvider.document ? : return text
147276 val fileNode = file.node ? : return text
0 commit comments