Improve the ergonomics of creating local buffers (#10347)

Nathan Sobo and Piotr Osiewicz created

This PR renames `language::Buffer::new` to `language::Buffer::local` and
simplifies its interface. Instead of taking a replica id (which should
always be 0 for the local case) and a `BufferId`, which was awkward and
verbose to construct, it simply takes text and a `cx`.

It uses the `cx` to derive a `BufferId` from the `EntityId` associated
with the `cx`, which should always be positive based on the following
analysis...

We convert the entity id to a u64 using this method on `EntityId`, which
is defined by macros in the `slotmap` crate:

```rust
    pub fn as_ffi(self) -> u64 {
        (u64::from(self.version.get()) << 32) | u64::from(self.idx)
    }
```

If you look at the type of `version` in `KeyData`, it is non-zero:

```rust
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyData {
    idx: u32,
    version: NonZeroU32,
}
```

This commit also adds `Context::reserve_model` and
`Context::insert_model` to determine a model's entity ID before it is
created, which we need in order to assign a `BufferId` in the background
when loading a buffer asynchronously.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>

Change summary

crates/assistant/src/assistant_panel.rs              |  10 
crates/assistant/src/codegen.rs                      |  19 
crates/copilot/src/copilot.rs                        |  12 
crates/copilot_ui/src/copilot_completion_provider.rs |  18 -
crates/editor/src/display_map.rs                     |  16 -
crates/editor/src/editor.rs                          |  24 -
crates/editor/src/editor_tests.rs                    | 139 +----------
crates/editor/src/movement.rs                        |   9 
crates/editor/src/test/editor_test_context.rs        |   6 
crates/gpui/src/app.rs                               |  46 ++-
crates/gpui/src/app/async_context.rs                 |  53 +++
crates/gpui/src/app/entity_map.rs                    |  12 
crates/gpui/src/app/model_context.rs                 |  29 +
crates/gpui/src/app/test_context.rs                  |  26 ++
crates/gpui/src/gpui.rs                              |  24 ++
crates/gpui/src/window.rs                            |  62 +++-
crates/gpui/src/window/element_cx.rs                 |  12 +
crates/language/src/buffer.rs                        |   8 
crates/language/src/buffer_tests.rs                  | 157 +++++--------
crates/languages/src/c.rs                            |   4 
crates/languages/src/css.rs                          |   7 
crates/languages/src/python.rs                       |   4 
crates/languages/src/rust.rs                         |   4 
crates/languages/src/typescript.rs                   |   7 
crates/multi_buffer/src/multi_buffer.rs              | 110 +--------
crates/project/src/project.rs                        |  11 
crates/search/src/buffer_search.rs                   |  23 -
crates/text/src/text.rs                              |   7 
crates/vim/src/editor_events.rs                      |   6 
crates/worktree/src/worktree.rs                      |  13 
crates/worktree/src/worktree_tests.rs                |  17 
31 files changed, 376 insertions(+), 519 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs πŸ”—

@@ -31,7 +31,7 @@ use gpui::{
     StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
     View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
 };
-use language::{language_settings::SoftWrap, Buffer, BufferId, LanguageRegistry, ToOffset as _};
+use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
 use parking_lot::Mutex;
 use project::Project;
 use search::{buffer_search::DivRegistrar, BufferSearchBar};
@@ -1310,7 +1310,7 @@ impl Conversation {
     ) -> Self {
         let markdown = language_registry.language_for_name("Markdown");
         let buffer = cx.new_model(|cx| {
-            let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "");
+            let mut buffer = Buffer::local("", cx);
             buffer.set_language_registry(language_registry);
             cx.spawn(|buffer, mut cx| async move {
                 let markdown = markdown.await?;
@@ -1398,11 +1398,7 @@ impl Conversation {
         let mut message_anchors = Vec::new();
         let mut next_message_id = MessageId(0);
         let buffer = cx.new_model(|cx| {
-            let mut buffer = Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                saved_conversation.text,
-            );
+            let mut buffer = Buffer::local(saved_conversation.text, cx);
             for message in saved_conversation.messages {
                 message_anchors.push(MessageAnchor {
                     id: message.id,

crates/assistant/src/codegen.rs πŸ”—

@@ -361,8 +361,8 @@ mod tests {
     use gpui::{Context, TestAppContext};
     use indoc::indoc;
     use language::{
-        language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
-        LanguageMatcher, Point,
+        language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
+        Point,
     };
     use rand::prelude::*;
     use serde::Serialize;
@@ -388,9 +388,8 @@ mod tests {
                 }
             }
         "};
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
-        });
+        let buffer =
+            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         let range = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);
@@ -447,9 +446,8 @@ mod tests {
                 le
             }
         "};
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
-        });
+        let buffer =
+            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         let position = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);
@@ -506,9 +504,8 @@ mod tests {
             "  \n",
             "}\n" //
         );
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
-        });
+        let buffer =
+            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         let position = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);

crates/copilot/src/copilot.rs πŸ”—

@@ -1042,9 +1042,7 @@ mod tests {
     async fn test_buffer_management(cx: &mut TestAppContext) {
         let (copilot, mut lsp) = Copilot::fake(cx);
 
-        let buffer_1 = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "Hello")
-        });
+        let buffer_1 = cx.new_model(|cx| Buffer::local("Hello", cx));
         let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
             .parse()
             .unwrap();
@@ -1062,13 +1060,7 @@ mod tests {
             }
         );
 
-        let buffer_2 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                "Goodbye",
-            )
-        });
+        let buffer_2 = cx.new_model(|cx| Buffer::local("Goodbye", cx));
         let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
             .parse()
             .unwrap();

crates/copilot_ui/src/copilot_completion_provider.rs πŸ”—

@@ -272,7 +272,7 @@ mod tests {
     use indoc::indoc;
     use language::{
         language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
-        BufferId, Point,
+        Point,
     };
     use project::Project;
     use serde_json::json;
@@ -729,20 +729,8 @@ mod tests {
 
         let (copilot, copilot_lsp) = Copilot::fake(cx);
 
-        let buffer_1 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                "a = 1\nb = 2\n",
-            )
-        });
-        let buffer_2 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                "c = 3\nd = 4\n",
-            )
-        });
+        let buffer_1 = cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx));
+        let buffer_2 = cx.new_model(|cx| Buffer::local("c = 3\nd = 4\n", cx));
         let multibuffer = cx.new_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0, language::Capability::ReadWrite);
             multibuffer.push_excerpts(

crates/editor/src/display_map.rs πŸ”—

@@ -984,7 +984,6 @@ pub mod tests {
     use settings::SettingsStore;
     use smol::stream::StreamExt;
     use std::{env, sync::Arc};
-    use text::BufferId;
     use theme::{LoadThemes, SyntaxTheme};
     use util::test::{marked_text_ranges, sample_text};
     use Bias::*;
@@ -1445,10 +1444,7 @@ pub mod tests {
 
         cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
 
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-                .with_language(language, cx)
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
         cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 
@@ -1533,10 +1529,7 @@ pub mod tests {
 
         cx.update(|cx| init_test(cx, |_| {}));
 
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-                .with_language(language, cx)
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
         cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 
@@ -1602,10 +1595,7 @@ pub mod tests {
 
         let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
 
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-                .with_language(language, cx)
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
         cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
 
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));

crates/editor/src/editor.rs πŸ”—

@@ -1328,37 +1328,19 @@ impl InlayHintRefreshReason {
 
 impl Editor {
     pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                String::new(),
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local("", cx));
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         Self::new(EditorMode::SingleLine, buffer, None, cx)
     }
 
     pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                String::new(),
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local("", cx));
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         Self::new(EditorMode::Full, buffer, None, cx)
     }
 
     pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                String::new(),
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local("", cx));
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
         Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
     }

