terminal: Preserve terminal order and fix pinned count on workspace restore (#44464)

Vladislav and Kirill Bulatov created

Replace `FuturesUnordered` with `FuturesOrdered` to maintain terminal
tab ordering when restoring a workspace.
Also clamp `pinned_count` to `items_len()` to prevent panics when fewer
terminals are restored than expected.

The changes:
1. Order preservation: `FuturesUnordered` → `FuturesOrdered` ensures
terminals restore in their original order
2. Bounds fix: `.min(pane.items_len())` prevents setting pinned count
higher than actual items

Closes #44463

Release Notes:

- Preserved terminal order and fixed pinned count on workspace restore

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>

Change summary

crates/terminal_view/src/persistence.rs | 43 +++++++++++---------------
1 file changed, 19 insertions(+), 24 deletions(-)

Detailed changes

crates/terminal_view/src/persistence.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::Result;
 use async_recursion::async_recursion;
 use collections::HashSet;
-use futures::{StreamExt as _, stream::FuturesUnordered};
+use futures::future::join_all;
 use gpui::{AppContext as _, AsyncWindowContext, Axis, Entity, Task, WeakEntity};
 use project::Project;
 use serde::{Deserialize, Serialize};
@@ -242,7 +242,7 @@ async fn deserialize_pane_group(
 
                     let items = pane.update_in(cx, |pane, window, cx| {
                         populate_pane_items(pane, new_items, active_item, window, cx);
-                        pane.set_pinned_count(pinned_count);
+                        pane.set_pinned_count(pinned_count.min(pane.items_len()));
                         pane.items_len()
                     });
                     // Avoid blank panes in splits
@@ -290,30 +290,25 @@ fn deserialize_terminal_views(
     item_ids: &[u64],
     cx: &mut AsyncWindowContext,
 ) -> impl Future<Output = Vec<Entity<TerminalView>>> + use<> {
-    let mut deserialized_items = item_ids
-        .iter()
-        .map(|item_id| {
-            cx.update(|window, cx| {
-                TerminalView::deserialize(
-                    project.clone(),
-                    workspace.clone(),
-                    workspace_id,
-                    *item_id,
-                    window,
-                    cx,
-                )
-            })
-            .unwrap_or_else(|e| Task::ready(Err(e.context("no window present"))))
+    let deserialized_items = join_all(item_ids.iter().filter_map(|item_id| {
+        cx.update(|window, cx| {
+            TerminalView::deserialize(
+                project.clone(),
+                workspace.clone(),
+                workspace_id,
+                *item_id,
+                window,
+                cx,
+            )
         })
-        .collect::<FuturesUnordered<_>>();
+        .ok()
+    }));
     async move {
-        let mut items = Vec::with_capacity(deserialized_items.len());
-        while let Some(item) = deserialized_items.next().await {
-            if let Some(item) = item.log_err() {
-                items.push(item);
-            }
-        }
-        items
+        deserialized_items
+            .await
+            .into_iter()
+            .filter_map(|item| item.log_err())
+            .collect()
     }
 }