[vcs] IJPL-173924 Share state of unversioned and ignored files

GitOrigin-RevId: 1c528682d41244745c4c37281c7d5136451caa4f
This commit is contained in:
Ilia.Shulgin
2025-11-17 08:54:36 +00:00
committed by intellij-monorepo-bot
parent 697e71654d
commit d47ee685c7
4 changed files with 58 additions and 15 deletions
@@ -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<LocalChangeList>, unversionedFiles: List<FilePath>) {
@@ -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<ChangeLists> = 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<ChangeLists> = 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 <T> changeListsApiFlow(checkRegistry: Boolean = true, flowProducer: suspend FlowCollector<T>.(ChangeListsApi, ProjectId) -> Unit): Flow<T> =
if (checkRegistry && !RdLocalChanges.isEnabled()) emptyFlow()
@@ -60,11 +69,13 @@ class ChangeListsViewModel(
}
class ChangeLists(
val lists: List<LocalChangeList>,
val changes: Map<ChangeId, Change>
val changeLists: List<LocalChangeList>,
val changesIdMapping: Map<ChangeId, Change>,
val unversionedFiles: List<FilePath>,
val ignoredFiles: List<FilePath>,
) {
companion object {
val EMPTY = ChangeLists(emptyList(), emptyMap())
val EMPTY = ChangeLists(emptyList(), emptyMap(), emptyList(), emptyList())
}
}
}
@@ -19,6 +19,10 @@ interface ChangeListsApi : RemoteApi<Unit> {
suspend fun getChangeLists(projectId: ProjectId): Flow<List<ChangeListDto>>
suspend fun getUnversionedFiles(projectId: ProjectId): Flow<List<FilePathDto>>
suspend fun getIgnoredFiles(projectId: ProjectId): Flow<List<FilePathDto>>
companion object {
suspend fun getInstance(): ChangeListsApi = RemoteApiProviderService.Companion.resolve(remoteApiDescriptor<ChangeListsApi>())
}
@@ -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<List<FilePathDto>> =
observeUnchangedFiles(projectId) { it.unversionedFilesPaths }
override suspend fun getIgnoredFiles(projectId: ProjectId): Flow<List<FilePathDto>> =
observeUnchangedFiles(projectId) { it.ignoredFilePaths }
private suspend fun observeUnchangedFiles(projectId: ProjectId, onUpdate: (ChangeListManager) -> List<FilePath>): Flow<List<FilePathDto>> =
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,