text: Wrap BufferId into a newtype

Piotr Osiewicz created

Change summary

crates/assistant/src/assistant_panel.rs            |  10 
crates/assistant/src/codegen.rs                    |  19 +
crates/assistant/src/prompts.rs                    |   9 
crates/channel/src/channel_buffer.rs               |  14 
crates/collab/src/db/queries/buffers.rs            |   2 
crates/collab/src/db/tests/buffer_tests.rs         |  16 +
crates/copilot/src/copilot.rs                      |  15 +
crates/editor/src/display_map.rs                   |  10 
crates/editor/src/editor.rs                        |  26 +
crates/editor/src/editor_tests.rs                  | 105 +++++++-
crates/editor/src/inlay_hint_cache.rs              |   8 
crates/editor/src/items.rs                         |  43 ++-
crates/editor/src/movement.rs                      |  10 
crates/git/src/diff.rs                             |   6 
crates/language/src/buffer.rs                      |  22 +
crates/language/src/buffer_tests.rs                | 140 +++++++----
crates/language/src/proto.rs                       |   9 
crates/language/src/syntax_map/syntax_map_tests.rs |  10 
crates/multi_buffer/src/anchor.rs                  |   3 
crates/multi_buffer/src/multi_buffer.rs            | 148 ++++++++---
crates/project/src/lsp_command.rs                  |  89 ++++---
crates/project/src/lsp_ext_command.rs              |  10 
crates/project/src/project.rs                      | 187 +++++++++------
crates/project/src/worktree.rs                     |  11 
crates/project/src/worktree_tests.rs               |  17 
crates/search/src/buffer_search.rs                 |  20 +
crates/text/src/anchor.rs                          |   6 
crates/text/src/tests.rs                           |  40 +-
crates/text/src/text.rs                            |  40 ++
crates/vim/src/editor_events.rs                    |   6 
crates/zed/src/languages/c.rs                      |   5 
crates/zed/src/languages/python.rs                 |   5 
crates/zed/src/languages/rust.rs                   |   5 
crates/zed/src/languages/typescript.rs             |   4 
34 files changed, 687 insertions(+), 383 deletions(-)

Detailed changes

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

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

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

@@ -365,7 +365,9 @@ mod tests {
     use futures::stream::{self};
     use gpui::{Context, TestAppContext};
     use indoc::indoc;
-    use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
+    use language::{
+        language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
+    };
     use rand::prelude::*;
     use serde::Serialize;
     use settings::SettingsStore;
@@ -394,8 +396,9 @@ mod tests {
                 }
             }
         "};
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+        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| MultiBuffer::singleton(buffer, cx));
         let range = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);
@@ -460,8 +463,9 @@ mod tests {
                 le
             }
         "};
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+        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| MultiBuffer::singleton(buffer, cx));
         let position = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);
@@ -525,8 +529,9 @@ mod tests {
             "  \n",
             "}\n" //
         );
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+        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| MultiBuffer::singleton(buffer, cx));
         let position = buffer.read_with(cx, |buffer, cx| {
             let snapshot = buffer.snapshot(cx);

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

@@ -178,7 +178,9 @@ pub(crate) mod tests {
 
     use gpui::{AppContext, Context};
     use indoc::indoc;
-    use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
+    use language::{
+        language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig, Point,
+    };
     use settings::SettingsStore;
 
     pub(crate) fn rust_lang() -> Language {
@@ -253,8 +255,9 @@ pub(crate) mod tests {
                 }
             }
         "};
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
+        });
         let snapshot = buffer.read(cx).snapshot();
 
         assert_eq!(

crates/channel/src/channel_buffer.rs πŸ”—

@@ -9,6 +9,7 @@ use rpc::{
     TypedEnvelope,
 };
 use std::{sync::Arc, time::Duration};
+use text::BufferId;
 use util::ResultExt;
 
 pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
@@ -53,7 +54,7 @@ impl ChannelBuffer {
                 channel_id: channel.id,
             })
             .await?;
-
+        let buffer_id = BufferId::new(response.buffer_id)?;
         let base_text = response.base_text;
         let operations = response
             .operations
@@ -63,12 +64,7 @@ impl ChannelBuffer {
 
         let buffer = cx.new_model(|cx| {
             let capability = channel_store.read(cx).channel_capability(channel.id);
-            language::Buffer::remote(
-                response.buffer_id,
-                response.replica_id as u16,
-                capability,
-                base_text,
-            )
+            language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
         })?;
         buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
 
@@ -107,7 +103,7 @@ impl ChannelBuffer {
         }
     }
 
-    pub fn remote_id(&self, cx: &AppContext) -> u64 {
+    pub fn remote_id(&self, cx: &AppContext) -> BufferId {
         self.buffer.read(cx).remote_id()
     }
 
@@ -210,7 +206,7 @@ impl ChannelBuffer {
     pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
         let buffer = self.buffer.read(cx);
         let version = buffer.version();
-        let buffer_id = buffer.remote_id();
+        let buffer_id = buffer.remote_id().into();
         let client = self.client.clone();
         let epoch = self.epoch();
 

crates/collab/src/db/queries/buffers.rs πŸ”—

@@ -693,7 +693,7 @@ impl Database {
             return Ok(());
         }
 
-        let mut text_buffer = text::Buffer::new(0, 0, base_text);
+        let mut text_buffer = text::Buffer::new(0, text::BufferId::new(1).unwrap(), base_text);
         text_buffer
             .apply_ops(operations.into_iter().filter_map(operation_from_wire))
             .unwrap();

crates/collab/src/db/tests/buffer_tests.rs πŸ”—

@@ -67,7 +67,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
         .await
         .unwrap();
 
-    let mut buffer_a = Buffer::new(0, 0, "".to_string());
+    let mut buffer_a = Buffer::new(0, text::BufferId::new(0).unwrap(), "".to_string());
     let mut operations = Vec::new();
     operations.push(buffer_a.edit([(0..0, "hello world")]));
     operations.push(buffer_a.edit([(5..5, ", cruel")]));
@@ -90,7 +90,11 @@ async fn test_channel_buffers(db: &Arc<Database>) {
         .await
         .unwrap();
 
-    let mut buffer_b = Buffer::new(0, 0, buffer_response_b.base_text);
+    let mut buffer_b = Buffer::new(
+        0,
+        text::BufferId::new(0).unwrap(),
+        buffer_response_b.base_text,
+    );
     buffer_b
         .apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
             let operation = proto::deserialize_operation(operation).unwrap();
@@ -223,7 +227,11 @@ async fn test_channel_buffers_last_operations(db: &Database) {
                 .unwrap(),
         );
 
-        text_buffers.push(Buffer::new(0, 0, "".to_string()));
+        text_buffers.push(Buffer::new(
+            0,
+            text::BufferId::new(1).unwrap(),
+            "".to_string(),
+        ));
     }
 
     let operations = db
@@ -270,7 +278,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
     db.join_channel_buffer(buffers[1].channel_id, user_id, connection_id)
         .await
         .unwrap();
-    text_buffers[1] = Buffer::new(1, 0, "def".to_string());
+    text_buffers[1] = Buffer::new(1, text::BufferId::new(1).unwrap(), "def".to_string());
     update_buffer(
         buffers[1].channel_id,
         user_id,

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

@@ -1023,12 +1023,15 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
 mod tests {
     use super::*;
     use gpui::TestAppContext;
+    use language::BufferId;
 
     #[gpui::test(iterations = 10)]
     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, cx.entity_id().as_u64(), "Hello"));
+        let buffer_1 = cx.new_model(|cx| {
+            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "Hello")
+        });
         let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
             .parse()
             .unwrap();
@@ -1046,7 +1049,13 @@ mod tests {
             }
         );
 
-        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Goodbye"));
+        let buffer_2 = cx.new_model(|cx| {
+            Buffer::new(
+                0,
+                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+                "Goodbye",
+            )
+        });
         let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
             .parse()
             .unwrap();
@@ -1235,7 +1244,7 @@ mod tests {
 
         fn buffer_reloaded(
             &self,
-            _: u64,
+            _: BufferId,
             _: &clock::Global,
             _: language::RopeFingerprint,
             _: language::LineEnding,

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

@@ -1007,6 +1007,7 @@ 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::*;
@@ -1467,7 +1468,8 @@ 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, cx.entity_id().as_u64(), text).with_language(language, cx)
+            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+                .with_language(language, cx)
         });
         cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
@@ -1553,7 +1555,8 @@ pub mod tests {
         cx.update(|cx| init_test(cx, |_| {}));
 
         let buffer = cx.new_model(|cx| {
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+                .with_language(language, cx)
         });
         cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
         let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
@@ -1620,7 +1623,8 @@ 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, cx.entity_id().as_u64(), text).with_language(language, cx)
+            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+                .with_language(language, cx)
         });
         cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
 

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

@@ -109,7 +109,7 @@ use std::{
 };
 pub use sum_tree::Bias;
 use sum_tree::TreeMap;
-use text::{OffsetUtf16, Rope};
+use text::{BufferId, OffsetUtf16, Rope};
 use theme::{
     observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
     ThemeColors, ThemeSettings,
@@ -1289,19 +1289,37 @@ impl InlayHintRefreshReason {
 
 impl Editor {
     pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
+        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| 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, cx.entity_id().as_u64(), String::new()));
+        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| 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, cx.entity_id().as_u64(), String::new()));
+        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| MultiBuffer::singleton(buffer, cx));
         Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
     }

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

@@ -39,7 +39,8 @@ fn test_edit_events(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
     let buffer = cx.new_model(|cx| {
-        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
+        let mut buffer =
+            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456");
         buffer.set_group_interval(Duration::from_secs(1));
         buffer
     });
@@ -154,7 +155,9 @@ 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, cx.entity_id().as_u64(), "123456"));
+    let buffer = cx.new_model(|cx| {
+        language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456")
+    });
     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));
@@ -225,7 +228,8 @@ fn test_ime_composition(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
     let buffer = cx.new_model(|cx| {
-        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "abcde");
+        let mut buffer =
+            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcde");
         // Ensure automatic grouping doesn't occur.
         buffer.set_group_interval(Duration::ZERO);
         buffer
@@ -629,7 +633,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
 
             // Ensure we don't panic when navigation data contains invalid anchors *and* points.
             let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
-            invalid_anchor.text_anchor.buffer_id = Some(999);
+            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
             let invalid_point = Point::new(9999, 0);
             editor.navigate(
                 Box::new(NavigationData {
@@ -2342,11 +2346,20 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
     ));
 
     let toml_buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n").with_language(toml_language, cx)
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            "a = 1\nb = 2\n",
+        )
+        .with_language(toml_language, cx)
     });
     let rust_buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), "const c: usize = 3;\n")
-            .with_language(rust_language, cx)
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            "const c: usize = 3;\n",
+        )
+        .with_language(rust_language, cx)
     });
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
@@ -3984,8 +3997,10 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, 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| MultiBuffer::singleton(buffer, cx));
     let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 
@@ -4149,8 +4164,10 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
 
     let text = "fn a() {}";
 
-    let buffer = cx
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, 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| MultiBuffer::singleton(buffer, cx));
     let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
     editor
@@ -4713,8 +4730,10 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, 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| 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))
@@ -4862,8 +4881,10 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, 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| MultiBuffer::singleton(buffer, cx));
     let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
     editor
