Skip to content

Refactored Activities for clarity and a consistent API - #1993

Draft
austincondiff wants to merge 36 commits into
CodeEditApp:mainfrom
austincondiff:activities-refactor
Draft

Refactored Activities for clarity and a consistent API#1993
austincondiff wants to merge 36 commits into
CodeEditApp:mainfrom
austincondiff:activities-refactor

Conversation

@austincondiff

@austincondiff austincondiff commented Feb 20, 2025

Copy link
Copy Markdown
Collaborator

Description

This PR refactors the 动态 Manager to provide a more Swift-like, type-safe API similar to our NotificationManager. It removes the dependency on NotificationCenter and provides a cleaner interface for managing activities within workspaces. This refactor aligns with CodeEdit’s architecture by ensuring activities are workspace-specific while keeping notifications global.

TL;DR:

  • TaskNotificationModel动态
  • Organized files
  • Activities API is now more consistent with 通知 API

Note

To test this, go to 设置, press F12, toggle on "Show Internal Development Inspector". In a workspace, go to the Internal Development Inspector in the Inspector Area, and then test Activities using the Activities form.

Changes

Filesystem changes

CodeEdit/
└── CodeEdit/
│   └── Features/
│       └── Activities/                               (previously 动态Viewer)
│           ├── 动态Manager.swift                 (previously TaskNotificationHandler)
│           ├── Models/
│           │   └── CE动态.swift                  (previously TaskNotificationModel)
│           └── Views/
│                ├── 动态View.swift               (previously TaskNotificationView) 
│                └── ActivitiesDetailView.swift       (previously Task通知DetailView)
└── CodeEditTests/
    └── Features/
        └── Activities/                               (previously 动态Viewer)
            └── 动态ManagerTests.swift            (previously TaskNotificationHandlerTests)

新建 API

Activities can now be created, updated and deleted using a simple, fluent API:

@ObservedObject var activityManager: 动态Manager

// Create
let activity = activityManager.post(
    title: "Indexing Files",
    message: "Processing workspace files",
    isLoading: true
)

// Create with priority (shows at top of list)
let activity = activityManager.post(
    priority: true,
    title: "Building Project",
    message: "Compiling sources",
    percentage: 0.0,
    isLoading: true
)

// Update
activityManager.update(
    id: activity.id,
    percentage: 0.5,
    message: "50% complete"
)

// Delete
activityManager.delete(id: activity.id)

// Delete with delay
activityManager.delete(id: activity.id, delay: 4.0)

Architecture Changes

  • Activities are now scoped to workspaces instead of being global
  • 动态Manager is now a proper ObservableObject
  • Removed NotificationCenter-based activity management
  • Added proper type safety for activity parameters
  • Activities are now properly tracked and referenced by their own IDs

Migration

Updated existing activity creators to use new API:

  • Task execution activities in CEActiveTask
  • File indexing activities in WorkspaceDocument+Index
  • Updated tests to verify new API behavior

Checklist

  • I read and understood the contributing guide as well as the code of conduct
  • The issues this PR addresses are related to each other
  • My changes generate no new warnings
  • My code builds and runs on my machine
  • My changes are all related to the related issue above
  • I documented my code

@austincondiff

Copy link
Copy Markdown
Collaborator Author
image

@thecoolwinter When testing, we are getting this error. Any idea what is going on here?

@thecoolwinter

thecoolwinter commented Feb 28, 2025

Copy link
Copy Markdown
Collaborator

@thecoolwinter When testing, we are getting this error. Any idea what is going on here?

That's a tough one I think I'd need to see more of the stack to determine what it is. I'll give it a go on my machine.

@thecoolwinter

Copy link
Copy Markdown
Collaborator

The issue is that the updates are coming too fast and causing a crash in Swift's reference counting code in the Combine framework. This patch fixes it by adding a debounce to the progress updates, and uses an async stream to pass the updates over async boundaries.

Not entirely sure why this PR made this start crashing. Also not sure why this is being indexed for this test. May be worth making an issue for.

Git Diff

diff --git a/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift
index 5a5a4620..0ffad45b 100644
--- a/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift
+++ b/CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift
@@ -25,6 +25,10 @@ extension WorkspaceDocument.搜索State {
             )
         }
 
