diff --git a/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/FrontendCommitChangesViewWithToolbarPanel.kt b/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/FrontendCommitChangesViewWithToolbarPanel.kt index d54d2b4111a8..728df362f8d3 100644 --- a/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/FrontendCommitChangesViewWithToolbarPanel.kt +++ b/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/FrontendCommitChangesViewWithToolbarPanel.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.vcs.changes.LocalChangesListView import com.intellij.openapi.vcs.changes.ui.ChangesListView import com.intellij.platform.project.projectId import com.intellij.platform.vcs.impl.shared.changes.ChangeListsViewModel +import com.intellij.platform.vcs.impl.shared.changes.ChangesViewSettings import com.intellij.platform.vcs.impl.shared.rpc.BackendChangesViewEvent import com.intellij.platform.vcs.impl.shared.rpc.ChangesViewApi import fleet.rpc.client.durable @@ -25,15 +26,19 @@ internal class FrontendCommitChangesViewWithToolbarPanel( ) : CommitChangesViewWithToolbarPanel(changesView, cs) { init { changesView.setInclusionModel(inclusionModel) - ChangeListsViewModel.getInstance(project).changeLists.onEach { scheduleRefresh() }.launchIn(cs) + ChangeListsViewModel.getInstance(project).changeListsState.onEach { scheduleRefresh() }.launchIn(cs) cs.launch { subscribeToBackendEvents() } } override fun getModelData(): ModelData { - val changeLists = ChangeListsViewModel.getInstance(project).changeLists.value - return ModelData(changeLists.lists, emptyList(), emptyList()) { true } + val changeListsState = ChangeListsViewModel.getInstance(project).changeListsState.value + return ModelData( + changeLists = changeListsState.changeLists, + unversionedFiles = changeListsState.unversionedFiles, + ignoredFiles = changeListsState.ignoredFiles.takeIf { ChangesViewSettings.getInstance(project).showIgnored }.orEmpty(), + ) { true } } override fun synchronizeInclusion(changeLists: List, unversionedFiles: List) { diff --git a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt index 4c0b9d0b94b6..e209697b0e09 100644 --- a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt +++ b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt @@ -4,6 +4,7 @@ package com.intellij.platform.vcs.impl.shared.changes import com.intellij.openapi.components.Service import com.intellij.openapi.components.service 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.LocalChangeList import com.intellij.platform.project.ProjectId @@ -32,17 +33,25 @@ class ChangeListsViewModel( }.stateIn(cs, SharingStarted.Eagerly, ChangeListManagerState.Updating(ChangeListManagerState.FileHoldersState(true, true))) - val changeLists: StateFlow = changeListsApiFlow { api, projectId -> - emitAll(api.getChangeLists(projectId).map { dtos -> - val changeLists = dtos.map { it.getChangeList(project) } - ChangeLists( - changeLists, - changeLists.flatMap { it.changes }.associateBy { ChangeId.getId(it) } - ) - }) + val changeListsState: StateFlow = changeListsApiFlow { api, projectId -> + emitAll( + combine( + api.getChangeLists(projectId), + api.getUnversionedFiles(projectId), + api.getIgnoredFiles(projectId) + ) { changeListDtos, unversionedDtos, ignoredDtos -> + val changeLists = changeListDtos.map { it.getChangeList(project) } + ChangeLists( + changeLists = changeLists, + changesIdMapping = changeLists.flatMap { it.changes }.associateBy { ChangeId.getId(it) }, + unversionedFiles = unversionedDtos.map { it.filePath }, + ignoredFiles = ignoredDtos.map { it.filePath } + ) + } + ) }.stateIn(cs, SharingStarted.Eagerly, ChangeLists.EMPTY) - fun resolveChange(changeId: ChangeId): Change? = changeLists.value.changes[changeId] + fun resolveChange(changeId: ChangeId): Change? = changeListsState.value.changesIdMapping[changeId] private fun changeListsApiFlow(checkRegistry: Boolean = true, flowProducer: suspend FlowCollector.(ChangeListsApi, ProjectId) -> Unit): Flow = if (checkRegistry && !RdLocalChanges.isEnabled()) emptyFlow() @@ -60,11 +69,13 @@ class ChangeListsViewModel( } class ChangeLists( - val lists: List, - val changes: Map + val changeLists: List, + val changesIdMapping: Map, + val unversionedFiles: List, + val ignoredFiles: List, ) { companion object { - val EMPTY = ChangeLists(emptyList(), emptyMap()) + val EMPTY = ChangeLists(emptyList(), emptyMap(), emptyList(), emptyList()) } } } \ No newline at end of file diff --git a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/rpc/ChangeListsApi.kt b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/rpc/ChangeListsApi.kt index 3ca898fc0627..e26fc21c1598 100644 --- a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/rpc/ChangeListsApi.kt +++ b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/rpc/ChangeListsApi.kt @@ -19,6 +19,10 @@ interface ChangeListsApi : RemoteApi { suspend fun getChangeLists(projectId: ProjectId): Flow> + suspend fun getUnversionedFiles(projectId: ProjectId): Flow> + + suspend fun getIgnoredFiles(projectId: ProjectId): Flow> + companion object { suspend fun getInstance(): ChangeListsApi = RemoteApiProviderService.Companion.resolve(remoteApiDescriptor()) } diff --git a/platform/vcs-impl/src/com/intellij/vcs/changes/ChangeListsApiImpl.kt b/platform/vcs-impl/src/com/intellij/vcs/changes/ChangeListsApiImpl.kt index 22f720f50ea3..f0d8bab00a17 100644 --- a/platform/vcs-impl/src/com/intellij/vcs/changes/ChangeListsApiImpl.kt +++ b/platform/vcs-impl/src/com/intellij/vcs/changes/ChangeListsApiImpl.kt @@ -1,10 +1,12 @@ // 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.vcs.changes +import com.intellij.openapi.vcs.FilePath import com.intellij.openapi.vcs.changes.* import com.intellij.platform.project.ProjectId import com.intellij.platform.vcs.changes.ChangeListManagerState import com.intellij.platform.vcs.impl.shared.rpc.* +import com.intellij.util.asDisposable import com.intellij.vcs.rpc.ProjectScopeRpcHelper.getProjectScoped import com.intellij.vcs.rpc.ProjectScopeRpcHelper.projectScopedCallbackFlow import kotlinx.coroutines.channels.BufferOverflow @@ -38,6 +40,27 @@ internal class ChangeListsApiImpl : ChangeListsApi { send(changeListManager.changeLists.map { ChangeListEqualityWrapper(it) }) }.distinctUntilChanged().map { wrappers -> wrappers.map { it.changeList.toDto() } } + override suspend fun getUnversionedFiles(projectId: ProjectId): Flow> = + observeUnchangedFiles(projectId) { it.unversionedFilesPaths } + + override suspend fun getIgnoredFiles(projectId: ProjectId): Flow> = + observeUnchangedFiles(projectId) { it.ignoredFilePaths } + + private suspend fun observeUnchangedFiles(projectId: ProjectId, onUpdate: (ChangeListManager) -> List): Flow> = + projectScopedCallbackFlow(projectId) { project, _ -> + val changeListManager = ChangeListManager.getInstance(project) + changeListManager.addChangeListListener(object : ChangeListAdapter() { + override fun unchangedFileStatusChanged(upToDate: Boolean) { + if (upToDate) { + launch { + send(onUpdate(changeListManager)) + } + } + } + }, this@projectScopedCallbackFlow.asDisposable()) + send(onUpdate(changeListManager)) + }.distinctUntilChanged().map { filePaths -> filePaths.map { FilePathDto.toDto(it) } } + private fun LocalChangeList.toDto(): ChangeListDto = ChangeListDto( name = name, comment = comment,