crates/editor/src/editor_tests.rs πŸ”—

@@ -38,8 +38,7 @@ fn test_edit_events(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
     let buffer = cx.new_model(|cx| {
-        let mut buffer =
-            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456");
+        let mut buffer = language::Buffer::local("123456", cx);
         buffer.set_group_interval(Duration::from_secs(1));
         buffer
     });
@@ -154,9 +153,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
     let mut now = Instant::now();
-    let buffer = cx.new_model(|cx| {
-        language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456")
-    });
+    let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
     let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
     let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
@@ -227,8 +224,7 @@ fn test_ime_composition(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
     let buffer = cx.new_model(|cx| {
-        let mut buffer =
-            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcde");
+        let mut buffer = language::Buffer::local("abcde", cx);
         // Ensure automatic grouping doesn't occur.
         buffer.set_group_interval(Duration::ZERO);
         buffer
@@ -2344,21 +2340,10 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
         None,
     ));
 
-    let toml_buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            "a = 1\nb = 2\n",
-        )
-        .with_language(toml_language, cx)
-    });
+    let toml_buffer =
+        cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
     let rust_buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            "const c: usize = 3;\n",
-        )
-        .with_language(rust_language, cx)
+        Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
     });
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
@@ -4304,10 +4289,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(language, cx)
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
     let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 
@@ -4471,10 +4453,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
 
     let text = "fn a() {}";
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(language, cx)
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
     let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
     editor
@@ -5129,10 +5108,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(language, cx)
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
     let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
     view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
@@ -5280,10 +5256,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(language, cx)
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
     let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
     editor
@@ -5470,10 +5443,7 @@ async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
         Some(tree_sitter_rust::language()),
     ));
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
-            .with_language(language, cx)
-    });
+    let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
     let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
     editor
@@ -6939,13 +6909,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
 fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            sample_text(3, 4, 'a'),
-        )
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
         multibuffer.push_excerpts(
@@ -7029,13 +6993,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
             primary: None,
         }
     });
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            initial_text,
-        )
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
         multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
@@ -7093,13 +7051,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
 fn test_refresh_selections(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            sample_text(3, 4, 'a'),
-        )
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
     let mut excerpt1_id = None;
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
@@ -7184,13 +7136,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
 fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            sample_text(3, 4, 'a'),
-        )
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
     let mut excerpt1_id = None;
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
@@ -7285,10 +7231,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
         "{{} }\n",     //
     );
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(language, cx)
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
     let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
     let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
     view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
@@ -8989,31 +8932,13 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
         cx.executor().run_until_parked();
     }
 
-    let buffer_1 = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            sample_text_1.clone(),
-        )
-    });
+    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
     diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 
-    let buffer_2 = cx.new_model(|cx| {
-        Buffer::new(
-            1,
-            BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
-            sample_text_2.clone(),
-        )
-    });
+    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
     diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 
-    let buffer_3 = cx.new_model(|cx| {
-        Buffer::new(
-            2,
-            BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
-            sample_text_3.clone(),
-        )
-    });
+    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
     diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 
     let multibuffer = cx.new_model(|cx| {
@@ -9152,29 +9077,9 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
         "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
     );
 
-    let buffer_1 = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            sample_text_1.clone(),
-        )
-    });
-
-    let buffer_2 = cx.new_model(|cx| {
-        Buffer::new(
-            1,
-            BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
-            sample_text_2.clone(),
-        )
-    });
-
-    let buffer_3 = cx.new_model(|cx| {
-        Buffer::new(
-            2,
-            BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
-            sample_text_3.clone(),
-        )
-    });
+    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
+    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
+    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 
     let multi_buffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);

crates/editor/src/movement.rs πŸ”—

