native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
6.7 kB · 224 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225import Foundation
struct SidebarPresentation: Equatable { var items: [SidebarItem]
static func showsListHeader(repositoryCount: Int) -> Bool { true }
var repositoryOrderIDs: [Repository.ID] { items.compactMap(\.repositoryOrderID) }
func repositoryOrderAfterMove( fromOffsets source: IndexSet, toOffset destination: Int ) -> [Repository.ID] { var orderedIDs = repositoryOrderIDs orderedIDs.moveElements(fromOffsets: source, toOffset: destination) return orderedIDs }}
enum SidebarItem: Equatable, Identifiable { case listHeader(SidebarListHeaderModel) case repository(SidebarRepositoryContainerModel) case failedRepository(FailedRepositoryModel) case archivedWorktrees(ArchivedWorktreesRowModel)
var id: SidebarPresentationItemID { switch self { case .listHeader: return .listHeader case .repository(let model): return .repository(model.id) case .failedRepository(let model): return .failedRepository(model.id) case .archivedWorktrees: return .archivedWorktrees } }
var repositoryOrderID: Repository.ID? { switch self { case .repository(let model): return model.id case .failedRepository(let model) where model.isReorderable: return model.id case .listHeader, .failedRepository, .archivedWorktrees: return nil } }}
enum SidebarPresentationItemID: Equatable, Hashable { case listHeader case repository(Repository.ID) case failedRepository(Repository.ID) case archivedWorktrees}
enum SidebarScrollID: Equatable, Hashable { case repository(Repository.ID) case worktree(Worktree.ID) case archivedWorktrees}
struct SidebarListHeaderModel: Equatable, Identifiable { let id = SidebarPresentationItemID.listHeader var repositoryCount: Int}
struct SidebarRepositoryContainerModel: Equatable, Identifiable { var id: Repository.ID { repositoryID }
var repositoryID: Repository.ID var title: String var rootURL: URL var kind: Repository.Kind var isExpanded: Bool var isRemoving: Bool var isWorkspace: Bool var worktreeSections: WorktreeRowSections var workspaceChildRows: [WorkspaceChildRowModel]}
/// A display-only sidebar row for one workspace child repository. `branchName`/// is the live current branch (falling back to metadata); `info` carries the/// uncommitted diff counts and PR, mirroring a worktree row's badges.struct WorkspaceChildRowModel: Equatable, Identifiable { let id: String let repositoryName: String let branchName: String? let info: WorktreeInfoEntry? let workingDirectory: URL}
struct FailedRepositoryModel: Equatable, Identifiable { var id: Repository.ID var name: String var path: String var failureMessage: String var isGitUnavailable: Bool = false var isReorderable: Bool}
struct ArchivedWorktreesRowModel: Equatable, Identifiable { let id = SidebarPresentationItemID.archivedWorktrees var count: Int}
enum SidebarWorktreeSection: Equatable { case pinned case unpinned}
struct SidebarWorktreeDropTarget: Equatable { var repositoryID: Repository.ID var section: SidebarWorktreeSection var source: IndexSet var destination: Int
var action: RepositoriesFeature.WorktreeOrderingAction { switch section { case .pinned: return .pinnedWorktreesMoved(repositoryID: repositoryID, source, destination) case .unpinned: return .unpinnedWorktreesMoved(repositoryID: repositoryID, source, destination) } }}
extension RepositoriesFeature.State { func sidebarPresentation( expandedRepositoryIDs: Set<Repository.ID>, includesArchivedWorktreesRow: Bool = false ) -> SidebarPresentation { let repositoriesByID = Dictionary( repositories.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first } ) let roots = sidebarPresentationRoots() let repositoryCount = roots.count var items: [SidebarItem] = []
if SidebarPresentation.showsListHeader(repositoryCount: repositoryCount) { items.append(.listHeader(SidebarListHeaderModel(repositoryCount: repositoryCount))) }
for rootURL in roots { let standardizedRootURL = rootURL.standardizedFileURL let repositoryID = standardizedRootURL.path(percentEncoded: false) if let failureMessage = loadFailuresByID[repositoryID] { let path = standardizedRootURL.path(percentEncoded: false) items.append( .failedRepository( FailedRepositoryModel( id: repositoryID, name: Repository.name(for: standardizedRootURL), path: path, failureMessage: failureMessage, isGitUnavailable: gitUnavailableRepositoryIDs.contains(repositoryID), isReorderable: true ) ) ) } else if let repository = repositoriesByID[repositoryID] { let isExpanded = expandedRepositoryIDs.contains(repository.id) items.append( .repository( SidebarRepositoryContainerModel( repositoryID: repository.id, title: repository.name, rootURL: repository.rootURL, kind: repository.kind, isExpanded: isExpanded, isRemoving: isRemovingRepository(repository), isWorkspace: repository.isWorkspace, worktreeSections: isExpanded ? worktreeRowSections(in: repository) : .empty, workspaceChildRows: isExpanded && repository.isWorkspace ? workspaceChildRows(in: repository) : [] ) ) ) } }
if includesArchivedWorktreesRow, !archivedWorktrees.isEmpty { items.append(.archivedWorktrees(ArchivedWorktreesRowModel(count: archivedWorktrees.count))) }
return SidebarPresentation(items: items) }
private func sidebarPresentationRoots() -> [URL] { let orderedRoots = orderedRepositoryRoots() if !orderedRoots.isEmpty { return orderedRoots } return repositories.map(\.rootURL) }}
extension WorktreeRowSections { static let empty = WorktreeRowSections( main: nil, pinned: [], pending: [], unpinned: [] )}
extension Array { fileprivate mutating func moveElements(fromOffsets source: IndexSet, toOffset destination: Int) { let sourceIndexes = source.sorted() let movedElements = sourceIndexes.map { self[$0] } for index in sourceIndexes.reversed() { remove(at: index) } let removedBeforeDestination = sourceIndexes.count(where: { $0 < destination }) insert(contentsOf: movedElements, at: destination - removedBeforeDestination) }}