Skip to content

Commit d03cfad

Browse files
authored
Merge pull request #33 from aridclown/main
Bugfix: `LuaDocumentListener` being registered multiple times
2 parents 0bc3898 + fc59303 commit d03cfad

File tree

1 file changed

+58
-47
lines changed

1 file changed

+58
-47
lines changed
Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package com.tang.intellij.lua.editor
22

3+
import com.cppcxy.ide.lsp.GutterInfo
34
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
5+
import com.intellij.openapi.Disposable
46
import com.intellij.openapi.application.ApplicationManager
7+
import com.intellij.openapi.components.Service
8+
import com.intellij.openapi.components.Service.Level.PROJECT
9+
import com.intellij.openapi.components.service
510
import com.intellij.openapi.editor.Document
11+
import com.intellij.openapi.editor.EditorFactory
612
import com.intellij.openapi.editor.event.DocumentEvent
713
import com.intellij.openapi.editor.event.DocumentListener
814
import com.intellij.openapi.fileEditor.FileDocumentManager
@@ -11,41 +17,45 @@ import com.intellij.openapi.fileEditor.FileEditorManagerListener
1117
import com.intellij.openapi.project.Project
1218
import com.intellij.openapi.startup.ProjectActivity
1319
import com.intellij.openapi.vfs.VirtualFile
20+
import com.intellij.openapi.vfs.VirtualFileManager
21+
import com.intellij.openapi.vfs.newvfs.BulkFileListener
22+
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
1423
import com.intellij.psi.PsiManager
24+
import com.tang.intellij.lua.lang.LuaFileType
1525
import com.tang.intellij.lua.psi.LuaPsiFile
1626
import java.util.concurrent.ConcurrentHashMap
1727

