open new buffer (#11203)

Conrad Irwin created

Release Notes:

- Allow creating new untitled buffers in remote projects

TODO:
- Add a Test
- Fix version number check

Change summary

Cargo.lock                                          |  1 
crates/activity_indicator/Cargo.toml                |  1 
crates/activity_indicator/src/activity_indicator.rs | 43 +++++---
crates/auto_update/src/auto_update.rs               |  6 
crates/collab/src/rpc.rs                            | 47 +++++++++
crates/collab/src/rpc/connection_pool.rs            | 32 ++++++
crates/collab/src/tests/dev_server_tests.rs         | 30 ++++++
crates/collab/src/tests/integration_tests.rs        |  3 
crates/editor/src/editor.rs                         | 72 +++++++++-----
crates/editor/src/editor_tests.rs                   | 12 -
crates/editor/src/git.rs                            | 40 +++----
crates/editor/src/rust_analyzer_ext.rs              | 14 +-
crates/feedback/src/feedback_modal.rs               |  8 -
crates/project/src/project.rs                       | 43 +++++++-
crates/project/src/project_tests.rs                 |  4 
crates/project_panel/src/project_panel.rs           |  4 
crates/rpc/proto/zed.proto                          |  8 +
crates/rpc/src/proto.rs                             |  5 
crates/zed/src/zed.rs                               | 14 +-
19 files changed, 276 insertions(+), 111 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -16,7 +16,6 @@ dependencies = [
  "project",
  "smallvec",
  "ui",
- "util",
  "workspace",
 ]
 

crates/activity_indicator/Cargo.toml 🔗

@@ -23,7 +23,6 @@ language.workspace = true
 project.workspace = true
 smallvec.workspace = true
 ui.workspace = true
-util.workspace = true
 workspace.workspace = true
 
 [dev-dependencies]

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -12,7 +12,6 @@ use project::{LanguageServerProgress, Project};
 use smallvec::SmallVec;
 use std::{cmp::Reverse, fmt::Write, sync::Arc};
 use ui::prelude::*;
-use util::ResultExt;
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
 actions!(activity_indicator, [ShowErrorMessage]);
@@ -82,27 +81,37 @@ impl ActivityIndicator {
             }
         });
 
