Make tests less noisy (#12463)

Mikayla Maki created

When running the tests for linux, I found a lot of benign errors getting
logged. This PR cuts down some of the noise from unnecessary workspace
serialization and SVG renders

Release Notes:

- N/A

Change summary

crates/assets/src/assets.rs                           |  9 +
crates/audio/src/assets.rs                            |  7 +
crates/collab/src/tests/editor_tests.rs               | 12 --
crates/collab/src/tests/test_server.rs                | 16 ---
crates/collab_ui/src/channel_view.rs                  |  6 +
crates/diagnostics/src/diagnostics.rs                 |  2 
crates/editor/src/editor.rs                           |  2 
crates/editor/src/items.rs                            | 11 +
crates/editor/src/scroll.rs                           |  7 
crates/extensions_ui/src/extensions_ui.rs             |  2 
crates/file_icons/src/file_icons.rs                   |  9 -
crates/gpui/examples/animation.rs                     |  7 +
crates/gpui/src/assets.rs                             | 11 -
crates/gpui/src/platform.rs                           |  4 
crates/gpui/src/platform/blade/blade_atlas.rs         | 12 +-
crates/gpui/src/platform/mac/metal_atlas.rs           | 12 +-
crates/gpui/src/platform/test/window.rs               | 23 ++--
crates/gpui/src/svg_renderer.rs                       |  8 +
crates/gpui/src/window.rs                             | 49 ++++++----
crates/image_viewer/src/image_viewer.rs               | 26 +++--
crates/language_tools/src/syntax_tree_view.rs         |  2 
crates/recent_projects/src/recent_projects.rs         |  4 
crates/search/src/project_search.rs                   |  2 
crates/semantic_index/src/project_index_debug_view.rs |  2 
crates/storybook/src/assets.rs                        |  3 
crates/storybook/src/storybook.rs                     |  5 
crates/terminal_view/src/terminal_panel.rs            |  6 
crates/terminal_view/src/terminal_view.rs             | 39 ++++----
crates/theme/src/registry.rs                          |  4 
crates/theme_importer/src/assets.rs                   |  3 
crates/welcome/src/welcome.rs                         |  2 
crates/workspace/src/item.rs                          | 13 +-
crates/workspace/src/shared_screen.rs                 |  2 
crates/workspace/src/workspace.rs                     | 54 ++++++++----
crates/zed/src/main.rs                                |  2 
crates/zed/src/zed.rs                                 | 10 +
36 files changed, 216 insertions(+), 172 deletions(-)

Detailed changes

crates/assets/src/assets.rs 🔗

@@ -16,9 +16,9 @@ use rust_embed::RustEmbed;
 pub struct Assets;
 
 impl AssetSource for Assets {
-    fn load(&self, path: &str) -> Result<std::borrow::Cow<'static, [u8]>> {
+    fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
         Self::get(path)
-            .map(|f| f.data)
+            .map(|f| Some(f.data))
             .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
     }
 
@@ -42,7 +42,10 @@ impl Assets {
         let mut embedded_fonts = Vec::new();
         for font_path in font_paths {
             if font_path.ends_with(".ttf") {
-                let font_bytes = cx.asset_source().load(&font_path)?;
+                let font_bytes = cx
+                    .asset_source()
+                    .load(&font_path)?
+                    .expect("Assets should never return None");
                 embedded_fonts.push(font_bytes);
             }
         }

crates/audio/src/assets.rs 🔗

@@ -41,7 +41,12 @@ impl SoundRegistry {
         }
 
         let path = format!("sounds/{}.wav", name);
-        let bytes = self.assets.load(&path)?.into_owned();
+        let bytes = self
+            .assets
+            .load(&path)?
+            .map(|asset| Ok(asset))
+            .unwrap_or_else(|| Err(anyhow::anyhow!("No such asset available")))?
+            .into_owned();
         let cursor = Cursor::new(bytes);
         let source = Decoder::new(cursor)?.convert_samples::<f32>().buffered();
 

crates/collab/src/tests/editor_tests.rs 🔗

@@ -42,7 +42,7 @@ use std::{
     },
 };
 use text::Point;
-use workspace::{Workspace, WorkspaceId};
+use workspace::Workspace;
 
 #[gpui::test(iterations = 10)]
 async fn test_host_disconnect(
@@ -85,14 +85,8 @@ async fn test_host_disconnect(
 
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
-    let workspace_b = cx_b.add_window(|cx| {
-        Workspace::new(
-            WorkspaceId::default(),
-            project_b.clone(),
-            client_b.app_state.clone(),
-            cx,
-        )
-    });
+    let workspace_b = cx_b
+        .add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
     let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
     let workspace_b_view = workspace_b.root_view(cx_b).unwrap();
 

crates/collab/src/tests/test_server.rs 🔗

@@ -42,7 +42,7 @@ use std::{
         Arc,
     },
 };
-use workspace::{Workspace, WorkspaceId, WorkspaceStore};
+use workspace::{Workspace, WorkspaceStore};
 
 pub struct TestServer {
     pub app_state: Arc<AppState>,
@@ -906,12 +906,7 @@ impl TestClient {
     ) -> (View<Workspace>, &'a mut VisualTestContext) {
         cx.add_window_view(|cx| {
             cx.activate_window();
-            Workspace::new(
-                WorkspaceId::default(),
-                project.clone(),
-                self.app_state.clone(),
-                cx,
-            )
+            Workspace::new(None, project.clone(), self.app_state.clone(), cx)
         })
     }
 
@@ -922,12 +917,7 @@ impl TestClient {
         let project = self.build_test_project(cx).await;
         cx.add_window_view(|cx| {
             cx.activate_window();
-            Workspace::new(
-                WorkspaceId::default(),
-                project.clone(),
-                self.app_state.clone(),
-                cx,
-            )
+            Workspace::new(None, project.clone(), self.app_state.clone(), cx)
         })
     }
 

crates/collab_ui/src/channel_view.rs 🔗

@@ -400,7 +400,11 @@ impl Item for ChannelView {
         None
     }
 
-    fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<View<Self>> {
+    fn clone_on_split(
+        &self,
+        _: Option<WorkspaceId>,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<View<Self>> {
         Some(cx.new_view(|cx| {
             Self::new(
                 self.project.clone(),

crates/diagnostics/src/diagnostics.rs 🔗

@@ -704,7 +704,7 @@ impl Item for ProjectDiagnosticsEditor {
 
     fn clone_on_split(
         &self,
-        _workspace_id: workspace::WorkspaceId,
+        _workspace_id: Option<workspace::WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>>
     where

crates/editor/src/editor.rs 🔗

@@ -484,7 +484,7 @@ pub struct Editor {
     current_line_highlight: CurrentLineHighlight,
     collapse_matches: bool,
     autoindent_mode: Option<AutoindentMode>,
-    workspace: Option<(WeakView<Workspace>, WorkspaceId)>,
+    workspace: Option<(WeakView<Workspace>, Option<WorkspaceId>)>,
     keymap_context_layers: BTreeMap<TypeId, KeyContext>,
     input_enabled: bool,
     use_modal_editing: bool,

crates/editor/src/items.rs 🔗

@@ -657,7 +657,7 @@ impl Item for Editor {
 
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Option<View<Editor>>
     where
@@ -846,9 +846,12 @@ impl Item for Editor {
     }
 
     fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
-        let workspace_id = workspace.database_id();
-        let item_id = cx.view().item_id().as_u64() as ItemId;
         self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
+        let Some(workspace_id) = workspace.database_id() else {
+            return;
+        };
+
+        let item_id = cx.view().item_id().as_u64() as ItemId;
 
         fn serialize(
             buffer: Model<Buffer>,
@@ -873,7 +876,7 @@ impl Item for Editor {
             serialize(buffer.clone(), workspace_id, item_id, cx);
 
             cx.subscribe(&buffer, |this, buffer, event, cx| {
-                if let Some((_, workspace_id)) = this.workspace.as_ref() {
+                if let Some((_, Some(workspace_id))) = this.workspace.as_ref() {
                     if let language::Event::FileHandleChanged = event {
                         serialize(
                             buffer,

crates/editor/src/scroll.rs 🔗

@@ -389,7 +389,8 @@ impl Editor {
         cx: &mut ViewContext<Self>,
     ) {
         hide_hover(self, cx);
-        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
+        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
+
         self.scroll_manager.set_scroll_position(
             scroll_position,
             &display_map,
@@ -409,7 +410,7 @@ impl Editor {
 
     pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
         hide_hover(self, cx);
-        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
+        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
         let top_row = scroll_anchor
             .anchor
             .to_point(&self.buffer().read(cx).snapshot(cx))
@@ -424,7 +425,7 @@ impl Editor {
         cx: &mut ViewContext<Self>,
     ) {
         hide_hover(self, cx);
-        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
+        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
         let snapshot = &self.buffer().read(cx).snapshot(cx);
         if !scroll_anchor.anchor.is_valid(snapshot) {
             log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -992,7 +992,7 @@ impl Item for ExtensionsPage {
 
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         _: &mut ViewContext<Self>,
     ) -> Option<View<Self>> {
         None

crates/file_icons/src/file_icons.rs 🔗

@@ -38,11 +38,10 @@ impl FileIcons {
     pub fn new(assets: impl AssetSource) -> Self {
         assets
             .load("icons/file_icons/file_types.json")
-            .and_then(|file| {
-                serde_json::from_str::<FileIcons>(str::from_utf8(&file).unwrap())
-                    .map_err(Into::into)
-            })
-            .unwrap_or_else(|_| FileIcons {
+            .ok()
+            .flatten()
+            .and_then(|file| serde_json::from_str::<FileIcons>(str::from_utf8(&file).unwrap()).ok())
+            .unwrap_or_else(|| FileIcons {
                 stems: HashMap::default(),
                 suffixes: HashMap::default(),
                 types: HashMap::default(),

crates/gpui/examples/animation.rs 🔗

@@ -5,8 +5,11 @@ use gpui::*;
 struct Assets {}
 
 impl AssetSource for Assets {
-    fn load(&self, path: &str) -> Result<std::borrow::Cow<'static, [u8]>> {
-        std::fs::read(path).map(Into::into).map_err(Into::into)
+    fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
+        std::fs::read(path)
+            .map(Into::into)
+            .map_err(Into::into)
+            .map(|result| Some(result))
     }
 
     fn list(&self, path: &str) -> Result<Vec<SharedString>> {

crates/gpui/src/assets.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{size, DevicePixels, Result, SharedString, Size};
-use anyhow::anyhow;
+
 use image::{Bgra, ImageBuffer};
 use std::{
     borrow::Cow,
@@ -11,18 +11,15 @@ use std::{
 /// A source of assets for this app to use.
 pub trait AssetSource: 'static + Send + Sync {
     /// Load the given asset from the source path.
-    fn load(&self, path: &str) -> Result<Cow<'static, [u8]>>;
+    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>>;
 
     /// List the assets at the given path.
     fn list(&self, path: &str) -> Result<Vec<SharedString>>;
 }
 
 impl AssetSource for () {
-    fn load(&self, path: &str) -> Result<Cow<'static, [u8]>> {
-        Err(anyhow!(
-            "load called on empty asset provider with \"{}\"",
-            path
-        ))
+    fn load(&self, _path: &str) -> Result<Option<Cow<'static, [u8]>>> {
+        Ok(None)
     }
 
     fn list(&self, _path: &str) -> Result<Vec<SharedString>> {

crates/gpui/src/platform.rs 🔗

@@ -344,8 +344,8 @@ pub(crate) trait PlatformAtlas: Send + Sync {
     fn get_or_insert_with<'a>(
         &self,
         key: &AtlasKey,
-        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
-    ) -> Result<AtlasTile>;
+        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
+    ) -> Result<Option<AtlasTile>>;
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]

crates/gpui/src/platform/blade/blade_atlas.rs 🔗

@@ -114,18 +114,20 @@ impl PlatformAtlas for BladeAtlas {
     fn get_or_insert_with<'a>(
         &self,
         key: &AtlasKey,
-        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
-    ) -> Result<AtlasTile> {
+        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
+    ) -> Result<Option<AtlasTile>> {
         let mut lock = self.0.lock();
         if let Some(tile) = lock.tiles_by_key.get(key) {
-            Ok(tile.clone())
+            Ok(Some(tile.clone()))
         } else {
             profiling::scope!("new tile");
-            let (size, bytes) = build()?;
+            let Some((size, bytes)) = build()? else {
+                return Ok(None);
+            };
             let tile = lock.allocate(size, key.texture_kind());
             lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
             lock.tiles_by_key.insert(key.clone(), tile.clone());
-            Ok(tile)
+            Ok(Some(tile))
         }
     }
 }

crates/gpui/src/platform/mac/metal_atlas.rs 🔗

@@ -60,20 +60,22 @@ impl PlatformAtlas for MetalAtlas {
     fn get_or_insert_with<'a>(
         &self,
         key: &AtlasKey,
-        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
-    ) -> Result<AtlasTile> {
+        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
+    ) -> Result<Option<AtlasTile>> {
         let mut lock = self.0.lock();
         if let Some(tile) = lock.tiles_by_key.get(key) {
-            Ok(tile.clone())
+            Ok(Some(tile.clone()))
         } else {
-            let (size, bytes) = build()?;
+            let Some((size, bytes)) = build()? else {
+                return Ok(None);
+            };
             let tile = lock
                 .allocate(size, key.texture_kind())
                 .ok_or_else(|| anyhow!("failed to allocate"))?;
             let texture = lock.texture(tile.texture_id);
             texture.upload(tile.bounds, &bytes);
             lock.tiles_by_key.insert(key.clone(), tile.clone());
-            Ok(tile)
+            Ok(Some(tile))
         }
     }
 }

crates/gpui/src/platform/test/window.rs 🔗

@@ -291,25 +291,26 @@ impl PlatformAtlas for TestAtlas {
     fn get_or_insert_with<'a>(
         &self,
         key: &crate::AtlasKey,
-        build: &mut dyn FnMut() -> anyhow::Result<(
-            Size<crate::DevicePixels>,
-            std::borrow::Cow<'a, [u8]>,
-        )>,
-    ) -> anyhow::Result<crate::AtlasTile> {
+        build: &mut dyn FnMut() -> anyhow::Result<
+            Option<(Size<crate::DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
+        >,
+    ) -> anyhow::Result<Option<crate::AtlasTile>> {
         let mut state = self.0.lock();
         if let Some(tile) = state.tiles.get(key) {
-            return Ok(tile.clone());
+            return Ok(Some(tile.clone()));
         }
+        drop(state);
+
+        let Some((size, _)) = build()? else {
+            return Ok(None);
+        };
 
+        let mut state = self.0.lock();
         state.next_id += 1;
         let texture_id = state.next_id;
         state.next_id += 1;
         let tile_id = state.next_id;
 
-        drop(state);
-        let (size, _) = build()?;
-        let mut state = self.0.lock();
-
         state.tiles.insert(
             key.clone(),
             crate::AtlasTile {
@@ -326,6 +327,6 @@ impl PlatformAtlas for TestAtlas {
             },
         );
 
-        Ok(state.tiles[key].clone())
+        Ok(Some(state.tiles[key].clone()))
     }
 }

crates/gpui/src/svg_renderer.rs 🔗

@@ -24,13 +24,15 @@ impl SvgRenderer {
         Self { asset_source }
     }
 
-    pub fn render(&self, params: &RenderSvgParams) -> Result<Vec<u8>> {
+    pub fn render(&self, params: &RenderSvgParams) -> Result<Option<Vec<u8>>> {
         if params.size.is_zero() {
             return Err(anyhow!("can't render at a zero size"));
         }
 
         // Load the tree.
-        let bytes = self.asset_source.load(&params.path)?;
+        let Some(bytes) = self.asset_source.load(&params.path)? else {
+            return Ok(None);
+        };
 
         let pixmap = self.render_pixmap(&bytes, SvgSize::Size(params.size))?;
 
@@ -40,7 +42,7 @@ impl SvgRenderer {
             .iter()
             .map(|p| p.alpha())
             .collect::<Vec<_>>();
-        Ok(alpha_mask)
+        Ok(Some(alpha_mask))
     }
 
     pub fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {

crates/gpui/src/window.rs 🔗

@@ -2347,13 +2347,14 @@ impl<'a> WindowContext<'a> {
 
         let raster_bounds = self.text_system().raster_bounds(&params)?;
         if !raster_bounds.is_zero() {
-            let tile =
-                self.window
-                    .sprite_atlas
-                    .get_or_insert_with(&params.clone().into(), &mut || {
-                        let (size, bytes) = self.text_system().rasterize_glyph(&params)?;
-                        Ok((size, Cow::Owned(bytes)))
-                    })?;
+            let tile = self
+                .window
+                .sprite_atlas
+                .get_or_insert_with(&params.clone().into(), &mut || {
+                    let (size, bytes) = self.text_system().rasterize_glyph(&params)?;
+                    Ok(Some((size, Cow::Owned(bytes))))
+                })?
+                .expect("Callback above only errors or returns Some");
             let bounds = Bounds {
                 origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
                 size: tile.bounds.size.map(Into::into),
@@ -2410,13 +2411,15 @@ impl<'a> WindowContext<'a> {
 
         let raster_bounds = self.text_system().raster_bounds(&params)?;
         if !raster_bounds.is_zero() {
-            let tile =
-                self.window
-                    .sprite_atlas
-                    .get_or_insert_with(&params.clone().into(), &mut || {
-                        let (size, bytes) = self.text_system().rasterize_glyph(&params)?;
-                        Ok((size, Cow::Owned(bytes)))
-                    })?;
+            let tile = self
+                .window
+                .sprite_atlas
+                .get_or_insert_with(&params.clone().into(), &mut || {
+                    let (size, bytes) = self.text_system().rasterize_glyph(&params)?;
+                    Ok(Some((size, Cow::Owned(bytes))))
+                })?
+                .expect("Callback above only errors or returns Some");
+
             let bounds = Bounds {
                 origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
                 size: tile.bounds.size.map(Into::into),
@@ -2464,13 +2467,18 @@ impl<'a> WindowContext<'a> {
                 .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
         };
 
-        let tile =
+        let Some(tile) =
             self.window
                 .sprite_atlas
                 .get_or_insert_with(&params.clone().into(), &mut || {
-                    let bytes = self.svg_renderer.render(&params)?;
-                    Ok((params.size, Cow::Owned(bytes)))
-                })?;
+                    let Some(bytes) = self.svg_renderer.render(&params)? else {
+                        return Ok(None);
+                    };
+                    Ok(Some((params.size, Cow::Owned(bytes))))
+                })?
+        else {
+            return Ok(());
+        };
         let content_mask = self.content_mask().scale(scale_factor);
 
         self.window
@@ -2513,8 +2521,9 @@ impl<'a> WindowContext<'a> {
             .window
             .sprite_atlas
             .get_or_insert_with(&params.clone().into(), &mut || {
-                Ok((data.size(), Cow::Borrowed(data.as_bytes())))
-            })?;
+                Ok(Some((data.size(), Cow::Borrowed(data.as_bytes()))))
+            })?
+            .expect("Callback above only returns Some");
         let content_mask = self.content_mask().scale(scale_factor);
         let corner_radii = corner_radii.scale(scale_factor);
 

crates/image_viewer/src/image_viewer.rs 🔗

@@ -95,17 +95,19 @@ impl Item for ImageView {
         let workspace_id = workspace.database_id();
         let image_path = self.path.clone();
 
-        cx.background_executor()
-            .spawn({
-                let image_path = image_path.clone();
-                async move {
-                    IMAGE_VIEWER
-                        .save_image_path(item_id, workspace_id, image_path)
-                        .await
-                        .log_err();
-                }
-            })
-            .detach();
+        if let Some(workspace_id) = workspace_id {
+            cx.background_executor()
+                .spawn({
+                    let image_path = image_path.clone();
+                    async move {
+                        IMAGE_VIEWER
+                            .save_image_path(item_id, workspace_id, image_path)
+                            .await
+                            .log_err();
+                    }
+                })
+                .detach();
+        }
     }
 
     fn serialized_item_kind() -> Option<&'static str> {
@@ -133,7 +135,7 @@ impl Item for ImageView {
 
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>>
     where

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -407,7 +407,7 @@ impl Item for SyntaxTreeView {
 
     fn clone_on_split(
         &self,
-        _: workspace::WorkspaceId,
+        _: Option<workspace::WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>>
     where

crates/recent_projects/src/recent_projects.rs 🔗

@@ -287,7 +287,7 @@ impl PickerDelegate for RecentProjectsDelegate {
             };
             workspace
                 .update(cx, |workspace, cx| {
-                    if workspace.database_id() == *candidate_workspace_id {
+                    if workspace.database_id() == Some(*candidate_workspace_id) {
                         Task::ready(Ok(()))
                     } else {
                         match candidate_workspace_location {
@@ -675,7 +675,7 @@ impl RecentProjectsDelegate {
     ) -> bool {
         if let Some(workspace) = self.workspace.upgrade() {
             let workspace = workspace.read(cx);
-            if workspace_id == workspace.database_id() {
+            if Some(workspace_id) == workspace.database_id() {
                 return true;
             }
         }

crates/search/src/project_search.rs 🔗

@@ -456,7 +456,7 @@ impl Item for ProjectSearchView {
 
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>>
     where

crates/storybook/src/assets.rs 🔗

@@ -15,10 +15,11 @@ use rust_embed::RustEmbed;
 pub struct Assets;
 
 impl AssetSource for Assets {
-    fn load(&self, path: &str) -> Result<Cow<'static, [u8]>> {
+    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
         Self::get(path)
             .map(|f| f.data)
             .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+            .map(|data| Some(data))
     }
 
     fn list(&self, path: &str) -> Result<Vec<SharedString>> {

crates/storybook/src/storybook.rs 🔗

@@ -128,7 +128,10 @@ fn load_embedded_fonts(cx: &AppContext) -> gpui::Result<()> {
     let mut embedded_fonts = Vec::new();
     for font_path in font_paths {
         if font_path.ends_with(".ttf") {
-            let font_bytes = cx.asset_source().load(&font_path)?;
+            let font_bytes = cx
+                .asset_source()
+                .load(&font_path)?
+                .expect("Should never be None in the storybook");
             embedded_fonts.push(font_bytes);
         }
     }

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -221,7 +221,9 @@ impl TerminalPanel {
 
         let (panel, pane, items) = workspace.update(&mut cx, |workspace, cx| {
             let panel = cx.new_view(|cx| TerminalPanel::new(workspace, cx));
-            let items = if let Some(serialized_panel) = serialized_panel.as_ref() {
+            let items = if let Some((serialized_panel, database_id)) =
+                serialized_panel.as_ref().zip(workspace.database_id())
+            {
                 panel.update(cx, |panel, cx| {
                     cx.notify();
                     panel.height = serialized_panel.height.map(|h| h.round());
@@ -234,7 +236,7 @@ impl TerminalPanel {
                                 TerminalView::deserialize(
                                     workspace.project().clone(),
                                     workspace.weak_handle(),
-                                    workspace.database_id(),
+                                    database_id,
                                     *item_id,
                                     cx,
                                 )

crates/terminal_view/src/terminal_view.rs 🔗

@@ -91,7 +91,7 @@ pub struct TerminalView {
     blinking_paused: bool,
     blink_epoch: usize,
     can_navigate_to_selected_word: bool,
-    workspace_id: WorkspaceId,
+    workspace_id: Option<WorkspaceId>,
     show_title: bool,
     _subscriptions: Vec<Subscription>,
     _terminal_subscriptions: Vec<Subscription>,
@@ -142,7 +142,7 @@ impl TerminalView {
     pub fn new(
         terminal: Model<Terminal>,
         workspace: WeakView<Workspace>,
-        workspace_id: WorkspaceId,
+        workspace_id: Option<WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let workspace_handle = workspace.clone();
@@ -458,15 +458,16 @@ fn subscribe_for_terminal_events(
                 if terminal.task().is_none() {
                     if let Some(cwd) = terminal.get_cwd() {
                         let item_id = cx.entity_id();
-                        let workspace_id = this.workspace_id;
-                        cx.background_executor()
-                            .spawn(async move {
-                                TERMINAL_DB
-                                    .save_working_directory(item_id.as_u64(), workspace_id, cwd)
-                                    .await
-                                    .log_err();
-                            })
-                            .detach();
+                        if let Some(workspace_id) = this.workspace_id {
+                            cx.background_executor()
+                                .spawn(async move {
+                                    TERMINAL_DB
+                                        .save_working_directory(item_id.as_u64(), workspace_id, cwd)
+                                        .await
+                                        .log_err();
+                                })
+                                .detach();
+                        }
                     }
                 }
             }
@@ -853,7 +854,7 @@ impl Item for TerminalView {
 
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         _cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>> {
         //From what I can tell, there's no  way to tell the current working
@@ -941,20 +942,18 @@ impl Item for TerminalView {
                 project.create_terminal(cwd, None, window, cx)
             })??;
             pane.update(&mut cx, |_, cx| {
-                cx.new_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))
+                cx.new_view(|cx| TerminalView::new(terminal, workspace, Some(workspace_id), cx))
             })
         })
     }
 
     fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
         if self.terminal().read(cx).task().is_none() {
-            cx.background_executor()
-                .spawn(TERMINAL_DB.update_workspace_id(
-                    workspace.database_id(),
-                    self.workspace_id,
-                    cx.entity_id().as_u64(),
-                ))
-                .detach();
+            if let Some((new_id, old_id)) = workspace.database_id().zip(self.workspace_id) {
+                cx.background_executor()
+                    .spawn(TERMINAL_DB.update_workspace_id(new_id, old_id, cx.entity_id().as_u64()))
+                    .detach();
+            }
             self.workspace_id = workspace.database_id();
         }
     }

crates/theme/src/registry.rs 🔗

@@ -1,5 +1,5 @@
-use std::path::Path;
 use std::sync::Arc;
+use std::{fmt::Debug, path::Path};
 
 use anyhow::{anyhow, Context, Result};
 use collections::HashMap;
@@ -226,7 +226,7 @@ impl ThemeRegistry {
             .filter(|path| path.ends_with(".json"));
 
         for path in theme_paths {
-            let Some(theme) = self.assets.load(&path).log_err() else {
+            let Some(theme) = self.assets.load(&path).log_err().flatten() else {
                 continue;
             };
 

crates/theme_importer/src/assets.rs 🔗

@@ -11,10 +11,11 @@ use rust_embed::RustEmbed;
 pub struct Assets;
 
 impl AssetSource for Assets {
-    fn load(&self, path: &str) -> Result<Cow<'static, [u8]>> {
+    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
         Self::get(path)
             .map(|f| f.data)
             .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+            .map(|result| Some(result))
     }
 
     fn list(&self, path: &str) -> Result<Vec<SharedString>> {

crates/welcome/src/welcome.rs 🔗

@@ -313,7 +313,7 @@ impl Item for WelcomePage {
 
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>> {
         Some(cx.new_view(|cx| WelcomePage {

crates/workspace/src/item.rs 🔗

@@ -172,7 +172,7 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
     fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         _: &mut ViewContext<Self>,
     ) -> Option<View<Self>>
     where
@@ -287,7 +287,7 @@ pub trait ItemHandle: 'static + Send {
     fn boxed_clone(&self) -> Box<dyn ItemHandle>;
     fn clone_on_split(
         &self,
-        workspace_id: WorkspaceId,
+        workspace_id: Option<WorkspaceId>,
         cx: &mut WindowContext,
     ) -> Option<Box<dyn ItemHandle>>;
     fn added_to_pane(
@@ -437,7 +437,7 @@ impl<T: Item> ItemHandle for View<T> {
 
     fn clone_on_split(
         &self,
-        workspace_id: WorkspaceId,
+        workspace_id: Option<WorkspaceId>,
         cx: &mut WindowContext,
     ) -> Option<Box<dyn ItemHandle>> {
         self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx))
@@ -528,7 +528,6 @@ impl<T: Item> ItemHandle for View<T> {
                     {
                         pane
                     } else {
-                        log::error!("unexpected item event after pane was dropped");
                         return;
                     };
 
@@ -883,7 +882,7 @@ pub mod test {
     }
 
     pub struct TestItem {
-        pub workspace_id: WorkspaceId,
+        pub workspace_id: Option<WorkspaceId>,
         pub state: String,
         pub label: String,
         pub save_count: usize,
@@ -964,7 +963,7 @@ pub mod test {
 
         pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
             let mut this = Self::new(cx);
-            this.workspace_id = id;
+            this.workspace_id = Some(id);
             this
         }
 
@@ -1081,7 +1080,7 @@ pub mod test {
 
         fn clone_on_split(
             &self,
-            _workspace_id: WorkspaceId,
+            _workspace_id: Option<WorkspaceId>,
             cx: &mut ViewContext<Self>,
         ) -> Option<View<Self>>
         where

crates/workspace/src/shared_screen.rs 🔗

@@ -119,7 +119,7 @@ impl Item for SharedScreen {
 
     fn clone_on_split(
         &self,
-        _workspace_id: WorkspaceId,
+        _workspace_id: Option<WorkspaceId>,
         cx: &mut ViewContext<Self>,
     ) -> Option<View<Self>> {
         let track = self.track.upgrade()?;

crates/workspace/src/workspace.rs 🔗

@@ -588,7 +588,7 @@ pub struct Workspace {
     window_edited: bool,
     active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
-    database_id: WorkspaceId,
+    database_id: Option<WorkspaceId>,
     app_state: Arc<AppState>,
     dispatching_keystrokes: Rc<RefCell<Vec<Keystroke>>>,
     _subscriptions: Vec<Subscription>,
@@ -622,7 +622,7 @@ impl Workspace {
     const MAX_PADDING: f32 = 0.4;
 
     pub fn new(
-        workspace_id: WorkspaceId,
+        workspace_id: Option<WorkspaceId>,
         project: Model<Project>,
         app_state: Arc<AppState>,
         cx: &mut ViewContext<Self>,
@@ -795,13 +795,15 @@ impl Workspace {
                         if let Some(display) = cx.display() {
                             if let Some(display_uuid) = display.uuid().log_err() {
                                 let window_bounds = cx.window_bounds();
-                                cx.background_executor()
-                                    .spawn(DB.set_window_open_status(
-                                        workspace_id,
-                                        SerializedWindowBounds(window_bounds),
-                                        display_uuid,
-                                    ))
-                                    .detach_and_log_err(cx);
+                                if let Some(database_id) = workspace_id {
+                                    cx.background_executor()
+                                        .spawn(DB.set_window_open_status(
+                                            database_id,
+                                            SerializedWindowBounds(window_bounds),
+                                            display_uuid,
+                                        ))
+                                        .detach_and_log_err(cx);
+                                }
                             }
                         }
                         this.bounds_save_task_queued.take();
@@ -956,7 +958,12 @@ impl Workspace {
             let window = if let Some(window) = requesting_window {
                 cx.update_window(window.into(), |_, cx| {
                     cx.replace_root_view(|cx| {
-                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+                        Workspace::new(
+                            Some(workspace_id),
+                            project_handle.clone(),
+                            app_state.clone(),
+                            cx,
+                        )
                     });
                 })?;
                 window
@@ -994,7 +1001,7 @@ impl Workspace {
                     move |cx| {
                         cx.new_view(|cx| {
                             let mut workspace =
-                                Workspace::new(workspace_id, project_handle, app_state, cx);
+                                Workspace::new(Some(workspace_id), project_handle, app_state, cx);
                             workspace.centered_layout = centered_layout;
                             workspace
                         })
@@ -3464,9 +3471,12 @@ impl Workspace {
     pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
         if cx.is_window_active() {
             self.update_active_view_for_followers(cx);
-            cx.background_executor()
-                .spawn(persistence::DB.update_timestamp(self.database_id()))
-                .detach();
+
+            if let Some(database_id) = self.database_id {
+                cx.background_executor()
+                    .spawn(persistence::DB.update_timestamp(database_id))
+                    .detach();
+            }
         } else {
             for pane in &self.panes {
                 pane.update(cx, |pane, cx| {
@@ -3506,7 +3516,7 @@ impl Workspace {
         }
     }
 
-    pub fn database_id(&self) -> WorkspaceId {
+    pub fn database_id(&self) -> Option<WorkspaceId> {
         self.database_id
     }
 
@@ -3566,6 +3576,10 @@ impl Workspace {
     }
 
     fn serialize_workspace_internal(&self, cx: &mut WindowContext) -> Task<()> {
+        let Some(database_id) = self.database_id() else {
+            return Task::ready(());
+        };
+
         fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
             let (items, active) = {
                 let pane = pane_handle.read(cx);
@@ -3701,7 +3715,7 @@ impl Workspace {
             let docks = build_serialized_docks(self, cx);
             let window_bounds = Some(SerializedWindowBounds(cx.window_bounds()));
             let serialized_workspace = SerializedWorkspace {
-                id: self.database_id,
+                id: database_id,
                 location,
                 center_group,
                 window_bounds,
@@ -3944,9 +3958,11 @@ impl Workspace {
 
     pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
         self.centered_layout = !self.centered_layout;
-        cx.background_executor()
-            .spawn(DB.set_centered_layout(self.database_id, self.centered_layout))
-            .detach_and_log_err(cx);
+        if let Some(database_id) = self.database_id() {
+            cx.background_executor()
+                .spawn(DB.set_centered_layout(database_id, self.centered_layout))
+                .detach_and_log_err(cx);
+        }
         cx.notify();
     }
 

crates/zed/src/main.rs 🔗

@@ -832,7 +832,7 @@ fn load_embedded_fonts(cx: &AppContext) {
             }
 
             scope.spawn(async {
-                let font_bytes = asset_source.load(font_path).unwrap();
+                let font_bytes = asset_source.load(font_path).unwrap().unwrap();
                 embedded_fonts.lock().push(font_bytes);
             });
         }

crates/zed/src/zed.rs 🔗

@@ -3043,8 +3043,14 @@ mod tests {
     fn test_bundled_settings_and_themes(cx: &mut AppContext) {
         cx.text_system()
             .add_fonts(vec![
-                Assets.load("fonts/zed-sans/zed-sans-extended.ttf").unwrap(),
-                Assets.load("fonts/zed-mono/zed-mono-extended.ttf").unwrap(),
+                Assets
+                    .load("fonts/zed-sans/zed-sans-extended.ttf")
+                    .unwrap()
+                    .unwrap(),
+                Assets
+                    .load("fonts/zed-mono/zed-mono-extended.ttf")
+                    .unwrap()
+                    .unwrap(),
             ])
             .unwrap();
         let themes = ThemeRegistry::default();