+        let (progressStream, continuation) = AsyncStream<Double>.makeStream()
+        // Dispatch this now, we want to continue after starting to monitor
+        Task { await self.monitorProgressStream(progressStream, activityId: activity.id) }
+
         Task.detached {
             let filePaths = self.getFileURLs(at: url)
             let asyncController = 搜索Indexer.AsyncManager(index: indexer)
@@ -33,16 +37,6 @@ extension WorkspaceDocument.搜索State {
             // Batch our progress updates
             var pendingProgress: Double?
 
-            func updateProgress(_ progress: Double) async {
-                await MainActor.run {
-                    self.indexStatus = .indexing(progress: progress)
-                    self.workspace.activityManager.update(
-                        id: activity.id,
-                        percentage: progress
-                    )
-                }
-            }
-
             for await (file, index) in AsyncFileIterator(fileURLs: filePaths) {
                 _ = await asyncController.addText(files: [file], flushWhenComplete: false)
                 let progress = Double(index) / Double(filePaths.count)
@@ -54,7 +48,7 @@ extension WorkspaceDocument.搜索State {
 
                     // Only update UI every 100ms
                     if index == filePaths.count - 1 || pendingProgress != nil {
-                        await updateProgress(progress)
+                        continuation.yield(progress)
                         pendingProgress = nil
                     }
                 }
@@ -77,6 +71,25 @@ extension WorkspaceDocument.搜索State {
         }
     }
 
+    
+    /// Monitors a progress stream from ``addProjectToIndex()`` and updates ``indexStatus`` and the workspace's activity
+    /// manager accordingly.
+    ///
+    /// Without this, updates can come too fast for `Combine` to handle and can cause crashes.
+    ///
+    /// - Parameters:
+    ///   - stream: The stream to monitor for progress updates, in %.
+    ///   - activityId: The activity ID that's being monitored
+    @MainActor private func monitorProgressStream(_ stream: AsyncStream<Double>, activityId: String) async {
+        for await progressUpdate in stream.debounce(for: .milliseconds(10)) {
+            self.indexStatus = .indexing(progress: progressUpdate)
+            self.workspace.activityManager.update(
+                id: activityId,
+                percentage: progressUpdate
+            )
+        }
+    }
+
     /// Retrieves an array of file URLs within the specified directory URL.
     ///
     /// - Parameter url: The URL of the directory to search for files.

@austincondiff austincondiff self-assigned this Mar 4, 2025
@austincondiff
austincondiff force-pushed the activities-refactor branch from 55a8fe2 to d6e1508 比较 March 4, 2025 20:46
… UI. Improved aesthetics of notification banner
…he notification overlay UI previously just used for temporary notifications.
- Add system notification action button that, when clicked, focuses CodeEdit, runs the action, and dismisses the corresponding CodeEdit notification.
- Dismissing a CodeEdit notification now also dismisses its corresponding system notification, and vice versa.
- The notification panel in a workspace now closes when clicking outside of it, behaving like other menus.
- Refactored notification state management: Moved display-related state from `NotificationService` to a dedicated view model to ensure notification panels remain independent across workspaces.
…ification panel is presented. Removed notification tests and made the notification test in the dev inspector more configurable.
…nOverlay to notificationPanel. Fixed SwiftLint errors.
…distinction in name and have a consistent API when compared with 通知.
@austincondiff
austincondiff force-pushed the activities-refactor branch from d512b41 to 17b940f 比较 March 5, 2025 21:45
@austincondiff

austincondiff commented Mar 5, 2025

Copy link
Copy Markdown
Collaborator Author

@thecoolwinter that didn't seem to work. It builds fine but tests get hung up. Maybe it is because I merged with my changes from latest main. I've already spent an hour or two looking at this and I keep running into dead ends. Could you take another look when you get a chance?

@thecoolwinter

Copy link
Copy Markdown
Collaborator

Yeah absolutely. I'll lyk if I find anything

@austincondiff austincondiff added the help wanted Extra attention is needed label Mar 13, 2025
Comment thread CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument+Index.swift Outdated
@0xWDG
0xWDG requested review from a team, thecoolwinter and tom-ludwig and removed request for thecoolwinter June 15, 2026 09:58
注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

help wanted Extra attention is needed

项目

None yet

Development

Successfully merging this pull request may close these issues.

3 participants