-        cx.subscribe(&this, move |workspace, _, event, cx| match event {
+        cx.subscribe(&this, move |_, _, event, cx| match event {
             Event::ShowError { lsp_name, error } => {
-                if let Some(buffer) = project
-                    .update(cx, |project, cx| project.create_buffer(error, None, cx))
-                    .log_err()
-                {
-                    buffer.update(cx, |buffer, cx| {
+                let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
+                let project = project.clone();
+                let error = error.clone();
+                let lsp_name = lsp_name.clone();
+                cx.spawn(|workspace, mut cx| async move {
+                    let buffer = create_buffer.await?;
+                    buffer.update(&mut cx, |buffer, cx| {
                         buffer.edit(
-                            [(0..0, format!("Language server error: {}\n\n", lsp_name))],
+                            [(
+                                0..0,
+                                format!("Language server error: {}\n\n{}", lsp_name, error),
+                            )],
                             None,
                             cx,
                         );
-                    });
-                    workspace.add_item_to_active_pane(
-                        Box::new(
-                            cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
-                        ),
-                        None,
-                        cx,
-                    );
-                }
+                    })?;
+                    workspace.update(&mut cx, |workspace, cx| {
+                        workspace.add_item_to_active_pane(
+                            Box::new(cx.new_view(|cx| {
+                                Editor::for_buffer(buffer, Some(project.clone()), cx)
+                            })),
+                            None,
+                            cx,
+                        );
+                    })?;
+
+                    anyhow::Ok(())
+                })
+                .detach();
             }
         })
         .detach();

crates/auto_update/src/auto_update.rs 🔗

@@ -221,9 +221,9 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
                     workspace
                         .update(&mut cx, |workspace, cx| {
                             let project = workspace.project().clone();
-                            let buffer = project
-                                .update(cx, |project, cx| project.create_buffer("", markdown, cx))
-                                .expect("creating buffers on a local workspace always succeeds");
+                            let buffer = project.update(cx, |project, cx| {
+                                project.create_local_buffer("", markdown, cx)
+                            });
                             buffer.update(cx, |buffer, cx| {
                                 buffer.edit([(0..0, body.release_notes)], None, cx)
                             });

crates/collab/src/rpc.rs 🔗

@@ -74,6 +74,8 @@ use tracing::{
 };
 use util::http::IsahcHttpClient;
 
+use self::connection_pool::VersionedMessage;
+
 pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
 
 // kubernetes gives terminated pods 10s to shutdown gracefully. After they're gone, we can clean up old resources.
@@ -465,6 +467,9 @@ impl Server {
             .add_request_handler(user_handler(
                 forward_mutating_project_request::<proto::ApplyCompletionAdditionalEdits>,
             ))
+            .add_request_handler(user_handler(
+                forward_versioned_mutating_project_request::<proto::OpenNewBuffer>,
+            ))
             .add_request_handler(user_handler(
                 forward_mutating_project_request::<proto::ResolveCompletionDocumentation>,
             ))
@@ -505,7 +510,7 @@ impl Server {
                 forward_mutating_project_request::<proto::OnTypeFormatting>,
             ))
             .add_request_handler(user_handler(
-                forward_mutating_project_request::<proto::SaveBuffer>,
+                forward_versioned_mutating_project_request::<proto::SaveBuffer>,
             ))
             .add_request_handler(user_handler(
                 forward_mutating_project_request::<proto::BlameBuffer>,
@@ -2677,11 +2682,51 @@ where
     T: EntityMessage + RequestMessage,
 {
     let project_id = ProjectId::from_proto(request.remote_entity_id());
+
+    let host_connection_id = session
+        .db()
+        .await
+        .host_for_mutating_project_request(project_id, session.connection_id, session.user_id())
+        .await?;
+    let payload = session
+        .peer
+        .forward_request(session.connection_id, host_connection_id, request)
+        .await?;
+    response.send(payload)?;
+    Ok(())
+}
+
+/// forward a project request to the host. These requests are disallowed
+/// for guests.
+async fn forward_versioned_mutating_project_request<T>(
+    request: T,
+    response: Response<T>,
+    session: UserSession,
+) -> Result<()>
+where
+    T: EntityMessage + RequestMessage + VersionedMessage,
+{
+    let project_id = ProjectId::from_proto(request.remote_entity_id());
+
     let host_connection_id = session
         .db()
         .await
         .host_for_mutating_project_request(project_id, session.connection_id, session.user_id())
         .await?;
+    if let Some(host_version) = session
+        .connection_pool()
+        .await
+        .connection(host_connection_id)
+        .map(|c| c.zed_version)
+    {
+        if let Some(min_required_version) = request.required_host_version() {
+            if min_required_version > host_version {
+                return Err(anyhow!(ErrorCode::RemoteUpgradeRequired
+                    .with_tag("required", &min_required_version.to_string())))?;
+            }
+        }
+    }
+
     let payload = session
         .peer
         .forward_request(session.connection_id, host_connection_id, request)

crates/collab/src/rpc/connection_pool.rs 🔗

@@ -21,7 +21,7 @@ struct ConnectedPrincipal {
     connection_ids: HashSet<ConnectionId>,
 }
 
-#[derive(Debug, Serialize)]
+#[derive(Copy, Clone, Debug, Serialize, PartialOrd, PartialEq, Eq, Ord)]
 pub struct ZedVersion(pub SemanticVersion);
 
 impl fmt::Display for ZedVersion {
@@ -34,6 +34,32 @@ impl ZedVersion {
     pub fn can_collaborate(&self) -> bool {
         self.0 >= SemanticVersion::new(0, 129, 2)
     }
+
+    pub fn with_save_as() -> ZedVersion {
+        ZedVersion(SemanticVersion::new(0, 134, 0))
+    }
+}
+
+pub trait VersionedMessage {
+    fn required_host_version(&self) -> Option<ZedVersion> {
+        None
+    }
+}
+
+impl VersionedMessage for proto::SaveBuffer {
+    fn required_host_version(&self) -> Option<ZedVersion> {
+        if self.new_path.is_some() {
+            Some(ZedVersion::with_save_as())
+        } else {
+            None
+        }
+    }
+}
+
+impl VersionedMessage for proto::OpenNewBuffer {
+    fn required_host_version(&self) -> Option<ZedVersion> {
+        Some(ZedVersion::with_save_as())
+    }
 }
 
 #[derive(Serialize)]
@@ -50,6 +76,10 @@ impl ConnectionPool {
         self.channels.clear();
     }
 
+    pub fn connection(&mut self, connection_id: ConnectionId) -> Option<&Connection> {
+        self.connections.get(&connection_id)
+    }
+
     #[instrument(skip(self))]
     pub fn add_connection(
         &mut self,

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

@@ -398,3 +398,33 @@ async fn test_save_as_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::Tes
         "remote\nremote\nremote"
     );
 }
+
+#[gpui::test]
+async fn test_new_file_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
+    let (server, client1) = TestServer::start1(cx1).await;
+
+    // Creating a project with a path that does exist should not fail
+    let (dev_server, remote_workspace) =
+        create_remote_project(&server, client1.app_state.clone(), cx1, cx2).await;
+
+    let mut cx = VisualTestContext::from_window(remote_workspace.into(), cx1);
+
+    cx.simulate_keystrokes("cmd-n");
+    cx.simulate_input("new!");
+    cx.simulate_keystrokes("cmd-shift-s");
+    cx.simulate_input("2.txt");
+    cx.simulate_keystrokes("enter");
+
+    cx.executor().run_until_parked();
+
+    let title = remote_workspace
+        .update(&mut cx, |ws, cx| {
+            ws.active_item(cx).unwrap().tab_description(0, &cx).unwrap()
+        })
+        .unwrap();
+
+    assert_eq!(title, "2.txt");
+
+    let path = Path::new("/remote/2.txt");
+    assert_eq!(dev_server.fs().load(&path).await.unwrap(), "new!");
+}

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

@@ -2450,7 +2450,8 @@ async fn test_propagate_saves_and_fs_changes(
     });
 
     let new_buffer_a = project_a
-        .update(cx_a, |p, cx| p.create_buffer("", None, cx))
+        .update(cx_a, |p, cx| p.create_buffer(cx))
+        .await
         .unwrap();
 
     let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());

crates/editor/src/editor.rs 🔗

@@ -99,7 +99,7 @@ use project::{
     CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath, ProjectTransaction,
 };
 use rand::prelude::*;
-use rpc::proto::*;
+use rpc::{proto::*, ErrorExt};
 use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
 use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
@@ -131,7 +131,7 @@ use ui::{
 };
 use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::item::{ItemHandle, PreviewTabsSettings};
-use workspace::notifications::NotificationId;
+use workspace::notifications::{DetachAndPromptErr, NotificationId};
 use workspace::{
     searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
 };
@@ -1610,18 +1610,27 @@ impl Editor {
         cx: &mut ViewContext<Workspace>,
     ) {
         let project = workspace.project().clone();
-        if project.read(cx).is_remote() {
-            cx.propagate();
-        } else if let Some(buffer) = project
-            .update(cx, |project, cx| project.create_buffer("", None, cx))
-            .log_err()
-        {
-            workspace.add_item_to_active_pane(
-                Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
-                None,
-                cx,
-            );
-        }
+        let create = project.update(cx, |project, cx| project.create_buffer(cx));
+
+        cx.spawn(|workspace, mut cx| async move {
+            let buffer = create.await?;
+            workspace.update(&mut cx, |workspace, cx| {
+                workspace.add_item_to_active_pane(
+                    Box::new(
+                        cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
+                    ),
+                    None,
+                    cx,
+                )
+            })
+        })
+        .detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
+            ErrorCode::RemoteUpgradeRequired => Some(format!(
+                "The remote instance of Zed does not support this yet. It must be upgraded to {}",
+                e.error_tag("required").unwrap_or("the latest version")
+            )),
+            _ => None,
+        });
     }
 
     pub fn new_file_in_direction(
@@ -1630,18 +1639,29 @@ impl Editor {
         cx: &mut ViewContext<Workspace>,
     ) {
         let project = workspace.project().clone();
-        if project.read(cx).is_remote() {
-            cx.propagate();
-        } else if let Some(buffer) = project
-            .update(cx, |project, cx| project.create_buffer("", None, cx))
-            .log_err()
-        {
-            workspace.split_item(
-                action.0,
-                Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
-                cx,
-            );
-        }
+        let create = project.update(cx, |project, cx| project.create_buffer(cx));
+        let direction = action.0;
+
+        cx.spawn(|workspace, mut cx| async move {
+            let buffer = create.await?;
+            workspace.update(&mut cx, move |workspace, cx| {
+                workspace.split_item(
+                    direction,
+                    Box::new(
+                        cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
+                    ),
+                    cx,
+                )
+            })?;
+            anyhow::Ok(())
+        })
+        .detach_and_prompt_err("Failed to create buffer", cx, |e, _| match e.error_code() {
+            ErrorCode::RemoteUpgradeRequired => Some(format!(
+                "The remote instance of Zed does not support this yet. It must be upgraded to {}",
+                e.error_tag("required").unwrap_or("the latest version")
+            )),
+            _ => None,
+        });
     }
 
     pub fn replica_id(&self, cx: &AppContext) -> ReplicaId {

crates/editor/src/editor_tests.rs 🔗

@@ -7356,9 +7356,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 
     let buffer = project.update(cx, |project, cx| {
-        let buffer = project
-            .create_buffer(&sample_text(16, 8, 'a'), None, cx)
-            .unwrap();
+        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
         cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
     });
     let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
@@ -7565,12 +7563,8 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
 
     let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
         (
-            project
-                .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
-                .unwrap(),
-            project
-                .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
-                .unwrap(),
+            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
+            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
         )
     });
 

crates/editor/src/git.rs 🔗

@@ -108,10 +108,9 @@ mod tests {
         let project = Project::test(fs, [], cx).await;
 
         // buffer has two modified hunks with two rows each
-        let buffer_1 = project
-            .update(cx, |project, cx| {
-                project.create_buffer(
-                    "
+        let buffer_1 = project.update(cx, |project, cx| {
+            project.create_local_buffer(
+                "
                         1.zero
                         1.ONE
                         1.TWO
@@ -120,13 +119,12 @@ mod tests {
                         1.FIVE
                         1.six
                     "
-                    .unindent()
-                    .as_str(),
-                    None,
-                    cx,
-                )
-            })
-            .unwrap();
+                .unindent()
+                .as_str(),
+                None,
+                cx,
+            )
+        });
         buffer_1.update(cx, |buffer, cx| {
             buffer.set_diff_base(
                 Some(
@@ -146,10 +144,9 @@ mod tests {
         });
 
         // buffer has a deletion hunk and an insertion hunk
-        let buffer_2 = project
-            .update(cx, |project, cx| {
-                project.create_buffer(
-                    "
+        let buffer_2 = project.update(cx, |project, cx| {
+            project.create_local_buffer(
+                "
                         2.zero
                         2.one
                         2.two
@@ -158,13 +155,12 @@ mod tests {
                         2.five
                         2.six
                     "
-                    .unindent()
-                    .as_str(),
-                    None,
-                    cx,
-                )
-            })
-            .unwrap();
+                .unindent()
+                .as_str(),
+                None,
+                cx,
+            )
+        });
         buffer_2.update(cx, |buffer, cx| {
             buffer.set_diff_base(
                 Some(

crates/editor/src/rust_analyzer_ext.rs 🔗

@@ -97,15 +97,19 @@ pub fn expand_macro_recursively(
             return Ok(());
         }
 
-        let buffer = project.update(&mut cx, |project, cx| {
-            project.create_buffer(&macro_expansion.expansion, Some(rust_language), cx)
-        })??;
+        let buffer = project
+            .update(&mut cx, |project, cx| project.create_buffer(cx))?
+            .await?;
         workspace.update(&mut cx, |workspace, cx| {
-            let buffer = cx.new_model(|cx| {
+            buffer.update(cx, |buffer, cx| {
+                buffer.edit([(0..0, macro_expansion.expansion)], None, cx);
+                buffer.set_language(Some(rust_language), cx)
+            });
+            let multibuffer = cx.new_model(|cx| {
                 MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
             });
             workspace.add_item_to_active_pane(
-                Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
+                Box::new(cx.new_view(|cx| Editor::for_multibuffer(multibuffer, Some(project), cx))),
                 None,
                 cx,
             );

crates/feedback/src/feedback_modal.rs 🔗

@@ -142,11 +142,9 @@ impl FeedbackModal {
 
             cx.spawn(|workspace, mut cx| async move {
                 let markdown = markdown.await.log_err();
-                let buffer = project
-                    .update(&mut cx, |project, cx| {
-                        project.create_buffer("", markdown, cx)
-                    })?
-                    .expect("creating buffers on a local workspace always succeeds");
+                let buffer = project.update(&mut cx, |project, cx| {
+                    project.create_local_buffer("", markdown, cx)
+                })?;
 
                 workspace.update(&mut cx, |workspace, cx| {
                     let system_specs = SystemSpecs::new(cx);

crates/project/src/project.rs 🔗

@@ -665,6 +665,7 @@ impl Project {
         client.add_model_request_handler(Self::handle_open_buffer_for_symbol);
         client.add_model_request_handler(Self::handle_open_buffer_by_id);
         client.add_model_request_handler(Self::handle_open_buffer_by_path);
+        client.add_model_request_handler(Self::handle_open_new_buffer);
         client.add_model_request_handler(Self::handle_save_buffer);
         client.add_model_message_handler(Self::handle_update_diff_base);
         client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
@@ -1955,21 +1956,41 @@ impl Project {
         !self.is_local()
     }
 
-    pub fn create_buffer(
+    pub fn create_buffer(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<Model<Buffer>>> {
+        if self.is_remote() {
+            let create = self.client.request(proto::OpenNewBuffer {
+                project_id: self.remote_id().unwrap(),
+            });
+            cx.spawn(|this, mut cx| async move {
+                let response = create.await?;
+                let buffer_id = BufferId::new(response.buffer_id)?;
+
+                this.update(&mut cx, |this, cx| {
+                    this.wait_for_remote_buffer(buffer_id, cx)
+                })?
+                .await
+            })
+        } else {
+            Task::ready(Ok(self.create_local_buffer("", None, cx)))
+        }
+    }
+
+    pub fn create_local_buffer(
         &mut self,
         text: &str,
         language: Option<Arc<Language>>,
         cx: &mut ModelContext<Self>,
-    ) -> Result<Model<Buffer>> {
+    ) -> Model<Buffer> {
         if self.is_remote() {
-            return Err(anyhow!("creating buffers as a guest is not supported yet"));
+            panic!("called create_local_buffer on a remote project")
         }
         let buffer = cx.new_model(|cx| {
             Buffer::local(text, cx)
                 .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
         });
-        self.register_buffer(&buffer, cx)?;
-        Ok(buffer)
+        self.register_buffer(&buffer, cx)
+            .expect("creating local buffers always succeeds");
+        buffer
     }
 
     pub fn open_path(
@@ -9415,6 +9436,18 @@ impl Project {
         Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
     }
 
+    async fn handle_open_new_buffer(
+        this: Model<Self>,
+        envelope: TypedEnvelope<proto::OpenNewBuffer>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::OpenBufferResponse> {
+        let buffer = this.update(&mut cx, |this, cx| this.create_local_buffer("", None, cx))?;
+        let peer_id = envelope.original_sender_id()?;
+
+        Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
+    }
+
     fn respond_to_open_buffer_request(
         this: Model<Self>,
         buffer: Model<Buffer>,

crates/project/src/project_tests.rs 🔗

@@ -2931,9 +2931,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
     let languages = project.update(cx, |project, _| project.languages().clone());
     languages.add(rust_lang());
 
-    let buffer = project.update(cx, |project, cx| {
-        project.create_buffer("", None, cx).unwrap()
-    });
+    let buffer = project.update(cx, |project, cx| project.create_local_buffer("", None, cx));
     buffer.update(cx, |buffer, cx| {
         buffer.edit([(0..0, "abc")], None, cx);
         assert!(buffer.is_dirty());

crates/project_panel/src/project_panel.rs 🔗

@@ -3394,7 +3394,9 @@ mod tests {
             })
             .unwrap();
 
-        // "Save as"" the buffer, creating a new backing file for it
+        cx.executor().run_until_parked();
+
+        // "Save as" the buffer, creating a new backing file for it
         let save_task = workspace
             .update(cx, |workspace, cx| {
                 workspace.save_active_item(workspace::SaveIntent::Save, cx)

crates/rpc/proto/zed.proto 🔗

@@ -235,8 +235,9 @@ message Envelope {
         RejoinRemoteProjectsResponse rejoin_remote_projects_response = 187;
 
         RemoteProjectsUpdate remote_projects_update = 193;
-        ValidateRemoteProjectRequest validate_remote_project_request = 194; // Current max
+        ValidateRemoteProjectRequest validate_remote_project_request = 194;
         DeleteDevServer delete_dev_server = 195;
+        OpenNewBuffer open_new_buffer = 196; // Current max
     }
 
     reserved 158 to 161;
@@ -275,6 +276,7 @@ enum ErrorCode {
     DevServerAlreadyOnline = 14;
     DevServerOffline = 15;
     RemoteProjectPathDoesNotExist = 16;
+    RemoteUpgradeRequired = 17;
     reserved 6;
 }
 
@@ -736,6 +738,10 @@ message OpenBufferById {
     uint64 id = 2;
 }
 
+message OpenNewBuffer {
+    uint64 project_id = 1;
+}
+
 message OpenBufferResponse {
     uint64 buffer_id = 1;
 }

crates/rpc/src/proto.rs 🔗

@@ -319,7 +319,8 @@ messages!(
     (MultiLspQueryResponse, Background),
     (RemoteProjectsUpdate, Foreground),
     (ValidateRemoteProjectRequest, Background),
-    (DeleteDevServer, Foreground)
+    (DeleteDevServer, Foreground),
+    (OpenNewBuffer, Foreground)
 );
 
 request_messages!(
@@ -377,6 +378,7 @@ request_messages!(
     (OpenBufferById, OpenBufferResponse),
     (OpenBufferByPath, OpenBufferResponse),
     (OpenBufferForSymbol, OpenBufferForSymbolResponse),
+    (OpenNewBuffer, OpenBufferResponse),
     (PerformRename, PerformRenameResponse),
     (Ping, Ack),
     (PrepareRename, PrepareRenameResponse),
@@ -453,6 +455,7 @@ entity_messages!(
     LeaveProject,
     MultiLspQuery,
     OnTypeFormatting,
+    OpenNewBuffer,
     OpenBufferById,
     OpenBufferByPath,
     OpenBufferForSymbol,

crates/zed/src/zed.rs 🔗

@@ -599,9 +599,9 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
                             return;
                         };
                         let project = workspace.project().clone();
-                        let buffer = project
-                            .update(cx, |project, cx| project.create_buffer(&log, None, cx))
-                            .expect("creating buffers on a local workspace always succeeds");
+                        let buffer = project.update(cx, |project, cx| {
+                            project.create_local_buffer(&log, None, cx)
+                        });
 
                         let buffer = cx.new_model(|cx| {
                             MultiBuffer::singleton(buffer, cx).with_title("Log".into())
@@ -812,8 +812,7 @@ fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Works
             workspace.update(&mut cx, |workspace, cx| {
                 let project = workspace.project().clone();
                 let buffer = project
-                    .update(cx, |project, cx| project.create_buffer("", None, cx))
-                    .expect("creating buffers on a local workspace always succeeds");
+                    .update(cx, |project, cx| project.create_local_buffer("", None, cx));
                 buffer.update(cx, |buffer, cx| {
                     buffer.set_language(json, cx);
                     buffer.edit(
@@ -862,9 +861,7 @@ fn open_bundled_file(
                 workspace.with_local_workspace(cx, |workspace, cx| {
                     let project = workspace.project();
                     let buffer = project.update(cx, move |project, cx| {
-                        project
-                            .create_buffer(text.as_ref(), language, cx)
-                            .expect("creating buffers on a local workspace always succeeds")
+                        project.create_local_buffer(text.as_ref(), language, cx)
                     });
                     let buffer = cx.new_model(|cx| {
                         MultiBuffer::singleton(buffer, cx).with_title(title.into())
@@ -1335,6 +1332,7 @@ mod tests {
             })
         })
         .await;
+        cx.run_until_parked();
 
         let workspace = cx
             .update(|cx| cx.windows().first().unwrap().downcast::<Workspace>())