1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.vcs.impl
4 import com.google.common.collect.HashMultiset
5 import com.google.common.collect.Multiset
6 import com.intellij.diagnostic.ThreadDumper
7 import com.intellij.icons.AllIcons
8 import com.intellij.notification.Notification
9 import com.intellij.notification.NotificationAction
10 import com.intellij.notification.NotificationType
11 import com.intellij.notification.NotificationsManager
12 import com.intellij.openapi.Disposable
13 import com.intellij.openapi.application.*
14 import com.intellij.openapi.command.CommandEvent
15 import com.intellij.openapi.command.CommandListener
16 import com.intellij.openapi.command.CommandProcessor
17 import com.intellij.openapi.components.service
18 import com.intellij.openapi.diagnostic.Logger
19 import com.intellij.openapi.editor.Document
20 import com.intellij.openapi.editor.Editor
21 import com.intellij.openapi.editor.EditorFactory
22 import com.intellij.openapi.editor.event.DocumentEvent
23 import com.intellij.openapi.editor.event.DocumentListener
24 import com.intellij.openapi.editor.event.EditorFactoryEvent
25 import com.intellij.openapi.editor.event.EditorFactoryListener
26 import com.intellij.openapi.editor.ex.EditorEx
27 import com.intellij.openapi.extensions.ExtensionPointName
28 import com.intellij.openapi.fileEditor.FileDocumentManager
29 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
30 import com.intellij.openapi.progress.ProcessCanceledException
31 import com.intellij.openapi.progress.util.BackgroundTaskUtil
32 import com.intellij.openapi.project.Project
33 import com.intellij.openapi.util.Disposer
34 import com.intellij.openapi.util.io.FileUtilRt
35 import com.intellij.openapi.util.text.StringUtil
36 import com.intellij.openapi.vcs.*
37 import com.intellij.openapi.vcs.changes.*
38 import com.intellij.openapi.vcs.changes.conflicts.ChangelistConflictFileStatusProvider
39 import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager.Companion.LOCAL_CHANGES
40 import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager.Companion.getToolWindowFor
41 import com.intellij.openapi.vcs.checkin.CheckinHandler
42 import com.intellij.openapi.vcs.checkin.CheckinHandlerFactory
43 import com.intellij.openapi.vcs.ex.*
44 import com.intellij.openapi.vcs.history.VcsRevisionNumber
45 import com.intellij.openapi.vcs.impl.LineStatusTrackerContentLoader.ContentInfo
46 import com.intellij.openapi.vcs.impl.LineStatusTrackerContentLoader.TrackerContent
47 import com.intellij.openapi.vfs.VfsUtil
48 import com.intellij.openapi.vfs.VirtualFile
49 import com.intellij.openapi.vfs.VirtualFileManager
50 import com.intellij.openapi.vfs.newvfs.BulkFileListener
51 import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
52 import com.intellij.openapi.vfs.newvfs.events.VFileEvent
53 import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
54 import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent
55 import com.intellij.testFramework.LightVirtualFile
56 import com.intellij.util.EventDispatcher
57 import com.intellij.util.concurrency.Semaphore
58 import com.intellij.util.ui.UIUtil
59 import com.intellij.vcs.commit.isNonModalCommit
60 import com.intellij.vcsUtil.VcsUtil
61 import org.jetbrains.annotations.*
62 import java.nio.charset.Charset
64 import java.util.concurrent.Future
65 import java.util.function.Supplier
67 class LineStatusTrackerManager(private val project: Project) : LineStatusTrackerManagerI, Disposable {
68 private val LOCK = Any()
69 private var isDisposed = false
71 private val trackers = HashMap<Document, TrackerData>()
72 private val forcedDocuments = HashMap<Document, Multiset<Any>>()
74 private val eventDispatcher = EventDispatcher.create(Listener::class.java)
76 private var partialChangeListsEnabled = VcsApplicationSettings.getInstance().ENABLE_PARTIAL_CHANGELISTS
77 private val documentsInDefaultChangeList = HashSet<Document>()
78 private var clmFreezeCounter: Int = 0
80 private val filesWithDamagedInactiveRanges = HashSet<VirtualFile>()
81 private val fileStatesAwaitingRefresh = HashMap<VirtualFile, ChangelistsLocalLineStatusTracker.State>()
83 private val loader = MyBaseRevisionLoader()
86 private val LOG = Logger.getInstance(LineStatusTrackerManager::class.java)
89 fun getInstance(project: Project): LineStatusTrackerManagerI = project.service()
92 fun getInstanceImpl(project: Project): LineStatusTrackerManager {
93 return getInstance(project) as LineStatusTrackerManager
97 class MyStartupActivity : VcsStartupActivity {
98 override fun runActivity(project: Project) {
99 LineStatusTrackerManager.getInstanceImpl(project).startListenForEditors()
102 override fun getOrder(): Int = VcsInitObject.OTHER_INITIALIZATION.order
105 private fun startListenForEditors() {
106 val busConnection = project.messageBus.connect()
107 busConnection.subscribe(LineStatusTrackerSettingListener.TOPIC, MyLineStatusTrackerSettingListener())
108 busConnection.subscribe(VcsFreezingProcess.Listener.TOPIC, MyFreezeListener())
109 busConnection.subscribe(CommandListener.TOPIC, MyCommandListener())
110 busConnection.subscribe(ChangeListListener.TOPIC, MyChangeListListener())
112 ApplicationManager.getApplication().messageBus.connect(this)
113 .subscribe(VirtualFileManager.VFS_CHANGES, MyVirtualFileListener())
115 LocalLineStatusTrackerProvider.EP_NAME.addChangeListener(Runnable { updateTrackingSettings() }, this)
118 if (project.isDisposed) return@runInEdt
120 ApplicationManager.getApplication().addApplicationListener(MyApplicationListener(), this)
121 FileStatusManager.getInstance(project).addFileStatusListener(MyFileStatusListener(), this)
123 EditorFactory.getInstance().eventMulticaster.addDocumentListener(MyDocumentListener(), this)
125 MyEditorFactoryListener().install(this)
126 onEverythingChanged()
128 val states = project.service<PartialLineStatusTrackerManagerState>().getStatesAndClear()
129 if (states.isNotEmpty()) {
130 ChangeListManager.getInstance(project).invokeAfterUpdate({ restoreTrackersForPartiallyChangedFiles(states) },
131 InvokeAfterUpdateMode.SILENT, null, null)
136 override fun dispose() {
138 Disposer.dispose(loader)
141 for ((document, multiset) in forcedDocuments) {
142 for (requester in multiset.elementSet()) {
143 warn("Tracker is being held on dispose by $requester", document)
146 forcedDocuments.clear()
148 for (data in trackers.values) {
149 unregisterTrackerInCLM(data)
150 data.tracker.release()
156 override fun getLineStatusTracker(document: Document): LineStatusTracker<*>? {
158 return trackers[document]?.tracker
162 override fun getLineStatusTracker(file: VirtualFile): LineStatusTracker<*>? {
163 val document = FileDocumentManager.getInstance().getCachedDocument(file) ?: return null
164 return getLineStatusTracker(document)
168 override fun requestTrackerFor(document: Document, requester: Any) {
169 ApplicationManager.getApplication().assertIsWriteThread()
172 warn("Tracker is being requested after dispose by $requester", document)
176 val multiset = forcedDocuments.computeIfAbsent(document) { HashMultiset.create<Any>() }
177 multiset.add(requester)
179 if (trackers[document] == null) {
180 val virtualFile = FileDocumentManager.getInstance().getFile(document) ?: return
181 switchTracker(virtualFile, document)
187 override fun releaseTrackerFor(document: Document, requester: Any) {
188 ApplicationManager.getApplication().assertIsWriteThread()
190 val multiset = forcedDocuments[document]
191 if (multiset == null || !multiset.contains(requester)) {
192 warn("Tracker release underflow by $requester", document)
196 multiset.remove(requester)
198 if (multiset.isEmpty()) {
199 forcedDocuments.remove(document)
200 checkIfTrackerCanBeReleased(document)
205 override fun invokeAfterUpdate(task: Runnable) {
206 loader.addAfterUpdateRunnable(task)
210 fun getTrackers(): List<LineStatusTracker<*>> {
212 return trackers.values.map { it.tracker }
216 fun addTrackerListener(listener: Listener, disposable: Disposable) {
217 eventDispatcher.addListener(listener, disposable)
220 open class ListenerAdapter : Listener
221 interface Listener : EventListener {
222 fun onTrackerAdded(tracker: LineStatusTracker<*>) {
225 fun onTrackerRemoved(tracker: LineStatusTracker<*>) {
231 private fun checkIfTrackerCanBeReleased(document: Document) {
233 val data = trackers[document] ?: return
235 if (forcedDocuments.containsKey(document)) return
237 if (data.tracker is ChangelistsLocalLineStatusTracker) {
238 val hasPartialChanges = data.tracker.hasPartialState()
239 if (hasPartialChanges) {
240 log("checkIfTrackerCanBeReleased - hasPartialChanges", data.tracker.virtualFile)
244 val isLoading = loader.hasRequestFor(document)
246 log("checkIfTrackerCanBeReleased - isLoading", data.tracker.virtualFile)
251 releaseTracker(document)
257 private fun onEverythingChanged() {
258 ApplicationManager.getApplication().assertIsWriteThread()
260 if (isDisposed) return
261 log("onEverythingChanged", null)
263 val files = HashSet<VirtualFile>()
265 for (data in trackers.values) {
266 files.add(data.tracker.virtualFile)
268 for (document in forcedDocuments.keys) {
269 val file = FileDocumentManager.getInstance().getFile(document)
270 if (file != null) files.add(file)
273 for (file in files) {
280 private fun onFileChanged(virtualFile: VirtualFile) {
281 val document = FileDocumentManager.getInstance().getCachedDocument(virtualFile) ?: return
284 if (isDisposed) return
285 log("onFileChanged", virtualFile)
286 val tracker = trackers[document]?.tracker
288 if (tracker != null || forcedDocuments.containsKey(document)) {
289 switchTracker(virtualFile, document, refreshExisting = true)
294 private fun registerTrackerInCLM(data: TrackerData) {
295 val tracker = data.tracker
296 if (tracker !is ChangelistsLocalLineStatusTracker) return
298 val filePath = VcsUtil.getFilePath(tracker.virtualFile)
299 if (data.clmFilePath != null) {
300 LOG.error("[registerTrackerInCLM] tracker already registered")
304 ChangeListManagerImpl.getInstanceImpl(project).registerChangeTracker(filePath, tracker)
305 data.clmFilePath = filePath
308 private fun unregisterTrackerInCLM(data: TrackerData) {
309 val tracker = data.tracker
310 if (tracker !is ChangelistsLocalLineStatusTracker) return
312 val filePath = data.clmFilePath
313 if (filePath == null) {
314 LOG.error("[unregisterTrackerInCLM] tracker is not registered")
318 ChangeListManagerImpl.getInstanceImpl(project).unregisterChangeTracker(filePath, tracker)
319 data.clmFilePath = null
321 val actualFilePath = VcsUtil.getFilePath(tracker.virtualFile)
322 if (filePath != actualFilePath) {
323 LOG.error("[unregisterTrackerInCLM] unexpected file path: expected: $filePath, actual: $actualFilePath")
327 private fun reregisterTrackerInCLM(data: TrackerData) {
328 val tracker = data.tracker
329 if (tracker !is ChangelistsLocalLineStatusTracker) return
331 val oldFilePath = data.clmFilePath
332 val newFilePath = VcsUtil.getFilePath(tracker.virtualFile)
334 if (oldFilePath == null) {
335 LOG.error("[reregisterTrackerInCLM] tracker is not registered")
339 if (oldFilePath != newFilePath) {
340 ChangeListManagerImpl.getInstanceImpl(project).unregisterChangeTracker(oldFilePath, tracker)
341 ChangeListManagerImpl.getInstanceImpl(project).registerChangeTracker(newFilePath, tracker)
342 data.clmFilePath = newFilePath
346 private fun canCreateTrackerFor(virtualFile: VirtualFile?): Boolean {
347 if (isDisposed) return false
348 if (virtualFile == null || virtualFile is LightVirtualFile) return false
349 if (runReadAction { !virtualFile.isValid || virtualFile.fileType.isBinary || FileUtilRt.isTooLarge(virtualFile.length) }) return false
353 override fun arePartialChangelistsEnabled(virtualFile: VirtualFile): Boolean {
354 if (!partialChangeListsEnabled) return false
356 val vcs = VcsUtil.getVcsFor(project, virtualFile)
357 return vcs != null && vcs.arePartialChangelistsSupported()
361 private fun switchTracker(virtualFile: VirtualFile, document: Document,
362 refreshExisting: Boolean = false) {
363 val provider = getTrackerProvider(virtualFile)
365 val oldTracker = trackers[document]?.tracker
366 if (oldTracker != null && provider != null && provider.isMyTracker(oldTracker)) {
367 if (refreshExisting) {
368 refreshTracker(oldTracker, provider)
372 releaseTracker(document)
373 if (provider != null) installTracker(virtualFile, document, provider)
377 private fun installTracker(virtualFile: VirtualFile, document: Document,
378 provider: LocalLineStatusTrackerProvider): LocalLineStatusTracker<*>? {
379 if (isDisposed) return null
380 if (trackers[document] != null) return null
382 val tracker = provider.createTracker(project, virtualFile) ?: return null
383 tracker.mode = getTrackingMode()
385 val data = TrackerData(tracker)
386 val replacedData = trackers.put(document, data)
387 LOG.assertTrue(replacedData == null)
389 registerTrackerInCLM(data)
390 refreshTracker(tracker, provider)
391 eventDispatcher.multicaster.onTrackerAdded(tracker)
393 if (clmFreezeCounter > 0) {
397 log("Tracker installed", virtualFile)
401 private fun getTrackerProvider(virtualFile: VirtualFile): LocalLineStatusTrackerProvider? {
402 if (!canCreateTrackerFor(virtualFile)) return null
404 val customTracker = LocalLineStatusTrackerProvider.EP_NAME.findFirstSafe { it.isTrackedFile(project, virtualFile) }
405 if (customTracker != null) return customTracker
407 return listOf(ChangelistsLocalStatusTrackerProvider, DefaultLocalStatusTrackerProvider).find { it.isTrackedFile(project, virtualFile) }
411 private fun releaseTracker(document: Document) {
412 val data = trackers.remove(document) ?: return
414 eventDispatcher.multicaster.onTrackerRemoved(data.tracker)
415 unregisterTrackerInCLM(data)
416 data.tracker.release()
418 log("Tracker released", data.tracker.virtualFile)
421 private fun updateTrackingSettings() {
423 if (isDisposed) return
424 val mode = getTrackingMode()
425 for (data in trackers.values) {
426 data.tracker.mode = mode
430 onEverythingChanged()
433 private fun getTrackingMode(): LocalLineStatusTracker.Mode {
434 val settings = VcsApplicationSettings.getInstance()
435 return LocalLineStatusTracker.Mode(settings.SHOW_LST_GUTTER_MARKERS,
436 settings.SHOW_LST_ERROR_STRIPE_MARKERS,
437 settings.SHOW_WHITESPACES_IN_LST)
441 private fun refreshTracker(tracker: LocalLineStatusTracker<*>,
442 provider: LocalLineStatusTrackerProvider) {
443 if (isDisposed) return
444 if (provider !is LineStatusTrackerContentLoader) return
445 loader.scheduleRefresh(RefreshRequest(tracker.document, provider))
447 log("Refresh queued", tracker.virtualFile)
450 private inner class MyBaseRevisionLoader : SingleThreadLoader<RefreshRequest, RefreshData>() {
451 fun hasRequestFor(document: Document): Boolean {
452 return hasRequest { it.document == document }
455 override fun loadRequest(request: RefreshRequest): Result<RefreshData> {
456 if (isDisposed) return Result.Canceled()
457 val document = request.document
458 val virtualFile = FileDocumentManager.getInstance().getFile(document)
459 val loader = request.loader
461 log("Loading started", virtualFile)
463 if (virtualFile == null || !virtualFile.isValid) {
464 log("Loading error: virtual file is not valid", virtualFile)
465 return Result.Error()
468 if (!canCreateTrackerFor(virtualFile) || !loader.isTrackedFile(project, virtualFile)) {
469 log("Loading error: virtual file is not a tracked file", virtualFile)
470 return Result.Error()
473 val newContentInfo = loader.getContentInfo(project, virtualFile)
474 if (newContentInfo == null) {
475 log("Loading error: base revision not found", virtualFile)
476 return Result.Error()
480 val data = trackers[document]
482 log("Loading cancelled: tracker not found", virtualFile)
483 return Result.Canceled()
486 if (!loader.shouldBeUpdated(data.contentInfo, newContentInfo)) {
487 log("Loading cancelled: no need to update", virtualFile)
488 return Result.Canceled()
492 val content = loader.loadContent(project, newContentInfo)
493 if (content == null) {
494 log("Loading error: provider failure", virtualFile)
495 return Result.Error()
498 log("Loading successful", virtualFile)
499 return Result.Success(RefreshData(content, newContentInfo))
503 override fun handleResult(request: RefreshRequest, result: Result<RefreshData>) {
504 val document = request.document
506 is Result.Canceled -> handleCanceled(document)
507 is Result.Error -> handleError(request, document)
508 is Result.Success -> handleSuccess(request, document, result.data)
511 checkIfTrackerCanBeReleased(document)
514 private fun handleCanceled(document: Document) {
515 restorePendingTrackerState(document)
518 private fun handleError(request: RefreshRequest, document: Document) {
520 val loader = request.loader
521 val data = trackers[document] ?: return
522 val tracker = data.tracker
524 if (loader.isMyTracker(tracker)) {
525 loader.handleLoadingError(tracker)
527 data.contentInfo = null
531 private fun handleSuccess(request: RefreshRequest, document: Document, refreshData: RefreshData) {
532 val virtualFile = FileDocumentManager.getInstance().getFile(document)!!
533 val loader = request.loader
535 val tracker: LocalLineStatusTracker<*>
537 val data = trackers[document]
539 log("Loading finished: tracker already released", virtualFile)
542 if (!loader.shouldBeUpdated(data.contentInfo, refreshData.contentInfo)) {
543 log("Loading finished: no need to update", virtualFile)
547 data.contentInfo = refreshData.contentInfo
548 tracker = data.tracker
551 if (loader.isMyTracker(tracker)) {
552 loader.setLoadedContent(tracker, refreshData.content)
553 log("Loading finished: success", virtualFile)
556 log("Loading finished: wrong tracker. tracker: $tracker, loader: $loader", virtualFile)
559 restorePendingTrackerState(document)
562 private fun restorePendingTrackerState(document: Document) {
563 val tracker = getLineStatusTracker(document)
564 if (tracker is ChangelistsLocalLineStatusTracker) {
565 val virtualFile = tracker.virtualFile
567 val state = synchronized(LOCK) {
568 fileStatesAwaitingRefresh.remove(virtualFile) ?: return
571 val success = tracker.restoreState(state)
572 log("Pending state restored. success - $success", virtualFile)
578 * We can speedup initial content loading if it was already loaded by someone.
579 * We do not set 'contentInfo' here to ensure, that following refresh will fix potential inconsistency.
583 fun offerTrackerContent(document: Document, text: CharSequence) {
584 val tracker: LocalLineStatusTracker<*>
586 val data = trackers[document]
587 if (data == null || data.contentInfo != null) return
589 tracker = data.tracker
592 if (tracker is LocalLineStatusTrackerImpl<*>) {
593 tracker.setBaseRevision(text)
594 log("Offered content", tracker.virtualFile)
598 private inner class MyFileStatusListener : FileStatusListener {
599 override fun fileStatusesChanged() {
600 onEverythingChanged()
603 override fun fileStatusChanged(virtualFile: VirtualFile) {
604 onFileChanged(virtualFile)
608 private inner class MyEditorFactoryListener : EditorFactoryListener {
609 fun install(disposable: Disposable) {
610 val editorFactory = EditorFactory.getInstance()
611 for (editor in editorFactory.allEditors) {
612 if (isTrackedEditor(editor)) {
613 requestTrackerFor(editor.document, editor)
616 editorFactory.addEditorFactoryListener(this, disposable)
619 override fun editorCreated(event: EditorFactoryEvent) {
620 val editor = event.editor
621 if (isTrackedEditor(editor)) {
622 requestTrackerFor(editor.document, editor)
626 override fun editorReleased(event: EditorFactoryEvent) {
627 val editor = event.editor
628 if (isTrackedEditor(editor)) {
629 releaseTrackerFor(editor.document, editor)
633 private fun isTrackedEditor(editor: Editor): Boolean {
634 // can't filter out "!isInLocalFileSystem" files, custom VcsBaseContentProvider can handle them
635 if (FileDocumentManager.getInstance().getFile(editor.document) == null) {
638 return editor.project == null || editor.project == project
642 private inner class MyVirtualFileListener : BulkFileListener {
643 override fun before(events: List<VFileEvent>) {
644 for (event in events) {
646 is VFileDeleteEvent -> handleFileDeletion(event.file)
651 override fun after(events: List<VFileEvent>) {
652 for (event in events) {
654 is VFilePropertyChangeEvent -> when {
655 VirtualFile.PROP_ENCODING == event.propertyName -> onFileChanged(event.file)
656 event.isRename -> handleFileMovement(event.file)
658 is VFileMoveEvent -> handleFileMovement(event.file)
663 private fun handleFileMovement(file: VirtualFile) {
664 if (!partialChangeListsEnabled) return
667 forEachTrackerUnder(file) { data ->
668 reregisterTrackerInCLM(data)
673 private fun handleFileDeletion(file: VirtualFile) {
674 if (!partialChangeListsEnabled) return
677 forEachTrackerUnder(file) { data ->
678 releaseTracker(data.tracker.document)
683 private fun forEachTrackerUnder(file: VirtualFile, action: (TrackerData) -> Unit) {
684 if (file.isDirectory) {
685 val affected = trackers.values.filter { VfsUtil.isAncestor(file, it.tracker.virtualFile, false) }
686 for (data in affected) {
691 val document = FileDocumentManager.getInstance().getCachedDocument(file) ?: return
692 val data = trackers[document] ?: return
699 private inner class MyDocumentListener : DocumentListener {
700 override fun documentChanged(event: DocumentEvent) {
701 if (!ApplicationManager.getApplication().isDispatchThread) return // disable for documents forUseInNonAWTThread
702 if (!partialChangeListsEnabled || project.isDisposed) return
704 val document = event.document
705 if (documentsInDefaultChangeList.contains(document)) return
707 val virtualFile = FileDocumentManager.getInstance().getFile(document) ?: return
708 if (getLineStatusTracker(document) != null) return
710 val provider = getTrackerProvider(virtualFile)
711 if (provider != ChangelistsLocalStatusTrackerProvider) return
713 val changeList = ChangeListManagerImpl.getInstanceImpl(project).getChangeList(virtualFile)
714 if (changeList != null && !changeList.isDefault) {
715 log("Tracker install from DocumentListener: ", virtualFile)
717 val tracker = synchronized(LOCK) {
718 installTracker(virtualFile, document, provider)
720 if (tracker is ChangelistsLocalLineStatusTracker) {
721 tracker.replayChangesFromDocumentEvents(listOf(event))
725 documentsInDefaultChangeList.add(document)
730 private inner class MyApplicationListener : ApplicationListener {
731 override fun afterWriteActionFinished(action: Any) {
732 documentsInDefaultChangeList.clear()
735 val documents = trackers.values.map { it.tracker.document }
736 for (document in documents) {
737 checkIfTrackerCanBeReleased(document)
743 private inner class MyLineStatusTrackerSettingListener : LineStatusTrackerSettingListener {
744 override fun settingsUpdated() {
745 partialChangeListsEnabled = VcsApplicationSettings.getInstance().ENABLE_PARTIAL_CHANGELISTS
747 updateTrackingSettings()
751 private inner class MyChangeListListener : ChangeListAdapter() {
752 override fun defaultListChanged(oldDefaultList: ChangeList?, newDefaultList: ChangeList?) {
753 runInEdt(ModalityState.any()) {
754 if (project.isDisposed) return@runInEdt
756 expireInactiveRangesDamagedNotifications()
758 EditorFactory.getInstance().allEditors
759 .filterIsInstance(EditorEx::class.java)
761 it.gutterComponentEx.repaint()
767 private inner class MyCommandListener : CommandListener {
768 override fun commandFinished(event: CommandEvent) {
769 if (!partialChangeListsEnabled) return
771 if (CommandProcessor.getInstance().currentCommand == null &&
772 !filesWithDamagedInactiveRanges.isEmpty()) {
773 showInactiveRangesDamagedNotification()
778 class CheckinFactory : CheckinHandlerFactory() {
779 override fun createHandler(panel: CheckinProjectPanel, commitContext: CommitContext): CheckinHandler {
780 val project = panel.project
781 return object : CheckinHandler() {
782 override fun checkinSuccessful() {
783 resetExcludedFromCommit()
786 override fun checkinFailed(exception: MutableList<VcsException>?) {
787 resetExcludedFromCommit()
790 private fun resetExcludedFromCommit() {
792 // TODO Move this to SingleChangeListCommitWorkflow
793 if (!project.isDisposed && !panel.isNonModalCommit) getInstanceImpl(project).resetExcludedFromCommitMarkers()
800 private inner class MyFreezeListener : VcsFreezingProcess.Listener {
801 override fun onFreeze() {
804 if (clmFreezeCounter == 0) {
805 for (data in trackers.values) {
807 data.tracker.freeze()
809 catch (e: Throwable) {
819 override fun onUnfreeze() {
820 runInEdt(ModalityState.any()) {
823 if (clmFreezeCounter == 0) {
824 for (data in trackers.values) {
826 data.tracker.unfreeze()
828 catch (e: Throwable) {
839 private class TrackerData(val tracker: LocalLineStatusTracker<*>,
840 var contentInfo: ContentInfo? = null,
841 var clmFilePath: FilePath? = null)
843 private class RefreshRequest(val document: Document, val loader: LineStatusTrackerContentLoader) {
844 override fun equals(other: Any?): Boolean = other is RefreshRequest && document == other.document
845 override fun hashCode(): Int = document.hashCode()
846 override fun toString(): String = "RefreshRequest: " + (FileDocumentManager.getInstance().getFile(document)?.path ?: "unknown")
849 private class RefreshData(val content: TrackerContent,
850 val contentInfo: ContentInfo)
853 private fun log(message: String, file: VirtualFile?) {
854 if (LOG.isDebugEnabled) {
856 LOG.debug(message + "; file: " + file.path)
864 private fun warn(message: String, document: Document?) {
865 val file = document?.let { FileDocumentManager.getInstance().getFile(it) }
869 private fun warn(message: String, file: VirtualFile?) {
871 LOG.warn(message + "; file: " + file.path)
880 fun resetExcludedFromCommitMarkers() {
881 ApplicationManager.getApplication().assertIsWriteThread()
883 val documents = mutableListOf<Document>()
885 for (data in trackers.values) {
886 val tracker = data.tracker
887 if (tracker is ChangelistsLocalLineStatusTracker) {
888 tracker.resetExcludedFromCommitMarkers()
889 documents.add(tracker.document)
893 for (document in documents) {
894 checkIfTrackerCanBeReleased(document)
901 internal fun collectPartiallyChangedFilesStates(): List<ChangelistsLocalLineStatusTracker.FullState> {
902 ApplicationManager.getApplication().assertIsWriteThread()
903 val result = mutableListOf<ChangelistsLocalLineStatusTracker.FullState>()
905 for (data in trackers.values) {
906 val tracker = data.tracker
907 if (tracker is ChangelistsLocalLineStatusTracker) {
908 val hasPartialChanges = tracker.affectedChangeListsIds.size > 1
909 if (hasPartialChanges) {
910 result.add(tracker.storeTrackerState())
919 private fun restoreTrackersForPartiallyChangedFiles(trackerStates: List<ChangelistsLocalLineStatusTracker.State>) {
922 if (isDisposed) return@runWriteAction
923 for (state in trackerStates) {
924 val virtualFile = state.virtualFile
925 val document = FileDocumentManager.getInstance().getDocument(virtualFile) ?: continue
927 val provider = getTrackerProvider(virtualFile)
928 if (provider != ChangelistsLocalStatusTrackerProvider) continue
930 switchTracker(virtualFile, document)
932 val tracker = trackers[document]?.tracker
933 if (tracker !is ChangelistsLocalLineStatusTracker) continue
935 val isLoading = loader.hasRequestFor(document)
937 fileStatesAwaitingRefresh.put(state.virtualFile, state)
938 log("State restoration scheduled", virtualFile)
941 val success = tracker.restoreState(state)
942 log("State restored. success - $success", virtualFile)
946 loader.addAfterUpdateRunnable(Runnable {
948 log("State restoration finished", null)
949 fileStatesAwaitingRefresh.clear()
958 internal fun notifyInactiveRangesDamaged(virtualFile: VirtualFile) {
959 ApplicationManager.getApplication().assertIsWriteThread()
960 if (filesWithDamagedInactiveRanges.contains(virtualFile) || virtualFile == FileEditorManagerEx.getInstanceEx(project).currentFile) {
963 filesWithDamagedInactiveRanges.add(virtualFile)
966 private fun showInactiveRangesDamagedNotification() {
967 val currentNotifications = NotificationsManager.getNotificationsManager()
968 .getNotificationsOfType(InactiveRangesDamagedNotification::class.java, project)
970 val lastNotification = currentNotifications.lastOrNull { !it.isExpired }
971 if (lastNotification != null) filesWithDamagedInactiveRanges.addAll(lastNotification.virtualFiles)
973 currentNotifications.forEach { it.expire() }
975 val files = filesWithDamagedInactiveRanges.toSet()
976 filesWithDamagedInactiveRanges.clear()
978 InactiveRangesDamagedNotification(project, files).notify(project)
982 private fun expireInactiveRangesDamagedNotifications() {
983 filesWithDamagedInactiveRanges.clear()
985 val currentNotifications = NotificationsManager.getNotificationsManager()
986 .getNotificationsOfType(InactiveRangesDamagedNotification::class.java, project)
987 currentNotifications.forEach { it.expire() }
990 private class InactiveRangesDamagedNotification(project: Project, val virtualFiles: Set<VirtualFile>)
991 : Notification(VcsNotifier.STANDARD_NOTIFICATION.displayId,
992 AllIcons.Toolwindows.ToolWindowChanges,
995 VcsBundle.getString("lst.inactive.ranges.damaged.notification"),
996 NotificationType.INFORMATION,
999 addAction(NotificationAction.createSimple(
1000 Supplier { VcsBundle.message("action.NotificationAction.InactiveRangesDamagedNotification.text.view.changes") },
1002 val defaultList = ChangeListManager.getInstance(project).defaultChangeList
1003 val changes = defaultList.changes.filter { virtualFiles.contains(it.virtualFile) }
1005 val window = getToolWindowFor(project, LOCAL_CHANGES)
1006 window?.activate { ChangesViewManager.getInstance(project).selectChanges(changes) }
1014 fun waitUntilBaseContentsLoaded() {
1015 assert(ApplicationManager.getApplication().isUnitTestMode)
1017 if (ApplicationManager.getApplication().isDispatchThread) {
1018 UIUtil.dispatchAllInvocationEvents()
1021 val semaphore = Semaphore()
1024 loader.addAfterUpdateRunnable(Runnable {
1028 val start = System.currentTimeMillis()
1030 if (ApplicationManager.getApplication().isDispatchThread) {
1031 UIUtil.dispatchAllInvocationEvents()
1033 if (semaphore.waitFor(10)) {
1036 if (System.currentTimeMillis() - start > 10000) {
1037 loader.dumpInternalState()
1038 System.err.println(ThreadDumper.dumpThreadsToString())
1039 throw IllegalStateException("Couldn't await base contents")
1045 fun releaseAllTrackers() {
1046 synchronized(LOCK) {
1047 forcedDocuments.clear()
1049 for (data in trackers.values) {
1050 unregisterTrackerInCLM(data)
1051 data.tracker.release()
1060 * Single threaded queue with the following properties:
1061 * - Ignores duplicated requests (the first queued is used).
1062 * - Allows to check whether request is scheduled or is waiting for completion.
1063 * - Notifies callbacks when queue is exhausted.
1065 private abstract class SingleThreadLoader<Request, T> : Disposable {
1066 private val LOG = Logger.getInstance(SingleThreadLoader::class.java)
1067 private val LOCK: Any = Any()
1069 private val taskQueue = ArrayDeque<Request>()
1070 private val waitingForRefresh = HashSet<Request>()
1072 private val callbacksWaitingUpdateCompletion = ArrayList<Runnable>()
1074 private var isScheduled: Boolean = false
1075 private var isDisposed: Boolean = false
1076 private var lastFuture: Future<*>? = null
1079 protected abstract fun loadRequest(request: Request): Result<T>
1082 protected abstract fun handleResult(request: Request, result: Result<T>)
1086 fun scheduleRefresh(request: Request) {
1087 if (isDisposed) return
1089 synchronized(LOCK) {
1090 if (taskQueue.contains(request)) return
1091 taskQueue.add(request)
1098 override fun dispose() {
1099 val callbacks = mutableListOf<Runnable>()
1100 synchronized(LOCK) {
1103 waitingForRefresh.clear()
1104 lastFuture?.cancel(true)
1106 callbacks += callbacksWaitingUpdateCompletion
1107 callbacksWaitingUpdateCompletion.clear()
1110 executeCallbacks(callbacksWaitingUpdateCompletion)
1114 protected fun hasRequest(condition: (Request) -> Boolean): Boolean {
1115 synchronized(LOCK) {
1116 return taskQueue.any(condition) ||
1117 waitingForRefresh.any(condition)
1122 fun addAfterUpdateRunnable(task: Runnable) {
1123 val updateScheduled = putRunnableIfUpdateScheduled(task)
1124 if (updateScheduled) return
1126 runInEdt(ModalityState.any()) {
1127 if (!putRunnableIfUpdateScheduled(task)) {
1133 private fun putRunnableIfUpdateScheduled(task: Runnable): Boolean {
1134 synchronized(LOCK) {
1135 if (taskQueue.isEmpty() && waitingForRefresh.isEmpty()) return false
1136 callbacksWaitingUpdateCompletion.add(task)
1142 private fun schedule() {
1143 if (isDisposed) return
1145 synchronized(LOCK) {
1146 if (isScheduled) return
1147 if (taskQueue.isEmpty()) return
1150 lastFuture = ApplicationManager.getApplication().executeOnPooledThread {
1151 BackgroundTaskUtil.runUnderDisposeAwareIndicator(this, Runnable {
1158 private fun handleRequests() {
1160 val request = synchronized(LOCK) {
1161 val request = taskQueue.poll()
1163 if (isDisposed || request == null) {
1168 waitingForRefresh.add(request)
1169 return@synchronized request
1172 handleSingleRequest(request)
1176 private fun handleSingleRequest(request: Request) {
1177 val result: Result<T> = try {
1178 loadRequest(request)
1180 catch (e: ProcessCanceledException) {
1183 catch (e: Throwable) {
1188 runInEdt(ModalityState.any()) {
1190 synchronized(LOCK) {
1191 waitingForRefresh.remove(request)
1194 handleResult(request, result)
1197 notifyTrackerRefreshed()
1203 private fun notifyTrackerRefreshed() {
1204 if (isDisposed) return
1206 val callbacks = mutableListOf<Runnable>()
1207 synchronized(LOCK) {
1208 if (taskQueue.isEmpty() && waitingForRefresh.isEmpty()) {
1209 callbacks += callbacksWaitingUpdateCompletion
1210 callbacksWaitingUpdateCompletion.clear()
1214 executeCallbacks(callbacks)
1218 private fun executeCallbacks(callbacks: List<Runnable>) {
1219 for (callback in callbacks) {
1223 catch (e: ProcessCanceledException) {
1225 catch (e: Throwable) {
1232 fun dumpInternalState() {
1233 synchronized(LOCK) {
1234 LOG.debug("isScheduled - $isScheduled")
1235 LOG.debug("pending callbacks: ${callbacksWaitingUpdateCompletion.size}")
1238 LOG.debug("pending task: ${it}")
1240 waitingForRefresh.forEach {
1241 LOG.debug("waiting refresh: ${it}")
1247 private sealed class Result<T> {
1248 class Success<T>(val data: T) : Result<T>()
1249 class Canceled<T> : Result<T>()
1250 class Error<T> : Result<T>()
1253 private object ChangelistsLocalStatusTrackerProvider : BaseRevisionStatusTrackerContentLoader() {
1254 override fun isTrackedFile(project: Project, file: VirtualFile): Boolean {
1255 if (!LineStatusTrackerManager.getInstance(project).arePartialChangelistsEnabled(file)) return false
1256 if (!super.isTrackedFile(project, file)) return false
1258 val status = FileStatusManager.getInstance(project).getStatus(file)
1259 if (status != FileStatus.MODIFIED &&
1260 status != ChangelistConflictFileStatusProvider.MODIFIED_OUTSIDE &&
1261 status != FileStatus.NOT_CHANGED) return false
1263 val change = ChangeListManager.getInstance(project).getChange(file)
1264 return change != null && change.javaClass == Change::class.java &&
1265 (change.type == Change.Type.MODIFICATION || change.type == Change.Type.MOVED) &&
1266 change.afterRevision is CurrentContentRevision
1269 override fun isMyTracker(tracker: LocalLineStatusTracker<*>): Boolean = tracker is ChangelistsLocalLineStatusTracker
1271 override fun createTracker(project: Project, file: VirtualFile): LocalLineStatusTracker<*>? {
1272 val document = FileDocumentManager.getInstance().getDocument(file) ?: return null
1273 return ChangelistsLocalLineStatusTracker.createTracker(project, document, file)
1277 private object DefaultLocalStatusTrackerProvider : BaseRevisionStatusTrackerContentLoader() {
1278 override fun isMyTracker(tracker: LocalLineStatusTracker<*>): Boolean = tracker is SimpleLocalLineStatusTracker
1280 override fun createTracker(project: Project, file: VirtualFile): LocalLineStatusTracker<*>? {
1281 val document = FileDocumentManager.getInstance().getDocument(file) ?: return null
1282 return SimpleLocalLineStatusTracker.createTracker(project, document, file)
1286 private abstract class BaseRevisionStatusTrackerContentLoader : LineStatusTrackerContentLoader {
1287 override fun isTrackedFile(project: Project, file: VirtualFile): Boolean {
1288 if (!VcsFileStatusProvider.getInstance(project).isSupported(file)) return false
1290 val status = FileStatusManager.getInstance(project).getStatus(file)
1291 if (status == FileStatus.ADDED ||
1292 status == FileStatus.DELETED ||
1293 status == FileStatus.UNKNOWN ||
1294 status == FileStatus.IGNORED) {
1300 override fun getContentInfo(project: Project, file: VirtualFile): ContentInfo? {
1301 val baseContent = VcsFileStatusProvider.getInstance(project).getBaseRevision(file) ?: return null
1302 return BaseRevisionContentInfo(baseContent, file.charset)
1305 override fun shouldBeUpdated(oldInfo: ContentInfo?, newInfo: ContentInfo): Boolean {
1306 newInfo as BaseRevisionContentInfo
1307 return oldInfo == null ||
1308 oldInfo !is BaseRevisionContentInfo ||
1309 oldInfo.baseContent.revisionNumber != newInfo.baseContent.revisionNumber ||
1310 oldInfo.baseContent.revisionNumber == VcsRevisionNumber.NULL ||
1311 oldInfo.charset != newInfo.charset
1314 override fun loadContent(project: Project, info: ContentInfo): BaseRevisionContent? {
1315 info as BaseRevisionContentInfo
1316 val lastUpToDateContent = info.baseContent.loadContent() ?: return null
1317 val correctedText = StringUtil.convertLineSeparators(lastUpToDateContent)
1318 return BaseRevisionContent(correctedText)
1321 override fun setLoadedContent(tracker: LocalLineStatusTracker<*>, content: TrackerContent) {
1322 tracker as LocalLineStatusTrackerImpl<*>
1323 content as BaseRevisionContent
1324 tracker.setBaseRevision(content.text)
1327 override fun handleLoadingError(tracker: LocalLineStatusTracker<*>) {
1328 tracker as LocalLineStatusTrackerImpl<*>
1329 tracker.dropBaseRevision()
1332 private class BaseRevisionContentInfo(val baseContent: VcsBaseContentProvider.BaseContent, val charset: Charset) : ContentInfo
1333 private class BaseRevisionContent(val text: CharSequence) : TrackerContent
1337 interface LocalLineStatusTrackerProvider {
1338 fun isTrackedFile(project: Project, file: VirtualFile): Boolean
1339 fun isMyTracker(tracker: LocalLineStatusTracker<*>): Boolean
1340 fun createTracker(project: Project, file: VirtualFile): LocalLineStatusTracker<*>?
1343 val EP_NAME: ExtensionPointName<LocalLineStatusTrackerProvider> =
1344 ExtensionPointName.create("com.intellij.openapi.vcs.impl.LocalLineStatusTrackerProvider")
1348 interface LineStatusTrackerContentLoader : LocalLineStatusTrackerProvider {
1349 fun getContentInfo(project: Project, file: VirtualFile): ContentInfo?
1350 fun shouldBeUpdated(oldInfo: ContentInfo?, newInfo: ContentInfo): Boolean
1351 fun loadContent(project: Project, info: ContentInfo): TrackerContent?
1353 fun setLoadedContent(tracker: LocalLineStatusTracker<*>, content: TrackerContent)
1354 fun handleLoadingError(tracker: LocalLineStatusTracker<*>)
1356 interface ContentInfo
1357 interface TrackerContent