1828
/**
1929
* Manager to handle gutter cache and trigger updates
2030
*/
2131
object LuaGutterCacheManager {
22-
private val gutterCache = ConcurrentHashMap<String, List<com.cppcxy.ide.lsp.GutterInfo>>()
32+
private val gutterCache = ConcurrentHashMap<String, List<GutterInfo>>()
2333
private val cacheTimestamps = ConcurrentHashMap<String, Long>()
24-
34+
2535
fun clearCache(uri: String) {
2636
gutterCache.remove(uri)
2737
cacheTimestamps.remove(uri)
2838
}
29-
39+
3040
fun clearAllCache() {
3141
gutterCache.clear()
3242
cacheTimestamps.clear()
3343
}
34-
35-
fun getCache(uri: String): List<com.cppcxy.ide.lsp.GutterInfo>? {
44+
45+
fun getCache(uri: String): List<GutterInfo>? {
3646
return gutterCache[uri]
3747
}
38-
39-
fun setCache(uri: String, infos: List<com.cppcxy.ide.lsp.GutterInfo>) {
48+
49+
fun setCache(uri: String, infos: List<GutterInfo>) {
4050
gutterCache[uri] = infos
4151
cacheTimestamps[uri] = System.currentTimeMillis()
4252
}
43-
53+
4454
fun getCacheAge(uri: String): Long {
4555
val timestamp = cacheTimestamps[uri] ?: return Long.MAX_VALUE
4656
return System.currentTimeMillis() - timestamp
4757
}
48-
58+
4959
fun isCacheStale(uri: String, maxAgeMs: Long = 1000): Boolean {
5060
return getCacheAge(uri) > maxAgeMs
5161
}
@@ -57,28 +67,28 @@ object LuaGutterCacheManager {
5767
class LuaDocumentListener(private val project: Project) : DocumentListener {
5868
private val updateScheduler = mutableMapOf<Document, Long>()
5969
private val pendingUpdates = mutableMapOf<Document, Runnable>()
60-
70+
6171
override fun documentChanged(event: DocumentEvent) {
6272
val document = event.document
6373
val file = FileDocumentManager.getInstance().getFile(document) ?: return
64-
65-
if (file.extension != "lua") return
66-
74+
75+
if (file.fileType !== LuaFileType.INSTANCE) return
76+
6777
// Clear cache immediately for instant refresh
6878
LuaGutterCacheManager.clearCache(file.url)
69-
79+
7080
// Cancel any pending update
71-
pendingUpdates[document]?.let {
81+
pendingUpdates[document]?.let {
7282
// The runnable will be replaced
7383
}
74-
84+
7585
// Schedule restart of code analysis with shorter debounce (200ms)
7686
val now = System.currentTimeMillis()
7787
val lastUpdate = updateScheduler[document] ?: 0
78-
88+
7989
// Reduced debounce time to 200ms for better responsiveness
8090
val debounceTime = 200L
81-
91+
8292
val updateRunnable = Runnable {
8393
ApplicationManager.getApplication().invokeLater {
8494
val psiFile = PsiManager.getInstance(project).findFile(file)
@@ -87,9 +97,9 @@ class LuaDocumentListener(private val project: Project) : DocumentListener {
8797
}
8898
}
8999
}
90-
100+
91101
pendingUpdates[document] = updateRunnable
92-
102+
93103
if (now - lastUpdate > debounceTime) {
94104
updateScheduler[document] = now
95105
// Execute immediately if enough time has passed
@@ -115,10 +125,10 @@ class LuaDocumentListener(private val project: Project) : DocumentListener {
115125
*/
116126
class LuaFileEditorListener(private val project: Project) : FileEditorManagerListener {
117127
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
118-
if (file.extension == "lua") {
128+
if (file.fileType === LuaFileType.INSTANCE) {
119129
// Clear cache for newly opened file to ensure fresh data
120130
LuaGutterCacheManager.clearCache(file.url)
121-
131+
122132
// Trigger code analysis
123133
ApplicationManager.getApplication().invokeLater {
124134
val psiFile = PsiManager.getInstance(project).findFile(file)
@@ -137,38 +147,32 @@ class LuaGutterCacheStartupActivity : ProjectActivity {
137147
override suspend fun execute(project: Project) {
138148
// Register document listener
139149
val documentListener = LuaDocumentListener(project)
140-
val connection = ApplicationManager.getApplication().messageBus.connect(project)
141-
150+
val parentDisposable = project.service<LuaGutterCacheListenerDisposable>()
151+
val appConnection = ApplicationManager.getApplication().messageBus.connect(parentDisposable)
152+
val projectConnection = project.messageBus.connect(parentDisposable)
153+
142154
// Listen to editor creation events to attach document listener
143-
val editorFactory = com.intellij.openapi.editor.EditorFactory.getInstance()
144-
editorFactory.addEditorFactoryListener(
145-
object : com.intellij.openapi.editor.event.EditorFactoryListener {
146-
override fun editorCreated(event: com.intellij.openapi.editor.event.EditorFactoryEvent) {
147-
event.editor.document.addDocumentListener(documentListener, project)
148-
}
149-
},
150-
project
151-
)
152-
155+
EditorFactory.getInstance()
156+
.eventMulticaster
157+
.addDocumentListener(documentListener, parentDisposable)
158+
153159
// Register file editor listener
154-
project.messageBus
155-
.connect()
156-
.subscribe(
157-
FileEditorManagerListener.FILE_EDITOR_MANAGER,
158-
LuaFileEditorListener(project)
159-
)
160-
160+
projectConnection.subscribe(
161+
FileEditorManagerListener.FILE_EDITOR_MANAGER,
162+
LuaFileEditorListener(project)
163+
)
164+
161165
// Register bulk file listener to detect external changes
162-
connection.subscribe(
163-
com.intellij.openapi.vfs.VirtualFileManager.VFS_CHANGES,
164-
object : com.intellij.openapi.vfs.newvfs.BulkFileListener {
165-
override fun after(events: List<com.intellij.openapi.vfs.newvfs.events.VFileEvent>) {
166+
appConnection.subscribe(
167+
VirtualFileManager.VFS_CHANGES,
168+
object : BulkFileListener {
169+
override fun after(events: List<VFileEvent>) {
166170
for (event in events) {
167171
val file = event.file
168-
if (file != null && file.extension == "lua") {
172+
if (file != null && file.fileType === LuaFileType.INSTANCE) {
169173
// Clear cache when file changes externally
170174
LuaGutterCacheManager.clearCache(file.url)
171-
175+
172176
// Trigger update
173177
ApplicationManager.getApplication().invokeLater {
174178
val psiFile = PsiManager.getInstance(project).findFile(file)
@@ -183,3 +187,10 @@ class LuaGutterCacheStartupActivity : ProjectActivity {
183187
)
184188
}
185189
}
190+
191+
@Service(PROJECT)
192+
class LuaGutterCacheListenerDisposable : Disposable {
193+
override fun dispose() {
194+
// IntelliJ will automatically dispose of it when the project is disposed
195+
}
196+
}

0 commit comments

Comments
 (0)