@@ -6095,7 +6116,13 @@ 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, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            sample_text(3, 4, 'a'),
+        )
+    });
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
         multibuffer.push_excerpts(
@@ -6179,7 +6206,13 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
             primary: None,
         }
     });
-    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            initial_text,
+        )
+    });
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
         multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
@@ -6237,7 +6270,13 @@ 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, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            sample_text(3, 4, 'a'),
+        )
+    });
     let mut excerpt1_id = None;
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
@@ -6322,7 +6361,13 @@ 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, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
+    let buffer = cx.new_model(|cx| {
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            sample_text(3, 4, 'a'),
+        )
+    });
     let mut excerpt1_id = None;
     let multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
@@ -6417,8 +6462,10 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
         "{{} }\n",     //
     );
 
-    let buffer = cx
-        .new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, 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| 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))
@@ -7498,8 +7545,20 @@ async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::T
     let (copilot, copilot_lsp) = Copilot::fake(cx);
     _ = cx.update(|cx| cx.set_global(copilot));
 
-    let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
-    let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
+    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 multibuffer = cx.new_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0, ReadWrite);
         multibuffer.push_excerpts(

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

@@ -28,7 +28,7 @@ use collections::{hash_map, HashMap, HashSet};
 use language::language_settings::InlayHintSettings;
 use smol::lock::Semaphore;
 use sum_tree::Bias;
-use text::{ToOffset, ToPoint};
+use text::{BufferId, ToOffset, ToPoint};
 use util::post_inc;
 
 pub struct InlayHintCache {
@@ -50,7 +50,7 @@ struct TasksForRanges {
 struct CachedExcerptHints {
     version: usize,
     buffer_version: Global,
-    buffer_id: u64,
+    buffer_id: BufferId,
     ordered_hints: Vec<InlayId>,
     hints_by_id: HashMap<InlayId, InlayHint>,
 }
@@ -93,7 +93,7 @@ struct ExcerptHintsUpdate {
 
 #[derive(Debug, Clone, Copy)]
 struct ExcerptQuery {
-    buffer_id: u64,
+    buffer_id: BufferId,
     excerpt_id: ExcerptId,
     cache_version: usize,
     invalidate: InvalidationStrategy,
@@ -553,7 +553,7 @@ impl InlayHintCache {
     /// Queries a certain hint from the cache for extra data via the LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHint_resolve">resolve</a> request.
     pub(super) fn spawn_hint_resolve(
         &self,
-        buffer_id: u64,
+        buffer_id: BufferId,
         excerpt_id: ExcerptId,
         id: InlayId,
         cx: &mut ViewContext<'_, Editor>,

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

@@ -30,7 +30,7 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use text::Selection;
+use text::{BufferId, Selection};
 use theme::Theme;
 use ui::{h_flex, prelude::*, Label};
 use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
@@ -73,12 +73,14 @@ impl FollowableItem for Editor {
             .iter()
             .map(|excerpt| excerpt.buffer_id)
             .collect::<HashSet<_>>();
-        let buffers = project.update(cx, |project, cx| {
-            buffer_ids
-                .iter()
-                .map(|id| project.open_buffer_by_id(*id, cx))
-                .collect::<Vec<_>>()
-        });
+        let buffers = project
+            .update(cx, |project, cx| {
+                buffer_ids
+                    .iter()
+                    .map(|id| BufferId::new(*id).map(|id| project.open_buffer_by_id(id, cx)))
+                    .collect::<Result<Vec<_>>>()
+            })
+            .ok()?;
 
         let pane = pane.downgrade();
         Some(cx.spawn(|mut cx| async move {
@@ -109,10 +111,12 @@ impl FollowableItem for Editor {
                                 MultiBuffer::new(replica_id, project.read(cx).capability());
                             let mut excerpts = state.excerpts.into_iter().peekable();
                             while let Some(excerpt) = excerpts.peek() {
-                                let buffer_id = excerpt.buffer_id;
+                                let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
+                                    continue;
+                                };
                                 let buffer_excerpts = iter::from_fn(|| {
                                     let excerpt = excerpts.peek()?;
-                                    (excerpt.buffer_id == buffer_id)
+                                    (excerpt.buffer_id == u64::from(buffer_id))
                                         .then(|| excerpts.next().unwrap())
                                 });
                                 let buffer =
@@ -189,7 +193,7 @@ impl FollowableItem for Editor {
             .excerpts()
             .map(|(id, buffer, range)| proto::Excerpt {
                 id: id.to_proto(),
-                buffer_id: buffer.remote_id(),
+                buffer_id: buffer.remote_id().into(),
                 context_start: Some(serialize_text_anchor(&range.context.start)),
                 context_end: Some(serialize_text_anchor(&range.context.end)),
                 primary_start: range
@@ -336,9 +340,9 @@ async fn update_editor_from_message(
     let inserted_excerpt_buffers = project.update(cx, |project, cx| {
         inserted_excerpt_buffer_ids
             .into_iter()
-            .map(|id| project.open_buffer_by_id(id, cx))
-            .collect::<Vec<_>>()
-    })?;
+            .map(|id| BufferId::new(id).map(|id| project.open_buffer_by_id(id, cx)))
+            .collect::<Result<Vec<_>>>()
+    })??;
     let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
 
     // Update the editor's excerpts.
@@ -362,7 +366,7 @@ async fn update_editor_from_message(
                 let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
                     continue;
                 };
-                let buffer_id = excerpt.buffer_id;
+                let buffer_id = BufferId::new(excerpt.buffer_id)?;
                 let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
                     continue;
                 };
@@ -370,7 +374,7 @@ async fn update_editor_from_message(
                 let adjacent_excerpts = iter::from_fn(|| {
                     let insertion = insertions.peek()?;
                     if insertion.previous_excerpt_id.is_none()
-                        && insertion.excerpt.as_ref()?.buffer_id == buffer_id
+                        && insertion.excerpt.as_ref()?.buffer_id == u64::from(buffer_id)
                     {
                         insertions.next()?.excerpt
                     } else {
@@ -395,8 +399,9 @@ async fn update_editor_from_message(
             }
 
             multibuffer.remove_excerpts(removed_excerpt_ids, cx);
-        });
-    })?;
+            Result::<(), anyhow::Error>::Ok(())
+        })
+    })??;
 
     // Deserialize the editor state.
     let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
@@ -450,13 +455,13 @@ async fn update_editor_from_message(
 }
 
 fn serialize_excerpt(
-    buffer_id: u64,
+    buffer_id: BufferId,
     id: &ExcerptId,
     range: &ExcerptRange<language::Anchor>,
 ) -> Option<proto::Excerpt> {
     Some(proto::Excerpt {
         id: id.to_proto(),
-        buffer_id,
+        buffer_id: buffer_id.into(),
         context_start: Some(serialize_text_anchor(&range.context.start)),
         context_end: Some(serialize_text_anchor(&range.context.end)),
         primary_start: range

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

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

crates/git/src/diff.rs πŸ”—

@@ -314,7 +314,7 @@ mod tests {
     use std::assert_eq;
 
     use super::*;
-    use text::Buffer;
+    use text::{Buffer, BufferId};
     use unindent::Unindent as _;
 
     #[test]
@@ -333,7 +333,7 @@ mod tests {
         "
         .unindent();
 
-        let mut buffer = Buffer::new(0, 0, buffer_text);
+        let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
         let mut diff = BufferDiff::new();
         smol::block_on(diff.update(&diff_base, &buffer));
         assert_hunks(
@@ -393,7 +393,7 @@ mod tests {
         "
         .unindent();
 
-        let buffer = Buffer::new(0, 0, buffer_text);
+        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
         let mut diff = BufferDiff::new();
         smol::block_on(diff.update(&diff_base, &buffer));
         assert_eq!(diff.hunks(&buffer).count(), 8);

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

@@ -15,7 +15,7 @@ use crate::{
     },
     CodeLabel, LanguageScope, Outline,
 };
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context, Result};
 pub use clock::ReplicaId;
 use futures::channel::oneshot;
 use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLabel};
@@ -44,10 +44,10 @@ use sum_tree::TreeMap;
 use text::operation_queue::OperationQueue;
 use text::*;
 pub use text::{
-    Anchor, Bias, Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Edit, OffsetRangeExt,
-    OffsetUtf16, Patch, Point, PointUtf16, Rope, RopeFingerprint, Selection, SelectionGoal,
-    Subscription, TextDimension, TextSummary, ToOffset, ToOffsetUtf16, ToPoint, ToPointUtf16,
-    Transaction, TransactionId, Unclipped,
+    Anchor, Bias, Buffer as TextBuffer, BufferId, BufferSnapshot as TextBufferSnapshot, Edit,
+    OffsetRangeExt, OffsetUtf16, Patch, Point, PointUtf16, Rope, RopeFingerprint, Selection,
+    SelectionGoal, Subscription, TextDimension, TextSummary, ToOffset, ToOffsetUtf16, ToPoint,
+    ToPointUtf16, Transaction, TransactionId, Unclipped,
 };
 use theme::SyntaxTheme;
 #[cfg(any(test, feature = "test-support"))]
@@ -396,7 +396,7 @@ pub trait LocalFile: File {
     /// Called when the buffer is reloaded from disk.
     fn buffer_reloaded(
         &self,
-        buffer_id: u64,
+        buffer_id: BufferId,
         version: &clock::Global,
         fingerprint: RopeFingerprint,
         line_ending: LineEnding,
@@ -517,7 +517,7 @@ pub enum CharKind {
 
 impl Buffer {
     /// Create a new buffer with the given base text.
-    pub fn new<T: Into<String>>(replica_id: ReplicaId, id: u64, base_text: T) -> Self {
+    pub fn new<T: Into<String>>(replica_id: ReplicaId, id: BufferId, base_text: T) -> Self {
         Self::build(
             TextBuffer::new(replica_id, id, base_text.into()),
             None,
@@ -528,7 +528,7 @@ impl Buffer {
 
     /// Create a new buffer that is a replica of a remote buffer.
     pub fn remote(
-        remote_id: u64,
+        remote_id: BufferId,
         replica_id: ReplicaId,
         capability: Capability,
         base_text: String,
@@ -549,7 +549,9 @@ impl Buffer {
         message: proto::BufferState,
         file: Option<Arc<dyn File>>,
     ) -> Result<Self> {
-        let buffer = TextBuffer::new(replica_id, message.id, message.base_text);
+        let buffer_id = BufferId::new(message.id)
+            .with_context(|| anyhow!("Could not deserialize buffer_id"))?;
+        let buffer = TextBuffer::new(replica_id, buffer_id, message.base_text);
         let mut this = Self::build(
             buffer,
             message.diff_base.map(|text| text.into_boxed_str().into()),
@@ -572,7 +574,7 @@ impl Buffer {
     /// Serialize the buffer's state to a protobuf message.
     pub fn to_proto(&self) -> proto::BufferState {
         proto::BufferState {
-            id: self.remote_id(),
+            id: self.remote_id().into(),
             file: self.file.as_ref().map(|f| f.to_proto()),
             base_text: self.base_text().to_string(),
             diff_base: self.diff_base.as_ref().map(|h| h.to_string()),

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

@@ -18,7 +18,7 @@ use std::{
     time::{Duration, Instant},
 };
 use text::network::Network;
-use text::LineEnding;
+use text::{BufferId, LineEnding};
 use text::{Point, ToPoint};
 use unindent::Unindent as _;
 use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
@@ -43,8 +43,12 @@ fn test_line_endings(cx: &mut gpui::AppContext) {
     init_settings(cx, |_| {});
 
     cx.new_model(|cx| {
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "one\r\ntwo\rthree")
-            .with_language(Arc::new(rust_lang()), 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);
         assert_eq!(buffer.text(), "one\ntwo\nthree");
         assert_eq!(buffer.line_ending(), LineEnding::Windows);
 
@@ -138,8 +142,10 @@ 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, cx.entity_id().as_u64(), "abcdef"));
-    let buffer2 = cx.new_model(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
+    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_ops = Arc::new(Mutex::new(Vec::new()));
     buffer1.update(cx, {
         let buffer1_ops = buffer1_ops.clone();
@@ -218,7 +224,8 @@ 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, cx.entity_id().as_u64(), text));
+    let buffer =
+        cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
     let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
 
     let text = "a\nccc\ndddd\nffffff\n";
@@ -250,7 +257,8 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
     ]
     .join("\n");
 
-    let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
+    let buffer =
+        cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
 
     // Spawn a task to format the buffer's whitespace.
     // Pause so that the foratting task starts running.
@@ -315,7 +323,8 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
 async fn test_reparse(cx: &mut gpui::TestAppContext) {
     let text = "fn a() {}";
     let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
+        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .with_language(Arc::new(rust_lang()), cx)
     });
 
     // Wait for the initial text to parse
@@ -443,8 +452,8 @@ 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, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "{}")
+            .with_language(Arc::new(rust_lang()), cx);
         buffer.set_sync_parse_timeout(Duration::ZERO);
         buffer
     });
@@ -493,7 +502,8 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
     .unindent();
 
     let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
+        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
         .update(cx, |buffer, _| buffer.snapshot().outline(None))
@@ -579,7 +589,8 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
     .unindent();
 
     let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
+        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
         .update(cx, |buffer, _| buffer.snapshot().outline(None))
@@ -617,7 +628,8 @@ async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
     .unindent();
 
     let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx)
+        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .with_language(Arc::new(language), cx)
     });
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
 
@@ -661,7 +673,8 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
     .unindent();
 
     let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
+        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .with_language(Arc::new(rust_lang()), cx)
     });
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
 
@@ -883,8 +896,8 @@ 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, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
+        let buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .with_language(Arc::new(rust_lang()), cx);
         let snapshot = buffer.snapshot();
 
         assert_eq!(
@@ -924,8 +937,8 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
 
     cx.new_model(|cx| {
         let text = "fn a() {}";
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .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}");
@@ -967,8 +980,8 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
 
     cx.new_model(|cx| {
         let text = "fn a() {}";
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .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}");
@@ -1007,10 +1020,9 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
     init_settings(cx, |_| {});
 
     cx.new_model(|cx| {
-        let entity_id = cx.entity_id();
         let mut buffer = Buffer::new(
             0,
-            entity_id.as_u64(),
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
             "
             fn a() {
             c;
@@ -1085,7 +1097,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
 
         let mut buffer = Buffer::new(
             0,
-            cx.entity_id().as_u64(),
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
             "
             fn a() {
                 b();
@@ -1150,7 +1162,7 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
     cx.new_model(|cx| {
         let mut buffer = Buffer::new(
             0,
-            cx.entity_id().as_u64(),
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
             "
             fn a() {
                 i
@@ -1212,7 +1224,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
     cx.new_model(|cx| {
         let mut buffer = Buffer::new(
             0,
-            cx.entity_id().as_u64(),
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
             "
             fn a() {}
             "
@@ -1268,8 +1280,8 @@ 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, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .with_language(Arc::new(rust_lang()), cx);
         buffer.edit(
             [(0..1, "\n"), (2..3, "\n")],
             Some(AutoindentMode::EachLine),
@@ -1295,8 +1307,8 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
         "
         .unindent();
 
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .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),
@@ -1333,8 +1345,8 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
             }
         "#
         .unindent();
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .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
@@ -1419,8 +1431,8 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
             }
         "#
         .unindent();
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            .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
@@ -1499,17 +1511,18 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
         "
         .unindent();
 
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), 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::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,
+            );
         buffer.edit(
             [(Point::new(3, 0)..Point::new(3, 0), "\n")],
             Some(AutoindentMode::EachLine),
@@ -1575,7 +1588,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
             false,
         );
 
-        let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text);
         buffer.set_language_registry(language_registry);
         buffer.set_language(Some(html_language), cx);
         buffer.edit(
@@ -1611,8 +1624,8 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
     });
 
     cx.new_model(|cx| {
-        let mut buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), "").with_language(Arc::new(ruby_lang()), cx);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
+            .with_language(Arc::new(ruby_lang()), cx);
 
         let text = r#"
             class C
@@ -1713,8 +1726,8 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
         "#
         .unindent();
 
-        let buffer =
-            Buffer::new(0, cx.entity_id().as_u64(), &text).with_language(Arc::new(language), cx);
+        let buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), &text)
+            .with_language(Arc::new(language), cx);
         let snapshot = buffer.snapshot();
 
         let config = snapshot.language_scope_at(0).unwrap();
@@ -1831,8 +1844,12 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
         "#
         .unindent();
 
-        let buffer = Buffer::new(0, cx.entity_id().as_u64(), text.clone())
-            .with_language(Arc::new(language), cx);
+        let buffer = Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            text.clone(),
+        )
+        .with_language(Arc::new(language), cx);
         let snapshot = buffer.snapshot();
 
         // By default, all brackets are enabled
@@ -1876,7 +1893,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, cx.entity_id().as_u64(), text);
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text);
         buffer.set_language_registry(language_registry.clone());
         buffer.set_language(
             language_registry
@@ -1911,7 +1928,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, cx.entity_id().as_u64(), "abc");
+        let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abc");
         buffer.edit([(3..3, "D")], None, cx);
 
         now += Duration::from_secs(1);
@@ -1966,8 +1983,13 @@ 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, cx.entity_id().as_u64(), base_text.as_str()));
+    let base_buffer = cx.new_model(|cx| {
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            base_text.as_str(),
+        )
+    });
 
     for i in 0..rng.gen_range(min_peers..=max_peers) {
         let buffer = cx.new_model(|cx| {
@@ -2475,8 +2497,12 @@ fn assert_bracket_pairs(
 ) {
     let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
     let buffer = cx.new_model(|cx| {
-        Buffer::new(0, cx.entity_id().as_u64(), expected_text.clone())
-            .with_language(Arc::new(language), cx)
+        Buffer::new(
+            0,
+            BufferId::new(cx.entity_id().as_u64()).unwrap(),
+            expected_text.clone(),
+        )
+        .with_language(Arc::new(language), cx)
     });
     let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
 

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

@@ -241,7 +241,7 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
             Bias::Left => proto::Bias::Left as i32,
             Bias::Right => proto::Bias::Right as i32,
         },
-        buffer_id: anchor.buffer_id,
+        buffer_id: anchor.buffer_id.map(Into::into),
     }
 }
 
@@ -420,6 +420,11 @@ pub fn deserialize_diagnostics(
 
 /// Deserializes an [`Anchor`] from the RPC representation.
 pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
+    let buffer_id = if let Some(id) = anchor.buffer_id {
+        Some(BufferId::new(id).ok()?)
+    } else {
+        None
+    };
     Some(Anchor {
         timestamp: clock::Lamport {
             replica_id: anchor.replica_id as ReplicaId,
@@ -430,7 +435,7 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
             proto::Bias::Left => Bias::Left,
             proto::Bias::Right => Bias::Right,
         },
-        buffer_id: anchor.buffer_id,
+        buffer_id,
     })
 }
 

crates/language/src/syntax_map/syntax_map_tests.rs πŸ”—

@@ -2,7 +2,7 @@ use super::*;
 use crate::LanguageConfig;
 use rand::rngs::StdRng;
 use std::{env, ops::Range, sync::Arc};
-use text::Buffer;
+use text::{Buffer, BufferId};
 use tree_sitter::Node;
 use unindent::Unindent as _;
 use util::test::marked_text_ranges;
@@ -86,7 +86,7 @@ fn test_syntax_map_layers_for_range() {
 
     let mut buffer = Buffer::new(
         0,
-        0,
+        BufferId::new(1).unwrap(),
         r#"
             fn a() {
                 assert_eq!(
@@ -185,7 +185,7 @@ fn test_dynamic_language_injection() {
 
     let mut buffer = Buffer::new(
         0,
-        0,
+        BufferId::new(1).unwrap(),
         r#"
             This is a code block:
 
@@ -860,7 +860,7 @@ fn test_random_edits(
         .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
         .unwrap_or(10);
 
-    let mut buffer = Buffer::new(0, 0, text);
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), text);
 
     let mut syntax_map = SyntaxMap::new();
     syntax_map.set_language_registry(registry.clone());
@@ -1040,7 +1040,7 @@ fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap
         .now_or_never()
         .unwrap()
         .unwrap();
-    let mut buffer = Buffer::new(0, 0, Default::default());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), Default::default());
 
     let mut mutated_syntax_map = SyntaxMap::new();
     mutated_syntax_map.set_language_registry(registry.clone());

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

@@ -5,10 +5,11 @@ use std::{
     ops::{Range, Sub},
 };
 use sum_tree::Bias;
+use text::BufferId;
 
 #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
 pub struct Anchor {
-    pub buffer_id: Option<u64>,
+    pub buffer_id: Option<BufferId>,
     pub excerpt_id: ExcerptId,
     pub text_anchor: text::Anchor,
 }

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

@@ -33,7 +33,7 @@ use sum_tree::{Bias, Cursor, SumTree};
 use text::{
     locator::Locator,
     subscription::{Subscription, Topic},
-    Edit, TextSummary,
+    BufferId, Edit, TextSummary,
 };
 use theme::SyntaxTheme;
 use util::post_inc;
@@ -48,7 +48,7 @@ pub struct ExcerptId(usize);
 
 pub struct MultiBuffer {
     snapshot: RefCell<MultiBufferSnapshot>,
-    buffers: RefCell<HashMap<u64, BufferState>>,
+    buffers: RefCell<HashMap<BufferId, BufferState>>,
     next_excerpt_id: usize,
     subscriptions: Topic,
     singleton: bool,
@@ -101,7 +101,7 @@ struct History {
 #[derive(Clone)]
 struct Transaction {
     id: TransactionId,
-    buffer_transactions: HashMap<u64, text::TransactionId>,
+    buffer_transactions: HashMap<BufferId, text::TransactionId>,
     first_edit_at: Instant,
     last_edit_at: Instant,
     suppress_grouping: bool,
@@ -161,7 +161,7 @@ pub struct ExcerptBoundary {
 struct Excerpt {
     id: ExcerptId,
     locator: Locator,
-    buffer_id: u64,
+    buffer_id: BufferId,
     buffer: BufferSnapshot,
     range: ExcerptRange<text::Anchor>,
     max_buffer_row: u32,
@@ -366,7 +366,7 @@ impl MultiBuffer {
         offset: T,
         theme: Option<&SyntaxTheme>,
         cx: &AppContext,
-    ) -> Option<(u64, Vec<OutlineItem<Anchor>>)> {
+    ) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> {
         self.read(cx).symbols_containing(offset, theme)
     }
 
@@ -412,7 +412,7 @@ impl MultiBuffer {
             is_insertion: bool,
             original_indent_column: u32,
         }
-        let mut buffer_edits: HashMap<u64, Vec<BufferEdit>> = Default::default();
+        let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
         let mut edited_excerpt_ids = Vec::new();
         let mut cursor = snapshot.excerpts.cursor::<usize>();
         for (ix, (range, new_text)) in edits.enumerate() {
@@ -514,7 +514,7 @@ impl MultiBuffer {
         // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
         fn tail(
             this: &mut MultiBuffer,
-            buffer_edits: HashMap<u64, Vec<BufferEdit>>,
+            buffer_edits: HashMap<BufferId, Vec<BufferEdit>>,
             autoindent_mode: Option<AutoindentMode>,
             edited_excerpt_ids: Vec<ExcerptId>,
             cx: &mut ModelContext<MultiBuffer>,
@@ -720,7 +720,7 @@ impl MultiBuffer {
         cursor_shape: CursorShape,
         cx: &mut ModelContext<Self>,
     ) {
-        let mut selections_by_buffer: HashMap<u64, Vec<Selection<text::Anchor>>> =
+        let mut selections_by_buffer: HashMap<BufferId, Vec<Selection<text::Anchor>>> =
             Default::default();
         let snapshot = self.read(cx);
         let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
@@ -1440,7 +1440,7 @@ impl MultiBuffer {
             .collect()
     }
 
-    pub fn buffer(&self, buffer_id: u64) -> Option<Model<Buffer>> {
+    pub fn buffer(&self, buffer_id: BufferId) -> Option<Model<Buffer>> {
         self.buffers
             .borrow()
             .get(&buffer_id)
@@ -1661,7 +1661,8 @@ 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, cx.entity_id().as_u64(), text));
+        let buffer = cx
+            .new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));
         cx.new_model(|cx| Self::singleton(buffer, cx))
     }
 
@@ -1671,7 +1672,9 @@ 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, cx.entity_id().as_u64(), text));
+            let buffer = cx.new_model(|cx| {
+                Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+            });
             let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
                 context: range,
                 primary: None,
@@ -1760,7 +1763,9 @@ 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, cx.entity_id().as_u64(), text)));
+                    buffers.push(cx.new_model(|cx| {
+                        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
+                    }));
                     let buffer = buffers.last().unwrap().read(cx);
                     log::info!(
                         "Creating new buffer {} with text: {:?}",
@@ -1987,7 +1992,7 @@ impl MultiBufferSnapshot {
         (start..end, word_kind)
     }
 
-    pub fn as_singleton(&self) -> Option<(&ExcerptId, u64, &BufferSnapshot)> {
+    pub fn as_singleton(&self) -> Option<(&ExcerptId, BufferId, &BufferSnapshot)> {
         if self.singleton {
             self.excerpts
                 .iter()
@@ -3209,7 +3214,7 @@ impl MultiBufferSnapshot {
         &self,
         offset: T,
         theme: Option<&SyntaxTheme>,
-    ) -> Option<(u64, Vec<OutlineItem<Anchor>>)> {
+    ) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> {
         let anchor = self.anchor_before(offset);
         let excerpt_id = anchor.excerpt_id;
         let excerpt = self.excerpt(excerpt_id)?;
@@ -3249,7 +3254,7 @@ impl MultiBufferSnapshot {
         }
     }
 
-    pub fn buffer_id_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<u64> {
+    pub fn buffer_id_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<BufferId> {
         Some(self.excerpt(excerpt_id)?.buffer_id)
     }
 
@@ -3387,7 +3392,7 @@ impl History {
     fn end_transaction(
         &mut self,
         now: Instant,
-        buffer_transactions: HashMap<u64, TransactionId>,
+        buffer_transactions: HashMap<BufferId, TransactionId>,
     ) -> bool {
         assert_ne!(self.transaction_depth, 0);
         self.transaction_depth -= 1;
@@ -3561,7 +3566,7 @@ impl Excerpt {
     fn new(
         id: ExcerptId,
         locator: Locator,
-        buffer_id: u64,
+        buffer_id: BufferId,
         buffer: BufferSnapshot,
         range: ExcerptRange<text::Anchor>,
         has_trailing_newline: bool,
@@ -4154,8 +4159,13 @@ mod tests {
 
     #[gpui::test]
     fn test_singleton(cx: &mut AppContext) {
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(
+                0,
+                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+                sample_text(6, 6, 'a'),
+            )
+        });
         let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 
         let snapshot = multibuffer.read(cx).snapshot(cx);
@@ -4182,7 +4192,8 @@ mod tests {
 
     #[gpui::test]
     fn test_remote(cx: &mut AppContext) {
-        let host_buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a"));
+        let host_buffer =
+            cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "a"));
         let guest_buffer = cx.new_model(|cx| {
             let state = host_buffer.read(cx).to_proto();
             let ops = cx
@@ -4213,10 +4224,20 @@ mod tests {
 
     #[gpui::test]
     fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) {
-        let buffer_1 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
-        let buffer_2 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g')));
+        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 multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
 
         let events = Arc::new(RwLock::new(Vec::<Event>::new()));
@@ -4449,10 +4470,20 @@ mod tests {
 
     #[gpui::test]
     fn test_excerpt_events(cx: &mut AppContext) {
-        let buffer_1 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'a')));
-        let buffer_2 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm')));
+        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 leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
         let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
@@ -4557,8 +4588,13 @@ mod tests {
 
     #[gpui::test]
     fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(
+                0,
+                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+                sample_text(20, 3, 'a'),
+            )
+        });
         let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
         let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
             multibuffer.push_excerpts_with_context_lines(
@@ -4594,8 +4630,13 @@ mod tests {
 
     #[gpui::test]
     async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) {
-        let buffer =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(
+                0,
+                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+                sample_text(20, 3, 'a'),
+            )
+        });
         let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
         let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
             let snapshot = buffer.read(cx);
@@ -4641,7 +4682,9 @@ mod tests {
 
     #[gpui::test]
     fn test_singleton_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcd")
+        });
         let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
         let old_snapshot = multibuffer.read(cx).snapshot(cx);
         buffer.update(cx, |buffer, cx| {
@@ -4661,8 +4704,12 @@ mod tests {
 
     #[gpui::test]
     fn test_multibuffer_anchors(cx: &mut AppContext) {
-        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
-        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi"));
+        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 multibuffer = cx.new_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
             multibuffer.push_excerpts(
@@ -4719,9 +4766,16 @@ 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, cx.entity_id().as_u64(), "abcd"));
-        let buffer_2 =
-            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP"));
+        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 multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
 
         // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
@@ -4932,9 +4986,13 @@ mod tests {
                         let base_text = util::RandomCharIter::new(&mut rng)
                             .take(10)
                             .collect::<String>();
-                        buffers.push(
-                            cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text)),
-                        );
+                        buffers.push(cx.new_model(|cx| {
+                            Buffer::new(
+                                0,
+                                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+                                base_text,
+                            )
+                        }));
                         buffers.last().unwrap()
                     } else {
                         buffers.choose(&mut rng).unwrap()
@@ -5276,8 +5334,12 @@ mod tests {
         let test_settings = SettingsStore::test(cx);
         cx.set_global(test_settings);
 
-        let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234"));
-        let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678"));
+        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 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/lsp_command.rs πŸ”—

@@ -21,7 +21,7 @@ use lsp::{
     OneOf, ServerCapabilities,
 };
 use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
-use text::LineEnding;
+use text::{BufferId, LineEnding};
 
 pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
     lsp::FormattingOptions {
@@ -84,7 +84,7 @@ pub trait LspCommand: 'static + Sized + Send {
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
 
-    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
+    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId>;
 }
 
 pub(crate) struct PrepareRename {
@@ -205,7 +205,7 @@ impl LspCommand for PrepareRename {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
         proto::PrepareRename {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -274,8 +274,8 @@ impl LspCommand for PrepareRename {
         }
     }
 
-    fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::PrepareRename) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -332,7 +332,7 @@ impl LspCommand for PerformRename {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
         proto::PerformRename {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -393,8 +393,8 @@ impl LspCommand for PerformRename {
             .await
     }
 
-    fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::PerformRename) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -437,7 +437,7 @@ impl LspCommand for GetDefinition {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
         proto::GetDefinition {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -486,8 +486,8 @@ impl LspCommand for GetDefinition {
         location_links_from_proto(message.links, project, cx).await
     }
 
-    fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::GetDefinition) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -538,7 +538,7 @@ impl LspCommand for GetTypeDefinition {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
         proto::GetTypeDefinition {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -587,8 +587,8 @@ impl LspCommand for GetTypeDefinition {
         location_links_from_proto(message.links, project, cx).await
     }
 
-    fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -617,9 +617,10 @@ async fn location_links_from_proto(
     for link in proto_links {
         let origin = match link.origin {
             Some(origin) => {
+                let buffer_id = BufferId::new(origin.buffer_id)?;
                 let buffer = project
                     .update(&mut cx, |this, cx| {
-                        this.wait_for_remote_buffer(origin.buffer_id, cx)
+                        this.wait_for_remote_buffer(buffer_id, cx)
                     })?
                     .await?;
                 let start = origin
@@ -642,9 +643,10 @@ async fn location_links_from_proto(
         };
 
         let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
+        let buffer_id = BufferId::new(target.buffer_id)?;
         let buffer = project
             .update(&mut cx, |this, cx| {
-                this.wait_for_remote_buffer(target.buffer_id, cx)
+                this.wait_for_remote_buffer(buffer_id, cx)
             })?
             .await?;
         let start = target
@@ -761,7 +763,9 @@ fn location_links_to_proto(
         .into_iter()
         .map(|definition| {
             let origin = definition.origin.map(|origin| {
-                let buffer_id = project.create_buffer_for_peer(&origin.buffer, peer_id, cx);
+                let buffer_id = project
+                    .create_buffer_for_peer(&origin.buffer, peer_id, cx)
+                    .into();
                 proto::Location {
                     start: Some(serialize_anchor(&origin.range.start)),
                     end: Some(serialize_anchor(&origin.range.end)),
@@ -769,7 +773,9 @@ fn location_links_to_proto(
                 }
             });
 
-            let buffer_id = project.create_buffer_for_peer(&definition.target.buffer, peer_id, cx);
+            let buffer_id = project
+                .create_buffer_for_peer(&definition.target.buffer, peer_id, cx)
+                .into();
             let target = proto::Location {
                 start: Some(serialize_anchor(&definition.target.range.start)),
                 end: Some(serialize_anchor(&definition.target.range.end)),
@@ -859,7 +865,7 @@ impl LspCommand for GetReferences {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences {
         proto::GetReferences {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -901,7 +907,7 @@ impl LspCommand for GetReferences {
                 proto::Location {
                     start: Some(serialize_anchor(&definition.range.start)),
                     end: Some(serialize_anchor(&definition.range.end)),
-                    buffer_id,
+                    buffer_id: buffer_id.into(),
                 }
             })
             .collect();
@@ -917,9 +923,10 @@ impl LspCommand for GetReferences {
     ) -> Result<Vec<Location>> {
         let mut locations = Vec::new();
         for location in message.locations {
+            let buffer_id = BufferId::new(location.buffer_id)?;
             let target_buffer = project
                 .update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(location.buffer_id, cx)
+                    this.wait_for_remote_buffer(buffer_id, cx)
                 })?
                 .await?;
             let start = location
@@ -941,8 +948,8 @@ impl LspCommand for GetReferences {
         Ok(locations)
     }
 
-    fn buffer_id_from_proto(message: &proto::GetReferences) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::GetReferences) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -1007,7 +1014,7 @@ impl LspCommand for GetDocumentHighlights {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentHighlights {
         proto::GetDocumentHighlights {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -1092,8 +1099,8 @@ impl LspCommand for GetDocumentHighlights {
         Ok(highlights)
     }
 
-    fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -1195,7 +1202,7 @@ impl LspCommand for GetHover {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
         proto::GetHover {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -1308,8 +1315,8 @@ impl LspCommand for GetHover {
         }))
     }
 
-    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -1492,7 +1499,7 @@ impl LspCommand for GetCompletions {
         let anchor = buffer.anchor_after(self.position);
         proto::GetCompletions {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(&anchor)),
             version: serialize_version(&buffer.version()),
         }
@@ -1556,8 +1563,8 @@ impl LspCommand for GetCompletions {
         future::try_join_all(completions).await
     }
 
-    fn buffer_id_from_proto(message: &proto::GetCompletions) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::GetCompletions) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -1630,7 +1637,7 @@ impl LspCommand for GetCodeActions {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCodeActions {
         proto::GetCodeActions {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             start: Some(language::proto::serialize_anchor(&self.range.start)),
             end: Some(language::proto::serialize_anchor(&self.range.end)),
             version: serialize_version(&buffer.version()),
@@ -1695,8 +1702,8 @@ impl LspCommand for GetCodeActions {
             .collect()
     }
 
-    fn buffer_id_from_proto(message: &proto::GetCodeActions) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::GetCodeActions) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -1768,7 +1775,7 @@ impl LspCommand for OnTypeFormatting {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
         proto::OnTypeFormatting {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -1831,8 +1838,8 @@ impl LspCommand for OnTypeFormatting {
         Ok(Some(language::proto::deserialize_transaction(transaction)?))
     }
 
-    fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }
 
@@ -2291,7 +2298,7 @@ impl LspCommand for InlayHints {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::InlayHints {
         proto::InlayHints {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             start: Some(language::proto::serialize_anchor(&self.range.start)),
             end: Some(language::proto::serialize_anchor(&self.range.end)),
             version: serialize_version(&buffer.version()),
@@ -2358,7 +2365,7 @@ impl LspCommand for InlayHints {
         Ok(hints)
     }
 
-    fn buffer_id_from_proto(message: &proto::InlayHints) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::InlayHints) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }

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

@@ -1,13 +1,13 @@
 use std::{path::Path, sync::Arc};
 
-use anyhow::Context;
+use anyhow::{Context, Result};
 use async_trait::async_trait;
 use gpui::{AppContext, AsyncAppContext, Model};
 use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
 use lsp::{LanguageServer, LanguageServerId};
 use rpc::proto::{self, PeerId};
 use serde::{Deserialize, Serialize};
-use text::{PointUtf16, ToPointUtf16};
+use text::{BufferId, PointUtf16, ToPointUtf16};
 
 use crate::{lsp_command::LspCommand, Project};
 
@@ -83,7 +83,7 @@ impl LspCommand for ExpandMacro {
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
         proto::LspExtExpandMacro {
             project_id,
-            buffer_id: buffer.remote_id(),
+            buffer_id: buffer.remote_id().into(),
             position: Some(language::proto::serialize_anchor(
                 &buffer.anchor_before(self.position),
             )),
@@ -131,7 +131,7 @@ impl LspCommand for ExpandMacro {
         })
     }
 
-    fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {
-        message.buffer_id
+    fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
     }
 }

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

@@ -12,7 +12,7 @@ mod project_tests;
 #[cfg(test)]
 mod worktree_tests;
 
-use anyhow::{anyhow, Context as _, Result};
+use anyhow::{anyhow, bail, Context as _, Result};
 use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
 use clock::ReplicaId;
 use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
@@ -81,7 +81,7 @@ use std::{
     time::{Duration, Instant},
 };
 use terminals::Terminals;
-use text::Anchor;
+use text::{Anchor, BufferId};
 use util::{
     debug_panic, defer, http::HttpClient, merge_json_value_into,
     paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _,
@@ -120,9 +120,9 @@ pub struct Project {
     collaborators: HashMap<proto::PeerId, Collaborator>,
     client_subscriptions: Vec<client::Subscription>,
     _subscriptions: Vec<gpui::Subscription>,
-    next_buffer_id: u64,
+    next_buffer_id: BufferId,
     opened_buffer: (watch::Sender<()>, watch::Receiver<()>),
-    shared_buffers: HashMap<proto::PeerId, HashSet<u64>>,
+    shared_buffers: HashMap<proto::PeerId, HashSet<BufferId>>,
     #[allow(clippy::type_complexity)]
     loading_buffers_by_path: HashMap<
         ProjectPath,
@@ -131,14 +131,14 @@ pub struct Project {
     #[allow(clippy::type_complexity)]
     loading_local_worktrees:
         HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
-    opened_buffers: HashMap<u64, OpenBuffer>,
-    local_buffer_ids_by_path: HashMap<ProjectPath, u64>,
-    local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, u64>,
+    opened_buffers: HashMap<BufferId, OpenBuffer>,
+    local_buffer_ids_by_path: HashMap<ProjectPath, BufferId>,
+    local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, BufferId>,
     /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
     /// Used for re-issuing buffer requests when peers temporarily disconnect
-    incomplete_remote_buffers: HashMap<u64, Option<Model<Buffer>>>,
-    buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
-    buffers_being_formatted: HashSet<u64>,
+    incomplete_remote_buffers: HashMap<BufferId, Option<Model<Buffer>>>,
+    buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
+    buffers_being_formatted: HashSet<BufferId>,
     buffers_needing_diff: HashSet<WeakModel<Buffer>>,
     git_diff_debouncer: DelayedDebounced,
     nonce: u128,
@@ -210,7 +210,7 @@ struct LspBufferSnapshot {
 /// Message ordered with respect to buffer operations
 enum BufferOrderedMessage {
     Operation {
-        buffer_id: u64,
+        buffer_id: BufferId,
         operation: proto::Operation,
     },
     LanguageServerUpdate {
@@ -224,7 +224,7 @@ enum LocalProjectUpdate {
     WorktreesChanged,
     CreateBufferForPeer {
         peer_id: proto::PeerId,
-        buffer_id: u64,
+        buffer_id: BufferId,
     },
 }
 
@@ -636,7 +636,7 @@ impl Project {
                 worktrees: Vec::new(),
                 buffer_ordered_messages_tx: tx,
                 collaborators: Default::default(),
-                next_buffer_id: 0,
+                next_buffer_id: BufferId::new(1).unwrap(),
                 opened_buffers: Default::default(),
                 shared_buffers: Default::default(),
                 incomplete_remote_buffers: Default::default(),
@@ -722,7 +722,7 @@ impl Project {
                 worktrees: Vec::new(),
                 buffer_ordered_messages_tx: tx,
                 loading_buffers_by_path: Default::default(),
-                next_buffer_id: 0,
+                next_buffer_id: BufferId::default(),
                 opened_buffer: watch::channel(),
                 shared_buffers: Default::default(),
                 incomplete_remote_buffers: Default::default(),
@@ -997,7 +997,7 @@ impl Project {
         cx.notify();
     }
 
-    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Model<Buffer>> {
+    pub fn buffer_for_id(&self, remote_id: BufferId) -> Option<Model<Buffer>> {
         self.opened_buffers
             .get(&remote_id)
             .and_then(|buffer| buffer.upgrade())
@@ -1479,7 +1479,7 @@ impl Project {
                                                 variant: Some(
                                                     proto::create_buffer_for_peer::Variant::Chunk(
                                                         proto::BufferChunk {
-                                                            buffer_id,
+                                                            buffer_id: buffer_id.into(),
                                                             operations: chunk,
                                                             is_last,
                                                         },
@@ -1713,7 +1713,7 @@ impl Project {
         if self.is_remote() {
             return Err(anyhow!("creating buffers as a guest is not supported yet"));
         }
-        let id = post_inc(&mut self.next_buffer_id);
+        let id = self.next_buffer_id.next();
         let buffer = cx.new_model(|cx| {
             Buffer::new(self.replica_id(), id, text)
                 .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx)
@@ -1814,7 +1814,7 @@ impl Project {
         worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Model<Buffer>>> {
-        let buffer_id = post_inc(&mut self.next_buffer_id);
+        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)
@@ -1845,8 +1845,9 @@ impl Project {
                     path: path_string,
                 })
                 .await?;
+            let buffer_id = BufferId::new(response.buffer_id)?;
             this.update(&mut cx, |this, cx| {
-                this.wait_for_remote_buffer(response.buffer_id, cx)
+                this.wait_for_remote_buffer(buffer_id, cx)
             })?
             .await
         })
@@ -1895,7 +1896,7 @@ impl Project {
 
     pub fn open_buffer_by_id(
         &mut self,
-        id: u64,
+        id: BufferId,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Model<Buffer>>> {
         if let Some(buffer) = self.buffer_for_id(id) {
@@ -1903,11 +1904,12 @@ impl Project {
         } else if self.is_local() {
             Task::ready(Err(anyhow!("buffer {} does not exist", id)))
         } else if let Some(project_id) = self.remote_id() {
-            let request = self
-                .client
-                .request(proto::OpenBufferById { project_id, id });
+            let request = self.client.request(proto::OpenBufferById {
+                project_id,
+                id: id.into(),
+            });
             cx.spawn(move |this, mut cx| async move {
-                let buffer_id = request.await?.buffer_id;
+                let buffer_id = BufferId::new(request.await?.buffer_id)?;
                 this.update(&mut cx, |this, cx| {
                     this.wait_for_remote_buffer(buffer_id, cx)
                 })?
@@ -2223,7 +2225,7 @@ impl Project {
         let mut operations_by_buffer_id = HashMap::default();
         async fn flush_operations(
             this: &WeakModel<Project>,
-            operations_by_buffer_id: &mut HashMap<u64, Vec<proto::Operation>>,
+            operations_by_buffer_id: &mut HashMap<BufferId, Vec<proto::Operation>>,
             needs_resync_with_host: &mut bool,
             is_local: bool,
             cx: &mut AsyncAppContext,
@@ -2232,7 +2234,7 @@ impl Project {
                 let request = this.update(cx, |this, _| {
                     let project_id = this.remote_id()?;
                     Some(this.client.request(proto::UpdateBuffer {
-                        buffer_id,
+                        buffer_id: buffer_id.into(),
                         project_id,
                         operations,
                     }))
@@ -4078,7 +4080,9 @@ impl Project {
                         buffer_ids: remote_buffers
                             .iter()
                             .filter_map(|buffer| {
-                                buffer.update(&mut cx, |buffer, _| buffer.remote_id()).ok()
+                                buffer
+                                    .update(&mut cx, |buffer, _| buffer.remote_id().into())
+                                    .ok()
                             })
                             .collect(),
                     })
@@ -4324,7 +4328,7 @@ impl Project {
                             buffer_ids: buffers
                                 .iter()
                                 .map(|buffer| {
-                                    buffer.update(&mut cx, |buffer, _| buffer.remote_id())
+                                    buffer.update(&mut cx, |buffer, _| buffer.remote_id().into())
                                 })
                                 .collect::<Result<_>>()?,
                         })
@@ -4720,8 +4724,9 @@ impl Project {
             });
             cx.spawn(move |this, mut cx| async move {
                 let response = request.await?;
+                let buffer_id = BufferId::new(response.buffer_id)?;
                 this.update(&mut cx, |this, cx| {
-                    this.wait_for_remote_buffer(response.buffer_id, cx)
+                    this.wait_for_remote_buffer(buffer_id, cx)
                 })?
                 .await
             })
@@ -5047,7 +5052,7 @@ impl Project {
                 let response = client
                     .request(proto::ApplyCompletionAdditionalEdits {
                         project_id,
-                        buffer_id,
+                        buffer_id: buffer_id.into(),
                         completion: Some(language::proto::serialize_completion(&completion)),
                     })
                     .await?;
@@ -5179,7 +5184,7 @@ impl Project {
             let client = self.client.clone();
             let request = proto::ApplyCodeAction {
                 project_id,
-                buffer_id: buffer_handle.read(cx).remote_id(),
+                buffer_id: buffer_handle.read(cx).remote_id().into(),
                 action: Some(language::proto::serialize_code_action(&action)),
             };
             cx.spawn(move |this, mut cx| async move {
@@ -5242,7 +5247,7 @@ impl Project {
             let client = self.client.clone();
             let request = proto::OnTypeFormatting {
                 project_id,
-                buffer_id: buffer.read(cx).remote_id(),
+                buffer_id: buffer.read(cx).remote_id().into(),
                 position: Some(serialize_anchor(&position)),
                 trigger,
                 version: serialize_version(&buffer.read(cx).version()),
@@ -5531,7 +5536,7 @@ impl Project {
         let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
         let range_start = range.start;
         let range_end = range.end;
-        let buffer_id = buffer.remote_id();
+        let buffer_id = buffer.remote_id().into();
         let buffer_version = buffer.version().clone();
         let lsp_request = InlayHints { range };
 
@@ -5624,7 +5629,7 @@ impl Project {
             let client = self.client.clone();
             let request = proto::ResolveInlayHint {
                 project_id,
-                buffer_id: buffer_handle.read(cx).remote_id(),
+                buffer_id: buffer_handle.read(cx).remote_id().into(),
                 language_server_id: server_id.0 as u64,
                 hint: Some(InlayHints::project_to_proto_hint(hint.clone())),
             };
@@ -5659,9 +5664,10 @@ impl Project {
                 let response = request.await?;
                 let mut result = HashMap::default();
                 for location in response.locations {
+                    let buffer_id = BufferId::new(location.buffer_id)?;
                     let target_buffer = this
                         .update(&mut cx, |this, cx| {
-                            this.wait_for_remote_buffer(location.buffer_id, cx)
+                            this.wait_for_remote_buffer(buffer_id, cx)
                         })?
                         .await?;
                     let start = location
@@ -6555,7 +6561,7 @@ impl Project {
                             self.client
                                 .send(proto::UpdateBufferFile {
                                     project_id,
-                                    buffer_id: buffer_id as u64,
+                                    buffer_id: buffer_id.into(),
                                     file: Some(new_file.to_proto()),
                                 })
                                 .log_err();
@@ -6721,7 +6727,7 @@ impl Project {
             for (buffer, diff_base) in diff_bases_by_buffer {
                 let buffer_id = buffer.update(&mut cx, |buffer, cx| {
                     buffer.set_diff_base(diff_base.clone(), cx);
-                    buffer.remote_id()
+                    buffer.remote_id().into()
                 })?;
                 if let Some(project_id) = remote_id {
                     client
@@ -7353,7 +7359,7 @@ impl Project {
     ) -> Result<proto::Ack> {
         this.update(&mut cx, |this, cx| {
             let payload = envelope.payload.clone();
-            let buffer_id = payload.buffer_id;
+            let buffer_id = BufferId::new(payload.buffer_id)?;
             let ops = payload
                 .operations
                 .into_iter()
@@ -7404,7 +7410,7 @@ impl Project {
                             as Arc<dyn language::File>);
                     }
 
-                    let buffer_id = state.id;
+                    let buffer_id = BufferId::new(state.id)?;
                     let buffer = cx.new_model(|_| {
                         Buffer::from_proto(this.replica_id(), this.capability(), state, buffer_file)
                             .unwrap()
@@ -7413,9 +7419,10 @@ impl Project {
                         .insert(buffer_id, Some(buffer));
                 }
                 proto::create_buffer_for_peer::Variant::Chunk(chunk) => {
+                    let buffer_id = BufferId::new(chunk.buffer_id)?;
                     let buffer = this
                         .incomplete_remote_buffers
-                        .get(&chunk.buffer_id)
+                        .get(&buffer_id)
                         .cloned()
                         .flatten()
                         .ok_or_else(|| {
@@ -7432,7 +7439,7 @@ impl Project {
                     buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
 
                     if chunk.is_last {
-                        this.incomplete_remote_buffers.remove(&chunk.buffer_id);
+                        this.incomplete_remote_buffers.remove(&buffer_id);
                         this.register_buffer(&buffer, cx)?;
                     }
                 }
@@ -7450,6 +7457,7 @@ impl Project {
     ) -> Result<()> {
         this.update(&mut cx, |this, cx| {
             let buffer_id = envelope.payload.buffer_id;
+            let buffer_id = BufferId::new(buffer_id)?;
             let diff_base = envelope.payload.diff_base;
             if let Some(buffer) = this
                 .opened_buffers
@@ -7475,6 +7483,7 @@ impl Project {
         mut cx: AsyncAppContext,
     ) -> Result<()> {
         let buffer_id = envelope.payload.buffer_id;
+        let buffer_id = BufferId::new(buffer_id)?;
 
         this.update(&mut cx, |this, cx| {
             let payload = envelope.payload.clone();
@@ -7509,7 +7518,7 @@ impl Project {
         _: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<proto::BufferSaved> {
-        let buffer_id = envelope.payload.buffer_id;
+        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
         let (project_id, buffer) = this.update(&mut cx, |this, _cx| {
             let project_id = this.remote_id().ok_or_else(|| anyhow!("not connected"))?;
             let buffer = this
@@ -7530,7 +7539,7 @@ impl Project {
             .await?;
         Ok(buffer.update(&mut cx, |buffer, _| proto::BufferSaved {
             project_id,
-            buffer_id,
+            buffer_id: buffer_id.into(),
             version: serialize_version(buffer.saved_version()),
             mtime: Some(buffer.saved_mtime().into()),
             fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()),
@@ -7547,9 +7556,10 @@ impl Project {
         let reload = this.update(&mut cx, |this, cx| {
             let mut buffers = HashSet::default();
             for buffer_id in &envelope.payload.buffer_ids {
+                let buffer_id = BufferId::new(*buffer_id)?;
                 buffers.insert(
                     this.opened_buffers
-                        .get(buffer_id)
+                        .get(&buffer_id)
                         .and_then(|buffer| buffer.upgrade())
                         .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
                 );
@@ -7580,12 +7590,12 @@ impl Project {
         this.update(&mut cx, |this, cx| {
             let Some(guest_id) = envelope.original_sender_id else {
                 error!("missing original_sender_id on SynchronizeBuffers request");
-                return;
+                bail!("missing original_sender_id on SynchronizeBuffers request");
             };
 
             this.shared_buffers.entry(guest_id).or_default().clear();
             for buffer in envelope.payload.buffers {
-                let buffer_id = buffer.id;
+                let buffer_id = BufferId::new(buffer.id)?;
                 let remote_version = language::proto::deserialize_version(&buffer.version);
                 if let Some(buffer) = this.buffer_for_id(buffer_id) {
                     this.shared_buffers
@@ -7595,7 +7605,7 @@ impl Project {
 
                     let buffer = buffer.read(cx);
                     response.buffers.push(proto::BufferVersion {
-                        id: buffer_id,
+                        id: buffer_id.into(),
                         version: language::proto::serialize_version(&buffer.version),
                     });
 
@@ -7605,7 +7615,7 @@ impl Project {
                         client
                             .send(proto::UpdateBufferFile {
                                 project_id,
-                                buffer_id: buffer_id as u64,
+                                buffer_id: buffer_id.into(),
                                 file: Some(file.to_proto()),
                             })
                             .log_err();
@@ -7614,7 +7624,7 @@ impl Project {
                     client
                         .send(proto::UpdateDiffBase {
                             project_id,
-                            buffer_id: buffer_id as u64,
+                            buffer_id: buffer_id.into(),
                             diff_base: buffer.diff_base().map(Into::into),
                         })
                         .log_err();
@@ -7622,7 +7632,7 @@ impl Project {
                     client
                         .send(proto::BufferReloaded {
                             project_id,
-                            buffer_id,
+                            buffer_id: buffer_id.into(),
                             version: language::proto::serialize_version(buffer.saved_version()),
                             mtime: Some(buffer.saved_mtime().into()),
                             fingerprint: language::proto::serialize_fingerprint(
@@ -7642,7 +7652,7 @@ impl Project {
                                     client
                                         .request(proto::UpdateBuffer {
                                             project_id,
-                                            buffer_id,
+                                            buffer_id: buffer_id.into(),
                                             operations: chunk,
                                         })
                                         .await?;
@@ -7654,7 +7664,8 @@ impl Project {
                         .detach();
                 }
             }
-        })?;
+            Ok(())
+        })??;
 
         Ok(response)
     }
@@ -7669,9 +7680,10 @@ impl Project {
         let format = this.update(&mut cx, |this, cx| {
             let mut buffers = HashSet::default();
             for buffer_id in &envelope.payload.buffer_ids {
+                let buffer_id = BufferId::new(*buffer_id)?;
                 buffers.insert(
                     this.opened_buffers
-                        .get(buffer_id)
+                        .get(&buffer_id)
                         .and_then(|buffer| buffer.upgrade())
                         .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
                 );
@@ -7696,11 +7708,12 @@ impl Project {
         mut cx: AsyncAppContext,
     ) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
         let (buffer, completion) = this.update(&mut cx, |this, cx| {
+            let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
             let buffer = this
                 .opened_buffers
-                .get(&envelope.payload.buffer_id)
+                .get(&buffer_id)
                 .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
+                .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
             let language = buffer.read(cx).language();
             let completion = language::proto::deserialize_completion(
                 envelope
@@ -7774,9 +7787,10 @@ impl Project {
                 .ok_or_else(|| anyhow!("invalid action"))?,
         )?;
         let apply_code_action = this.update(&mut cx, |this, cx| {
+            let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
             let buffer = this
                 .opened_buffers
-                .get(&envelope.payload.buffer_id)
+                .get(&buffer_id)
                 .and_then(|buffer| buffer.upgrade())
                 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
             Ok::<_, anyhow::Error>(this.apply_code_action(buffer, action, false, cx))
@@ -7798,11 +7812,12 @@ impl Project {
         mut cx: AsyncAppContext,
     ) -> Result<proto::OnTypeFormattingResponse> {
         let on_type_formatting = this.update(&mut cx, |this, cx| {
+            let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
             let buffer = this
                 .opened_buffers
-                .get(&envelope.payload.buffer_id)
+                .get(&buffer_id)
                 .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
+                .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
             let position = envelope
                 .payload
                 .position
@@ -7830,9 +7845,10 @@ impl Project {
         mut cx: AsyncAppContext,
     ) -> Result<proto::InlayHintsResponse> {
         let sender_id = envelope.original_sender_id()?;
+        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
         let buffer = this.update(&mut cx, |this, _| {
             this.opened_buffers
-                .get(&envelope.payload.buffer_id)
+                .get(&buffer_id)
                 .and_then(|buffer| buffer.upgrade())
                 .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
         })??;
@@ -7886,10 +7902,11 @@ impl Project {
         let hint = InlayHints::proto_to_project_hint(proto_hint)
             .context("resolved proto inlay hint conversion")?;
         let buffer = this.update(&mut cx, |this, _cx| {
+            let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
             this.opened_buffers
-                .get(&envelope.payload.buffer_id)
+                .get(&buffer_id)
                 .and_then(|buffer| buffer.upgrade())
-                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
+                .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))
         })??;
         let response_hint = this
             .update(&mut cx, |project, cx| {
@@ -7930,7 +7947,7 @@ impl Project {
         <T::LspRequest as lsp::request::Request>::Result: Send,
     {
         let sender_id = envelope.original_sender_id()?;
-        let buffer_id = T::buffer_id_from_proto(&envelope.payload);
+        let buffer_id = T::buffer_id_from_proto(&envelope.payload)?;
         let buffer_handle = this.update(&mut cx, |this, _cx| {
             this.opened_buffers
                 .get(&buffer_id)
@@ -7995,7 +8012,7 @@ impl Project {
                     let start = serialize_anchor(&range.start);
                     let end = serialize_anchor(&range.end);
                     let buffer_id = this.update(&mut cx, |this, cx| {
-                        this.create_buffer_for_peer(&buffer, peer_id, cx)
+                        this.create_buffer_for_peer(&buffer, peer_id, cx).into()
                     })?;
                     locations.push(proto::Location {
                         buffer_id,
@@ -8037,7 +8054,7 @@ impl Project {
 
         Ok(proto::OpenBufferForSymbolResponse {
             buffer_id: this.update(&mut cx, |this, cx| {
-                this.create_buffer_for_peer(&buffer, peer_id, cx)
+                this.create_buffer_for_peer(&buffer, peer_id, cx).into()
             })?,
         })
     }
@@ -8057,14 +8074,13 @@ impl Project {
         mut cx: AsyncAppContext,
     ) -> Result<proto::OpenBufferResponse> {
         let peer_id = envelope.original_sender_id()?;
+        let buffer_id = BufferId::new(envelope.payload.id)?;
         let buffer = this
-            .update(&mut cx, |this, cx| {
-                this.open_buffer_by_id(envelope.payload.id, cx)
-            })?
+            .update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
             .await?;
         this.update(&mut cx, |this, cx| {
             Ok(proto::OpenBufferResponse {
-                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx),
+                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
             })
         })?
     }
@@ -8090,7 +8106,7 @@ impl Project {
         let buffer = open_buffer.await?;
         this.update(&mut cx, |this, cx| {
             Ok(proto::OpenBufferResponse {
-                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx),
+                buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
             })
         })?
     }
@@ -8108,7 +8124,7 @@ impl Project {
         for (buffer, transaction) in project_transaction.0 {
             serialized_transaction
                 .buffer_ids
-                .push(self.create_buffer_for_peer(&buffer, peer_id, cx));
+                .push(self.create_buffer_for_peer(&buffer, peer_id, cx).into());
             serialized_transaction
                 .transactions
                 .push(language::proto::serialize_transaction(&transaction));
@@ -8126,6 +8142,7 @@ impl Project {
             let mut project_transaction = ProjectTransaction::default();
             for (buffer_id, transaction) in message.buffer_ids.into_iter().zip(message.transactions)
             {
+                let buffer_id = BufferId::new(buffer_id)?;
                 let buffer = this
                     .update(&mut cx, |this, cx| {
                         this.wait_for_remote_buffer(buffer_id, cx)
@@ -8158,7 +8175,7 @@ impl Project {
         buffer: &Model<Buffer>,
         peer_id: proto::PeerId,
         cx: &mut AppContext,
-    ) -> u64 {
+    ) -> BufferId {
         let buffer_id = buffer.read(cx).remote_id();
         if let ProjectClientState::Shared { updates_tx, .. } = &self.client_state {
             updates_tx
@@ -8170,7 +8187,7 @@ impl Project {
 
     fn wait_for_remote_buffer(
         &mut self,
-        id: u64,
+        id: BufferId,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Model<Buffer>>> {
         let mut opened_buffer_rx = self.opened_buffer.1.clone();
@@ -8239,7 +8256,7 @@ impl Project {
                     .filter_map(|(id, buffer)| {
                         let buffer = buffer.upgrade()?;
                         Some(proto::BufferVersion {
-                            id: *id,
+                            id: (*id).into(),
                             version: language::proto::serialize_version(&buffer.read(cx).version),
                         })
                     })
@@ -8265,7 +8282,12 @@ impl Project {
                     .into_iter()
                     .map(|buffer| {
                         let client = client.clone();
-                        let buffer_id = buffer.id;
+                        let buffer_id = match BufferId::new(buffer.id) {
+                            Ok(id) => id,
+                            Err(e) => {
+                                return Task::ready(Err(e));
+                            }
+                        };
                         let remote_version = language::proto::deserialize_version(&buffer.version);
                         if let Some(buffer) = this.buffer_for_id(buffer_id) {
                             let operations =
@@ -8276,7 +8298,7 @@ impl Project {
                                     client
                                         .request(proto::UpdateBuffer {
                                             project_id,
-                                            buffer_id,
+                                            buffer_id: buffer_id.into(),
                                             operations: chunk,
                                         })
                                         .await?;
@@ -8294,7 +8316,10 @@ impl Project {
             // creates these buffers for us again to unblock any waiting futures.
             for id in incomplete_buffer_ids {
                 cx.background_executor()
-                    .spawn(client.request(proto::OpenBufferById { project_id, id }))
+                    .spawn(client.request(proto::OpenBufferById {
+                        project_id,
+                        id: id.into(),
+                    }))
                     .detach();
             }
 
@@ -8436,6 +8461,7 @@ impl Project {
     ) -> Result<()> {
         let fingerprint = deserialize_fingerprint(&envelope.payload.fingerprint)?;
         let version = deserialize_version(&envelope.payload.version);
+        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
         let mtime = envelope
             .payload
             .mtime
@@ -8445,11 +8471,11 @@ impl Project {
         this.update(&mut cx, |this, cx| {
             let buffer = this
                 .opened_buffers
-                .get(&envelope.payload.buffer_id)
+                .get(&buffer_id)
                 .and_then(|buffer| buffer.upgrade())
                 .or_else(|| {
                     this.incomplete_remote_buffers
-                        .get(&envelope.payload.buffer_id)
+                        .get(&buffer_id)
                         .and_then(|b| b.clone())
                 });
             if let Some(buffer) = buffer {
@@ -8478,14 +8504,15 @@ impl Project {
             .mtime
             .ok_or_else(|| anyhow!("missing mtime"))?
             .into();
+        let buffer_id = BufferId::new(payload.buffer_id)?;
         this.update(&mut cx, |this, cx| {
             let buffer = this
                 .opened_buffers
-                .get(&payload.buffer_id)
+                .get(&buffer_id)
                 .and_then(|buffer| buffer.upgrade())
                 .or_else(|| {
                     this.incomplete_remote_buffers
-                        .get(&payload.buffer_id)
+                        .get(&buffer_id)
                         .cloned()
                         .flatten()
                 });

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

@@ -62,6 +62,7 @@ use std::{
     time::{Duration, SystemTime},
 };
 use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
+use text::BufferId;
 use util::{
     paths::{PathMatcher, HOME},
     ResultExt,
@@ -672,7 +673,7 @@ impl LocalWorktree {
 
     pub(crate) fn load_buffer(
         &mut self,
-        id: u64,
+        id: BufferId,
         path: &Path,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<Model<Buffer>>> {
@@ -1043,7 +1044,7 @@ impl LocalWorktree {
         let buffer = buffer_handle.read(cx);
 
         let rpc = self.client.clone();
-        let buffer_id = buffer.remote_id();
+        let buffer_id: u64 = buffer.remote_id().into();
         let project_id = self.share.as_ref().map(|share| share.project_id);
 
         let text = buffer.as_rope().clone();
@@ -1481,7 +1482,7 @@ impl RemoteWorktree {
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
         let buffer = buffer_handle.read(cx);
-        let buffer_id = buffer.remote_id();
+        let buffer_id = buffer.remote_id().into();
         let version = buffer.version();
         let rpc = self.client.clone();
         let project_id = self.project_id;
@@ -2840,7 +2841,7 @@ impl language::LocalFile for File {
 
     fn buffer_reloaded(
         &self,
-        buffer_id: u64,
+        buffer_id: BufferId,
         version: &clock::Global,
         fingerprint: RopeFingerprint,
         line_ending: LineEnding,
@@ -2853,7 +2854,7 @@ impl language::LocalFile for File {
                 .client
                 .send(proto::BufferReloaded {
                     project_id,
-                    buffer_id,
+                    buffer_id: buffer_id.into(),
                     version: serialize_version(version),
                     mtime: Some(mtime.into()),
                     fingerprint: serialize_fingerprint(fingerprint),

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

@@ -21,6 +21,7 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
+use text::BufferId;
 use util::{http::FakeHttpClient, test::temp_tree, ResultExt};
 
 #[gpui::test]
@@ -511,9 +512,11 @@ 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(0, "one/node_modules/b/b1.js".as_ref(), cx)
+            tree.as_local_mut().unwrap().load_buffer(
+                BufferId::new(1).unwrap(),
+                "one/node_modules/b/b1.js".as_ref(),
+                cx,
+            )
         })
         .await
         .unwrap();
@@ -553,9 +556,11 @@ 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(0, "one/node_modules/a/a2.js".as_ref(), cx)
+            tree.as_local_mut().unwrap().load_buffer(
+                BufferId::new(1).unwrap(),
+                "one/node_modules/a/a2.js".as_ref(),
+                cx,
+            )
         })
         .await
         .unwrap();

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

@@ -1007,7 +1007,7 @@ mod tests {
     use super::*;
     use editor::{DisplayPoint, Editor};
     use gpui::{Context, Hsla, TestAppContext, VisualTestContext};
-    use language::Buffer;
+    use language::{Buffer, BufferId};
     use smol::stream::StreamExt as _;
     use unindent::Unindent as _;
 
@@ -1029,7 +1029,7 @@ mod tests {
         let buffer = cx.new_model(|cx| {
             Buffer::new(
                 0,
-                cx.entity_id().as_u64(),
+                BufferId::new(cx.entity_id().as_u64()).unwrap(),
                 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
@@ -1385,7 +1385,13 @@ mod tests {
             expected_query_matches_count > 1,
             "Should pick a query with multiple results"
         );
-        let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(
+                0,
+                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+                buffer_text,
+            )
+        });
         let window = cx.add_window(|_| ());
 
         let editor = window.build_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
@@ -1581,7 +1587,13 @@ 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, cx.entity_id().as_u64(), buffer_text));
+        let buffer = cx.new_model(|cx| {
+            Buffer::new(
+                0,
+                BufferId::new(cx.entity_id().as_u64()).unwrap(),
+                buffer_text,
+            )
+        });
         let cx = cx.add_empty_window();
 
         let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));

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

@@ -1,6 +1,6 @@
 use crate::{
-    locator::Locator, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint,
-    ToPointUtf16,
+    locator::Locator, BufferId, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset,
+    ToPoint, ToPointUtf16,
 };
 use anyhow::Result;
 use std::{cmp::Ordering, fmt::Debug, ops::Range};
@@ -11,7 +11,7 @@ pub struct Anchor {
     pub timestamp: clock::Lamport,
     pub offset: usize,
     pub bias: Bias,
-    pub buffer_id: Option<u64>,
+    pub buffer_id: Option<BufferId>,
 }
 
 impl Anchor {

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

@@ -18,7 +18,7 @@ fn init_logger() {
 
 #[test]
 fn test_edit() {
-    let mut buffer = Buffer::new(0, 0, "abc".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "abc".into());
     assert_eq!(buffer.text(), "abc");
     buffer.edit([(3..3, "def")]);
     assert_eq!(buffer.text(), "abcdef");
@@ -42,7 +42,7 @@ fn test_random_edits(mut rng: StdRng) {
     let mut reference_string = RandomCharIter::new(&mut rng)
         .take(reference_string_len)
         .collect::<String>();
-    let mut buffer = Buffer::new(0, 0, reference_string.clone());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), reference_string.clone());
     LineEnding::normalize(&mut reference_string);
 
     buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
@@ -164,7 +164,7 @@ fn test_line_endings() {
         LineEnding::Windows
     );
 
-    let mut buffer = Buffer::new(0, 0, "one\r\ntwo\rthree".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "one\r\ntwo\rthree".into());
     assert_eq!(buffer.text(), "one\ntwo\nthree");
     assert_eq!(buffer.line_ending(), LineEnding::Windows);
     buffer.check_invariants();
@@ -178,7 +178,7 @@ fn test_line_endings() {
 
 #[test]
 fn test_line_len() {
-    let mut buffer = Buffer::new(0, 0, "".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "".into());
     buffer.edit([(0..0, "abcd\nefg\nhij")]);
     buffer.edit([(12..12, "kl\nmno")]);
     buffer.edit([(18..18, "\npqrs\n")]);
@@ -195,7 +195,7 @@ fn test_line_len() {
 #[test]
 fn test_common_prefix_at_position() {
     let text = "a = str; b = δα";
-    let buffer = Buffer::new(0, 0, text.into());
+    let buffer = Buffer::new(0, BufferId::new(1).unwrap(), text.into());
 
     let offset1 = offset_after(text, "str");
     let offset2 = offset_after(text, "δα");
@@ -243,7 +243,11 @@ fn test_common_prefix_at_position() {
 
 #[test]
 fn test_text_summary_for_range() {
-    let buffer = Buffer::new(0, 0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz".into());
+    let buffer = Buffer::new(
+        0,
+        BufferId::new(1).unwrap(),
+        "ab\nefg\nhklm\nnopqrs\ntuvwxyz".into(),
+    );
     assert_eq!(
         buffer.text_summary_for_range::<TextSummary, _>(1..3),
         TextSummary {
@@ -313,7 +317,7 @@ fn test_text_summary_for_range() {
 
 #[test]
 fn test_chars_at() {
-    let mut buffer = Buffer::new(0, 0, "".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "".into());
     buffer.edit([(0..0, "abcd\nefgh\nij")]);
     buffer.edit([(12..12, "kl\nmno")]);
     buffer.edit([(18..18, "\npqrs")]);
@@ -335,7 +339,7 @@ fn test_chars_at() {
     assert_eq!(chars.collect::<String>(), "PQrs");
 
     // Regression test:
-    let mut buffer = Buffer::new(0, 0, "".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "".into());
     buffer.edit([(0..0, "[workspace]\nmembers = [\n    \"xray_core\",\n    \"xray_server\",\n    \"xray_cli\",\n    \"xray_wasm\",\n]\n")]);
     buffer.edit([(60..60, "\n")]);
 
@@ -345,7 +349,7 @@ fn test_chars_at() {
 
 #[test]
 fn test_anchors() {
-    let mut buffer = Buffer::new(0, 0, "".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "".into());
     buffer.edit([(0..0, "abc")]);
     let left_anchor = buffer.anchor_before(2);
     let right_anchor = buffer.anchor_after(2);
@@ -463,7 +467,7 @@ fn test_anchors() {
 
 #[test]
 fn test_anchors_at_start_and_end() {
-    let mut buffer = Buffer::new(0, 0, "".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "".into());
     let before_start_anchor = buffer.anchor_before(0);
     let after_end_anchor = buffer.anchor_after(0);
 
@@ -486,7 +490,7 @@ fn test_anchors_at_start_and_end() {
 
 #[test]
 fn test_undo_redo() {
-    let mut buffer = Buffer::new(0, 0, "1234".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "1234".into());
     // Set group interval to zero so as to not group edits in the undo stack.
     buffer.set_group_interval(Duration::from_secs(0));
 
@@ -523,7 +527,7 @@ fn test_undo_redo() {
 #[test]
 fn test_history() {
     let mut now = Instant::now();
-    let mut buffer = Buffer::new(0, 0, "123456".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "123456".into());
     buffer.set_group_interval(Duration::from_millis(300));
 
     let transaction_1 = buffer.start_transaction_at(now).unwrap();
@@ -590,7 +594,7 @@ fn test_history() {
 #[test]
 fn test_finalize_last_transaction() {
     let now = Instant::now();
-    let mut buffer = Buffer::new(0, 0, "123456".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "123456".into());
 
     buffer.start_transaction_at(now);
     buffer.edit([(2..4, "cd")]);
@@ -625,7 +629,7 @@ fn test_finalize_last_transaction() {
 #[test]
 fn test_edited_ranges_for_transaction() {
     let now = Instant::now();
-    let mut buffer = Buffer::new(0, 0, "1234567".into());
+    let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "1234567".into());
 
     buffer.start_transaction_at(now);
     buffer.edit([(2..4, "cd")]);
@@ -664,9 +668,9 @@ fn test_edited_ranges_for_transaction() {
 fn test_concurrent_edits() {
     let text = "abcdef";
 
-    let mut buffer1 = Buffer::new(1, 0, text.into());
-    let mut buffer2 = Buffer::new(2, 0, text.into());
-    let mut buffer3 = Buffer::new(3, 0, text.into());
+    let mut buffer1 = Buffer::new(1, BufferId::new(1).unwrap(), text.into());
+    let mut buffer2 = Buffer::new(2, BufferId::new(1).unwrap(), text.into());
+    let mut buffer3 = Buffer::new(3, BufferId::new(1).unwrap(), text.into());
 
     let buf1_op = buffer1.edit([(1..2, "12")]);
     assert_eq!(buffer1.text(), "a12cdef");
@@ -705,7 +709,7 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
     let mut network = Network::new(rng.clone());
 
     for i in 0..peers {
-        let mut buffer = Buffer::new(i as ReplicaId, 0, base_text.clone());
+        let mut buffer = Buffer::new(i as ReplicaId, BufferId::new(1).unwrap(), base_text.clone());
         buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
         buffers.push(buffer);
         replica_ids.push(i as u16);

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

@@ -26,6 +26,7 @@ pub use selection::*;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering, Reverse},
+    fmt::Display,
     future::Future,
     iter::Iterator,
     ops::{self, Deref, Range, Sub},
@@ -59,10 +60,39 @@ pub struct Buffer {
     wait_for_version_txs: Vec<(clock::Global, oneshot::Sender<()>)>,
 }
 
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, PartialOrd, Ord, Eq)]
+pub struct BufferId(u64);
+
+impl Display for BufferId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl BufferId {
+    /// Returns Err if `id` is outside of BufferId domain.
+    pub fn new(id: u64) -> anyhow::Result<Self> {
+        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 {
+        let old = *self;
+        self.0 += 1;
+        old
+    }
+}
+impl From<BufferId> for u64 {
+    fn from(id: BufferId) -> Self {
+        id.0
+    }
+}
+
 #[derive(Clone)]
 pub struct BufferSnapshot {
     replica_id: ReplicaId,
-    remote_id: u64,
+    remote_id: BufferId,
     visible_text: Rope,
     deleted_text: Rope,
     line_ending: LineEnding,
@@ -369,7 +399,7 @@ struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> {
     old_end: D,
     new_end: D,
     range: Range<(&'a Locator, usize)>,
-    buffer_id: u64,
+    buffer_id: BufferId,
 }
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
@@ -478,7 +508,7 @@ pub struct UndoOperation {
 }
 
 impl Buffer {
-    pub fn new(replica_id: u16, remote_id: u64, mut base_text: String) -> Buffer {
+    pub fn new(replica_id: u16, remote_id: BufferId, mut base_text: String) -> Buffer {
         let line_ending = LineEnding::detect(&base_text);
         LineEnding::normalize(&mut base_text);
 
@@ -545,7 +575,7 @@ impl Buffer {
         self.lamport_clock.replica_id
     }
 
-    pub fn remote_id(&self) -> u64 {
+    pub fn remote_id(&self) -> BufferId {
         self.remote_id
     }
 
@@ -1590,7 +1620,7 @@ impl BufferSnapshot {
         &self.visible_text
     }
 
-    pub fn remote_id(&self) -> u64 {
+    pub fn remote_id(&self) -> BufferId {
         self.remote_id
     }
 

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

@@ -69,14 +69,14 @@ mod test {
     use crate::{test::VimTestContext, Vim};
     use editor::Editor;
     use gpui::{Context, Entity, VisualTestContext};
-    use language::Buffer;
+    use language::{Buffer, BufferId};
 
     // 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, 0, "a = 1\nb = 2\n"));
+        let buffer = cx.new_model(|_| Buffer::new(0, BufferId::new(1).unwrap(), "a = 1\nb = 2\n"));
         let window2 = cx.add_window(|cx| Editor::for_buffer(buffer, None, cx));
         let editor2 = cx
             .update(|cx| {
@@ -111,7 +111,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, 0, "a = 1\nb = 2\n"));
+        let buffer = cx.new_model(|_| Buffer::new(0, BufferId::new(1).unwrap(), "a = 1\nb = 2\n"));
         let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx));
 
         editor2.update(cx2, |_, cx| {

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

@@ -278,6 +278,7 @@ 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) {
@@ -295,8 +296,8 @@ mod tests {
         let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
 
         cx.new_model(|cx| {
-            let mut buffer =
-                Buffer::new(0, cx.entity_id().as_u64(), "").with_language(language, cx);
+            let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
+                .with_language(language, cx);
 
             // empty function
             buffer.edit([(0..0, "int main() {}")], None, cx);

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

@@ -181,6 +181,7 @@ 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) {
@@ -199,8 +200,8 @@ mod tests {
         });
 
         cx.new_model(|cx| {
-            let mut buffer =
-                Buffer::new(0, cx.entity_id().as_u64(), "").with_language(language, cx);
+            let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
+                .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/zed/src/languages/rust.rs πŸ”—

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

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

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