@@ -570,7 +570,6 @@ mod tests {
     use language::Capability;
     use project::Project;
     use settings::SettingsStore;
-    use text::BufferId;
     use util::post_inc;
 
     #[gpui::test]
@@ -870,13 +869,7 @@ mod tests {
 
             let font = font("Helvetica");
 
-            let buffer = cx.new_model(|cx| {
-                Buffer::new(
-                    0,
-                    BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                    "abc\ndefg\nhijkl\nmn",
-                )
-            });
+            let buffer = cx.new_model(|cx| Buffer::local("abc\ndefg\nhijkl\nmn", cx));
             let multibuffer = cx.new_model(|cx| {
                 let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
                 multibuffer.push_excerpts(

crates/editor/src/test/editor_test_context.rs πŸ”—

@@ -21,7 +21,6 @@ use std::{
         Arc,
     },
 };
-use text::BufferId;
 use ui::Context;
 use util::{
     assert_set_eq,
@@ -76,10 +75,9 @@ impl EditorTestContext {
     ) -> EditorTestContext {
         let mut multibuffer = MultiBuffer::new(0, language::Capability::ReadWrite);
         let buffer = cx.new_model(|cx| {
-            for (i, excerpt) in excerpts.into_iter().enumerate() {
+            for excerpt in excerpts.into_iter() {
                 let (text, ranges) = marked_text_ranges(excerpt, false);
-                let buffer =
-                    cx.new_model(|_| Buffer::new(0, BufferId::new(i as u64 + 1).unwrap(), text));
+                let buffer = cx.new_model(|cx| Buffer::local(text, cx));
                 multibuffer.push_excerpts(
                     buffer,
                     ranges.into_iter().map(|range| ExcerptRange {

crates/gpui/src/app.rs πŸ”—

@@ -32,9 +32,9 @@ use crate::{
     AppMetadata, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context,
     DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
     Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
-    PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString,
-    SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window,
-    WindowAppearance, WindowContext, WindowHandle, WindowId,
+    PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
+    SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
+    Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
 };
 
 mod async_context;
@@ -1251,6 +1251,22 @@ impl Context for AppContext {
         })
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Self::Result<Reservation<T>> {
+        Reservation(self.entities.reserve())
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>> {
+        self.update(|cx| {
+            let slot = reservation.0;
+            let entity = build_model(&mut ModelContext::new(cx, slot.downgrade()));
+            cx.entities.insert(slot, entity)
+        })
+    }
+
     /// Updates the entity referenced by the given model. The function is passed a mutable reference to the
     /// entity along with a `ModelContext` for the entity.
     fn update_model<T: 'static, R>(
@@ -1266,6 +1282,18 @@ impl Context for AppContext {
         })
     }
 
+    fn read_model<T, R>(
+        &self,
+        handle: &Model<T>,
+        read: impl FnOnce(&T, &AppContext) -> R,
+    ) -> Self::Result<R>
+    where
+        T: 'static,
+    {
+        let entity = self.entities.read(handle);
+        read(entity, self)
+    }
+
     fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
     where
         F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
@@ -1295,18 +1323,6 @@ impl Context for AppContext {
         })
     }
 
-    fn read_model<T, R>(
-        &self,
-        handle: &Model<T>,
-        read: impl FnOnce(&T, &AppContext) -> R,
-    ) -> Self::Result<R>
-    where
-        T: 'static,
-    {
-        let entity = self.entities.read(handle);
-        read(entity, self)
-    }
-
     fn read_window<T, R>(
         &self,
         window: &WindowHandle<T>,

crates/gpui/src/app/async_context.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{
     AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, BorrowAppContext, Context,
-    DismissEvent, FocusableView, ForegroundExecutor, Global, Model, ModelContext, Render, Result,
-    Task, View, ViewContext, VisualContext, WindowContext, WindowHandle,
+    DismissEvent, FocusableView, ForegroundExecutor, Global, Model, ModelContext, Render,
+    Reservation, Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle,
 };
 use anyhow::{anyhow, Context as _};
 use derive_more::{Deref, DerefMut};
@@ -35,6 +35,28 @@ impl Context for AsyncAppContext {
         Ok(app.new_model(build_model))
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Result<Reservation<T>> {
+        let app = self
+            .app
+            .upgrade()
+            .ok_or_else(|| anyhow!("app was released"))?;
+        let mut app = app.borrow_mut();
+        Ok(app.reserve_model())
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Result<Model<T>> {
+        let app = self
+            .app
+            .upgrade()
+            .ok_or_else(|| anyhow!("app was released"))?;
+        let mut app = app.borrow_mut();
+        Ok(app.insert_model(reservation, build_model))
+    }
+
     fn update_model<T: 'static, R>(
         &mut self,
         handle: &Model<T>,
@@ -278,6 +300,19 @@ impl Context for AsyncWindowContext {
         self.window.update(self, |_, cx| cx.new_model(build_model))
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Result<Reservation<T>> {
+        self.window.update(self, |_, cx| cx.reserve_model())
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>> {
+        self.window
+            .update(self, |_, cx| cx.insert_model(reservation, build_model))
+    }
+
     fn update_model<T: 'static, R>(
         &mut self,
         handle: &Model<T>,
@@ -287,13 +322,6 @@ impl Context for AsyncWindowContext {
             .update(self, |_, cx| cx.update_model(handle, update))
     }
 
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
-    where
-        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
-    {
-        self.app.update_window(window, update)
-    }
-
     fn read_model<T, R>(
         &self,
         handle: &Model<T>,
@@ -305,6 +333,13 @@ impl Context for AsyncWindowContext {
         self.app.read_model(handle, read)
     }
 
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
+    where
+        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
+    {
+        self.app.update_window(window, update)
+    }
+
     fn read_window<T, R>(
         &self,
         window: &WindowHandle<T>,

crates/gpui/src/app/entity_map.rs πŸ”—

@@ -9,6 +9,7 @@ use std::{
     hash::{Hash, Hasher},
     marker::PhantomData,
     mem,
+    num::NonZeroU64,
     sync::{
         atomic::{AtomicUsize, Ordering::SeqCst},
         Arc, Weak,
@@ -31,6 +32,11 @@ impl From<u64> for EntityId {
 }
 
 impl EntityId {
+    /// Converts this entity id to a [NonZeroU64]
+    pub fn as_non_zero_u64(self) -> NonZeroU64 {
+        NonZeroU64::new(self.0.as_ffi()).unwrap()
+    }
+
     /// Converts this entity id to a [u64]
     pub fn as_u64(self) -> u64 {
         self.0.as_ffi()
@@ -128,14 +134,16 @@ impl EntityMap {
 
         dropped_entity_ids
             .into_iter()
-            .map(|entity_id| {
+            .filter_map(|entity_id| {
                 let count = ref_counts.counts.remove(entity_id).unwrap();
                 debug_assert_eq!(
                     count.load(SeqCst),
                     0,
                     "dropped an entity that was referenced"
                 );
-                (entity_id, self.entities.remove(entity_id).unwrap())
+                // If the EntityId was allocated with `Context::reserve`,
+                // the entity may not have been inserted.
+                Some((entity_id, self.entities.remove(entity_id)?))
             })
             .collect()
     }

crates/gpui/src/app/model_context.rs πŸ”—

@@ -1,6 +1,7 @@
 use crate::{
     AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId,
-    EventEmitter, Model, Subscription, Task, View, WeakModel, WindowContext, WindowHandle,
+    EventEmitter, Model, Reservation, Subscription, Task, View, WeakModel, WindowContext,
+    WindowHandle,
 };
 use anyhow::Result;
 use derive_more::{Deref, DerefMut};
@@ -229,6 +230,18 @@ impl<'a, T> Context for ModelContext<'a, T> {
         self.app.new_model(build_model)
     }
 
+    fn reserve_model<U: 'static>(&mut self) -> Reservation<U> {
+        self.app.reserve_model()
+    }
+
+    fn insert_model<U: 'static>(
+        &mut self,
+        reservation: Reservation<U>,
+        build_model: impl FnOnce(&mut ModelContext<'_, U>) -> U,
+    ) -> Self::Result<Model<U>> {
+        self.app.insert_model(reservation, build_model)
+    }
+
     fn update_model<U: 'static, R>(
         &mut self,
         handle: &Model<U>,
@@ -237,13 +250,6 @@ impl<'a, T> Context for ModelContext<'a, T> {
         self.app.update_model(handle, update)
     }
 
-    fn update_window<R, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<R>
-    where
-        F: FnOnce(AnyView, &mut WindowContext<'_>) -> R,
-    {
-        self.app.update_window(window, update)
-    }
-
     fn read_model<U, R>(
         &self,
         handle: &Model<U>,
@@ -255,6 +261,13 @@ impl<'a, T> Context for ModelContext<'a, T> {
         self.app.read_model(handle, read)
     }
 
+    fn update_window<R, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<R>
+    where
+        F: FnOnce(AnyView, &mut WindowContext<'_>) -> R,
+    {
+        self.app.update_window(window, update)
+    }
+
     fn read_window<U, R>(
         &self,
         window: &WindowHandle<U>,

crates/gpui/src/app/test_context.rs πŸ”—

@@ -42,6 +42,20 @@ impl Context for TestAppContext {
         app.new_model(build_model)
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Self::Result<crate::Reservation<T>> {
+        let mut app = self.app.borrow_mut();
+        app.reserve_model()
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: crate::Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>> {
+        let mut app = self.app.borrow_mut();
+        app.insert_model(reservation, build_model)
+    }
+
     fn update_model<T: 'static, R>(
         &mut self,
         handle: &Model<T>,
@@ -803,6 +817,18 @@ impl Context for VisualTestContext {
         self.cx.new_model(build_model)
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Self::Result<crate::Reservation<T>> {
+        self.cx.reserve_model()
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: crate::Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>> {
+        self.cx.insert_model(reservation, build_model)
+    }
+
     fn update_model<T, R>(
         &mut self,
         handle: &Model<T>,

crates/gpui/src/gpui.rs πŸ”—

@@ -165,6 +165,19 @@ pub trait Context {
         build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
     ) -> Self::Result<Model<T>>;
 
+    /// Reserve a slot for a model to be inserted later.
+    /// The returned [Reservation] allows you to obtain the [EntityId] for the future model.
+    fn reserve_model<T: 'static>(&mut self) -> Self::Result<Reservation<T>>;
+
+    /// Insert a new model in the app context based on a [Reservation] previously obtained from [`reserve_model`].
+    ///
+    /// [`reserve_model`]: Self::reserve_model
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>;
+
     /// Update a model in the app context.
     fn update_model<T, R>(
         &mut self,
@@ -198,6 +211,17 @@ pub trait Context {
         T: 'static;
 }
 
+/// Returned by [Context::reserve_model] to later be passed to [Context::insert_model].
+/// Allows you to obtain the [EntityId] for a model before it is created.
+pub struct Reservation<T>(pub(crate) Slot<T>);
+
+impl<T: 'static> Reservation<T> {
+    /// Returns the [EntityId] that will be associated with the model once it is inserted.
+    pub fn entity_id(&self) -> EntityId {
+        self.0.entity_id()
+    }
+}
+
 /// This trait is used for the different visual contexts in GPUI that
 /// require a window to be present.
 pub trait VisualContext: Context {

crates/gpui/src/window.rs πŸ”—

@@ -1830,6 +1830,18 @@ impl Context for WindowContext<'_> {
         self.entities.insert(slot, model)
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Self::Result<crate::Reservation<T>> {
+        self.app.reserve_model()
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: crate::Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>> {
+        self.app.insert_model(reservation, build_model)
+    }
+
     fn update_model<T: 'static, R>(
         &mut self,
         model: &Model<T>,
@@ -1844,18 +1856,6 @@ impl Context for WindowContext<'_> {
         result
     }
 
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
-    where
-        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
-    {
-        if window == self.window.handle {
-            let root_view = self.window.root_view.clone().unwrap();
-            Ok(update(root_view, self))
-        } else {
-            window.update(self.app, update)
-        }
-    }
-
     fn read_model<T, R>(
         &self,
         handle: &Model<T>,
@@ -1868,6 +1868,18 @@ impl Context for WindowContext<'_> {
         read(entity, &*self.app)
     }
 
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
+    where
+        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
+    {
+        if window == self.window.handle {
+            let root_view = self.window.root_view.clone().unwrap();
+            Ok(update(root_view, self))
+        } else {
+            window.update(self.app, update)
+        }
+    }
+
     fn read_window<T, R>(
         &self,
         window: &WindowHandle<T>,
@@ -2478,6 +2490,18 @@ impl<V> Context for ViewContext<'_, V> {
         self.window_cx.new_model(build_model)
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Self::Result<crate::Reservation<T>> {
+        self.window_cx.reserve_model()
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: crate::Reservation<T>,
+        build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>> {
+        self.window_cx.insert_model(reservation, build_model)
+    }
+
     fn update_model<T: 'static, R>(
         &mut self,
         model: &Model<T>,
@@ -2486,13 +2510,6 @@ impl<V> Context for ViewContext<'_, V> {
         self.window_cx.update_model(model, update)
     }
 
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
-    where
-        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
-    {
-        self.window_cx.update_window(window, update)
-    }
-
     fn read_model<T, R>(
         &self,
         handle: &Model<T>,
@@ -2504,6 +2521,13 @@ impl<V> Context for ViewContext<'_, V> {
         self.window_cx.read_model(handle, read)
     }
 
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
+    where
+        F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
+    {
+        self.window_cx.update_window(window, update)
+    }
+
     fn read_window<T, R>(
         &self,
         window: &WindowHandle<T>,

crates/gpui/src/window/element_cx.rs πŸ”—

@@ -264,6 +264,18 @@ impl<'a> Context for ElementContext<'a> {
         self.cx.new_model(build_model)
     }
 
+    fn reserve_model<T: 'static>(&mut self) -> Self::Result<crate::Reservation<T>> {
+        self.cx.reserve_model()
+    }
+
+    fn insert_model<T: 'static>(
+        &mut self,
+        reservation: crate::Reservation<T>,
+        build_model: impl FnOnce(&mut crate::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<crate::Model<T>> {
+        self.cx.insert_model(reservation, build_model)
+    }
+
     fn update_model<T, R>(
         &mut self,
         handle: &crate::Model<T>,

crates/language/src/buffer.rs πŸ”—

@@ -503,9 +503,9 @@ pub enum CharKind {
 
 impl Buffer {
     /// Create a new buffer with the given base text.
-    pub fn new<T: Into<String>>(replica_id: ReplicaId, id: BufferId, base_text: T) -> Self {
+    pub fn local<T: Into<String>>(base_text: T, cx: &mut ModelContext<Self>) -> Self {
         Self::build(
-            TextBuffer::new(replica_id, id, base_text.into()),
+            TextBuffer::new(0, cx.entity_id().as_non_zero_u64().into(), base_text.into()),
             None,
             None,
             Capability::ReadWrite,
@@ -517,10 +517,10 @@ impl Buffer {
         remote_id: BufferId,
         replica_id: ReplicaId,
         capability: Capability,
-        base_text: String,
+        base_text: impl Into<String>,
     ) -> Self {
         Self::build(
-            TextBuffer::new(replica_id, remote_id, base_text),
+            TextBuffer::new(replica_id, remote_id, base_text.into()),
             None,
             None,
             capability,

crates/language/src/buffer_tests.rs πŸ”—

@@ -44,12 +44,8 @@ fn test_line_endings(cx: &mut gpui::AppContext) {
     init_settings(cx, |_| {});
 
     cx.new_model(|cx| {
-        let mut buffer = Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            "one\r\ntwo\rthree",
-        )
-        .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer =
+            Buffer::local("one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
         assert_eq!(buffer.text(), "one\ntwo\nthree");
         assert_eq!(buffer.line_ending(), LineEnding::Windows);
 
@@ -256,10 +252,15 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
     let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
     let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
 
-    let buffer1 = cx
-        .new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcdef"));
-    let buffer2 = cx
-        .new_model(|cx| Buffer::new(1, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcdef"));
+    let buffer1 = cx.new_model(|cx| Buffer::local("abcdef", cx));
+    let buffer2 = cx.new_model(|cx| {
+        Buffer::remote(
+            BufferId::from(cx.entity_id().as_non_zero_u64()),
+            1,
+            Capability::ReadWrite,
+            "abcdef",
+        )
+    });
     let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
     buffer1.update(cx, {
         let buffer1_ops = buffer1_ops.clone();
@@ -338,8 +339,7 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
 #[gpui::test]
 async fn test_apply_diff(cx: &mut TestAppContext) {
     let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
-    let buffer =
-        cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx));
     let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
 
     let text = "a\nccc\ndddd\nffffff\n";
@@ -371,8 +371,7 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
     ]
     .join("\n");
 
-    let buffer =
-        cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx));
 
     // Spawn a task to format the buffer's whitespace.
     // Pause so that the foratting task starts running.
@@ -436,10 +435,8 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
 #[gpui::test]
 async fn test_reparse(cx: &mut gpui::TestAppContext) {
     let text = "fn a() {}";
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx)
-    });
+    let buffer =
+        cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
 
     // Wait for the initial text to parse
     cx.executor().run_until_parked();
@@ -566,8 +563,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
 #[gpui::test]
 async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
     let buffer = cx.new_model(|cx| {
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "{}")
-            .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::local("{}", cx).with_language(Arc::new(rust_lang()), cx);
         buffer.set_sync_parse_timeout(Duration::ZERO);
         buffer
     });
@@ -615,10 +611,8 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx)
-    });
+    let buffer =
+        cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
     let outline = buffer
         .update(cx, |buffer, _| buffer.snapshot().outline(None))
         .unwrap();
@@ -702,10 +696,8 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx)
-    });
+    let buffer =
+        cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
     let outline = buffer
         .update(cx, |buffer, _| buffer.snapshot().outline(None))
         .unwrap();
@@ -741,10 +733,7 @@ async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(language), cx)
-    });
+    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(language), cx));
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
 
     // extra context nodes are included in the outline.
@@ -786,10 +775,8 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.new_model(|cx| {
-        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx)
-    });
+    let buffer =
+        cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
 
     // point is at the start of an item
@@ -1010,8 +997,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &
 fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
     cx.new_model(|cx| {
         let text = "fn a() { b(|c| {}) }";
-        let buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx);
+        let buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
         let snapshot = buffer.snapshot();
 
         assert_eq!(
@@ -1051,8 +1037,7 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
 
     cx.new_model(|cx| {
         let text = "fn a() {}";
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
 
         buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
         assert_eq!(buffer.text(), "fn a() {\n    \n}");
@@ -1094,8 +1079,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
 
     cx.new_model(|cx| {
         let text = "fn a() {}";
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
 
         buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
         assert_eq!(buffer.text(), "fn a() {\n\t\n}");
@@ -1134,9 +1118,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
     init_settings(cx, |_| {});
 
     cx.new_model(|cx| {
-        let mut buffer = Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+        let mut buffer = Buffer::local(
             "
             fn a() {
             c;
@@ -1144,6 +1126,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
             }
             "
             .unindent(),
+            cx,
         )
         .with_language(Arc::new(rust_lang()), cx);
 
@@ -1209,9 +1192,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
     cx.new_model(|cx| {
         eprintln!("second buffer: {:?}", cx.entity_id());
 
-        let mut buffer = Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+        let mut buffer = Buffer::local(
             "
             fn a() {
                 b();
@@ -1219,6 +1200,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
             "
             .replace('|', "") // marker to preserve trailing whitespace
             .unindent(),
+            cx,
         )
         .with_language(Arc::new(rust_lang()), cx);
 
@@ -1274,15 +1256,14 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
     init_settings(cx, |_| {});
 
     cx.new_model(|cx| {
-        let mut buffer = Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+        let mut buffer = Buffer::local(
             "
             fn a() {
                 i
             }
             "
             .unindent(),
+            cx,
         )
         .with_language(Arc::new(rust_lang()), cx);
 
@@ -1336,13 +1317,12 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
     cx.new_model(|cx| {
-        let mut buffer = Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+        let mut buffer = Buffer::local(
             "
             fn a() {}
             "
             .unindent(),
+            cx,
         )
         .with_language(Arc::new(rust_lang()), cx);
 
@@ -1394,8 +1374,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
 
     cx.new_model(|cx| {
         let text = "a\nb";
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
         buffer.edit(
             [(0..1, "\n"), (2..3, "\n")],
             Some(AutoindentMode::EachLine),
@@ -1421,8 +1400,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
         "
         .unindent();
 
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
         buffer.edit(
             [(Point::new(3, 0)..Point::new(3, 0), "e(\n    f()\n);\n")],
             Some(AutoindentMode::EachLine),
@@ -1459,8 +1437,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
             }
         "#
         .unindent();
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
 
         // When this text was copied, both of the quotation marks were at the same
         // indent level, but the indentation of the first line was not included in
@@ -1545,8 +1522,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
             }
         "#
         .unindent();
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
 
         // The original indent columns are not known, so this text is
         // auto-indented in a block as if the first line was copied in
@@ -1625,18 +1601,17 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
         "
         .unindent();
 
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            .with_language(
-                Arc::new(Language::new(
-                    LanguageConfig {
-                        name: "Markdown".into(),
-                        auto_indent_using_last_non_empty_line: false,
-                        ..Default::default()
-                    },
-                    Some(tree_sitter_json::language()),
-                )),
-                cx,
-            );
+        let mut buffer = Buffer::local(text, cx).with_language(
+            Arc::new(Language::new(
+                LanguageConfig {
+                    name: "Markdown".into(),
+                    auto_indent_using_last_non_empty_line: false,
+                    ..Default::default()
+                },
+                Some(tree_sitter_json::language()),
+            )),
+            cx,
+        );
         buffer.edit(
             [(Point::new(3, 0)..Point::new(3, 0), "\n")],
             Some(AutoindentMode::EachLine),
@@ -1702,7 +1677,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
             false,
         );
 
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text);
+        let mut buffer = Buffer::local(text, cx);
         buffer.set_language_registry(language_registry);
         buffer.set_language(Some(html_language), cx);
         buffer.edit(
@@ -1738,8 +1713,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
     });
 
     cx.new_model(|cx| {
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
-            .with_language(Arc::new(ruby_lang()), cx);
+        let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
 
         let text = r#"
             class C
@@ -1840,8 +1814,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
         "#
         .unindent();
 
-        let buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), &text)
-            .with_language(Arc::new(language), cx);
+        let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
         let snapshot = buffer.snapshot();
 
         let config = snapshot.language_scope_at(0).unwrap();
@@ -1958,12 +1931,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
         "#
         .unindent();
 
-        let buffer = Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            text.clone(),
-        )
-        .with_language(Arc::new(language), cx);
+        let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
         let snapshot = buffer.snapshot();
 
         // By default, all brackets are enabled
@@ -2007,7 +1975,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
         language_registry.add(Arc::new(html_lang()));
         language_registry.add(Arc::new(erb_lang()));
 
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text);
+        let mut buffer = Buffer::local(text, cx);
         buffer.set_language_registry(language_registry.clone());
         buffer.set_language(
             language_registry
@@ -2042,7 +2010,7 @@ fn test_serialization(cx: &mut gpui::AppContext) {
     let mut now = Instant::now();
 
     let buffer1 = cx.new_model(|cx| {
-        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abc");
+        let mut buffer = Buffer::local("abc", cx);
         buffer.edit([(3..3, "D")], None, cx);
 
         now += Duration::from_secs(1);
@@ -2097,13 +2065,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
     let mut replica_ids = Vec::new();
     let mut buffers = Vec::new();
     let network = Arc::new(Mutex::new(Network::new(rng.clone())));
-    let base_buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            base_text.as_str(),
-        )
-    });
+    let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
 
     for i in 0..rng.gen_range(min_peers..=max_peers) {
         let buffer = cx.new_model(|cx| {
@@ -2623,12 +2585,7 @@ fn assert_bracket_pairs(
 ) {
     let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
     let buffer = cx.new_model(|cx| {
-        Buffer::new(
-            0,
-            BufferId::new(cx.entity_id().as_u64()).unwrap(),
-            expected_text.clone(),
-        )
-        .with_language(Arc::new(language), cx)
+        Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
     });
     let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
 

crates/languages/src/c.rs πŸ”—

@@ -297,7 +297,6 @@ mod tests {
     use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
     use settings::SettingsStore;
     use std::num::NonZeroU32;
-    use text::BufferId;
 
     #[gpui::test]
     async fn test_c_autoindent(cx: &mut TestAppContext) {
@@ -315,8 +314,7 @@ mod tests {
         let language = crate::language("c", tree_sitter_c::language());
 
         cx.new_model(|cx| {
-            let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
-                .with_language(language, cx);
+            let mut buffer = Buffer::local("", cx).with_language(language, cx);
 
             // empty function
             buffer.edit([(0..0, "int main() {}")], None, cx);

crates/languages/src/css.rs πŸ”—

@@ -136,7 +136,6 @@ async fn get_cached_server_binary(
 #[cfg(test)]
 mod tests {
     use gpui::{Context, TestAppContext};
-    use text::BufferId;
     use unindent::Unindent;
 
     #[gpui::test]
@@ -168,10 +167,8 @@ mod tests {
         "#
         .unindent();
 
-        let buffer = cx.new_model(|cx| {
-            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-                .with_language(language, cx)
-        });
+        let buffer =
+            cx.new_model(|cx| language::Buffer::local(text, cx).with_language(language, cx));
         let outline = buffer.update(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
         assert_eq!(
             outline

crates/languages/src/python.rs πŸ”—

@@ -186,7 +186,6 @@ mod tests {
     use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
     use settings::SettingsStore;
     use std::num::NonZeroU32;
-    use text::BufferId;
 
     #[gpui::test]
     async fn test_python_autoindent(cx: &mut TestAppContext) {
@@ -204,8 +203,7 @@ mod tests {
         });
 
         cx.new_model(|cx| {
-            let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
-                .with_language(language, cx);
+            let mut buffer = Buffer::local("", cx).with_language(language, cx);
             let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
                 let ix = buffer.len();
                 buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);

crates/languages/src/rust.rs πŸ”—

@@ -465,7 +465,6 @@ mod tests {
     use gpui::{BorrowAppContext, Context, Hsla, TestAppContext};
     use language::language_settings::AllLanguageSettings;
     use settings::SettingsStore;
-    use text::BufferId;
     use theme::SyntaxTheme;
 
     #[gpui::test]
@@ -682,8 +681,7 @@ mod tests {
         let language = crate::language("rust", tree_sitter_rust::language());
 
         cx.new_model(|cx| {
-            let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
-                .with_language(language, cx);
+            let mut buffer = Buffer::local("", cx).with_language(language, cx);
 
             // indent between braces
             buffer.set_text("fn a() {}", cx);

crates/languages/src/typescript.rs πŸ”—

@@ -413,7 +413,6 @@ async fn get_cached_eslint_server_binary(
 #[cfg(test)]
 mod tests {
     use gpui::{Context, TestAppContext};
-    use text::BufferId;
     use unindent::Unindent;
 
     #[gpui::test]
@@ -435,10 +434,8 @@ mod tests {
         "#
         .unindent();
 
-        let buffer = cx.new_model(|cx| {
-            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-                .with_language(language, cx)
-        });
+        let buffer =
+            cx.new_model(|cx| language::Buffer::local(text, cx).with_language(language, cx));
         let outline = buffer.update(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
         assert_eq!(
             outline

crates/multi_buffer/src/multi_buffer.rs πŸ”—

@@ -1700,8 +1700,7 @@ impl MultiBuffer {
 #[cfg(any(test, feature = "test-support"))]
 impl MultiBuffer {
     pub fn build_simple(text: &str, cx: &mut gpui::AppContext) -> Model<Self> {
-        let buffer = cx
-            .new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
+        let buffer = cx.new_model(|cx| Buffer::local(text, cx));
         cx.new_model(|cx| Self::singleton(buffer, cx))
     }
 
@@ -1711,9 +1710,7 @@ impl MultiBuffer {
     ) -> Model<Self> {
         let multi = cx.new_model(|_| Self::new(0, Capability::ReadWrite));
         for (text, ranges) in excerpts {
-            let buffer = cx.new_model(|cx| {
-                Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-            });
+            let buffer = cx.new_model(|cx| Buffer::local(text, cx));
             let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
                 context: range,
                 primary: None,
@@ -1802,9 +1799,7 @@ impl MultiBuffer {
             if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) {
                 let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() {
                     let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>();
-                    buffers.push(cx.new_model(|cx| {
-                        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
-                    }));
+                    buffers.push(cx.new_model(|cx| Buffer::local(text, cx)));
                     let buffer = buffers.last().unwrap().read(cx);
                     log::info!(
                         "Creating new buffer {} with text: {:?}",
@@ -4327,13 +4322,7 @@ mod tests {
 
     #[gpui::test]
     fn test_singleton(cx: &mut AppContext) {
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                sample_text(6, 6, 'a'),
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
         let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 
         let snapshot = multibuffer.read(cx).snapshot(cx);
@@ -4360,8 +4349,7 @@ mod tests {
 
     #[gpui::test]
     fn test_remote(cx: &mut AppContext) {
-        let host_buffer =
-            cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "a"));
+        let host_buffer = cx.new_model(|cx| Buffer::local("a", cx));
         let guest_buffer = cx.new_model(|cx| {
             let state = host_buffer.read(cx).to_proto();
             let ops = cx
@@ -4392,20 +4380,8 @@ mod tests {
 
     #[gpui::test]
     fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) {
-        let buffer_1 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                sample_text(6, 6, 'a'),
-            )
-        });
-        let buffer_2 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                sample_text(6, 6, 'g'),
-            )
-        });
+        let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
+        let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
         let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
 
         let events = Arc::new(RwLock::new(Vec::<Event>::new()));
@@ -4638,20 +4614,8 @@ mod tests {
 
     #[gpui::test]
     fn test_excerpt_events(cx: &mut AppContext) {
-        let buffer_1 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                sample_text(10, 3, 'a'),
-            )
-        });
-        let buffer_2 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                sample_text(10, 3, 'm'),
-            )
-        });
+        let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text(10, 3, 'a'), cx));
+        let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text(10, 3, 'm'), cx));
 
         let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
         let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
@@ -4756,13 +4720,7 @@ mod tests {
 
     #[gpui::test]
     fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                sample_text(20, 3, 'a'),
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
         let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
         let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
             multibuffer.push_excerpts_with_context_lines(
@@ -4798,13 +4756,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) {
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                sample_text(20, 3, 'a'),
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
         let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
         let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
             let snapshot = buffer.read(cx);
@@ -4850,9 +4802,7 @@ mod tests {
 
     #[gpui::test]
     fn test_singleton_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcd")
-        });
+        let buffer = cx.new_model(|cx| Buffer::local("abcd", cx));
         let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
         let old_snapshot = multibuffer.read(cx).snapshot(cx);
         buffer.update(cx, |buffer, cx| {
@@ -4872,12 +4822,8 @@ mod tests {
 
     #[gpui::test]
     fn test_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer_1 = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcd")
-        });
-        let buffer_2 = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "efghi")
-        });
+        let buffer_1 = cx.new_model(|cx| Buffer::local("abcd", cx));
+        let buffer_2 = cx.new_model(|cx| Buffer::local("efghi", cx));
         let multibuffer = cx.new_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
             multibuffer.push_excerpts(
@@ -4934,16 +4880,8 @@ mod tests {
 
     #[gpui::test]
     fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) {
-        let buffer_1 = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcd")
-        });
-        let buffer_2 = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                "ABCDEFGHIJKLMNOP",
-            )
-        });
+        let buffer_1 = cx.new_model(|cx| Buffer::local("abcd", cx));
+        let buffer_2 = cx.new_model(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
         let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
 
         // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
@@ -5154,13 +5092,7 @@ mod tests {
                         let base_text = util::RandomCharIter::new(&mut rng)
                             .take(10)
                             .collect::<String>();
-                        buffers.push(cx.new_model(|cx| {
-                            Buffer::new(
-                                0,
-                                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                                base_text,
-                            )
-                        }));
+                        buffers.push(cx.new_model(|cx| Buffer::local(base_text, cx)));
                         buffers.last().unwrap()
                     } else {
                         buffers.choose(&mut rng).unwrap()
@@ -5502,12 +5434,8 @@ mod tests {
         let test_settings = SettingsStore::test(cx);
         cx.set_global(test_settings);
 
-        let buffer_1 = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "1234")
-        });
-        let buffer_2 = cx.new_model(|cx| {
-            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "5678")
-        });
+        let buffer_1 = cx.new_model(|cx| Buffer::local("1234", cx));
+        let buffer_2 = cx.new_model(|cx| Buffer::local("5678", cx));
         let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
         let group_interval = multibuffer.read(cx).history.group_interval;
         multibuffer.update(cx, |multibuffer, cx| {

crates/project/src/project.rs πŸ”—

@@ -178,7 +178,6 @@ pub struct Project {
     collaborators: HashMap<proto::PeerId, Collaborator>,
     client_subscriptions: Vec<client::Subscription>,
     _subscriptions: Vec<gpui::Subscription>,
-    next_buffer_id: BufferId,
     loading_buffers: HashMap<BufferId, Vec<oneshot::Sender<Result<Model<Buffer>, anyhow::Error>>>>,
     incomplete_remote_buffers: HashMap<BufferId, Model<Buffer>>,
     shared_buffers: HashMap<proto::PeerId, HashSet<BufferId>>,
@@ -675,7 +674,6 @@ impl Project {
                 flush_language_server_update: None,
                 pending_language_server_update: None,
                 collaborators: Default::default(),
-                next_buffer_id: BufferId::new(1).unwrap(),
                 opened_buffers: Default::default(),
                 shared_buffers: Default::default(),
                 loading_buffers_by_path: Default::default(),
@@ -792,7 +790,6 @@ impl Project {
                 pending_language_server_update: None,
                 flush_language_server_update: None,
                 loading_buffers_by_path: Default::default(),
-                next_buffer_id: BufferId::new(1).unwrap(),
                 loading_buffers: Default::default(),
                 shared_buffers: Default::default(),
                 incomplete_remote_buffers: Default::default(),
@@ -1849,9 +1846,8 @@ impl Project {
         if self.is_remote() {
             return Err(anyhow!("creating buffers as a guest is not supported yet"));
         }
-        let id = self.next_buffer_id.next();
         let buffer = cx.new_model(|cx| {
-            Buffer::new(self.replica_id(), id, text)
+            Buffer::local(text, cx)
                 .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
         });
         self.register_buffer(&buffer, cx)?;
@@ -1950,10 +1946,9 @@ impl Project {
         worktree: Model<Worktree>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Model<Buffer>>> {
-        let buffer_id = self.next_buffer_id.next();
         let load_buffer = worktree.update(cx, |worktree, cx| {
             let worktree = worktree.as_local_mut().unwrap();
-            worktree.load_buffer(buffer_id, &path, cx)
+            worktree.load_buffer(&path, cx)
         });
         fn is_not_found_error(error: &anyhow::Error) -> bool {
             error
@@ -1967,7 +1962,7 @@ impl Project {
                 Err(error) if is_not_found_error(&error) => {
                     worktree.update(&mut cx, |worktree, cx| {
                         let worktree = worktree.as_local_mut().unwrap();
-                        worktree.new_buffer(buffer_id, path, cx)
+                        worktree.new_buffer(path, cx)
                     })
                 }
                 Err(e) => Err(e),

crates/search/src/buffer_search.rs πŸ”—

@@ -1141,7 +1141,7 @@ mod tests {
     use super::*;
     use editor::{DisplayPoint, Editor};
     use gpui::{Context, Hsla, TestAppContext, VisualTestContext};
-    use language::{Buffer, BufferId};
+    use language::Buffer;
     use smol::stream::StreamExt as _;
     use unindent::Unindent as _;
 
@@ -1161,9 +1161,7 @@ mod tests {
     ) -> (View<Editor>, View<BufferSearchBar>, &mut VisualTestContext) {
         init_globals(cx);
         let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            Buffer::local(
                 r#"
                 A regular expression (shortened as regex or regexp;[1] also referred to as
                 rational expression[2][3]) is a sequence of characters that specifies a search
@@ -1171,6 +1169,7 @@ mod tests {
                 for "find" or "find and replace" operations on strings, or for input validation.
                 "#
                 .unindent(),
+                cx,
             )
         });
         let cx = cx.add_empty_window();
@@ -1519,13 +1518,7 @@ mod tests {
             expected_query_matches_count > 1,
             "Should pick a query with multiple results"
         );
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                buffer_text,
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(buffer_text, cx));
         let window = cx.add_window(|_| gpui::Empty);
 
         let editor = window.build_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
@@ -1721,13 +1714,7 @@ mod tests {
         for "find" or "find and replace" operations on strings, or for input validation.
         "#
         .unindent();
-        let buffer = cx.new_model(|cx| {
-            Buffer::new(
-                0,
-                BufferId::new(cx.entity_id().as_u64()).unwrap(),
-                buffer_text,
-            )
-        });
+        let buffer = cx.new_model(|cx| Buffer::local(buffer_text, cx));
         let cx = cx.add_empty_window();
 
         let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));

crates/text/src/text.rs πŸ”—

@@ -71,12 +71,19 @@ impl Display for BufferId {
     }
 }
 
+impl From<NonZeroU64> for BufferId {
+    fn from(id: NonZeroU64) -> Self {
+        BufferId(id)
+    }
+}
+
 impl BufferId {
     /// Returns Err if `id` is outside of BufferId domain.
     pub fn new(id: u64) -> anyhow::Result<Self> {
         let id = NonZeroU64::new(id).context("Buffer id cannot be 0.")?;
         Ok(Self(id))
     }
+
     /// Increments this buffer id, returning the old value.
     /// So that's a post-increment operator in disguise.
     pub fn next(&mut self) -> Self {

crates/vim/src/editor_events.rs πŸ”—

@@ -73,14 +73,14 @@ mod test {
     use crate::{test::VimTestContext, Vim};
     use editor::Editor;
     use gpui::{Context, Entity, VisualTestContext};
-    use language::{Buffer, BufferId};
+    use language::Buffer;
 
     // regression test for blur called with a different active editor
     #[gpui::test]
     async fn test_blur_focus(cx: &mut gpui::TestAppContext) {
         let mut cx = VimTestContext::new(cx, true).await;
 
-        let buffer = cx.new_model(|_| Buffer::new(0, BufferId::new(1).unwrap(), "a = 1\nb = 2\n"));
+        let buffer = cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx));
         let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
         let editor2 = cx
             .update(|cx| {
@@ -115,7 +115,7 @@ mod test {
         let mut cx1 = VisualTestContext::from_window(cx.window, &cx);
         let editor1 = cx.editor.clone();
 
-        let buffer = cx.new_model(|_| Buffer::new(0, BufferId::new(1).unwrap(), "a = 1\nb = 2\n"));
+        let buffer = cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx));
         let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx));
 
         editor2.update(cx2, |_, cx| {

crates/worktree/src/worktree.rs πŸ”—

@@ -751,20 +751,21 @@ impl LocalWorktree {
 
     pub fn load_buffer(
         &mut self,
-        id: BufferId,
         path: &Path,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<Model<Buffer>>> {
         let path = Arc::from(path);
+        let reservation = cx.reserve_model();
+        let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
         cx.spawn(move |this, mut cx| async move {
             let (file, contents, diff_base) = this
                 .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx))?
                 .await?;
             let text_buffer = cx
                 .background_executor()
-                .spawn(async move { text::Buffer::new(0, id, contents) })
+                .spawn(async move { text::Buffer::new(0, buffer_id, contents) })
                 .await;
-            cx.new_model(|_| {
+            cx.insert_model(reservation, |_| {
                 Buffer::build(
                     text_buffer,
                     diff_base,
@@ -777,13 +778,13 @@ impl LocalWorktree {
 
     pub fn new_buffer(
         &mut self,
-        buffer_id: BufferId,
         path: Arc<Path>,
         cx: &mut ModelContext<Worktree>,
     ) -> Model<Buffer> {
-        let text_buffer = text::Buffer::new(0, buffer_id, "".into());
         let worktree = cx.handle();
-        cx.new_model(|_| {
+        cx.new_model(|cx| {
+            let buffer_id = BufferId::from(cx.entity_id().as_non_zero_u64());
+            let text_buffer = text::Buffer::new(0, buffer_id, "".into());
             Buffer::build(
                 text_buffer,
                 None,

crates/worktree/src/worktree_tests.rs πŸ”—

@@ -21,7 +21,6 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use text::BufferId;
 use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
 
 #[gpui::test]
@@ -577,11 +576,9 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
     let prev_read_dir_count = fs.read_dir_call_count();
     let buffer = tree
         .update(cx, |tree, cx| {
-            tree.as_local_mut().unwrap().load_buffer(
-                BufferId::new(1).unwrap(),
-                "one/node_modules/b/b1.js".as_ref(),
-                cx,
-            )
+            tree.as_local_mut()
+                .unwrap()
+                .load_buffer("one/node_modules/b/b1.js".as_ref(), cx)
         })
         .await
         .unwrap();
@@ -621,11 +618,9 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
     let prev_read_dir_count = fs.read_dir_call_count();
     let buffer = tree
         .update(cx, |tree, cx| {
-            tree.as_local_mut().unwrap().load_buffer(
-                BufferId::new(1).unwrap(),
-                "one/node_modules/a/a2.js".as_ref(),
-                cx,
-            )
+            tree.as_local_mut()
+                .unwrap()
+                .load_buffer("one/node_modules/a/a2.js".as_ref(), cx)
         })
         .await
         .unwrap();