[vcs] IJPL-173924 Make ChangesViewUtil shared

GitOrigin-RevId: 9909eb2268d2dfde1cd702eab648d7bc70eeea85
This commit is contained in:
Ilia.Shulgin
2025-10-20 19:19:33 +00:00
committed by intellij-monorepo-bot
parent abdcf1913d
commit b653201351
7 changed files with 49 additions and 54 deletions
@@ -4,8 +4,6 @@ package com.intellij.platform.vcs.impl.frontend.changes
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.*
import com.intellij.openapi.vcs.changes.ui.ChangesGroupingPolicyFactory
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder
import com.intellij.platform.vcs.impl.shared.changes.ChangeListsViewModel
import com.intellij.ui.split.SplitComponentBinding
import com.intellij.ui.split.SplitComponentProvider
@@ -21,11 +19,11 @@ internal class ChangesViewSplitComponentProvider : SplitComponentProvider<Change
val panel = CommitChangesViewWithToolbarPanel(tree, scope)
panel.id = modelId
panel.initPanel(object : CommitChangesViewWithToolbarPanel.ModelProvider {
override fun getModel(grouping: ChangesGroupingPolicyFactory): CommitChangesViewWithToolbarPanel.ModelProvider.ExtendedTreeModel {
override fun getModelData(): CommitChangesViewWithToolbarPanel.ModelProvider.ModelData {
val changeLists = ChangeListsViewModel.getInstance(project).changeLists.value
return CommitChangesViewWithToolbarPanel.ModelProvider.ExtendedTreeModel(
changeLists.lists, emptyList(), buildModel(project, grouping, changeLists)
)
return CommitChangesViewWithToolbarPanel.ModelProvider.ModelData(changeLists.lists,
emptyList(),
emptyList()) { true }
}
override fun synchronizeInclusion(changeLists: List<LocalChangeList>, unversionedFiles: List<FilePath>) {
@@ -40,10 +38,4 @@ internal class ChangesViewSplitComponentProvider : SplitComponentProvider<Change
return panel
}
private fun buildModel(
project: Project,
grouping: ChangesGroupingPolicyFactory,
lists: ChangeListsViewModel.ChangeLists
) = TreeModelBuilder(project, grouping).setChangeLists(lists.lists, false, null).build()
}
@@ -1,54 +1,41 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.vcs.changes
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.InitialVfsRefreshService
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.ui.ChangeNodeDecorator
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserUnversionedLoadingPendingNode
import com.intellij.openapi.vcs.changes.ui.ChangesGroupingPolicyFactory
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder
import com.intellij.platform.vcs.impl.shared.changes.ChangeListsViewModel
import com.intellij.platform.vcs.impl.shared.changes.TreeModelBuilderEx
import com.intellij.platform.vcs.impl.shared.commit.PartialCommitChangeNodeDecorator
import org.jetbrains.annotations.ApiStatus
import javax.swing.tree.DefaultTreeModel
// TODO make shared
private typealias DecoratorProvider = (ChangeNodeDecorator) -> ChangeNodeDecorator
@ApiStatus.Internal
object ChangesViewUtil {
private fun getChangeDecoratorProvider(project: Project, isAllowExcludeFromCommit: () -> Boolean): (ChangeNodeDecorator?) -> PartialCommitChangeNodeDecorator {
return { baseDecorator: ChangeNodeDecorator? -> PartialCommitChangeNodeDecorator(project, baseDecorator, isAllowExcludeFromCommit) }
}
private fun getChangeDecoratorProvider(project: Project, isAllowExcludeFromCommit: () -> Boolean): DecoratorProvider =
{ baseDecorator -> PartialCommitChangeNodeDecorator(project, baseDecorator, isAllowExcludeFromCommit) }
fun createTreeModel(
project: Project,
grouping: ChangesGroupingPolicyFactory,
changeLists: List<LocalChangeList>,
unversionedFiles: List<FilePath>,
showIgnoredFiles: Boolean,
ignoredFiles: List<FilePath>,
isAllowExcludeFromCommit: () -> Boolean
): DefaultTreeModel {
val changeListManager = ChangeListManagerImpl.getInstanceImpl(project)
val shouldShowUntrackedLoading = unversionedFiles.isEmpty() &&
!project.getService(InitialVfsRefreshService::class.java).isInitialVfsRefreshFinished() &&
changeListManager.isUnversionedInUpdateMode
val skipSingleDefaultChangeList = !changeListManager.areChangeListsEnabled()
val skipSingleDefaultChangeList = !ChangeListsViewModel.getInstance(project).areChangeListsEnabled.value
val treeModelBuilder = TreeModelBuilder(project, grouping)
.setChangeLists(changeLists, skipSingleDefaultChangeList, getChangeDecoratorProvider(project, isAllowExcludeFromCommit))
.also { TreeModelBuilderEx.getInstanceOrNull(project)?.modifyTreeModelBuilder(it) }
.also { TreeModelBuilderEx.getInstanceOrNull(project)?.modifyTreeModelBuilder(it, unversionedFiles) }
.setUnversioned(unversionedFiles)
if (showIgnoredFiles) {
val ignoredFilePaths: List<FilePath> = changeListManager.getIgnoredFilePaths()
treeModelBuilder.setIgnored(ignoredFilePaths)
}
if (shouldShowUntrackedLoading) {
treeModelBuilder.insertSubtreeRoot(ChangesBrowserUnversionedLoadingPendingNode())
}
treeModelBuilder.setIgnored(ignoredFiles)
for (extension in ChangesViewModifier.KEY.getExtensions(project)) {
try {
@@ -11,7 +11,6 @@ import com.intellij.openapi.progress.checkCanceled
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.ChangesViewModifier.ChangesViewModifierListener
import com.intellij.openapi.vcs.changes.ui.ChangesGroupingPolicyFactory
import com.intellij.openapi.vcs.changes.ui.ChangesListView
import com.intellij.openapi.vcs.changes.ui.ChangesTree
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder
@@ -34,7 +33,6 @@ import kotlinx.coroutines.flow.collectLatest
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CalledInAny
import java.lang.Runnable
import javax.swing.tree.DefaultTreeModel
import kotlin.time.Duration.Companion.milliseconds
@ApiStatus.Internal
@@ -141,16 +139,25 @@ class CommitChangesViewWithToolbarPanel(
if (!cs.isActive || !project.isInitialized || application.isUnitTestMode) return
val modelProvider = modelProvider ?: return
val model = TRACER.spanBuilder(ChangesView.ChangesViewRefreshBackground.name).use {
modelProvider.getModel(changesView.grouping)
val (modelData, model) = TRACER.spanBuilder(ChangesView.ChangesViewRefreshBackground.name).use {
val modelData = modelProvider.getModelData()
modelData to ChangesViewUtil.createTreeModel(
project,
changesView.grouping,
modelData.changeLists,
modelData.unversionedFiles,
modelData.ignoredFiles,
modelData.isAllowExcludeFromCommit,
)
}
checkCanceled()
withContext(Dispatchers.EDT) {
TRACER.spanBuilder(ChangesView.ChangesViewRefreshEdt.getName()).use {
changesView.updateTreeModel(model.treeModel, ChangesViewTreeStateStrategy())
changesView.updateTreeModel(model, ChangesViewTreeStateStrategy())
checkCanceled()
modelProvider.synchronizeInclusion(model.changeLists, model.unversionedFiles)
modelProvider.synchronizeInclusion(modelData.changeLists, modelData.unversionedFiles)
}
}
}
@@ -191,11 +198,14 @@ class CommitChangesViewWithToolbarPanel(
}
interface ModelProvider {
fun getModel(grouping: ChangesGroupingPolicyFactory): ExtendedTreeModel
fun getModelData(): ModelData
fun synchronizeInclusion(changeLists: List<LocalChangeList>, unversionedFiles: List<FilePath>)
class ExtendedTreeModel(val changeLists: List<LocalChangeList>, val unversionedFiles: List<FilePath>, val treeModel: DefaultTreeModel)
class ModelData(val changeLists: List<LocalChangeList>,
val unversionedFiles: List<FilePath>,
val ignoredFiles: List<FilePath>,
val isAllowExcludeFromCommit: () -> Boolean)
}
interface Initializer {
@@ -22,7 +22,7 @@ class ChangeListsViewModel(
) {
val areChangeListsEnabled: StateFlow<Boolean> = changeListsApiFlow(checkRegistry = false) { api, projectId ->
emitAll(api.areChangeListsEnabled(projectId))
}.stateIn(cs, SharingStarted.Eagerly, false)
}.stateIn(cs, SharingStarted.Eagerly, true)
val changeListManagerState: StateFlow<ChangeListManagerState> = changeListsApiFlow(checkRegistry = false) { api, projectId ->
emitAll(api.getChangeListManagerState(projectId))
@@ -3,6 +3,7 @@ package com.intellij.platform.vcs.impl.shared.changes
import com.intellij.openapi.components.serviceOrNull
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vcs.changes.ui.ChangeListRemoteState
import com.intellij.openapi.vcs.changes.ui.ChangeNodeDecorator
@@ -19,7 +20,7 @@ interface TreeModelBuilderEx {
*/
fun getChangeNodeInChangelistBaseDecorator(listRemoteState: ChangeListRemoteState, change: Change, index: Int): ChangeNodeDecorator?
fun modifyTreeModelBuilder(modelBuilder: TreeModelBuilder)
fun modifyTreeModelBuilder(modelBuilder: TreeModelBuilder, unversionedFiles: List<FilePath>)
companion object {
@JvmStatic
@@ -1,7 +1,9 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.vcs.changes.ui
import com.intellij.openapi.project.InitialVfsRefreshService
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.FileStatus
import com.intellij.openapi.vcs.changes.*
import com.intellij.openapi.vfs.VirtualFile
@@ -22,9 +24,16 @@ internal class BackendChangesViewModelBuilderService(private val project: Projec
): ChangeNodeDecorator =
RemoteStatusChangeNodeDecorator(revisionsCache, listRemoteState, index)
override fun modifyTreeModelBuilder(modelBuilder: TreeModelBuilder) {
override fun modifyTreeModelBuilder(modelBuilder: TreeModelBuilder, unversionedFiles: List<FilePath>) {
val changeListManager = ChangeListManagerImpl.getInstanceImpl(project)
val shouldShowUntrackedLoading = unversionedFiles.isEmpty() &&
!project.getService(InitialVfsRefreshService::class.java).isInitialVfsRefreshFinished() &&
changeListManager.isUnversionedInUpdateMode
if (shouldShowUntrackedLoading) {
modelBuilder.insertSubtreeRoot(ChangesBrowserUnversionedLoadingPendingNode())
}
modelBuilder.setLocallyDeletedPaths(changeListManager.deletedFiles)
.setModifiedWithoutEditing(changeListManager.modifiedWithoutEditing)
.setSwitchedFiles(changeListManager.switchedFilesMap)
@@ -4,9 +4,7 @@ package com.intellij.openapi.vcs.changes
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.CommitChangesViewWithToolbarPanel.ModelProvider.ExtendedTreeModel
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode.UNVERSIONED_FILES_TAG
import com.intellij.openapi.vcs.changes.ui.ChangesGroupingPolicyFactory
import com.intellij.openapi.vcs.changes.ui.ChangesListView
import com.intellij.openapi.vcs.changes.ui.VcsTreeModelData.*
import com.intellij.openapi.vfs.VirtualFile
@@ -107,20 +105,18 @@ internal class BackendLocalCommitChangesViewModel(private val panel: CommitChang
override fun getPreferredFocusableComponent(): JComponent = panel.changesView.preferredFocusedComponent
private inner class ModelProvider : CommitChangesViewWithToolbarPanel.ModelProvider {
override fun getModel(grouping: ChangesGroupingPolicyFactory): ExtendedTreeModel {
override fun getModelData(): CommitChangesViewWithToolbarPanel.ModelProvider.ModelData {
val project = panel.project
val changeListManager = ChangeListManagerImpl.getInstanceImpl(project)
val changeLists = changeListManager.changeLists
val unversionedFiles = changeListManager.unversionedFilesPaths
val treeModel = ChangesViewUtil.createTreeModel(
project,
grouping,
val ignoredFiles = if (ChangesViewSettings.getInstance(project).showIgnored) changeListManager.ignoredFilePaths else emptyList()
return CommitChangesViewWithToolbarPanel.ModelProvider.ModelData(
changeLists,
unversionedFiles,
ChangesViewSettings.getInstance(project).showIgnored
ignoredFiles,
) { commitWorkflowHandler?.isActive == true }
return ExtendedTreeModel(changeLists, unversionedFiles, treeModel)
}
override fun synchronizeInclusion(changeLists: List<LocalChangeList>, unversionedFiles: List<FilePath>) {