Merge pull request #213 from zed-industries/language-buffer

Max Brunsfeld created

Extract Buffer's language-aware behavior into a new `language` crate

Change summary

Cargo.lock                                |   37 
crates/buffer/Cargo.toml                  |   16 
crates/buffer/src/lib.rs                  |  999 +---------------
crates/buffer/src/point.rs                |   18 
crates/buffer/src/tests.rs                |  660 +++++++++++
crates/buffer/src/tests/buffer.rs         |  790 -------------
crates/editor/Cargo.toml                  |    8 
crates/editor/src/display_map.rs          |    8 
crates/editor/src/display_map/fold_map.rs |   10 
crates/editor/src/display_map/tab_map.rs  |    2 
crates/editor/src/display_map/wrap_map.rs |    6 
crates/editor/src/element.rs              |    4 
crates/editor/src/lib.rs                  |   18 
crates/language/Cargo.toml                |   32 
crates/language/src/highlight_map.rs      |    0 
crates/language/src/language.rs           |    0 
crates/language/src/lib.rs                | 1477 +++++++++++++++++++++++++
crates/language/src/tests.rs              |   89 +
crates/project/Cargo.toml                 |    5 
crates/project/src/lib.rs                 |    4 
crates/project/src/worktree.rs            |  131 -
crates/server/src/rpc.rs                  |    2 
crates/workspace/Cargo.toml               |    3 
crates/workspace/src/items.rs             |    2 
crates/workspace/src/lib.rs               |    6 
crates/zed/Cargo.toml                     |    3 
crates/zed/src/language.rs                |    2 
crates/zed/src/lib.rs                     |    3 
crates/zed/src/test.rs                    |    2 
29 files changed, 2,449 insertions(+), 1,888 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -750,21 +750,12 @@ dependencies = [
  "arrayvec 0.7.1",
  "clock",
  "gpui",
- "lazy_static",
  "log",
- "parking_lot",
  "rand 0.8.3",
  "rpc",
  "seahash",
- "serde 1.0.125",
- "similar",
  "smallvec",
- "smol",
  "sum_tree",
- "theme",
- "tree-sitter",
- "tree-sitter-rust",
- "unindent",
 ]
 
 [[package]]
@@ -1624,6 +1615,7 @@ dependencies = [
  "buffer",
  "clock",
  "gpui",
+ "language",
  "lazy_static",
  "log",
  "parking_lot",
@@ -2816,6 +2808,30 @@ dependencies = [
  "log",
 ]
 
+[[package]]
+name = "language"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "buffer",
+ "clock",
+ "futures",
+ "gpui",
+ "lazy_static",
+ "log",
+ "parking_lot",
+ "rand 0.8.3",
+ "rpc",
+ "serde 1.0.125",
+ "similar",
+ "smol",
+ "theme",
+ "tree-sitter",
+ "tree-sitter-rust",
+ "unindent",
+ "util",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -3760,6 +3776,7 @@ dependencies = [
  "fuzzy",
  "gpui",
  "ignore",
+ "language",
  "lazy_static",
  "libc",
  "log",
@@ -6106,6 +6123,7 @@ dependencies = [
  "client",
  "editor",
  "gpui",
+ "language",
  "log",
  "postage",
  "project",
@@ -6176,6 +6194,7 @@ dependencies = [
  "ignore",
  "image 0.23.14",
  "indexmap",
+ "language",
  "lazy_static",
  "libc",
  "log",

crates/buffer/Cargo.toml 🔗

@@ -4,30 +4,20 @@ version = "0.1.0"
 edition = "2018"
 
 [features]
-test-support = ["rand"]
+test-support = ["rand", "seahash"]
 
 [dependencies]
 clock = { path = "../clock" }
-gpui = { path = "../gpui" }
 rpc = { path = "../rpc" }
 sum_tree = { path = "../sum_tree" }
-theme = { path = "../theme" }
 anyhow = "1.0.38"
 arrayvec = "0.7.1"
-lazy_static = "1.4"
 log = "0.4"
-parking_lot = "0.11.1"
 rand = { version = "0.8.3", optional = true }
-seahash = "4.1"
-serde = { version = "1", features = ["derive"] }
-similar = "1.3"
+seahash = { version = "4.1", optional = true }
 smallvec = { version = "1.6", features = ["union"] }
-smol = "1.2"
-tree-sitter = "0.19.5"
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
-
+seahash = "4.1"
 rand = "0.8.3"
-tree-sitter-rust = "0.19.0"
-unindent = "0.1.7"

crates/buffer/src/lib.rs 🔗

@@ -1,6 +1,4 @@
 mod anchor;
-mod highlight_map;
-mod language;
 mod operation_queue;
 mod point;
 #[cfg(any(test, feature = "test-support"))]
@@ -13,89 +11,35 @@ mod tests;
 pub use anchor::*;
 use anyhow::{anyhow, Result};
 use clock::ReplicaId;
-use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
-pub use highlight_map::{HighlightId, HighlightMap};
-use language::Tree;
-pub use language::{BracketPair, Language, LanguageConfig, LanguageRegistry};
-use lazy_static::lazy_static;
 use operation_queue::OperationQueue;
-use parking_lot::Mutex;
 pub use point::*;
 #[cfg(any(test, feature = "test-support"))]
 pub use random_char_iter::*;
 pub use rope::{Chunks, Rope, TextSummary};
 use rpc::proto;
-use seahash::SeaHasher;
 pub use selection::*;
-use similar::{ChangeTag, TextDiff};
-use smol::future::yield_now;
 use std::{
-    any::Any,
-    cell::RefCell,
     cmp,
-    collections::BTreeMap,
     convert::{TryFrom, TryInto},
-    ffi::OsString,
-    future::Future,
-    hash::BuildHasher,
     iter::Iterator,
-    ops::{Deref, DerefMut, Range},
-    path::{Path, PathBuf},
+    ops::Range,
     str,
     sync::Arc,
-    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
+    time::{Duration, Instant},
 };
-use sum_tree::{Bias, FilterCursor, SumTree};
-use tree_sitter::{InputEdit, Parser, QueryCursor};
-
-pub trait File {
-    fn worktree_id(&self) -> usize;
-
-    fn entry_id(&self) -> Option<usize>;
-
-    fn set_entry_id(&mut self, entry_id: Option<usize>);
-
-    fn mtime(&self) -> SystemTime;
-
-    fn set_mtime(&mut self, mtime: SystemTime);
-
-    fn path(&self) -> &Arc<Path>;
-
-    fn set_path(&mut self, path: Arc<Path>);
-
-    fn full_path(&self, cx: &AppContext) -> PathBuf;
-
-    /// Returns the last component of this handle's absolute path. If this handle refers to the root
-    /// of its worktree, then this method will return the name of the worktree itself.
-    fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option<OsString>;
-
-    fn is_deleted(&self) -> bool;
-
-    fn save(
-        &self,
-        buffer_id: u64,
-        text: Rope,
-        version: clock::Global,
-        cx: &mut MutableAppContext,
-    ) -> Task<Result<(clock::Global, SystemTime)>>;
-
-    fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
-
-    fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
-
-    fn boxed_clone(&self) -> Box<dyn File>;
-
-    fn as_any(&self) -> &dyn Any;
-}
+pub use sum_tree::Bias;
+use sum_tree::{FilterCursor, SumTree};
 
+#[cfg(any(test, feature = "test-support"))]
 #[derive(Clone, Default)]
 struct DeterministicState;
 
-impl BuildHasher for DeterministicState {
-    type Hasher = SeaHasher;
+#[cfg(any(test, feature = "test-support"))]
+impl std::hash::BuildHasher for DeterministicState {
+    type Hasher = seahash::SeaHasher;
 
     fn build_hasher(&self) -> Self::Hasher {
-        SeaHasher::new()
+        seahash::SeaHasher::new()
     }
 }
 
@@ -111,71 +55,15 @@ type HashMap<K, V> = std::collections::HashMap<K, V>;
 #[cfg(not(any(test, feature = "test-support")))]
 type HashSet<T> = std::collections::HashSet<T>;
 
-thread_local! {
-    static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
-}
-
-lazy_static! {
-    static ref QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Default::default();
-}
-
-// TODO - Make this configurable
-const INDENT_SIZE: u32 = 4;
-
-struct QueryCursorHandle(Option<QueryCursor>);
-
-impl QueryCursorHandle {
-    fn new() -> Self {
-        QueryCursorHandle(Some(
-            QUERY_CURSORS
-                .lock()
-                .pop()
-                .unwrap_or_else(|| QueryCursor::new()),
-        ))
-    }
-}
-
-impl Deref for QueryCursorHandle {
-    type Target = QueryCursor;
-
-    fn deref(&self) -> &Self::Target {
-        self.0.as_ref().unwrap()
-    }
-}
-
-impl DerefMut for QueryCursorHandle {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.0.as_mut().unwrap()
-    }
-}
-
-impl Drop for QueryCursorHandle {
-    fn drop(&mut self) {
-        let mut cursor = self.0.take().unwrap();
-        cursor.set_byte_range(0..usize::MAX);
-        cursor.set_point_range(Point::zero().into()..Point::MAX.into());
-        QUERY_CURSORS.lock().push(cursor)
-    }
-}
-
+#[derive(Clone)]
 pub struct Buffer {
     fragments: SumTree<Fragment>,
     visible_text: Rope,
     deleted_text: Rope,
     pub version: clock::Global,
-    saved_version: clock::Global,
-    saved_mtime: SystemTime,
     last_edit: clock::Local,
     undo_map: UndoMap,
     history: History,
-    file: Option<Box<dyn File>>,
-    language: Option<Arc<Language>>,
-    autoindent_requests: Vec<Arc<AutoindentRequest>>,
-    pending_autoindent: Option<Task<()>>,
-    sync_parse_timeout: Duration,
-    syntax_tree: Mutex<Option<SyntaxTree>>,
-    parsing_in_background: bool,
-    parse_count: usize,
     selections: HashMap<SelectionSetId, SelectionSet>,
     deferred_ops: OperationQueue,
     deferred_replicas: HashSet<ReplicaId>,
@@ -183,8 +71,6 @@ pub struct Buffer {
     remote_id: u64,
     local_clock: clock::Local,
     lamport_clock: clock::Lamport,
-    #[cfg(test)]
-    operations: Vec<Operation>,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]
@@ -193,25 +79,10 @@ pub struct SelectionSet {
     pub active: bool,
 }
 
-#[derive(Clone)]
-struct SyntaxTree {
-    tree: Tree,
-    version: clock::Global,
-}
-
-#[derive(Clone)]
-struct AutoindentRequest {
-    selection_set_ids: HashSet<SelectionSetId>,
-    before_edit: Snapshot,
-    edited: AnchorSet,
-    inserted: Option<AnchorRangeSet>,
-}
-
 #[derive(Clone, Debug)]
-struct Transaction {
+pub struct Transaction {
     start: clock::Global,
     end: clock::Global,
-    buffer_was_dirty: bool,
     edits: Vec<clock::Local>,
     ranges: Vec<Range<usize>>,
     selections_before: HashMap<SelectionSetId, Arc<[Selection]>>,
@@ -221,6 +92,10 @@ struct Transaction {
 }
 
 impl Transaction {
+    pub fn starting_selection_set_ids<'a>(&'a self) -> impl Iterator<Item = SelectionSetId> + 'a {
+        self.selections_before.keys().copied()
+    }
+
     fn push_edit(&mut self, edit: &EditOperation) {
         self.edits.push(edit.timestamp.local());
         self.end.observe(edit.timestamp.local());
@@ -298,7 +173,6 @@ impl History {
     fn start_transaction(
         &mut self,
         start: clock::Global,
-        buffer_was_dirty: bool,
         selections_before: HashMap<SelectionSetId, Arc<[Selection]>>,
         now: Instant,
     ) {
@@ -307,7 +181,6 @@ impl History {
             self.undo_stack.push(Transaction {
                 start: start.clone(),
                 end: start,
-                buffer_was_dirty,
                 edits: Vec::new(),
                 ranges: Vec::new(),
                 selections_before,
@@ -479,12 +352,6 @@ impl Edit {
     }
 }
 
-struct Diff {
-    base_version: clock::Global,
-    new_text: Arc<str>,
-    changes: Vec<(ChangeTag, usize)>,
-}
-
 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
 struct InsertionTimestamp {
     replica_id: ReplicaId,
@@ -575,53 +442,7 @@ pub struct UndoOperation {
 }
 
 impl Buffer {
-    pub fn new<T: Into<Arc<str>>>(
-        replica_id: ReplicaId,
-        base_text: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        Self::build(
-            replica_id,
-            History::new(base_text.into()),
-            None,
-            cx.model_id() as u64,
-            None,
-            cx,
-        )
-    }
-
-    pub fn from_history(
-        replica_id: ReplicaId,
-        history: History,
-        file: Option<Box<dyn File>>,
-        language: Option<Arc<Language>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        Self::build(
-            replica_id,
-            history,
-            file,
-            cx.model_id() as u64,
-            language,
-            cx,
-        )
-    }
-
-    fn build(
-        replica_id: ReplicaId,
-        history: History,
-        file: Option<Box<dyn File>>,
-        remote_id: u64,
-        language: Option<Arc<Language>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
-        let saved_mtime;
-        if let Some(file) = file.as_ref() {
-            saved_mtime = file.mtime();
-        } else {
-            saved_mtime = UNIX_EPOCH;
-        }
-
+    pub fn new(replica_id: u16, remote_id: u64, history: History) -> Buffer {
         let mut fragments = SumTree::new();
 
         let visible_text = Rope::from(history.base_text.as_ref());
@@ -638,24 +459,14 @@ impl Buffer {
             );
         }
 
-        let mut result = Self {
+        Buffer {
             visible_text,
             deleted_text: Rope::new(),
             fragments,
             version: clock::Global::new(),
-            saved_version: clock::Global::new(),
             last_edit: clock::Local::default(),
             undo_map: Default::default(),
             history,
-            file,
-            syntax_tree: Mutex::new(None),
-            parsing_in_background: false,
-            parse_count: 0,
-            sync_parse_timeout: Duration::from_millis(1),
-            autoindent_requests: Default::default(),
-            pending_autoindent: Default::default(),
-            language,
-            saved_mtime,
             selections: HashMap::default(),
             deferred_ops: OperationQueue::new(),
             deferred_replicas: HashSet::default(),
@@ -663,50 +474,16 @@ impl Buffer {
             remote_id,
             local_clock: clock::Local::new(replica_id),
             lamport_clock: clock::Lamport::new(replica_id),
-
-            #[cfg(test)]
-            operations: Default::default(),
-        };
-        result.reparse(cx);
-        result
-    }
-
-    pub fn replica_id(&self) -> ReplicaId {
-        self.local_clock.replica_id
-    }
-
-    pub fn snapshot(&self) -> Snapshot {
-        Snapshot {
-            visible_text: self.visible_text.clone(),
-            fragments: self.fragments.clone(),
-            version: self.version.clone(),
-            tree: self.syntax_tree(),
-            is_parsing: self.parsing_in_background,
-            language: self.language.clone(),
-            query_cursor: QueryCursorHandle::new(),
         }
     }
 
-    pub fn from_proto(
-        replica_id: ReplicaId,
-        message: proto::Buffer,
-        file: Option<Box<dyn File>>,
-        language: Option<Arc<Language>>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<Self> {
-        let mut buffer = Buffer::build(
-            replica_id,
-            History::new(message.content.into()),
-            file,
-            message.id,
-            language,
-            cx,
-        );
+    pub fn from_proto(replica_id: u16, message: proto::Buffer) -> Result<Self> {
+        let mut buffer = Buffer::new(replica_id, message.id, History::new(message.content.into()));
         let ops = message
             .history
             .into_iter()
             .map(|op| Operation::Edit(op.into()));
-        buffer.apply_ops(ops, cx)?;
+        buffer.apply_ops(ops)?;
         buffer.selections = message
             .selections
             .into_iter()
@@ -730,10 +507,10 @@ impl Buffer {
         Ok(buffer)
     }
 
-    pub fn to_proto(&self, cx: &mut ModelContext<Self>) -> proto::Buffer {
+    pub fn to_proto(&self) -> proto::Buffer {
         let ops = self.history.ops.values().map(Into::into).collect();
         proto::Buffer {
-            id: cx.model_id() as u64,
+            id: self.remote_id,
             content: self.history.base_text.to_string(),
             history: ops,
             selections: self
@@ -749,555 +526,62 @@ impl Buffer {
         }
     }
 
-    pub fn file(&self) -> Option<&dyn File> {
-        self.file.as_deref()
+    pub fn version(&self) -> clock::Global {
+        self.version.clone()
     }
 
-    pub fn file_mut(&mut self) -> Option<&mut dyn File> {
-        self.file.as_mut().map(|f| f.deref_mut() as &mut dyn File)
+    pub fn snapshot(&self) -> Snapshot {
+        Snapshot {
+            visible_text: self.visible_text.clone(),
+            fragments: self.fragments.clone(),
+            version: self.version.clone(),
+        }
     }
 
-    pub fn save(
-        &mut self,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<Task<Result<(clock::Global, SystemTime)>>> {
-        let file = self
-            .file
-            .as_ref()
-            .ok_or_else(|| anyhow!("buffer has no file"))?;
-        let text = self.visible_text.clone();
-        let version = self.version.clone();
-        let save = file.save(self.remote_id, text, version, cx.as_mut());
-        Ok(cx.spawn(|this, mut cx| async move {
-            let (version, mtime) = save.await?;
-            this.update(&mut cx, |this, cx| {
-                this.did_save(version.clone(), mtime, None, cx);
-            });
-            Ok((version, mtime))
-        }))
+    pub fn content<'a>(&'a self) -> Content<'a> {
+        self.into()
     }
 
     pub fn as_rope(&self) -> &Rope {
         &self.visible_text
     }
 
-    pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
-        self.language = language;
-        self.reparse(cx);
-    }
-
-    pub fn did_save(
-        &mut self,
-        version: clock::Global,
-        mtime: SystemTime,
-        new_file: Option<Box<dyn File>>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.saved_mtime = mtime;
-        self.saved_version = version;
-        if let Some(new_file) = new_file {
-            self.file = Some(new_file);
-        }
-        cx.emit(Event::Saved);
-    }
-
-    pub fn file_updated(
-        &mut self,
-        path: Arc<Path>,
-        mtime: SystemTime,
-        new_text: Option<String>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let file = self.file.as_mut().unwrap();
-        let mut changed = false;
-        if path != *file.path() {
-            file.set_path(path);
-            changed = true;
-        }
-
-        if mtime != file.mtime() {
-            file.set_mtime(mtime);
-            changed = true;
-            if let Some(new_text) = new_text {
-                if self.version == self.saved_version {
-                    cx.spawn(|this, mut cx| async move {
-                        let diff = this
-                            .read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
-                            .await;
-                        this.update(&mut cx, |this, cx| {
-                            if this.apply_diff(diff, cx) {
-                                this.saved_version = this.version.clone();
-                                this.saved_mtime = mtime;
-                                cx.emit(Event::Reloaded);
-                            }
-                        });
-                    })
-                    .detach();
-                }
-            }
-        }
-
-        if changed {
-            cx.emit(Event::FileHandleChanged);
-        }
-    }
-
-    pub fn file_deleted(&mut self, cx: &mut ModelContext<Self>) {
-        if self.version == self.saved_version {
-            cx.emit(Event::Dirtied);
-        }
-        cx.emit(Event::FileHandleChanged);
-    }
-
-    pub fn close(&mut self, cx: &mut ModelContext<Self>) {
-        cx.emit(Event::Closed);
-    }
-
-    pub fn language(&self) -> Option<&Arc<Language>> {
-        self.language.as_ref()
-    }
-
-    pub fn parse_count(&self) -> usize {
-        self.parse_count
-    }
-
-    fn syntax_tree(&self) -> Option<Tree> {
-        if let Some(syntax_tree) = self.syntax_tree.lock().as_mut() {
-            self.interpolate_tree(syntax_tree);
-            Some(syntax_tree.tree.clone())
-        } else {
-            None
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn is_parsing(&self) -> bool {
-        self.parsing_in_background
-    }
-
-    #[cfg(test)]
-    pub fn set_sync_parse_timeout(&mut self, timeout: Duration) {
-        self.sync_parse_timeout = timeout;
-    }
-
-    fn reparse(&mut self, cx: &mut ModelContext<Self>) -> bool {
-        if self.parsing_in_background {
-            return false;
-        }
-
-        if let Some(language) = self.language.clone() {
-            let old_tree = self.syntax_tree();
-            let text = self.visible_text.clone();
-            let parsed_version = self.version();
-            let parse_task = cx.background().spawn({
-                let language = language.clone();
-                async move { Self::parse_text(&text, old_tree, &language) }
-            });
-
-            match cx
-                .background()
-                .block_with_timeout(self.sync_parse_timeout, parse_task)
-            {
-                Ok(new_tree) => {
-                    self.did_finish_parsing(new_tree, parsed_version, cx);
-                    return true;
-                }
-                Err(parse_task) => {
-                    self.parsing_in_background = true;
-                    cx.spawn(move |this, mut cx| async move {
-                        let new_tree = parse_task.await;
-                        this.update(&mut cx, move |this, cx| {
-                            let language_changed =
-                                this.language.as_ref().map_or(true, |curr_language| {
-                                    !Arc::ptr_eq(curr_language, &language)
-                                });
-                            let parse_again = this.version > parsed_version || language_changed;
-                            this.parsing_in_background = false;
-                            this.did_finish_parsing(new_tree, parsed_version, cx);
-
-                            if parse_again && this.reparse(cx) {
-                                return;
-                            }
-                        });
-                    })
-                    .detach();
-                }
-            }
-        }
-        false
-    }
-
-    fn parse_text(text: &Rope, old_tree: Option<Tree>, language: &Language) -> Tree {
-        PARSER.with(|parser| {
-            let mut parser = parser.borrow_mut();
-            parser
-                .set_language(language.grammar)
-                .expect("incompatible grammar");
-            let mut chunks = text.chunks_in_range(0..text.len());
-            let tree = parser
-                .parse_with(
-                    &mut move |offset, _| {
-                        chunks.seek(offset);
-                        chunks.next().unwrap_or("").as_bytes()
-                    },
-                    old_tree.as_ref(),
-                )
-                .unwrap();
-            tree
-        })
-    }
-
-    fn interpolate_tree(&self, tree: &mut SyntaxTree) {
-        let mut delta = 0_isize;
-        for edit in self.edits_since(tree.version.clone()) {
-            let start_offset = (edit.old_bytes.start as isize + delta) as usize;
-            let start_point = self.visible_text.to_point(start_offset);
-            tree.tree.edit(&InputEdit {
-                start_byte: start_offset,
-                old_end_byte: start_offset + edit.deleted_bytes(),
-                new_end_byte: start_offset + edit.inserted_bytes(),
-                start_position: start_point.into(),
-                old_end_position: (start_point + edit.deleted_lines()).into(),
-                new_end_position: self
-                    .visible_text
-                    .to_point(start_offset + edit.inserted_bytes())
-                    .into(),
-            });
-            delta += edit.inserted_bytes() as isize - edit.deleted_bytes() as isize;
-        }
-        tree.version = self.version();
-    }
-
-    fn did_finish_parsing(
-        &mut self,
-        tree: Tree,
-        version: clock::Global,
-        cx: &mut ModelContext<Self>,
-    ) {
-        self.parse_count += 1;
-        *self.syntax_tree.lock() = Some(SyntaxTree { tree, version });
-        self.request_autoindent(cx);
-        cx.emit(Event::Reparsed);
-        cx.notify();
-    }
-
-    fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
-        if let Some(indent_columns) = self.compute_autoindents() {
-            let indent_columns = cx.background().spawn(indent_columns);
-            match cx
-                .background()
-                .block_with_timeout(Duration::from_micros(500), indent_columns)
-            {
-                Ok(indent_columns) => self.apply_autoindents(indent_columns, cx),
-                Err(indent_columns) => {
-                    self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move {
-                        let indent_columns = indent_columns.await;
-                        this.update(&mut cx, |this, cx| {
-                            this.apply_autoindents(indent_columns, cx);
-                        });
-                    }));
-                }
-            }
-        }
-    }
-
-    fn compute_autoindents(&self) -> Option<impl Future<Output = BTreeMap<u32, u32>>> {
-        let max_rows_between_yields = 100;
-        let snapshot = self.snapshot();
-        if snapshot.language.is_none()
-            || snapshot.tree.is_none()
-            || self.autoindent_requests.is_empty()
-        {
-            return None;
-        }
-
-        let autoindent_requests = self.autoindent_requests.clone();
-        Some(async move {
-            let mut indent_columns = BTreeMap::new();
-            for request in autoindent_requests {
-                let old_to_new_rows = request
-                    .edited
-                    .to_points(&request.before_edit)
-                    .map(|point| point.row)
-                    .zip(request.edited.to_points(&snapshot).map(|point| point.row))
-                    .collect::<BTreeMap<u32, u32>>();
-
-                let mut old_suggestions = HashMap::default();
-                let old_edited_ranges =
-                    contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
-                for old_edited_range in old_edited_ranges {
-                    let suggestions = request
-                        .before_edit
-                        .suggest_autoindents(old_edited_range.clone())
-                        .into_iter()
-                        .flatten();
-                    for (old_row, suggestion) in old_edited_range.zip(suggestions) {
-                        let indentation_basis = old_to_new_rows
-                            .get(&suggestion.basis_row)
-                            .and_then(|from_row| old_suggestions.get(from_row).copied())
-                            .unwrap_or_else(|| {
-                                request
-                                    .before_edit
-                                    .indent_column_for_line(suggestion.basis_row)
-                            });
-                        let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
-                        old_suggestions.insert(
-                            *old_to_new_rows.get(&old_row).unwrap(),
-                            indentation_basis + delta,
-                        );
-                    }
-                    yield_now().await;
-                }
-
-                // At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the
-                // buffer before the edit, but keyed by the row for these lines after the edits were applied.
-                let new_edited_row_ranges =
-                    contiguous_ranges(old_to_new_rows.values().copied(), max_rows_between_yields);
-                for new_edited_row_range in new_edited_row_ranges {
-                    let suggestions = snapshot
-                        .suggest_autoindents(new_edited_row_range.clone())
-                        .into_iter()
-                        .flatten();
-                    for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
-                        let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
-                        let new_indentation = indent_columns
-                            .get(&suggestion.basis_row)
-                            .copied()
-                            .unwrap_or_else(|| {
-                                snapshot.indent_column_for_line(suggestion.basis_row)
-                            })
-                            + delta;
-                        if old_suggestions
-                            .get(&new_row)
-                            .map_or(true, |old_indentation| new_indentation != *old_indentation)
-                        {
-                            indent_columns.insert(new_row, new_indentation);
-                        }
-                    }
-                    yield_now().await;
-                }
-
-                if let Some(inserted) = request.inserted.as_ref() {
-                    let inserted_row_ranges = contiguous_ranges(
-                        inserted
-                            .to_point_ranges(&snapshot)
-                            .flat_map(|range| range.start.row..range.end.row + 1),
-                        max_rows_between_yields,
-                    );
-                    for inserted_row_range in inserted_row_ranges {
-                        let suggestions = snapshot
-                            .suggest_autoindents(inserted_row_range.clone())
-                            .into_iter()
-                            .flatten();
-                        for (row, suggestion) in inserted_row_range.zip(suggestions) {
-                            let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
-                            let new_indentation = indent_columns
-                                .get(&suggestion.basis_row)
-                                .copied()
-                                .unwrap_or_else(|| {
-                                    snapshot.indent_column_for_line(suggestion.basis_row)
-                                })
-                                + delta;
-                            indent_columns.insert(row, new_indentation);
-                        }
-                        yield_now().await;
-                    }
-                }
-            }
-            indent_columns
-        })
-    }
-
-    fn apply_autoindents(
-        &mut self,
-        indent_columns: BTreeMap<u32, u32>,
-        cx: &mut ModelContext<Self>,
-    ) {
-        let selection_set_ids = self
-            .autoindent_requests
-            .drain(..)
-            .flat_map(|req| req.selection_set_ids.clone())
-            .collect::<HashSet<_>>();
-
-        self.start_transaction(selection_set_ids.iter().copied())
-            .unwrap();
-        for (row, indent_column) in &indent_columns {
-            self.set_indent_column_for_line(*row, *indent_column, cx);
-        }
-
-        for selection_set_id in &selection_set_ids {
-            if let Some(set) = self.selections.get(selection_set_id) {
-                let new_selections = set
-                    .selections
-                    .iter()
-                    .map(|selection| {
-                        let start_point = selection.start.to_point(&*self);
-                        if start_point.column == 0 {
-                            let end_point = selection.end.to_point(&*self);
-                            let delta = Point::new(
-                                0,
-                                indent_columns.get(&start_point.row).copied().unwrap_or(0),
-                            );
-                            if delta.column > 0 {
-                                return Selection {
-                                    id: selection.id,
-                                    goal: selection.goal,
-                                    reversed: selection.reversed,
-                                    start: self
-                                        .anchor_at(start_point + delta, selection.start.bias),
-                                    end: self.anchor_at(end_point + delta, selection.end.bias),
-                                };
-                            }
-                        }
-                        selection.clone()
-                    })
-                    .collect::<Arc<[_]>>();
-                self.update_selection_set(*selection_set_id, new_selections, cx)
-                    .unwrap();
-            }
-        }
-
-        self.end_transaction(selection_set_ids.iter().copied(), cx)
-            .unwrap();
-    }
-
-    pub fn indent_column_for_line(&self, row: u32) -> u32 {
-        self.content().indent_column_for_line(row)
+    pub fn text_summary_for_range(&self, range: Range<usize>) -> TextSummary {
+        self.content().text_summary_for_range(range)
     }
 
-    fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
-        let current_column = self.indent_column_for_line(row);
-        if column > current_column {
-            let offset = self.visible_text.to_offset(Point::new(row, 0));
-            self.edit(
-                [offset..offset],
-                " ".repeat((column - current_column) as usize),
-                cx,
-            );
-        } else if column < current_column {
-            self.edit(
-                [Point::new(row, 0)..Point::new(row, current_column - column)],
-                "",
-                cx,
-            );
-        }
+    pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
+        self.anchor_at(position, Bias::Left)
     }
 
-    pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
-        if let Some(tree) = self.syntax_tree() {
-            let root = tree.root_node();
-            let range = range.start.to_offset(self)..range.end.to_offset(self);
-            let mut node = root.descendant_for_byte_range(range.start, range.end);
-            while node.map_or(false, |n| n.byte_range() == range) {
-                node = node.unwrap().parent();
-            }
-            node.map(|n| n.byte_range())
-        } else {
-            None
-        }
+    pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
+        self.anchor_at(position, Bias::Right)
     }
 
-    pub fn enclosing_bracket_ranges<T: ToOffset>(
-        &self,
-        range: Range<T>,
-    ) -> Option<(Range<usize>, Range<usize>)> {
-        let (lang, tree) = self.language.as_ref().zip(self.syntax_tree())?;
-        let open_capture_ix = lang.brackets_query.capture_index_for_name("open")?;
-        let close_capture_ix = lang.brackets_query.capture_index_for_name("close")?;
-
-        // Find bracket pairs that *inclusively* contain the given range.
-        let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1;
-        let mut cursor = QueryCursorHandle::new();
-        let matches = cursor.set_byte_range(range).matches(
-            &lang.brackets_query,
-            tree.root_node(),
-            TextProvider(&self.visible_text),
-        );
-
-        // Get the ranges of the innermost pair of brackets.
-        matches
-            .filter_map(|mat| {
-                let open = mat.nodes_for_capture_index(open_capture_ix).next()?;
-                let close = mat.nodes_for_capture_index(close_capture_ix).next()?;
-                Some((open.byte_range(), close.byte_range()))
-            })
-            .min_by_key(|(open_range, close_range)| close_range.end - open_range.start)
-    }
-
-    fn diff(&self, new_text: Arc<str>, cx: &AppContext) -> Task<Diff> {
-        // TODO: it would be nice to not allocate here.
-        let old_text = self.text();
-        let base_version = self.version();
-        cx.background().spawn(async move {
-            let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
-                .iter_all_changes()
-                .map(|c| (c.tag(), c.value().len()))
-                .collect::<Vec<_>>();
-            Diff {
-                base_version,
-                new_text,
-                changes,
-            }
-        })
+    pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
+        self.content().anchor_at(position, bias)
     }
 
-    pub fn set_text_from_disk(&self, new_text: Arc<str>, cx: &mut ModelContext<Self>) -> Task<()> {
-        cx.spawn(|this, mut cx| async move {
-            let diff = this
-                .read_with(&cx, |this, cx| this.diff(new_text, cx))
-                .await;
-
-            this.update(&mut cx, |this, cx| {
-                if this.apply_diff(diff, cx) {
-                    this.saved_version = this.version.clone();
-                }
-            });
-        })
+    pub fn point_for_offset(&self, offset: usize) -> Result<Point> {
+        self.content().point_for_offset(offset)
     }
 
-    fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool {
-        if self.version == diff.base_version {
-            self.start_transaction(None).unwrap();
-            let mut offset = 0;
-            for (tag, len) in diff.changes {
-                let range = offset..(offset + len);
-                match tag {
-                    ChangeTag::Equal => offset += len,
-                    ChangeTag::Delete => self.edit(Some(range), "", cx),
-                    ChangeTag::Insert => {
-                        self.edit(Some(offset..offset), &diff.new_text[range], cx);
-                        offset += len;
-                    }
-                }
-            }
-            self.end_transaction(None, cx).unwrap();
-            true
-        } else {
-            false
-        }
+    pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
+        self.visible_text.clip_point(point, bias)
     }
 
-    pub fn is_dirty(&self) -> bool {
-        self.version > self.saved_version
-            || self.file.as_ref().map_or(false, |file| file.is_deleted())
+    pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
+        self.visible_text.clip_offset(offset, bias)
     }
 
-    pub fn has_conflict(&self) -> bool {
-        self.version > self.saved_version
-            && self
-                .file
-                .as_ref()
-                .map_or(false, |file| file.mtime() > self.saved_mtime)
+    pub fn replica_id(&self) -> ReplicaId {
+        self.local_clock.replica_id
     }
 
     pub fn remote_id(&self) -> u64 {
         self.remote_id
     }
 
-    pub fn version(&self) -> clock::Global {
-        self.version.clone()
-    }
-
     pub fn text_summary(&self) -> TextSummary {
         self.visible_text.summary()
     }
@@ -1365,178 +649,23 @@ impl Buffer {
                 .eq(needle.bytes())
     }
 
-    pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
-        let since_2 = since.clone();
-        let cursor = if since == self.version {
-            None
+    pub fn deferred_ops_len(&self) -> usize {
+        self.deferred_ops.len()
+    }
+
+    pub fn edit<R, I, S, T>(&mut self, ranges: R, new_text: T) -> EditOperation
+    where
+        R: IntoIterator<IntoIter = I>,
+        I: ExactSizeIterator<Item = Range<S>>,
+        S: ToOffset,
+        T: Into<String>,
+    {
+        let new_text = new_text.into();
+        let new_text_len = new_text.len();
+        let new_text = if new_text_len > 0 {
+            Some(new_text)
         } else {
-            Some(self.fragments.filter(
-                move |summary| summary.max_version.changed_since(&since_2),
-                &None,
-            ))
-        };
-
-        Edits {
-            visible_text: &self.visible_text,
-            deleted_text: &self.deleted_text,
-            cursor,
-            undos: &self.undo_map,
-            since,
-            old_offset: 0,
-            new_offset: 0,
-            old_point: Point::zero(),
-            new_point: Point::zero(),
-        }
-    }
-
-    pub fn deferred_ops_len(&self) -> usize {
-        self.deferred_ops.len()
-    }
-
-    pub fn start_transaction(
-        &mut self,
-        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
-    ) -> Result<()> {
-        self.start_transaction_at(selection_set_ids, Instant::now())
-    }
-
-    fn start_transaction_at(
-        &mut self,
-        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
-        now: Instant,
-    ) -> Result<()> {
-        let selections = selection_set_ids
-            .into_iter()
-            .map(|set_id| {
-                let set = self
-                    .selections
-                    .get(&set_id)
-                    .expect("invalid selection set id");
-                (set_id, set.selections.clone())
-            })
-            .collect();
-        self.history
-            .start_transaction(self.version.clone(), self.is_dirty(), selections, now);
-        Ok(())
-    }
-
-    pub fn end_transaction(
-        &mut self,
-        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        self.end_transaction_at(selection_set_ids, Instant::now(), cx)
-    }
-
-    fn end_transaction_at(
-        &mut self,
-        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
-        now: Instant,
-        cx: &mut ModelContext<Self>,
-    ) -> Result<()> {
-        let selections = selection_set_ids
-            .into_iter()
-            .map(|set_id| {
-                let set = self
-                    .selections
-                    .get(&set_id)
-                    .expect("invalid selection set id");
-                (set_id, set.selections.clone())
-            })
-            .collect();
-
-        if let Some(transaction) = self.history.end_transaction(selections, now) {
-            let since = transaction.start.clone();
-            let was_dirty = transaction.buffer_was_dirty;
-            self.history.group();
-
-            cx.notify();
-            if self.edits_since(since).next().is_some() {
-                self.did_edit(was_dirty, cx);
-                self.reparse(cx);
-            }
-        }
-
-        Ok(())
-    }
-
-    pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
-    where
-        I: IntoIterator<Item = Range<S>>,
-        S: ToOffset,
-        T: Into<String>,
-    {
-        self.edit_internal(ranges_iter, new_text, false, cx)
-    }
-
-    pub fn edit_with_autoindent<I, S, T>(
-        &mut self,
-        ranges_iter: I,
-        new_text: T,
-        cx: &mut ModelContext<Self>,
-    ) where
-        I: IntoIterator<Item = Range<S>>,
-        S: ToOffset,
-        T: Into<String>,
-    {
-        self.edit_internal(ranges_iter, new_text, true, cx)
-    }
-
-    pub fn edit_internal<I, S, T>(
-        &mut self,
-        ranges_iter: I,
-        new_text: T,
-        autoindent: bool,
-        cx: &mut ModelContext<Self>,
-    ) where
-        I: IntoIterator<Item = Range<S>>,
-        S: ToOffset,
-        T: Into<String>,
-    {
-        let new_text = new_text.into();
-
-        // Skip invalid ranges and coalesce contiguous ones.
-        let mut ranges: Vec<Range<usize>> = Vec::new();
-        for range in ranges_iter {
-            let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
-            if !new_text.is_empty() || !range.is_empty() {
-                if let Some(prev_range) = ranges.last_mut() {
-                    if prev_range.end >= range.start {
-                        prev_range.end = cmp::max(prev_range.end, range.end);
-                    } else {
-                        ranges.push(range);
-                    }
-                } else {
-                    ranges.push(range);
-                }
-            }
-        }
-        if ranges.is_empty() {
-            return;
-        }
-
-        self.pending_autoindent.take();
-        let autoindent_request = if autoindent && self.language.is_some() {
-            let before_edit = self.snapshot();
-            let edited = self.content().anchor_set(ranges.iter().filter_map(|range| {
-                let start = range.start.to_point(&*self);
-                if new_text.starts_with('\n') && start.column == self.line_len(start.row) {
-                    None
-                } else {
-                    Some((range.start, Bias::Left))
-                }
-            }));
-            Some((before_edit, edited))
-        } else {
-            None
-        };
-
-        let first_newline_ix = new_text.find('\n');
-        let new_text_len = new_text.len();
-        let new_text = if new_text_len > 0 {
-            Some(new_text)
-        } else {
-            None
+            None
         };
 
         self.start_transaction(None).unwrap();

crates/buffer/src/point.rs 🔗

@@ -109,21 +109,3 @@ impl Ord for Point {
         }
     }
 }
-
-impl Into<tree_sitter::Point> for Point {
-    fn into(self) -> tree_sitter::Point {
-        tree_sitter::Point {
-            row: self.row as usize,
-            column: self.column as usize,
-        }
-    }
-}
-
-impl From<tree_sitter::Point> for Point {
-    fn from(point: tree_sitter::Point) -> Self {
-        Self {
-            row: point.row as u32,
-            column: point.column as u32,
-        }
-    }
-}

crates/buffer/src/tests.rs 🔗

@@ -1,2 +1,658 @@
-mod buffer;
-mod syntax;
+use super::*;
+use clock::ReplicaId;
+use rand::prelude::*;
+use std::{
+    cmp::Ordering,
+    env,
+    iter::Iterator,
+    time::{Duration, Instant},
+};
+
+#[test]
+fn test_edit() {
+    let mut buffer = Buffer::new(0, 0, History::new("abc".into()));
+    assert_eq!(buffer.text(), "abc");
+    buffer.edit(vec![3..3], "def");
+    assert_eq!(buffer.text(), "abcdef");
+    buffer.edit(vec![0..0], "ghi");
+    assert_eq!(buffer.text(), "ghiabcdef");
+    buffer.edit(vec![5..5], "jkl");
+    assert_eq!(buffer.text(), "ghiabjklcdef");
+    buffer.edit(vec![6..7], "");
+    assert_eq!(buffer.text(), "ghiabjlcdef");
+    buffer.edit(vec![4..9], "mno");
+    assert_eq!(buffer.text(), "ghiamnoef");
+}
+
+#[gpui::test(iterations = 100)]
+fn test_random_edits(mut rng: StdRng) {
+    let operations = env::var("OPERATIONS")
+        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+        .unwrap_or(10);
+
+    let reference_string_len = rng.gen_range(0..3);
+    let mut reference_string = RandomCharIter::new(&mut rng)
+        .take(reference_string_len)
+        .collect::<String>();
+    let mut buffer = Buffer::new(0, 0, History::new(reference_string.clone().into()));
+    buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
+    let mut buffer_versions = Vec::new();
+    log::info!(
+        "buffer text {:?}, version: {:?}",
+        buffer.text(),
+        buffer.version()
+    );
+
+    for _i in 0..operations {
+        let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 5);
+        for old_range in old_ranges.iter().rev() {
+            reference_string.replace_range(old_range.clone(), &new_text);
+        }
+        assert_eq!(buffer.text(), reference_string);
+        log::info!(
+            "buffer text {:?}, version: {:?}",
+            buffer.text(),
+            buffer.version()
+        );
+
+        if rng.gen_bool(0.25) {
+            buffer.randomly_undo_redo(&mut rng);
+            reference_string = buffer.text();
+            log::info!(
+                "buffer text {:?}, version: {:?}",
+                buffer.text(),
+                buffer.version()
+            );
+        }
+
+        let range = buffer.random_byte_range(0, &mut rng);
+        assert_eq!(
+            buffer.text_summary_for_range(range.clone()),
+            TextSummary::from(&reference_string[range])
+        );
+
+        if rng.gen_bool(0.3) {
+            buffer_versions.push(buffer.clone());
+        }
+    }
+
+    for mut old_buffer in buffer_versions {
+        let edits = buffer
+            .edits_since(old_buffer.version.clone())
+            .collect::<Vec<_>>();
+
+        log::info!(
+            "mutating old buffer version {:?}, text: {:?}, edits since: {:?}",
+            old_buffer.version(),
+            old_buffer.text(),
+            edits,
+        );
+
+        let mut delta = 0_isize;
+        for edit in edits {
+            let old_start = (edit.old_bytes.start as isize + delta) as usize;
+            let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect();
+            old_buffer.edit(Some(old_start..old_start + edit.deleted_bytes()), new_text);
+            delta += edit.delta();
+        }
+        assert_eq!(old_buffer.text(), buffer.text());
+    }
+}
+
+#[test]
+fn test_line_len() {
+    let mut buffer = Buffer::new(0, 0, History::new("".into()));
+    buffer.edit(vec![0..0], "abcd\nefg\nhij");
+    buffer.edit(vec![12..12], "kl\nmno");
+    buffer.edit(vec![18..18], "\npqrs\n");
+    buffer.edit(vec![18..21], "\nPQ");
+
+    assert_eq!(buffer.line_len(0), 4);
+    assert_eq!(buffer.line_len(1), 3);
+    assert_eq!(buffer.line_len(2), 5);
+    assert_eq!(buffer.line_len(3), 3);
+    assert_eq!(buffer.line_len(4), 4);
+    assert_eq!(buffer.line_len(5), 0);
+}
+
+#[test]
+fn test_text_summary_for_range() {
+    let buffer = Buffer::new(0, 0, History::new("ab\nefg\nhklm\nnopqrs\ntuvwxyz".into()));
+    assert_eq!(
+        buffer.text_summary_for_range(1..3),
+        TextSummary {
+            bytes: 2,
+            lines: Point::new(1, 0),
+            first_line_chars: 1,
+            last_line_chars: 0,
+            longest_row: 0,
+            longest_row_chars: 1,
+        }
+    );
+    assert_eq!(
+        buffer.text_summary_for_range(1..12),
+        TextSummary {
+            bytes: 11,
+            lines: Point::new(3, 0),
+            first_line_chars: 1,
+            last_line_chars: 0,
+            longest_row: 2,
+            longest_row_chars: 4,
+        }
+    );
+    assert_eq!(
+        buffer.text_summary_for_range(0..20),
+        TextSummary {
+            bytes: 20,
+            lines: Point::new(4, 1),
+            first_line_chars: 2,
+            last_line_chars: 1,
+            longest_row: 3,
+            longest_row_chars: 6,
+        }
+    );
+    assert_eq!(
+        buffer.text_summary_for_range(0..22),
+        TextSummary {
+            bytes: 22,
+            lines: Point::new(4, 3),
+            first_line_chars: 2,
+            last_line_chars: 3,
+            longest_row: 3,
+            longest_row_chars: 6,
+        }
+    );
+    assert_eq!(
+        buffer.text_summary_for_range(7..22),
+        TextSummary {
+            bytes: 15,
+            lines: Point::new(2, 3),
+            first_line_chars: 4,
+            last_line_chars: 3,
+            longest_row: 1,
+            longest_row_chars: 6,
+        }
+    );
+}
+
+#[test]
+fn test_chars_at() {
+    let mut buffer = Buffer::new(0, 0, History::new("".into()));
+    buffer.edit(vec![0..0], "abcd\nefgh\nij");
+    buffer.edit(vec![12..12], "kl\nmno");
+    buffer.edit(vec![18..18], "\npqrs");
+    buffer.edit(vec![18..21], "\nPQ");
+
+    let chars = buffer.chars_at(Point::new(0, 0));
+    assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
+
+    let chars = buffer.chars_at(Point::new(1, 0));
+    assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
+
+    let chars = buffer.chars_at(Point::new(2, 0));
+    assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
+
+    let chars = buffer.chars_at(Point::new(3, 0));
+    assert_eq!(chars.collect::<String>(), "mno\nPQrs");
+
+    let chars = buffer.chars_at(Point::new(4, 0));
+    assert_eq!(chars.collect::<String>(), "PQrs");
+
+    // Regression test:
+    let mut buffer = Buffer::new(0, 0, History::new("".into()));
+    buffer.edit(vec![0..0], "[workspace]\nmembers = [\n    \"xray_core\",\n    \"xray_server\",\n    \"xray_cli\",\n    \"xray_wasm\",\n]\n");
+    buffer.edit(vec![60..60], "\n");
+
+    let chars = buffer.chars_at(Point::new(6, 0));
+    assert_eq!(chars.collect::<String>(), "    \"xray_wasm\",\n]\n");
+}
+
+#[test]
+fn test_anchors() {
+    let mut buffer = Buffer::new(0, 0, History::new("".into()));
+    buffer.edit(vec![0..0], "abc");
+    let left_anchor = buffer.anchor_before(2);
+    let right_anchor = buffer.anchor_after(2);
+
+    buffer.edit(vec![1..1], "def\n");
+    assert_eq!(buffer.text(), "adef\nbc");
+    assert_eq!(left_anchor.to_offset(&buffer), 6);
+    assert_eq!(right_anchor.to_offset(&buffer), 6);
+    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+
+    buffer.edit(vec![2..3], "");
+    assert_eq!(buffer.text(), "adf\nbc");
+    assert_eq!(left_anchor.to_offset(&buffer), 5);
+    assert_eq!(right_anchor.to_offset(&buffer), 5);
+    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+
+    buffer.edit(vec![5..5], "ghi\n");
+    assert_eq!(buffer.text(), "adf\nbghi\nc");
+    assert_eq!(left_anchor.to_offset(&buffer), 5);
+    assert_eq!(right_anchor.to_offset(&buffer), 9);
+    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+    assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 });
+
+    buffer.edit(vec![7..9], "");
+    assert_eq!(buffer.text(), "adf\nbghc");
+    assert_eq!(left_anchor.to_offset(&buffer), 5);
+    assert_eq!(right_anchor.to_offset(&buffer), 7);
+    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },);
+    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 });
+
+    // Ensure anchoring to a point is equivalent to anchoring to an offset.
+    assert_eq!(
+        buffer.anchor_before(Point { row: 0, column: 0 }),
+        buffer.anchor_before(0)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 0, column: 1 }),
+        buffer.anchor_before(1)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 0, column: 2 }),
+        buffer.anchor_before(2)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 0, column: 3 }),
+        buffer.anchor_before(3)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 1, column: 0 }),
+        buffer.anchor_before(4)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 1, column: 1 }),
+        buffer.anchor_before(5)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 1, column: 2 }),
+        buffer.anchor_before(6)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 1, column: 3 }),
+        buffer.anchor_before(7)
+    );
+    assert_eq!(
+        buffer.anchor_before(Point { row: 1, column: 4 }),
+        buffer.anchor_before(8)
+    );
+
+    // Comparison between anchors.
+    let anchor_at_offset_0 = buffer.anchor_before(0);
+    let anchor_at_offset_1 = buffer.anchor_before(1);
+    let anchor_at_offset_2 = buffer.anchor_before(2);
+
+    assert_eq!(
+        anchor_at_offset_0
+            .cmp(&anchor_at_offset_0, &buffer)
+            .unwrap(),
+        Ordering::Equal
+    );
+    assert_eq!(
+        anchor_at_offset_1
+            .cmp(&anchor_at_offset_1, &buffer)
+            .unwrap(),
+        Ordering::Equal
+    );
+    assert_eq!(
+        anchor_at_offset_2
+            .cmp(&anchor_at_offset_2, &buffer)
+            .unwrap(),
+        Ordering::Equal
+    );
+
+    assert_eq!(
+        anchor_at_offset_0
+            .cmp(&anchor_at_offset_1, &buffer)
+            .unwrap(),
+        Ordering::Less
+    );
+    assert_eq!(
+        anchor_at_offset_1
+            .cmp(&anchor_at_offset_2, &buffer)
+            .unwrap(),
+        Ordering::Less
+    );
+    assert_eq!(
+        anchor_at_offset_0
+            .cmp(&anchor_at_offset_2, &buffer)
+            .unwrap(),
+        Ordering::Less
+    );
+
+    assert_eq!(
+        anchor_at_offset_1
+            .cmp(&anchor_at_offset_0, &buffer)
+            .unwrap(),
+        Ordering::Greater
+    );
+    assert_eq!(
+        anchor_at_offset_2
+            .cmp(&anchor_at_offset_1, &buffer)
+            .unwrap(),
+        Ordering::Greater
+    );
+    assert_eq!(
+        anchor_at_offset_2
+            .cmp(&anchor_at_offset_0, &buffer)
+            .unwrap(),
+        Ordering::Greater
+    );
+}
+
+#[test]
+fn test_anchors_at_start_and_end() {
+    let mut buffer = Buffer::new(0, 0, History::new("".into()));
+    let before_start_anchor = buffer.anchor_before(0);
+    let after_end_anchor = buffer.anchor_after(0);
+
+    buffer.edit(vec![0..0], "abc");
+    assert_eq!(buffer.text(), "abc");
+    assert_eq!(before_start_anchor.to_offset(&buffer), 0);
+    assert_eq!(after_end_anchor.to_offset(&buffer), 3);
+
+    let after_start_anchor = buffer.anchor_after(0);
+    let before_end_anchor = buffer.anchor_before(3);
+
+    buffer.edit(vec![3..3], "def");
+    buffer.edit(vec![0..0], "ghi");
+    assert_eq!(buffer.text(), "ghiabcdef");
+    assert_eq!(before_start_anchor.to_offset(&buffer), 0);
+    assert_eq!(after_start_anchor.to_offset(&buffer), 3);
+    assert_eq!(before_end_anchor.to_offset(&buffer), 6);
+    assert_eq!(after_end_anchor.to_offset(&buffer), 9);
+}
+
+#[test]
+fn test_undo_redo() {
+    let mut buffer = Buffer::new(0, 0, History::new("1234".into()));
+    // Set group interval to zero so as to not group edits in the undo stack.
+    buffer.history.group_interval = Duration::from_secs(0);
+
+    buffer.edit(vec![1..1], "abx");
+    buffer.edit(vec![3..4], "yzef");
+    buffer.edit(vec![3..5], "cd");
+    assert_eq!(buffer.text(), "1abcdef234");
+
+    let transactions = buffer.history.undo_stack.clone();
+    assert_eq!(transactions.len(), 3);
+
+    buffer.undo_or_redo(transactions[0].clone()).unwrap();
+    assert_eq!(buffer.text(), "1cdef234");
+    buffer.undo_or_redo(transactions[0].clone()).unwrap();
+    assert_eq!(buffer.text(), "1abcdef234");
+
+    buffer.undo_or_redo(transactions[1].clone()).unwrap();
+    assert_eq!(buffer.text(), "1abcdx234");
+    buffer.undo_or_redo(transactions[2].clone()).unwrap();
+    assert_eq!(buffer.text(), "1abx234");
+    buffer.undo_or_redo(transactions[1].clone()).unwrap();
+    assert_eq!(buffer.text(), "1abyzef234");
+    buffer.undo_or_redo(transactions[2].clone()).unwrap();
+    assert_eq!(buffer.text(), "1abcdef234");
+
+    buffer.undo_or_redo(transactions[2].clone()).unwrap();
+    assert_eq!(buffer.text(), "1abyzef234");
+    buffer.undo_or_redo(transactions[0].clone()).unwrap();
+    assert_eq!(buffer.text(), "1yzef234");
+    buffer.undo_or_redo(transactions[1].clone()).unwrap();
+    assert_eq!(buffer.text(), "1234");
+}
+
+#[test]
+fn test_history() {
+    let mut now = Instant::now();
+    let mut buffer = Buffer::new(0, 0, History::new("123456".into()));
+
+    let set_id = if let Operation::UpdateSelections { set_id, .. } =
+        buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap())
+    {
+        set_id
+    } else {
+        unreachable!()
+    };
+    buffer.start_transaction_at(Some(set_id), now).unwrap();
+    buffer.edit(vec![2..4], "cd");
+    buffer.end_transaction_at(Some(set_id), now).unwrap();
+    assert_eq!(buffer.text(), "12cd56");
+    assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
+
+    buffer.start_transaction_at(Some(set_id), now).unwrap();
+    buffer
+        .update_selection_set(set_id, buffer.selections_from_ranges(vec![1..3]).unwrap())
+        .unwrap();
+    buffer.edit(vec![4..5], "e");
+    buffer.end_transaction_at(Some(set_id), now).unwrap();
+    assert_eq!(buffer.text(), "12cde6");
+    assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+    now += buffer.history.group_interval + Duration::from_millis(1);
+    buffer.start_transaction_at(Some(set_id), now).unwrap();
+    buffer
+        .update_selection_set(set_id, buffer.selections_from_ranges(vec![2..2]).unwrap())
+        .unwrap();
+    buffer.edit(vec![0..1], "a");
+    buffer.edit(vec![1..1], "b");
+    buffer.end_transaction_at(Some(set_id), now).unwrap();
+    assert_eq!(buffer.text(), "ab2cde6");
+    assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+
+    // Last transaction happened past the group interval, undo it on its
+    // own.
+    buffer.undo();
+    assert_eq!(buffer.text(), "12cde6");
+    assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+    // First two transactions happened within the group interval, undo them
+    // together.
+    buffer.undo();
+    assert_eq!(buffer.text(), "123456");
+    assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
+
+    // Redo the first two transactions together.
+    buffer.redo();
+    assert_eq!(buffer.text(), "12cde6");
+    assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+    // Redo the last transaction on its own.
+    buffer.redo();
+    assert_eq!(buffer.text(), "ab2cde6");
+    assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+
+    buffer.start_transaction_at(None, now).unwrap();
+    assert!(buffer.end_transaction_at(None, now).is_none());
+    buffer.undo();
+    assert_eq!(buffer.text(), "12cde6");
+}
+
+#[test]
+fn test_concurrent_edits() {
+    let text = "abcdef";
+
+    let mut buffer1 = Buffer::new(1, 0, History::new(text.into()));
+    let mut buffer2 = Buffer::new(2, 0, History::new(text.into()));
+    let mut buffer3 = Buffer::new(3, 0, History::new(text.into()));
+
+    let buf1_op = buffer1.edit(vec![1..2], "12");
+    assert_eq!(buffer1.text(), "a12cdef");
+    let buf2_op = buffer2.edit(vec![3..4], "34");
+    assert_eq!(buffer2.text(), "abc34ef");
+    let buf3_op = buffer3.edit(vec![5..6], "56");
+    assert_eq!(buffer3.text(), "abcde56");
+
+    buffer1.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
+    buffer1.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
+    buffer2.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
+    buffer2.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
+    buffer3.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
+    buffer3.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
+
+    assert_eq!(buffer1.text(), "a12c34e56");
+    assert_eq!(buffer2.text(), "a12c34e56");
+    assert_eq!(buffer3.text(), "a12c34e56");
+}
+
+#[gpui::test(iterations = 100)]
+fn test_random_concurrent_edits(mut rng: StdRng) {
+    let peers = env::var("PEERS")
+        .map(|i| i.parse().expect("invalid `PEERS` variable"))
+        .unwrap_or(5);
+    let operations = env::var("OPERATIONS")
+        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+        .unwrap_or(10);
+
+    let base_text_len = rng.gen_range(0..10);
+    let base_text = RandomCharIter::new(&mut rng)
+        .take(base_text_len)
+        .collect::<String>();
+    let mut replica_ids = Vec::new();
+    let mut buffers = Vec::new();
+    let mut network = Network::new(rng.clone());
+
+    for i in 0..peers {
+        let mut buffer = Buffer::new(i as ReplicaId, 0, History::new(base_text.clone().into()));
+        buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
+        buffers.push(buffer);
+        replica_ids.push(i as u16);
+        network.add_peer(i as u16);
+    }
+
+    log::info!("initial text: {:?}", base_text);
+
+    let mut mutation_count = operations;
+    loop {
+        let replica_index = rng.gen_range(0..peers);
+        let replica_id = replica_ids[replica_index];
+        let buffer = &mut buffers[replica_index];
+        match rng.gen_range(0..=100) {
+            0..=50 if mutation_count != 0 => {
+                let ops = buffer.randomly_mutate(&mut rng);
+                network.broadcast(buffer.replica_id, ops);
+                log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text());
+                mutation_count -= 1;
+            }
+            51..=70 if mutation_count != 0 => {
+                let ops = buffer.randomly_undo_redo(&mut rng);
+                network.broadcast(buffer.replica_id, ops);
+                mutation_count -= 1;
+            }
+            71..=100 if network.has_unreceived(replica_id) => {
+                let ops = network.receive(replica_id);
+                if !ops.is_empty() {
+                    log::info!(
+                        "peer {} applying {} ops from the network.",
+                        replica_id,
+                        ops.len()
+                    );
+                    buffer.apply_ops(ops).unwrap();
+                }
+            }
+            _ => {}
+        }
+
+        if mutation_count == 0 && network.is_idle() {
+            break;
+        }
+    }
+
+    let first_buffer = &buffers[0];
+    for buffer in &buffers[1..] {
+        assert_eq!(
+            buffer.text(),
+            first_buffer.text(),
+            "Replica {} text != Replica 0 text",
+            buffer.replica_id
+        );
+        assert_eq!(
+            buffer.selection_sets().collect::<HashMap<_, _>>(),
+            first_buffer.selection_sets().collect::<HashMap<_, _>>()
+        );
+        assert_eq!(
+            buffer.all_selection_ranges().collect::<HashMap<_, _>>(),
+            first_buffer
+                .all_selection_ranges()
+                .collect::<HashMap<_, _>>()
+        );
+    }
+}
+
+#[derive(Clone)]
+struct Envelope<T: Clone> {
+    message: T,
+    sender: ReplicaId,
+}
+
+struct Network<T: Clone, R: rand::Rng> {
+    inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
+    all_messages: Vec<T>,
+    rng: R,
+}
+
+impl<T: Clone, R: rand::Rng> Network<T, R> {
+    fn new(rng: R) -> Self {
+        Network {
+            inboxes: Default::default(),
+            all_messages: Vec::new(),
+            rng,
+        }
+    }
+
+    fn add_peer(&mut self, id: ReplicaId) {
+        self.inboxes.insert(id, Vec::new());
+    }
+
+    fn is_idle(&self) -> bool {
+        self.inboxes.values().all(|i| i.is_empty())
+    }
+
+    fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
+        for (replica, inbox) in self.inboxes.iter_mut() {
+            if *replica != sender {
+                for message in &messages {
+                    let min_index = inbox
+                        .iter()
+                        .enumerate()
+                        .rev()
+                        .find_map(|(index, envelope)| {
+                            if sender == envelope.sender {
+                                Some(index + 1)
+                            } else {
+                                None
+                            }
+                        })
+                        .unwrap_or(0);
+
+                    // Insert one or more duplicates of this message *after* the previous
+                    // message delivered by this replica.
+                    for _ in 0..self.rng.gen_range(1..4) {
+                        let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1);
+                        inbox.insert(
+                            insertion_index,
+                            Envelope {
+                                message: message.clone(),
+                                sender,
+                            },
+                        );
+                    }
+                }
+            }
+        }
+        self.all_messages.extend(messages);
+    }
+
+    fn has_unreceived(&self, receiver: ReplicaId) -> bool {
+        !self.inboxes[&receiver].is_empty()
+    }
+
+    fn receive(&mut self, receiver: ReplicaId) -> Vec<T> {
+        let inbox = self.inboxes.get_mut(&receiver).unwrap();
+        let count = self.rng.gen_range(0..inbox.len() + 1);
+        inbox
+            .drain(0..count)
+            .map(|envelope| envelope.message)
+            .collect()
+    }
+}

crates/buffer/src/tests/buffer.rs 🔗

@@ -1,790 +0,0 @@
-use crate::*;
-use clock::ReplicaId;
-use rand::prelude::*;
-use std::{
-    cell::RefCell,
-    cmp::Ordering,
-    env,
-    iter::Iterator,
-    mem,
-    rc::Rc,
-    time::{Duration, Instant},
-};
-
-#[gpui::test]
-fn test_edit(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, "abc", cx);
-        assert_eq!(buffer.text(), "abc");
-        buffer.edit(vec![3..3], "def", cx);
-        assert_eq!(buffer.text(), "abcdef");
-        buffer.edit(vec![0..0], "ghi", cx);
-        assert_eq!(buffer.text(), "ghiabcdef");
-        buffer.edit(vec![5..5], "jkl", cx);
-        assert_eq!(buffer.text(), "ghiabjklcdef");
-        buffer.edit(vec![6..7], "", cx);
-        assert_eq!(buffer.text(), "ghiabjlcdef");
-        buffer.edit(vec![4..9], "mno", cx);
-        assert_eq!(buffer.text(), "ghiamnoef");
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_edit_events(cx: &mut gpui::MutableAppContext) {
-    let mut now = Instant::now();
-    let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
-    let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
-
-    let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
-    let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
-    let buffer_ops = buffer1.update(cx, |buffer, cx| {
-        let buffer_1_events = buffer_1_events.clone();
-        cx.subscribe(&buffer1, move |_, _, event, _| {
-            buffer_1_events.borrow_mut().push(event.clone())
-        })
-        .detach();
-        let buffer_2_events = buffer_2_events.clone();
-        cx.subscribe(&buffer2, move |_, _, event, _| {
-            buffer_2_events.borrow_mut().push(event.clone())
-        })
-        .detach();
-
-        // An edit emits an edited event, followed by a dirtied event,
-        // since the buffer was previously in a clean state.
-        buffer.edit(Some(2..4), "XYZ", cx);
-
-        // An empty transaction does not emit any events.
-        buffer.start_transaction(None).unwrap();
-        buffer.end_transaction(None, cx).unwrap();
-
-        // A transaction containing two edits emits one edited event.
-        now += Duration::from_secs(1);
-        buffer.start_transaction_at(None, now).unwrap();
-        buffer.edit(Some(5..5), "u", cx);
-        buffer.edit(Some(6..6), "w", cx);
-        buffer.end_transaction_at(None, now, cx).unwrap();
-
-        // Undoing a transaction emits one edited event.
-        buffer.undo(cx);
-
-        buffer.operations.clone()
-    });
-
-    // Incorporating a set of remote ops emits a single edited event,
-    // followed by a dirtied event.
-    buffer2.update(cx, |buffer, cx| {
-        buffer.apply_ops(buffer_ops, cx).unwrap();
-    });
-
-    let buffer_1_events = buffer_1_events.borrow();
-    assert_eq!(
-        *buffer_1_events,
-        vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
-    );
-
-    let buffer_2_events = buffer_2_events.borrow();
-    assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
-}
-
-#[gpui::test(iterations = 100)]
-fn test_random_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
-    let operations = env::var("OPERATIONS")
-        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-        .unwrap_or(10);
-
-    let reference_string_len = rng.gen_range(0..3);
-    let mut reference_string = RandomCharIter::new(&mut rng)
-        .take(reference_string_len)
-        .collect::<String>();
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, reference_string.as_str(), cx);
-        buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
-        let mut buffer_versions = Vec::new();
-        log::info!(
-            "buffer text {:?}, version: {:?}",
-            buffer.text(),
-            buffer.version()
-        );
-
-        for _i in 0..operations {
-            let (old_ranges, new_text) = buffer.randomly_mutate(&mut rng, cx);
-            for old_range in old_ranges.iter().rev() {
-                reference_string.replace_range(old_range.clone(), &new_text);
-            }
-            assert_eq!(buffer.text(), reference_string);
-            log::info!(
-                "buffer text {:?}, version: {:?}",
-                buffer.text(),
-                buffer.version()
-            );
-
-            if rng.gen_bool(0.25) {
-                buffer.randomly_undo_redo(&mut rng, cx);
-                reference_string = buffer.text();
-                log::info!(
-                    "buffer text {:?}, version: {:?}",
-                    buffer.text(),
-                    buffer.version()
-                );
-            }
-
-            let range = buffer.random_byte_range(0, &mut rng);
-            assert_eq!(
-                buffer.text_summary_for_range(range.clone()),
-                TextSummary::from(&reference_string[range])
-            );
-
-            if rng.gen_bool(0.3) {
-                buffer_versions.push(buffer.clone());
-            }
-        }
-
-        for mut old_buffer in buffer_versions {
-            let edits = buffer
-                .edits_since(old_buffer.version.clone())
-                .collect::<Vec<_>>();
-
-            log::info!(
-                "mutating old buffer version {:?}, text: {:?}, edits since: {:?}",
-                old_buffer.version(),
-                old_buffer.text(),
-                edits,
-            );
-
-            let mut delta = 0_isize;
-            for edit in edits {
-                let old_start = (edit.old_bytes.start as isize + delta) as usize;
-                let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect();
-                old_buffer.edit(
-                    Some(old_start..old_start + edit.deleted_bytes()),
-                    new_text,
-                    cx,
-                );
-                delta += edit.delta();
-            }
-            assert_eq!(old_buffer.text(), buffer.text());
-        }
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_line_len(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, "", cx);
-        buffer.edit(vec![0..0], "abcd\nefg\nhij", cx);
-        buffer.edit(vec![12..12], "kl\nmno", cx);
-        buffer.edit(vec![18..18], "\npqrs\n", cx);
-        buffer.edit(vec![18..21], "\nPQ", cx);
-
-        assert_eq!(buffer.line_len(0), 4);
-        assert_eq!(buffer.line_len(1), 3);
-        assert_eq!(buffer.line_len(2), 5);
-        assert_eq!(buffer.line_len(3), 3);
-        assert_eq!(buffer.line_len(4), 4);
-        assert_eq!(buffer.line_len(5), 0);
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_text_summary_for_range(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", cx);
-        assert_eq!(
-            buffer.text_summary_for_range(1..3),
-            TextSummary {
-                bytes: 2,
-                lines: Point::new(1, 0),
-                first_line_chars: 1,
-                last_line_chars: 0,
-                longest_row: 0,
-                longest_row_chars: 1,
-            }
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(1..12),
-            TextSummary {
-                bytes: 11,
-                lines: Point::new(3, 0),
-                first_line_chars: 1,
-                last_line_chars: 0,
-                longest_row: 2,
-                longest_row_chars: 4,
-            }
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(0..20),
-            TextSummary {
-                bytes: 20,
-                lines: Point::new(4, 1),
-                first_line_chars: 2,
-                last_line_chars: 1,
-                longest_row: 3,
-                longest_row_chars: 6,
-            }
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(0..22),
-            TextSummary {
-                bytes: 22,
-                lines: Point::new(4, 3),
-                first_line_chars: 2,
-                last_line_chars: 3,
-                longest_row: 3,
-                longest_row_chars: 6,
-            }
-        );
-        assert_eq!(
-            buffer.text_summary_for_range(7..22),
-            TextSummary {
-                bytes: 15,
-                lines: Point::new(2, 3),
-                first_line_chars: 4,
-                last_line_chars: 3,
-                longest_row: 1,
-                longest_row_chars: 6,
-            }
-        );
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_chars_at(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, "", cx);
-        buffer.edit(vec![0..0], "abcd\nefgh\nij", cx);
-        buffer.edit(vec![12..12], "kl\nmno", cx);
-        buffer.edit(vec![18..18], "\npqrs", cx);
-        buffer.edit(vec![18..21], "\nPQ", cx);
-
-        let chars = buffer.chars_at(Point::new(0, 0));
-        assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
-
-        let chars = buffer.chars_at(Point::new(1, 0));
-        assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
-
-        let chars = buffer.chars_at(Point::new(2, 0));
-        assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
-
-        let chars = buffer.chars_at(Point::new(3, 0));
-        assert_eq!(chars.collect::<String>(), "mno\nPQrs");
-
-        let chars = buffer.chars_at(Point::new(4, 0));
-        assert_eq!(chars.collect::<String>(), "PQrs");
-
-        // Regression test:
-        let mut buffer = Buffer::new(0, "", cx);
-        buffer.edit(vec![0..0], "[workspace]\nmembers = [\n    \"xray_core\",\n    \"xray_server\",\n    \"xray_cli\",\n    \"xray_wasm\",\n]\n", cx);
-        buffer.edit(vec![60..60], "\n", cx);
-
-        let chars = buffer.chars_at(Point::new(6, 0));
-        assert_eq!(chars.collect::<String>(), "    \"xray_wasm\",\n]\n");
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_anchors(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, "", cx);
-        buffer.edit(vec![0..0], "abc", cx);
-        let left_anchor = buffer.anchor_before(2);
-        let right_anchor = buffer.anchor_after(2);
-
-        buffer.edit(vec![1..1], "def\n", cx);
-        assert_eq!(buffer.text(), "adef\nbc");
-        assert_eq!(left_anchor.to_offset(&buffer), 6);
-        assert_eq!(right_anchor.to_offset(&buffer), 6);
-        assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-        assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-
-        buffer.edit(vec![2..3], "", cx);
-        assert_eq!(buffer.text(), "adf\nbc");
-        assert_eq!(left_anchor.to_offset(&buffer), 5);
-        assert_eq!(right_anchor.to_offset(&buffer), 5);
-        assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-        assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-
-        buffer.edit(vec![5..5], "ghi\n", cx);
-        assert_eq!(buffer.text(), "adf\nbghi\nc");
-        assert_eq!(left_anchor.to_offset(&buffer), 5);
-        assert_eq!(right_anchor.to_offset(&buffer), 9);
-        assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-        assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 });
-
-        buffer.edit(vec![7..9], "", cx);
-        assert_eq!(buffer.text(), "adf\nbghc");
-        assert_eq!(left_anchor.to_offset(&buffer), 5);
-        assert_eq!(right_anchor.to_offset(&buffer), 7);
-        assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },);
-        assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 });
-
-        // Ensure anchoring to a point is equivalent to anchoring to an offset.
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 0 }),
-            buffer.anchor_before(0)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 1 }),
-            buffer.anchor_before(1)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 2 }),
-            buffer.anchor_before(2)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 0, column: 3 }),
-            buffer.anchor_before(3)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 0 }),
-            buffer.anchor_before(4)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 1 }),
-            buffer.anchor_before(5)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 2 }),
-            buffer.anchor_before(6)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 3 }),
-            buffer.anchor_before(7)
-        );
-        assert_eq!(
-            buffer.anchor_before(Point { row: 1, column: 4 }),
-            buffer.anchor_before(8)
-        );
-
-        // Comparison between anchors.
-        let anchor_at_offset_0 = buffer.anchor_before(0);
-        let anchor_at_offset_1 = buffer.anchor_before(1);
-        let anchor_at_offset_2 = buffer.anchor_before(2);
-
-        assert_eq!(
-            anchor_at_offset_0
-                .cmp(&anchor_at_offset_0, &buffer)
-                .unwrap(),
-            Ordering::Equal
-        );
-        assert_eq!(
-            anchor_at_offset_1
-                .cmp(&anchor_at_offset_1, &buffer)
-                .unwrap(),
-            Ordering::Equal
-        );
-        assert_eq!(
-            anchor_at_offset_2
-                .cmp(&anchor_at_offset_2, &buffer)
-                .unwrap(),
-            Ordering::Equal
-        );
-
-        assert_eq!(
-            anchor_at_offset_0
-                .cmp(&anchor_at_offset_1, &buffer)
-                .unwrap(),
-            Ordering::Less
-        );
-        assert_eq!(
-            anchor_at_offset_1
-                .cmp(&anchor_at_offset_2, &buffer)
-                .unwrap(),
-            Ordering::Less
-        );
-        assert_eq!(
-            anchor_at_offset_0
-                .cmp(&anchor_at_offset_2, &buffer)
-                .unwrap(),
-            Ordering::Less
-        );
-
-        assert_eq!(
-            anchor_at_offset_1
-                .cmp(&anchor_at_offset_0, &buffer)
-                .unwrap(),
-            Ordering::Greater
-        );
-        assert_eq!(
-            anchor_at_offset_2
-                .cmp(&anchor_at_offset_1, &buffer)
-                .unwrap(),
-            Ordering::Greater
-        );
-        assert_eq!(
-            anchor_at_offset_2
-                .cmp(&anchor_at_offset_0, &buffer)
-                .unwrap(),
-            Ordering::Greater
-        );
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_anchors_at_start_and_end(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, "", cx);
-        let before_start_anchor = buffer.anchor_before(0);
-        let after_end_anchor = buffer.anchor_after(0);
-
-        buffer.edit(vec![0..0], "abc", cx);
-        assert_eq!(buffer.text(), "abc");
-        assert_eq!(before_start_anchor.to_offset(&buffer), 0);
-        assert_eq!(after_end_anchor.to_offset(&buffer), 3);
-
-        let after_start_anchor = buffer.anchor_after(0);
-        let before_end_anchor = buffer.anchor_before(3);
-
-        buffer.edit(vec![3..3], "def", cx);
-        buffer.edit(vec![0..0], "ghi", cx);
-        assert_eq!(buffer.text(), "ghiabcdef");
-        assert_eq!(before_start_anchor.to_offset(&buffer), 0);
-        assert_eq!(after_start_anchor.to_offset(&buffer), 3);
-        assert_eq!(before_end_anchor.to_offset(&buffer), 6);
-        assert_eq!(after_end_anchor.to_offset(&buffer), 9);
-        buffer
-    });
-}
-
-#[gpui::test]
-async fn test_apply_diff(mut cx: gpui::TestAppContext) {
-    let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
-    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
-
-    let text = "a\nccc\ndddd\nffffff\n";
-    let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
-    buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
-    cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
-
-    let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
-    let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
-    buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
-    cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
-}
-
-#[gpui::test]
-fn test_undo_redo(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let mut buffer = Buffer::new(0, "1234", cx);
-        // Set group interval to zero so as to not group edits in the undo stack.
-        buffer.history.group_interval = Duration::from_secs(0);
-
-        buffer.edit(vec![1..1], "abx", cx);
-        buffer.edit(vec![3..4], "yzef", cx);
-        buffer.edit(vec![3..5], "cd", cx);
-        assert_eq!(buffer.text(), "1abcdef234");
-
-        let transactions = buffer.history.undo_stack.clone();
-        assert_eq!(transactions.len(), 3);
-
-        buffer.undo_or_redo(transactions[0].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1cdef234");
-        buffer.undo_or_redo(transactions[0].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1abcdef234");
-
-        buffer.undo_or_redo(transactions[1].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1abcdx234");
-        buffer.undo_or_redo(transactions[2].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1abx234");
-        buffer.undo_or_redo(transactions[1].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1abyzef234");
-        buffer.undo_or_redo(transactions[2].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1abcdef234");
-
-        buffer.undo_or_redo(transactions[2].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1abyzef234");
-        buffer.undo_or_redo(transactions[0].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1yzef234");
-        buffer.undo_or_redo(transactions[1].clone(), cx).unwrap();
-        assert_eq!(buffer.text(), "1234");
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_history(cx: &mut gpui::MutableAppContext) {
-    cx.add_model(|cx| {
-        let mut now = Instant::now();
-        let mut buffer = Buffer::new(0, "123456", cx);
-
-        let set_id =
-            buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), cx);
-        buffer.start_transaction_at(Some(set_id), now).unwrap();
-        buffer.edit(vec![2..4], "cd", cx);
-        buffer.end_transaction_at(Some(set_id), now, cx).unwrap();
-        assert_eq!(buffer.text(), "12cd56");
-        assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
-
-        buffer.start_transaction_at(Some(set_id), now).unwrap();
-        buffer
-            .update_selection_set(
-                set_id,
-                buffer.selections_from_ranges(vec![1..3]).unwrap(),
-                cx,
-            )
-            .unwrap();
-        buffer.edit(vec![4..5], "e", cx);
-        buffer.end_transaction_at(Some(set_id), now, cx).unwrap();
-        assert_eq!(buffer.text(), "12cde6");
-        assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
-        now += buffer.history.group_interval + Duration::from_millis(1);
-        buffer.start_transaction_at(Some(set_id), now).unwrap();
-        buffer
-            .update_selection_set(
-                set_id,
-                buffer.selections_from_ranges(vec![2..2]).unwrap(),
-                cx,
-            )
-            .unwrap();
-        buffer.edit(vec![0..1], "a", cx);
-        buffer.edit(vec![1..1], "b", cx);
-        buffer.end_transaction_at(Some(set_id), now, cx).unwrap();
-        assert_eq!(buffer.text(), "ab2cde6");
-        assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
-
-        // Last transaction happened past the group interval, undo it on its
-        // own.
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "12cde6");
-        assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
-        // First two transactions happened within the group interval, undo them
-        // together.
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "123456");
-        assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
-
-        // Redo the first two transactions together.
-        buffer.redo(cx);
-        assert_eq!(buffer.text(), "12cde6");
-        assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
-        // Redo the last transaction on its own.
-        buffer.redo(cx);
-        assert_eq!(buffer.text(), "ab2cde6");
-        assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
-
-        buffer.start_transaction_at(None, now).unwrap();
-        buffer.end_transaction_at(None, now, cx).unwrap();
-        buffer.undo(cx);
-        assert_eq!(buffer.text(), "12cde6");
-
-        buffer
-    });
-}
-
-#[gpui::test]
-fn test_concurrent_edits(cx: &mut gpui::MutableAppContext) {
-    let text = "abcdef";
-
-    let buffer1 = cx.add_model(|cx| Buffer::new(1, text, cx));
-    let buffer2 = cx.add_model(|cx| Buffer::new(2, text, cx));
-    let buffer3 = cx.add_model(|cx| Buffer::new(3, text, cx));
-
-    let buf1_op = buffer1.update(cx, |buffer, cx| {
-        buffer.edit(vec![1..2], "12", cx);
-        assert_eq!(buffer.text(), "a12cdef");
-        buffer.operations.last().unwrap().clone()
-    });
-    let buf2_op = buffer2.update(cx, |buffer, cx| {
-        buffer.edit(vec![3..4], "34", cx);
-        assert_eq!(buffer.text(), "abc34ef");
-        buffer.operations.last().unwrap().clone()
-    });
-    let buf3_op = buffer3.update(cx, |buffer, cx| {
-        buffer.edit(vec![5..6], "56", cx);
-        assert_eq!(buffer.text(), "abcde56");
-        buffer.operations.last().unwrap().clone()
-    });
-
-    buffer1.update(cx, |buffer, _| {
-        buffer.apply_op(buf2_op.clone()).unwrap();
-        buffer.apply_op(buf3_op.clone()).unwrap();
-    });
-    buffer2.update(cx, |buffer, _| {
-        buffer.apply_op(buf1_op.clone()).unwrap();
-        buffer.apply_op(buf3_op.clone()).unwrap();
-    });
-    buffer3.update(cx, |buffer, _| {
-        buffer.apply_op(buf1_op.clone()).unwrap();
-        buffer.apply_op(buf2_op.clone()).unwrap();
-    });
-
-    assert_eq!(buffer1.read(cx).text(), "a12c34e56");
-    assert_eq!(buffer2.read(cx).text(), "a12c34e56");
-    assert_eq!(buffer3.read(cx).text(), "a12c34e56");
-}
-
-#[gpui::test(iterations = 100)]
-fn test_random_concurrent_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
-    let peers = env::var("PEERS")
-        .map(|i| i.parse().expect("invalid `PEERS` variable"))
-        .unwrap_or(5);
-    let operations = env::var("OPERATIONS")
-        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-        .unwrap_or(10);
-
-    let base_text_len = rng.gen_range(0..10);
-    let base_text = RandomCharIter::new(&mut rng)
-        .take(base_text_len)
-        .collect::<String>();
-    let mut replica_ids = Vec::new();
-    let mut buffers = Vec::new();
-    let mut network = Network::new(rng.clone());
-
-    for i in 0..peers {
-        let buffer = cx.add_model(|cx| {
-            let mut buf = Buffer::new(i as ReplicaId, base_text.as_str(), cx);
-            buf.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
-            buf
-        });
-        buffers.push(buffer);
-        replica_ids.push(i as u16);
-        network.add_peer(i as u16);
-    }
-
-    log::info!("initial text: {:?}", base_text);
-
-    let mut mutation_count = operations;
-    loop {
-        let replica_index = rng.gen_range(0..peers);
-        let replica_id = replica_ids[replica_index];
-        buffers[replica_index].update(cx, |buffer, cx| match rng.gen_range(0..=100) {
-            0..=50 if mutation_count != 0 => {
-                buffer.randomly_mutate(&mut rng, cx);
-                network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations));
-                log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text());
-                mutation_count -= 1;
-            }
-            51..=70 if mutation_count != 0 => {
-                buffer.randomly_undo_redo(&mut rng, cx);
-                network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations));
-                mutation_count -= 1;
-            }
-            71..=100 if network.has_unreceived(replica_id) => {
-                let ops = network.receive(replica_id);
-                if !ops.is_empty() {
-                    log::info!(
-                        "peer {} applying {} ops from the network.",
-                        replica_id,
-                        ops.len()
-                    );
-                    buffer.apply_ops(ops, cx).unwrap();
-                }
-            }
-            _ => {}
-        });
-
-        if mutation_count == 0 && network.is_idle() {
-            break;
-        }
-    }
-
-    let first_buffer = buffers[0].read(cx);
-    for buffer in &buffers[1..] {
-        let buffer = buffer.read(cx);
-        assert_eq!(
-            buffer.text(),
-            first_buffer.text(),
-            "Replica {} text != Replica 0 text",
-            buffer.replica_id
-        );
-        assert_eq!(
-            buffer.selection_sets().collect::<HashMap<_, _>>(),
-            first_buffer.selection_sets().collect::<HashMap<_, _>>()
-        );
-        assert_eq!(
-            buffer.all_selection_ranges().collect::<HashMap<_, _>>(),
-            first_buffer
-                .all_selection_ranges()
-                .collect::<HashMap<_, _>>()
-        );
-    }
-}
-
-#[derive(Clone)]
-struct Envelope<T: Clone> {
-    message: T,
-    sender: ReplicaId,
-}
-
-struct Network<T: Clone, R: rand::Rng> {
-    inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
-    all_messages: Vec<T>,
-    rng: R,
-}
-
-impl<T: Clone, R: rand::Rng> Network<T, R> {
-    fn new(rng: R) -> Self {
-        Network {
-            inboxes: Default::default(),
-            all_messages: Vec::new(),
-            rng,
-        }
-    }
-
-    fn add_peer(&mut self, id: ReplicaId) {
-        self.inboxes.insert(id, Vec::new());
-    }
-
-    fn is_idle(&self) -> bool {
-        self.inboxes.values().all(|i| i.is_empty())
-    }
-
-    fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
-        for (replica, inbox) in self.inboxes.iter_mut() {
-            if *replica != sender {
-                for message in &messages {
-                    let min_index = inbox
-                        .iter()
-                        .enumerate()
-                        .rev()
-                        .find_map(|(index, envelope)| {
-                            if sender == envelope.sender {
-                                Some(index + 1)
-                            } else {
-                                None
-                            }
-                        })
-                        .unwrap_or(0);
-
-                    // Insert one or more duplicates of this message *after* the previous
-                    // message delivered by this replica.
-                    for _ in 0..self.rng.gen_range(1..4) {
-                        let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1);
-                        inbox.insert(
-                            insertion_index,
-                            Envelope {
-                                message: message.clone(),
-                                sender,
-                            },
-                        );
-                    }
-                }
-            }
-        }
-        self.all_messages.extend(messages);
-    }
-
-    fn has_unreceived(&self, receiver: ReplicaId) -> bool {
-        !self.inboxes[&receiver].is_empty()
-    }
-
-    fn receive(&mut self, receiver: ReplicaId) -> Vec<T> {
-        let inbox = self.inboxes.get_mut(&receiver).unwrap();
-        let count = self.rng.gen_range(0..inbox.len() + 1);
-        inbox
-            .drain(0..count)
-            .map(|envelope| envelope.message)
-            .collect()
-    }
-}

crates/editor/Cargo.toml 🔗

@@ -4,12 +4,17 @@ version = "0.1.0"
 edition = "2018"
 
 [features]
-test-support = ["buffer/test-support", "gpui/test-support"]
+test-support = [
+    "buffer/test-support",
+    "language/test-support",
+    "gpui/test-support",
+]
 
 [dependencies]
 buffer = { path = "../buffer" }
 clock = { path = "../clock" }
 gpui = { path = "../gpui" }
+language = { path = "../language" }
 sum_tree = { path = "../sum_tree" }
 theme = { path = "../theme" }
 util = { path = "../util" }
@@ -24,6 +29,7 @@ smol = "1.2"
 
 [dev-dependencies]
 buffer = { path = "../buffer", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 rand = "0.8"
 unindent = "0.1.7"

crates/editor/src/display_map.rs 🔗

@@ -2,9 +2,9 @@ mod fold_map;
 mod tab_map;
 mod wrap_map;
 
-use buffer::{Anchor, Buffer, Point, ToOffset, ToPoint};
 use fold_map::{FoldMap, ToFoldPoint as _};
 use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
+use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
 use std::ops::Range;
 use sum_tree::Bias;
 use tab_map::TabMap;
@@ -109,7 +109,7 @@ impl DisplayMap {
 }
 
 pub struct DisplayMapSnapshot {
-    buffer_snapshot: buffer::Snapshot,
+    buffer_snapshot: language::Snapshot,
     folds_snapshot: fold_map::Snapshot,
     tabs_snapshot: tab_map::Snapshot,
     wraps_snapshot: wrap_map::Snapshot,
@@ -358,8 +358,8 @@ impl ToDisplayPoint for Anchor {
 mod tests {
     use super::*;
     use crate::{movement, test::*};
-    use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal};
     use gpui::{color::Color, MutableAppContext};
+    use language::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal};
     use rand::{prelude::StdRng, Rng};
     use std::{env, sync::Arc};
     use theme::SyntaxTheme;
@@ -436,7 +436,7 @@ mod tests {
                     }
                 }
                 _ => {
-                    buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx));
+                    buffer.update(&mut cx, |buffer, _| buffer.randomly_edit(&mut rng, 5));
                 }
             }
 

crates/editor/src/display_map/fold_map.rs 🔗

@@ -1,5 +1,5 @@
-use buffer::{Anchor, Buffer, Point, ToOffset, AnchorRangeExt, HighlightId, TextSummary};
 use gpui::{AppContext, ModelHandle};
+use language::{Anchor, AnchorRangeExt, Buffer, HighlightId, Point, TextSummary, ToOffset};
 use parking_lot::Mutex;
 use std::{
     cmp::{self, Ordering},
@@ -485,7 +485,7 @@ impl FoldMap {
 pub struct Snapshot {
     transforms: SumTree<Transform>,
     folds: SumTree<Fold>,
-    buffer_snapshot: buffer::Snapshot,
+    buffer_snapshot: language::Snapshot,
     pub version: usize,
 }
 
@@ -994,7 +994,7 @@ impl<'a> Iterator for Chunks<'a> {
 
 pub struct HighlightedChunks<'a> {
     transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>,
-    buffer_chunks: buffer::HighlightedChunks<'a>,
+    buffer_chunks: language::HighlightedChunks<'a>,
     buffer_chunk: Option<(usize, &'a str, HighlightId)>,
     buffer_offset: usize,
 }
@@ -1331,10 +1331,10 @@ mod tests {
                     snapshot_edits.extend(map.randomly_mutate(&mut rng, cx.as_ref()));
                 }
                 _ => {
-                    let edits = buffer.update(cx, |buffer, cx| {
+                    let edits = buffer.update(cx, |buffer, _| {
                         let start_version = buffer.version.clone();
                         let edit_count = rng.gen_range(1..=5);
-                        buffer.randomly_edit(&mut rng, edit_count, cx);
+                        buffer.randomly_edit(&mut rng, edit_count);
                         buffer.edits_since(start_version).collect::<Vec<_>>()
                     });
                     log::info!("editing {:?}", edits);

crates/editor/src/display_map/tab_map.rs 🔗

@@ -1,5 +1,5 @@
 use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot};
-use buffer::{rope, HighlightId};
+use language::{rope, HighlightId};
 use parking_lot::Mutex;
 use std::{mem, ops::Range};
 use sum_tree::Bias;

crates/editor/src/display_map/wrap_map.rs 🔗

@@ -2,8 +2,8 @@ use super::{
     fold_map,
     tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
 };
-use buffer::{HighlightId, Point};
 use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
+use language::{HighlightId, Point};
 use lazy_static::lazy_static;
 use smol::future::yield_now;
 use std::{collections::VecDeque, ops::Range, time::Duration};
@@ -899,7 +899,7 @@ mod tests {
         display_map::{fold_map::FoldMap, tab_map::TabMap},
         test::Observer,
     };
-    use buffer::{Buffer, RandomCharIter};
+    use language::{Buffer, RandomCharIter};
     use rand::prelude::*;
     use std::env;
 
@@ -990,7 +990,7 @@ mod tests {
                     }
                 }
                 _ => {
-                    buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx));
+                    buffer.update(&mut cx, |buffer, _| buffer.randomly_mutate(&mut rng));
                 }
             }
 

crates/editor/src/element.rs 🔗

@@ -2,7 +2,6 @@ use super::{
     DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
     SelectPhase, Snapshot, MAX_LINE_LEN,
 };
-use buffer::HighlightId;
 use clock::ReplicaId;
 use gpui::{
     color::Color,
@@ -18,6 +17,7 @@ use gpui::{
     MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
 };
 use json::json;
+use language::HighlightId;
 use smallvec::SmallVec;
 use std::{
     cmp::{self, Ordering},
@@ -1043,7 +1043,7 @@ mod tests {
         test::sample_text,
         {Editor, EditorSettings},
     };
-    use buffer::Buffer;
+    use language::Buffer;
 
     #[gpui::test]
     fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {

crates/editor/src/lib.rs 🔗

@@ -5,7 +5,6 @@ pub mod movement;
 #[cfg(test)]
 mod test;
 
-use buffer::*;
 use clock::ReplicaId;
 pub use display_map::DisplayPoint;
 use display_map::*;
@@ -15,6 +14,7 @@ use gpui::{
     text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
     MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle,
 };
+use language::*;
 use serde::{Deserialize, Serialize};
 use smallvec::SmallVec;
 use smol::Timer;
@@ -2661,17 +2661,17 @@ impl Editor {
     fn on_buffer_event(
         &mut self,
         _: ModelHandle<Buffer>,
-        event: &buffer::Event,
+        event: &language::Event,
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            buffer::Event::Edited => cx.emit(Event::Edited),
-            buffer::Event::Dirtied => cx.emit(Event::Dirtied),
-            buffer::Event::Saved => cx.emit(Event::Saved),
-            buffer::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged),
-            buffer::Event::Reloaded => cx.emit(Event::FileHandleChanged),
-            buffer::Event::Closed => cx.emit(Event::Closed),
-            buffer::Event::Reparsed => {}
+            language::Event::Edited => cx.emit(Event::Edited),
+            language::Event::Dirtied => cx.emit(Event::Dirtied),
+            language::Event::Saved => cx.emit(Event::Saved),
+            language::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged),
+            language::Event::Reloaded => cx.emit(Event::FileHandleChanged),
+            language::Event::Closed => cx.emit(Event::Closed),
+            language::Event::Reparsed => {}
         }
     }
 

crates/language/Cargo.toml 🔗

@@ -0,0 +1,32 @@
+[package]
+name = "language"
+version = "0.1.0"
+edition = "2018"
+
+[features]
+test-support = ["rand", "buffer/test-support"]
+
+[dependencies]
+buffer = { path = "../buffer" }
+clock = { path = "../clock" }
+gpui = { path = "../gpui" }
+rpc = { path = "../rpc" }
+theme = { path = "../theme" }
+util = { path = "../util" }
+anyhow = "1.0.38"
+futures = "0.3"
+lazy_static = "1.4"
+log = "0.4"
+parking_lot = "0.11.1"
+rand = { version = "0.8.3", optional = true }
+serde = { version = "1", features = ["derive"] }
+similar = "1.3"
+smol = "1.2"
+tree-sitter = "0.19.5"
+
+[dev-dependencies]
+buffer = { path = "../buffer", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
+rand = "0.8.3"
+tree-sitter-rust = "0.19.0"
+unindent = "0.1.7"

crates/language/src/lib.rs 🔗

@@ -0,0 +1,1477 @@
+mod highlight_map;
+mod language;
+#[cfg(test)]
+mod tests;
+
+pub use self::{
+    highlight_map::{HighlightId, HighlightMap},
+    language::{BracketPair, Language, LanguageConfig, LanguageRegistry},
+};
+use anyhow::{anyhow, Result};
+pub use buffer::{Buffer as TextBuffer, *};
+use clock::ReplicaId;
+use futures::FutureExt as _;
+use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
+use lazy_static::lazy_static;
+use parking_lot::Mutex;
+use rpc::proto;
+use similar::{ChangeTag, TextDiff};
+use smol::future::yield_now;
+use std::{
+    any::Any,
+    cell::RefCell,
+    cmp,
+    collections::{BTreeMap, HashMap, HashSet},
+    ffi::OsString,
+    future::Future,
+    iter::Iterator,
+    ops::{Deref, DerefMut, Range},
+    path::{Path, PathBuf},
+    str,
+    sync::Arc,
+    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
+};
+use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
+use util::TryFutureExt as _;
+
+thread_local! {
+    static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
+}
+
+lazy_static! {
+    static ref QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Default::default();
+}
+
+// TODO - Make this configurable
+const INDENT_SIZE: u32 = 4;
+
+pub struct Buffer {
+    text: TextBuffer,
+    file: Option<Box<dyn File>>,
+    saved_version: clock::Global,
+    saved_mtime: SystemTime,
+    language: Option<Arc<Language>>,
+    autoindent_requests: Vec<Arc<AutoindentRequest>>,
+    pending_autoindent: Option<Task<()>>,
+    sync_parse_timeout: Duration,
+    syntax_tree: Mutex<Option<SyntaxTree>>,
+    parsing_in_background: bool,
+    parse_count: usize,
+    #[cfg(test)]
+    operations: Vec<Operation>,
+}
+
+pub struct Snapshot {
+    text: buffer::Snapshot,
+    tree: Option<Tree>,
+    is_parsing: bool,
+    language: Option<Arc<Language>>,
+    query_cursor: QueryCursorHandle,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Event {
+    Edited,
+    Dirtied,
+    Saved,
+    FileHandleChanged,
+    Reloaded,
+    Reparsed,
+    Closed,
+}
+
+pub trait File {
+    fn worktree_id(&self) -> usize;
+
+    fn entry_id(&self) -> Option<usize>;
+
+    fn mtime(&self) -> SystemTime;
+
+    fn path(&self) -> &Arc<Path>;
+
+    fn full_path(&self, cx: &AppContext) -> PathBuf;
+
+    /// Returns the last component of this handle's absolute path. If this handle refers to the root
+    /// of its worktree, then this method will return the name of the worktree itself.
+    fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option<OsString>;
+
+    fn is_deleted(&self) -> bool;
+
+    fn save(
+        &self,
+        buffer_id: u64,
+        text: Rope,
+        version: clock::Global,
+        cx: &mut MutableAppContext,
+    ) -> Task<Result<(clock::Global, SystemTime)>>;
+
+    fn load_local(&self, cx: &AppContext) -> Option<Task<Result<String>>>;
+
+    fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
+
+    fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
+
+    fn boxed_clone(&self) -> Box<dyn File>;
+
+    fn as_any(&self) -> &dyn Any;
+}
+
+struct QueryCursorHandle(Option<QueryCursor>);
+
+#[derive(Clone)]
+struct SyntaxTree {
+    tree: Tree,
+    version: clock::Global,
+}
+
+#[derive(Clone)]
+struct AutoindentRequest {
+    selection_set_ids: HashSet<SelectionSetId>,
+    before_edit: Snapshot,
+    edited: AnchorSet,
+    inserted: Option<AnchorRangeSet>,
+}
+
+#[derive(Debug)]
+struct IndentSuggestion {
+    basis_row: u32,
+    indent: bool,
+}
+
+struct TextProvider<'a>(&'a Rope);
+
+struct Highlights<'a> {
+    captures: tree_sitter::QueryCaptures<'a, 'a, TextProvider<'a>>,
+    next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>,
+    stack: Vec<(usize, HighlightId)>,
+    highlight_map: HighlightMap,
+}
+
+pub struct HighlightedChunks<'a> {
+    range: Range<usize>,
+    chunks: Chunks<'a>,
+    highlights: Option<Highlights<'a>>,
+}
+
+struct Diff {
+    base_version: clock::Global,
+    new_text: Arc<str>,
+    changes: Vec<(ChangeTag, usize)>,
+}
+
+impl Buffer {
+    pub fn new<T: Into<Arc<str>>>(
+        replica_id: ReplicaId,
+        base_text: T,
+        cx: &mut ModelContext<Self>,
+    ) -> Self {
+        Self::build(
+            TextBuffer::new(
+                replica_id,
+                cx.model_id() as u64,
+                History::new(base_text.into()),
+            ),
+            None,
+            None,
+            cx,
+        )
+    }
+
+    pub fn from_history(
+        replica_id: ReplicaId,
+        history: History,
+        file: Option<Box<dyn File>>,
+        language: Option<Arc<Language>>,
+        cx: &mut ModelContext<Self>,
+    ) -> Self {
+        Self::build(
+            TextBuffer::new(replica_id, cx.model_id() as u64, history),
+            file,
+            language,
+            cx,
+        )
+    }
+
+    pub fn from_proto(
+        replica_id: ReplicaId,
+        message: proto::Buffer,
+        file: Option<Box<dyn File>>,
+        language: Option<Arc<Language>>,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<Self> {
+        Ok(Self::build(
+            TextBuffer::from_proto(replica_id, message)?,
+            file,
+            language,
+            cx,
+        ))
+    }
+
+    fn build(
+        buffer: TextBuffer,
+        file: Option<Box<dyn File>>,
+        language: Option<Arc<Language>>,
+        cx: &mut ModelContext<Self>,
+    ) -> Self {
+        let saved_mtime;
+        if let Some(file) = file.as_ref() {
+            saved_mtime = file.mtime();
+        } else {
+            saved_mtime = UNIX_EPOCH;
+        }
+
+        let mut result = Self {
+            text: buffer,
+            saved_mtime,
+            saved_version: clock::Global::new(),
+            file,
+            syntax_tree: Mutex::new(None),
+            parsing_in_background: false,
+            parse_count: 0,
+            sync_parse_timeout: Duration::from_millis(1),
+            autoindent_requests: Default::default(),
+            pending_autoindent: Default::default(),
+            language,
+
+            #[cfg(test)]
+            operations: Default::default(),
+        };
+        result.reparse(cx);
+        result
+    }
+
+    pub fn snapshot(&self) -> Snapshot {
+        Snapshot {
+            text: self.text.snapshot(),
+            tree: self.syntax_tree(),
+            is_parsing: self.parsing_in_background,
+            language: self.language.clone(),
+            query_cursor: QueryCursorHandle::new(),
+        }
+    }
+
+    pub fn file(&self) -> Option<&dyn File> {
+        self.file.as_deref()
+    }
+
+    pub fn save(
+        &mut self,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<Task<Result<(clock::Global, SystemTime)>>> {
+        let file = self
+            .file
+            .as_ref()
+            .ok_or_else(|| anyhow!("buffer has no file"))?;
+        let text = self.as_rope().clone();
+        let version = self.version.clone();
+        let save = file.save(self.remote_id(), text, version, cx.as_mut());
+        Ok(cx.spawn(|this, mut cx| async move {
+            let (version, mtime) = save.await?;
+            this.update(&mut cx, |this, cx| {
+                this.did_save(version.clone(), mtime, None, cx);
+            });
+            Ok((version, mtime))
+        }))
+    }
+
+    pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
+        self.language = language;
+        self.reparse(cx);
+    }
+
+    pub fn did_save(
+        &mut self,
+        version: clock::Global,
+        mtime: SystemTime,
+        new_file: Option<Box<dyn File>>,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.saved_mtime = mtime;
+        self.saved_version = version;
+        if let Some(new_file) = new_file {
+            self.file = Some(new_file);
+        }
+        cx.emit(Event::Saved);
+    }
+
+    pub fn file_updated(
+        &mut self,
+        new_file: Box<dyn File>,
+        cx: &mut ModelContext<Self>,
+    ) -> Option<Task<()>> {
+        let old_file = self.file.as_ref()?;
+        let mut file_changed = false;
+        let mut task = None;
+
+        if new_file.path() != old_file.path() {
+            file_changed = true;
+        }
+
+        if new_file.is_deleted() {
+            if !old_file.is_deleted() {
+                file_changed = true;
+                if !self.is_dirty() {
+                    cx.emit(Event::Dirtied);
+                }
+            }
+        } else {
+            let new_mtime = new_file.mtime();
+            if new_mtime != old_file.mtime() {
+                file_changed = true;
+
+                if !self.is_dirty() {
+                    task = Some(cx.spawn(|this, mut cx| {
+                        async move {
+                            let new_text = this.read_with(&cx, |this, cx| {
+                                this.file.as_ref().and_then(|file| file.load_local(cx))
+                            });
+                            if let Some(new_text) = new_text {
+                                let new_text = new_text.await?;
+                                let diff = this
+                                    .read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
+                                    .await;
+                                this.update(&mut cx, |this, cx| {
+                                    if this.apply_diff(diff, cx) {
+                                        this.saved_version = this.version.clone();
+                                        this.saved_mtime = new_mtime;
+                                        cx.emit(Event::Reloaded);
+                                    }
+                                });
+                            }
+                            Ok(())
+                        }
+                        .log_err()
+                        .map(drop)
+                    }));
+                }
+            }
+        }
+
+        if file_changed {
+            cx.emit(Event::FileHandleChanged);
+        }
+        self.file = Some(new_file);
+        task
+    }
+
+    pub fn close(&mut self, cx: &mut ModelContext<Self>) {
+        cx.emit(Event::Closed);
+    }
+
+    pub fn language(&self) -> Option<&Arc<Language>> {
+        self.language.as_ref()
+    }
+
+    pub fn parse_count(&self) -> usize {
+        self.parse_count
+    }
+
+    fn syntax_tree(&self) -> Option<Tree> {
+        if let Some(syntax_tree) = self.syntax_tree.lock().as_mut() {
+            self.interpolate_tree(syntax_tree);
+            Some(syntax_tree.tree.clone())
+        } else {
+            None
+        }
+    }
+
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn is_parsing(&self) -> bool {
+        self.parsing_in_background
+    }
+
+    #[cfg(test)]
+    pub fn set_sync_parse_timeout(&mut self, timeout: Duration) {
+        self.sync_parse_timeout = timeout;
+    }
+
+    fn reparse(&mut self, cx: &mut ModelContext<Self>) -> bool {
+        if self.parsing_in_background {
+            return false;
+        }
+
+        if let Some(language) = self.language.clone() {
+            let old_tree = self.syntax_tree();
+            let text = self.as_rope().clone();
+            let parsed_version = self.version();
+            let parse_task = cx.background().spawn({
+                let language = language.clone();
+                async move { Self::parse_text(&text, old_tree, &language) }
+            });
+
+            match cx
+                .background()
+                .block_with_timeout(self.sync_parse_timeout, parse_task)
+            {
+                Ok(new_tree) => {
+                    self.did_finish_parsing(new_tree, parsed_version, cx);
+                    return true;
+                }
+                Err(parse_task) => {
+                    self.parsing_in_background = true;
+                    cx.spawn(move |this, mut cx| async move {
+                        let new_tree = parse_task.await;
+                        this.update(&mut cx, move |this, cx| {
+                            let language_changed =
+                                this.language.as_ref().map_or(true, |curr_language| {
+                                    !Arc::ptr_eq(curr_language, &language)
+                                });
+                            let parse_again = this.version > parsed_version || language_changed;
+                            this.parsing_in_background = false;
+                            this.did_finish_parsing(new_tree, parsed_version, cx);
+
+                            if parse_again && this.reparse(cx) {
+                                return;
+                            }
+                        });
+                    })
+                    .detach();
+                }
+            }
+        }
+        false
+    }
+
+    fn parse_text(text: &Rope, old_tree: Option<Tree>, language: &Language) -> Tree {
+        PARSER.with(|parser| {
+            let mut parser = parser.borrow_mut();
+            parser
+                .set_language(language.grammar)
+                .expect("incompatible grammar");
+            let mut chunks = text.chunks_in_range(0..text.len());
+            let tree = parser
+                .parse_with(
+                    &mut move |offset, _| {
+                        chunks.seek(offset);
+                        chunks.next().unwrap_or("").as_bytes()
+                    },
+                    old_tree.as_ref(),
+                )
+                .unwrap();
+            tree
+        })
+    }
+
+    fn interpolate_tree(&self, tree: &mut SyntaxTree) {
+        let mut delta = 0_isize;
+        for edit in self.edits_since(tree.version.clone()) {
+            let start_offset = (edit.old_bytes.start as isize + delta) as usize;
+            let start_point = self.as_rope().to_point(start_offset);
+            tree.tree.edit(&InputEdit {
+                start_byte: start_offset,
+                old_end_byte: start_offset + edit.deleted_bytes(),
+                new_end_byte: start_offset + edit.inserted_bytes(),
+                start_position: start_point.to_ts_point(),
+                old_end_position: (start_point + edit.deleted_lines()).to_ts_point(),
+                new_end_position: self
+                    .as_rope()
+                    .to_point(start_offset + edit.inserted_bytes())
+                    .to_ts_point(),
+            });
+            delta += edit.inserted_bytes() as isize - edit.deleted_bytes() as isize;
+        }
+        tree.version = self.version();
+    }
+
+    fn did_finish_parsing(
+        &mut self,
+        tree: Tree,
+        version: clock::Global,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.parse_count += 1;
+        *self.syntax_tree.lock() = Some(SyntaxTree { tree, version });
+        self.request_autoindent(cx);
+        cx.emit(Event::Reparsed);
+        cx.notify();
+    }
+
+    fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
+        if let Some(indent_columns) = self.compute_autoindents() {
+            let indent_columns = cx.background().spawn(indent_columns);
+            match cx
+                .background()
+                .block_with_timeout(Duration::from_micros(500), indent_columns)
+            {
+                Ok(indent_columns) => self.apply_autoindents(indent_columns, cx),
+                Err(indent_columns) => {
+                    self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move {
+                        let indent_columns = indent_columns.await;
+                        this.update(&mut cx, |this, cx| {
+                            this.apply_autoindents(indent_columns, cx);
+                        });
+                    }));
+                }
+            }
+        }
+    }
+
+    fn compute_autoindents(&self) -> Option<impl Future<Output = BTreeMap<u32, u32>>> {
+        let max_rows_between_yields = 100;
+        let snapshot = self.snapshot();
+        if snapshot.language.is_none()
+            || snapshot.tree.is_none()
+            || self.autoindent_requests.is_empty()
+        {
+            return None;
+        }
+
+        let autoindent_requests = self.autoindent_requests.clone();
+        Some(async move {
+            let mut indent_columns = BTreeMap::new();
+            for request in autoindent_requests {
+                let old_to_new_rows = request
+                    .edited
+                    .to_points(&request.before_edit)
+                    .map(|point| point.row)
+                    .zip(request.edited.to_points(&snapshot).map(|point| point.row))
+                    .collect::<BTreeMap<u32, u32>>();
+
+                let mut old_suggestions = HashMap::<u32, u32>::default();
+                let old_edited_ranges =
+                    contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
+                for old_edited_range in old_edited_ranges {
+                    let suggestions = request
+                        .before_edit
+                        .suggest_autoindents(old_edited_range.clone())
+                        .into_iter()
+                        .flatten();
+                    for (old_row, suggestion) in old_edited_range.zip(suggestions) {
+                        let indentation_basis = old_to_new_rows
+                            .get(&suggestion.basis_row)
+                            .and_then(|from_row| old_suggestions.get(from_row).copied())
+                            .unwrap_or_else(|| {
+                                request
+                                    .before_edit
+                                    .indent_column_for_line(suggestion.basis_row)
+                            });
+                        let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
+                        old_suggestions.insert(
+                            *old_to_new_rows.get(&old_row).unwrap(),
+                            indentation_basis + delta,
+                        );
+                    }
+                    yield_now().await;
+                }
+
+                // At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the
+                // buffer before the edit, but keyed by the row for these lines after the edits were applied.
+                let new_edited_row_ranges =
+                    contiguous_ranges(old_to_new_rows.values().copied(), max_rows_between_yields);
+                for new_edited_row_range in new_edited_row_ranges {
+                    let suggestions = snapshot
+                        .suggest_autoindents(new_edited_row_range.clone())
+                        .into_iter()
+                        .flatten();
+                    for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
+                        let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
+                        let new_indentation = indent_columns
+                            .get(&suggestion.basis_row)
+                            .copied()
+                            .unwrap_or_else(|| {
+                                snapshot.indent_column_for_line(suggestion.basis_row)
+                            })
+                            + delta;
+                        if old_suggestions
+                            .get(&new_row)
+                            .map_or(true, |old_indentation| new_indentation != *old_indentation)
+                        {
+                            indent_columns.insert(new_row, new_indentation);
+                        }
+                    }
+                    yield_now().await;
+                }
+
+                if let Some(inserted) = request.inserted.as_ref() {
+                    let inserted_row_ranges = contiguous_ranges(
+                        inserted
+                            .to_point_ranges(&snapshot)
+                            .flat_map(|range| range.start.row..range.end.row + 1),
+                        max_rows_between_yields,
+                    );
+                    for inserted_row_range in inserted_row_ranges {
+                        let suggestions = snapshot
+                            .suggest_autoindents(inserted_row_range.clone())
+                            .into_iter()
+                            .flatten();
+                        for (row, suggestion) in inserted_row_range.zip(suggestions) {
+                            let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
+                            let new_indentation = indent_columns
+                                .get(&suggestion.basis_row)
+                                .copied()
+                                .unwrap_or_else(|| {
+                                    snapshot.indent_column_for_line(suggestion.basis_row)
+                                })
+                                + delta;
+                            indent_columns.insert(row, new_indentation);
+                        }
+                        yield_now().await;
+                    }
+                }
+            }
+            indent_columns
+        })
+    }
+
+    fn apply_autoindents(
+        &mut self,
+        indent_columns: BTreeMap<u32, u32>,
+        cx: &mut ModelContext<Self>,
+    ) {
+        let selection_set_ids = self
+            .autoindent_requests
+            .drain(..)
+            .flat_map(|req| req.selection_set_ids.clone())
+            .collect::<HashSet<_>>();
+
+        self.start_transaction(selection_set_ids.iter().copied())
+            .unwrap();
+        for (row, indent_column) in &indent_columns {
+            self.set_indent_column_for_line(*row, *indent_column, cx);
+        }
+
+        for selection_set_id in &selection_set_ids {
+            if let Ok(set) = self.selection_set(*selection_set_id) {
+                let new_selections = set
+                    .selections
+                    .iter()
+                    .map(|selection| {
+                        let start_point = selection.start.to_point(&self.text);
+                        if start_point.column == 0 {
+                            let end_point = selection.end.to_point(&self.text);
+                            let delta = Point::new(
+                                0,
+                                indent_columns.get(&start_point.row).copied().unwrap_or(0),
+                            );
+                            if delta.column > 0 {
+                                return Selection {
+                                    id: selection.id,
+                                    goal: selection.goal,
+                                    reversed: selection.reversed,
+                                    start: self
+                                        .anchor_at(start_point + delta, selection.start.bias),
+                                    end: self.anchor_at(end_point + delta, selection.end.bias),
+                                };
+                            }
+                        }
+                        selection.clone()
+                    })
+                    .collect::<Arc<[_]>>();
+                self.update_selection_set(*selection_set_id, new_selections, cx)
+                    .unwrap();
+            }
+        }
+
+        self.end_transaction(selection_set_ids.iter().copied(), cx)
+            .unwrap();
+    }
+
+    pub fn indent_column_for_line(&self, row: u32) -> u32 {
+        self.content().indent_column_for_line(row)
+    }
+
+    fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
+        let current_column = self.indent_column_for_line(row);
+        if column > current_column {
+            let offset = Point::new(row, 0).to_offset(&*self);
+            self.edit(
+                [offset..offset],
+                " ".repeat((column - current_column) as usize),
+                cx,
+            );
+        } else if column < current_column {
+            self.edit(
+                [Point::new(row, 0)..Point::new(row, current_column - column)],
+                "",
+                cx,
+            );
+        }
+    }
+
+    pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
+        if let Some(tree) = self.syntax_tree() {
+            let root = tree.root_node();
+            let range = range.start.to_offset(self)..range.end.to_offset(self);
+            let mut node = root.descendant_for_byte_range(range.start, range.end);
+            while node.map_or(false, |n| n.byte_range() == range) {
+                node = node.unwrap().parent();
+            }
+            node.map(|n| n.byte_range())
+        } else {
+            None
+        }
+    }
+
+    pub fn enclosing_bracket_ranges<T: ToOffset>(
+        &self,
+        range: Range<T>,
+    ) -> Option<(Range<usize>, Range<usize>)> {
+        let (lang, tree) = self.language.as_ref().zip(self.syntax_tree())?;
+        let open_capture_ix = lang.brackets_query.capture_index_for_name("open")?;
+        let close_capture_ix = lang.brackets_query.capture_index_for_name("close")?;
+
+        // Find bracket pairs that *inclusively* contain the given range.
+        let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1;
+        let mut cursor = QueryCursorHandle::new();
+        let matches = cursor.set_byte_range(range).matches(
+            &lang.brackets_query,
+            tree.root_node(),
+            TextProvider(self.as_rope()),
+        );
+
+        // Get the ranges of the innermost pair of brackets.
+        matches
+            .filter_map(|mat| {
+                let open = mat.nodes_for_capture_index(open_capture_ix).next()?;
+                let close = mat.nodes_for_capture_index(close_capture_ix).next()?;
+                Some((open.byte_range(), close.byte_range()))
+            })
+            .min_by_key(|(open_range, close_range)| close_range.end - open_range.start)
+    }
+
+    fn diff(&self, new_text: Arc<str>, cx: &AppContext) -> Task<Diff> {
+        // TODO: it would be nice to not allocate here.
+        let old_text = self.text();
+        let base_version = self.version();
+        cx.background().spawn(async move {
+            let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
+                .iter_all_changes()
+                .map(|c| (c.tag(), c.value().len()))
+                .collect::<Vec<_>>();
+            Diff {
+                base_version,
+                new_text,
+                changes,
+            }
+        })
+    }
+
+    fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool {
+        if self.version == diff.base_version {
+            self.start_transaction(None).unwrap();
+            let mut offset = 0;
+            for (tag, len) in diff.changes {
+                let range = offset..(offset + len);
+                match tag {
+                    ChangeTag::Equal => offset += len,
+                    ChangeTag::Delete => self.edit(Some(range), "", cx),
+                    ChangeTag::Insert => {
+                        self.edit(Some(offset..offset), &diff.new_text[range], cx);
+                        offset += len;
+                    }
+                }
+            }
+            self.end_transaction(None, cx).unwrap();
+            true
+        } else {
+            false
+        }
+    }
+
+    pub fn is_dirty(&self) -> bool {
+        self.version > self.saved_version
+            || self.file.as_ref().map_or(false, |file| file.is_deleted())
+    }
+
+    pub fn has_conflict(&self) -> bool {
+        self.version > self.saved_version
+            && self
+                .file
+                .as_ref()
+                .map_or(false, |file| file.mtime() > self.saved_mtime)
+    }
+
+    pub fn start_transaction(
+        &mut self,
+        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+    ) -> Result<()> {
+        self.start_transaction_at(selection_set_ids, Instant::now())
+    }
+
+    fn start_transaction_at(
+        &mut self,
+        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+        now: Instant,
+    ) -> Result<()> {
+        self.text.start_transaction_at(selection_set_ids, now)
+    }
+
+    pub fn end_transaction(
+        &mut self,
+        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        self.end_transaction_at(selection_set_ids, Instant::now(), cx)
+    }
+
+    fn end_transaction_at(
+        &mut self,
+        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+        now: Instant,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        if let Some(start_version) = self.text.end_transaction_at(selection_set_ids, now) {
+            cx.notify();
+            let was_dirty = start_version != self.saved_version;
+            let edited = self.edits_since(start_version).next().is_some();
+            if edited {
+                self.did_edit(was_dirty, cx);
+                self.reparse(cx);
+            }
+        }
+        Ok(())
+    }
+
+    pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
+    where
+        I: IntoIterator<Item = Range<S>>,
+        S: ToOffset,
+        T: Into<String>,
+    {
+        self.edit_internal(ranges_iter, new_text, false, cx)
+    }
+
+    pub fn edit_with_autoindent<I, S, T>(
+        &mut self,
+        ranges_iter: I,
+        new_text: T,
+        cx: &mut ModelContext<Self>,
+    ) where
+        I: IntoIterator<Item = Range<S>>,
+        S: ToOffset,
+        T: Into<String>,
+    {
+        self.edit_internal(ranges_iter, new_text, true, cx)
+    }
+
+    pub fn edit_internal<I, S, T>(
+        &mut self,
+        ranges_iter: I,
+        new_text: T,
+        autoindent: bool,
+        cx: &mut ModelContext<Self>,
+    ) where
+        I: IntoIterator<Item = Range<S>>,
+        S: ToOffset,
+        T: Into<String>,
+    {
+        let new_text = new_text.into();
+
+        // Skip invalid ranges and coalesce contiguous ones.
+        let mut ranges: Vec<Range<usize>> = Vec::new();
+        for range in ranges_iter {
+            let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
+            if !new_text.is_empty() || !range.is_empty() {
+                if let Some(prev_range) = ranges.last_mut() {
+                    if prev_range.end >= range.start {
+                        prev_range.end = cmp::max(prev_range.end, range.end);
+                    } else {
+                        ranges.push(range);
+                    }
+                } else {
+                    ranges.push(range);
+                }
+            }
+        }
+        if ranges.is_empty() {
+            return;
+        }
+
+        self.start_transaction(None).unwrap();
+        self.pending_autoindent.take();
+        let autoindent_request = if autoindent && self.language.is_some() {
+            let before_edit = self.snapshot();
+            let edited = self.content().anchor_set(ranges.iter().filter_map(|range| {
+                let start = range.start.to_point(&*self);
+                if new_text.starts_with('\n') && start.column == self.line_len(start.row) {
+                    None
+                } else {
+                    Some((range.start, Bias::Left))
+                }
+            }));
+            Some((before_edit, edited))
+        } else {
+            None
+        };
+
+        let first_newline_ix = new_text.find('\n');
+        let new_text_len = new_text.len();
+
+        let edit = self.text.edit(ranges.iter().cloned(), new_text);
+
+        if let Some((before_edit, edited)) = autoindent_request {
+            let mut inserted = None;
+            if let Some(first_newline_ix) = first_newline_ix {
+                let mut delta = 0isize;
+                inserted = Some(self.content().anchor_range_set(ranges.iter().map(|range| {
+                    let start = (delta + range.start as isize) as usize + first_newline_ix + 1;
+                    let end = (delta + range.start as isize) as usize + new_text_len;
+                    delta += (range.end as isize - range.start as isize) + new_text_len as isize;
+                    (start, Bias::Left)..(end, Bias::Right)
+                })));
+            }
+
+            let selection_set_ids = self
+                .text
+                .peek_undo_stack()
+                .unwrap()
+                .starting_selection_set_ids()
+                .collect();
+            self.autoindent_requests.push(Arc::new(AutoindentRequest {
+                selection_set_ids,
+                before_edit,
+                edited,
+                inserted,
+            }));
+        }
+
+        self.end_transaction(None, cx).unwrap();
+        self.send_operation(Operation::Edit(edit), cx);
+    }
+
+    fn did_edit(&self, was_dirty: bool, cx: &mut ModelContext<Self>) {
+        cx.emit(Event::Edited);
+        if !was_dirty {
+            cx.emit(Event::Dirtied);
+        }
+    }
+
+    pub fn add_selection_set(
+        &mut self,
+        selections: impl Into<Arc<[Selection]>>,
+        cx: &mut ModelContext<Self>,
+    ) -> SelectionSetId {
+        let operation = self.text.add_selection_set(selections);
+        if let Operation::UpdateSelections { set_id, .. } = &operation {
+            let set_id = *set_id;
+            cx.notify();
+            self.send_operation(operation, cx);
+            set_id
+        } else {
+            unreachable!()
+        }
+    }
+
+    pub fn update_selection_set(
+        &mut self,
+        set_id: SelectionSetId,
+        selections: impl Into<Arc<[Selection]>>,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        let operation = self.text.update_selection_set(set_id, selections)?;
+        cx.notify();
+        self.send_operation(operation, cx);
+        Ok(())
+    }
+
+    pub fn set_active_selection_set(
+        &mut self,
+        set_id: Option<SelectionSetId>,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        let operation = self.text.set_active_selection_set(set_id)?;
+        self.send_operation(operation, cx);
+        Ok(())
+    }
+
+    pub fn remove_selection_set(
+        &mut self,
+        set_id: SelectionSetId,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        let operation = self.text.remove_selection_set(set_id)?;
+        cx.notify();
+        self.send_operation(operation, cx);
+        Ok(())
+    }
+
+    pub fn apply_ops<I: IntoIterator<Item = Operation>>(
+        &mut self,
+        ops: I,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        self.pending_autoindent.take();
+
+        let was_dirty = self.is_dirty();
+        let old_version = self.version.clone();
+
+        self.text.apply_ops(ops)?;
+
+        cx.notify();
+        if self.edits_since(old_version).next().is_some() {
+            self.did_edit(was_dirty, cx);
+            self.reparse(cx);
+        }
+
+        Ok(())
+    }
+
+    #[cfg(not(test))]
+    pub fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext<Self>) {
+        if let Some(file) = &self.file {
+            file.buffer_updated(self.remote_id(), operation, cx.as_mut());
+        }
+    }
+
+    #[cfg(test)]
+    pub fn send_operation(&mut self, operation: Operation, _: &mut ModelContext<Self>) {
+        self.operations.push(operation);
+    }
+
+    pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext<Self>) {
+        self.text.remove_peer(replica_id);
+        cx.notify();
+    }
+
+    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
+        let was_dirty = self.is_dirty();
+        let old_version = self.version.clone();
+
+        for operation in self.text.undo() {
+            self.send_operation(operation, cx);
+        }
+
+        cx.notify();
+        if self.edits_since(old_version).next().is_some() {
+            self.did_edit(was_dirty, cx);
+            self.reparse(cx);
+        }
+    }
+
+    pub fn redo(&mut self, cx: &mut ModelContext<Self>) {
+        let was_dirty = self.is_dirty();
+        let old_version = self.version.clone();
+
+        for operation in self.text.redo() {
+            self.send_operation(operation, cx);
+        }
+
+        cx.notify();
+        if self.edits_since(old_version).next().is_some() {
+            self.did_edit(was_dirty, cx);
+            self.reparse(cx);
+        }
+    }
+}
+
+#[cfg(any(test, feature = "test-support"))]
+impl Buffer {
+    pub fn randomly_edit<T>(&mut self, rng: &mut T, old_range_count: usize)
+    where
+        T: rand::Rng,
+    {
+        self.text.randomly_edit(rng, old_range_count);
+    }
+
+    pub fn randomly_mutate<T>(&mut self, rng: &mut T)
+    where
+        T: rand::Rng,
+    {
+        self.text.randomly_mutate(rng);
+    }
+}
+
+impl Entity for Buffer {
+    type Event = Event;
+
+    fn release(&mut self, cx: &mut gpui::MutableAppContext) {
+        if let Some(file) = self.file.as_ref() {
+            file.buffer_removed(self.remote_id(), cx);
+        }
+    }
+}
+
+impl Clone for Buffer {
+    fn clone(&self) -> Self {
+        Self {
+            text: self.text.clone(),
+            saved_version: self.saved_version.clone(),
+            saved_mtime: self.saved_mtime,
+            file: self.file.as_ref().map(|f| f.boxed_clone()),
+            language: self.language.clone(),
+            syntax_tree: Mutex::new(self.syntax_tree.lock().clone()),
+            parsing_in_background: false,
+            sync_parse_timeout: self.sync_parse_timeout,
+            parse_count: self.parse_count,
+            autoindent_requests: Default::default(),
+            pending_autoindent: Default::default(),
+
+            #[cfg(test)]
+            operations: self.operations.clone(),
+        }
+    }
+}
+
+impl Deref for Buffer {
+    type Target = TextBuffer;
+
+    fn deref(&self) -> &Self::Target {
+        &self.text
+    }
+}
+
+impl<'a> From<&'a Buffer> for Content<'a> {
+    fn from(buffer: &'a Buffer) -> Self {
+        Self::from(&buffer.text)
+    }
+}
+
+impl<'a> From<&'a mut Buffer> for Content<'a> {
+    fn from(buffer: &'a mut Buffer) -> Self {
+        Self::from(&buffer.text)
+    }
+}
+
+impl<'a> From<&'a Snapshot> for Content<'a> {
+    fn from(snapshot: &'a Snapshot) -> Self {
+        Self::from(&snapshot.text)
+    }
+}
+
+impl Snapshot {
+    fn suggest_autoindents<'a>(
+        &'a self,
+        row_range: Range<u32>,
+    ) -> Option<impl Iterator<Item = IndentSuggestion> + 'a> {
+        let mut query_cursor = QueryCursorHandle::new();
+        if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
+            let prev_non_blank_row = self.prev_non_blank_row(row_range.start);
+
+            // Get the "indentation ranges" that intersect this row range.
+            let indent_capture_ix = language.indents_query.capture_index_for_name("indent");
+            let end_capture_ix = language.indents_query.capture_index_for_name("end");
+            query_cursor.set_point_range(
+                Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).to_ts_point()
+                    ..Point::new(row_range.end, 0).to_ts_point(),
+            );
+            let mut indentation_ranges = Vec::<(Range<Point>, &'static str)>::new();
+            for mat in query_cursor.matches(
+                &language.indents_query,
+                tree.root_node(),
+                TextProvider(self.as_rope()),
+            ) {
+                let mut node_kind = "";
+                let mut start: Option<Point> = None;
+                let mut end: Option<Point> = None;
+                for capture in mat.captures {
+                    if Some(capture.index) == indent_capture_ix {
+                        node_kind = capture.node.kind();
+                        start.get_or_insert(Point::from_ts_point(capture.node.start_position()));
+                        end.get_or_insert(Point::from_ts_point(capture.node.end_position()));
+                    } else if Some(capture.index) == end_capture_ix {
+                        end = Some(Point::from_ts_point(capture.node.start_position().into()));
+                    }
+                }
+
+                if let Some((start, end)) = start.zip(end) {
+                    if start.row == end.row {
+                        continue;
+                    }
+
+                    let range = start..end;
+                    match indentation_ranges.binary_search_by_key(&range.start, |r| r.0.start) {
+                        Err(ix) => indentation_ranges.insert(ix, (range, node_kind)),
+                        Ok(ix) => {
+                            let prev_range = &mut indentation_ranges[ix];
+                            prev_range.0.end = prev_range.0.end.max(range.end);
+                        }
+                    }
+                }
+            }
+
+            let mut prev_row = prev_non_blank_row.unwrap_or(0);
+            Some(row_range.map(move |row| {
+                let row_start = Point::new(row, self.indent_column_for_line(row));
+
+                let mut indent_from_prev_row = false;
+                let mut outdent_to_row = u32::MAX;
+                for (range, _node_kind) in &indentation_ranges {
+                    if range.start.row >= row {
+                        break;
+                    }
+
+                    if range.start.row == prev_row && range.end > row_start {
+                        indent_from_prev_row = true;
+                    }
+                    if range.end.row >= prev_row && range.end <= row_start {
+                        outdent_to_row = outdent_to_row.min(range.start.row);
+                    }
+                }
+
+                let suggestion = if outdent_to_row == prev_row {
+                    IndentSuggestion {
+                        basis_row: prev_row,
+                        indent: false,
+                    }
+                } else if indent_from_prev_row {
+                    IndentSuggestion {
+                        basis_row: prev_row,
+                        indent: true,
+                    }
+                } else if outdent_to_row < prev_row {
+                    IndentSuggestion {
+                        basis_row: outdent_to_row,
+                        indent: false,
+                    }
+                } else {
+                    IndentSuggestion {
+                        basis_row: prev_row,
+                        indent: false,
+                    }
+                };
+
+                prev_row = row;
+                suggestion
+            }))
+        } else {
+            None
+        }
+    }
+
+    fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
+        while row > 0 {
+            row -= 1;
+            if !self.is_line_blank(row) {
+                return Some(row);
+            }
+        }
+        None
+    }
+
+    fn is_line_blank(&self, row: u32) -> bool {
+        self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
+            .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
+    }
+
+    pub fn highlighted_text_for_range<T: ToOffset>(
+        &mut self,
+        range: Range<T>,
+    ) -> HighlightedChunks {
+        let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
+        let chunks = self.text.as_rope().chunks_in_range(range.clone());
+        if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
+            let captures = self.query_cursor.set_byte_range(range.clone()).captures(
+                &language.highlights_query,
+                tree.root_node(),
+                TextProvider(self.text.as_rope()),
+            );
+
+            HighlightedChunks {
+                range,
+                chunks,
+                highlights: Some(Highlights {
+                    captures,
+                    next_capture: None,
+                    stack: Default::default(),
+                    highlight_map: language.highlight_map(),
+                }),
+            }
+        } else {
+            HighlightedChunks {
+                range,
+                chunks,
+                highlights: None,
+            }
+        }
+    }
+}
+
+impl Clone for Snapshot {
+    fn clone(&self) -> Self {
+        Self {
+            text: self.text.clone(),
+            tree: self.tree.clone(),
+            is_parsing: self.is_parsing,
+            language: self.language.clone(),
+            query_cursor: QueryCursorHandle::new(),
+        }
+    }
+}
+
+impl Deref for Snapshot {
+    type Target = buffer::Snapshot;
+
+    fn deref(&self) -> &Self::Target {
+        &self.text
+    }
+}
+
+impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> {
+    type I = ByteChunks<'a>;
+
+    fn text(&mut self, node: tree_sitter::Node) -> Self::I {
+        ByteChunks(self.0.chunks_in_range(node.byte_range()))
+    }
+}
+
+struct ByteChunks<'a>(rope::Chunks<'a>);
+
+impl<'a> Iterator for ByteChunks<'a> {
+    type Item = &'a [u8];
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0.next().map(str::as_bytes)
+    }
+}
+
+impl<'a> HighlightedChunks<'a> {
+    pub fn seek(&mut self, offset: usize) {
+        self.range.start = offset;
+        self.chunks.seek(self.range.start);
+        if let Some(highlights) = self.highlights.as_mut() {
+            highlights
+                .stack
+                .retain(|(end_offset, _)| *end_offset > offset);
+            if let Some((mat, capture_ix)) = &highlights.next_capture {
+                let capture = mat.captures[*capture_ix as usize];
+                if offset >= capture.node.start_byte() {
+                    let next_capture_end = capture.node.end_byte();
+                    if offset < next_capture_end {
+                        highlights.stack.push((
+                            next_capture_end,
+                            highlights.highlight_map.get(capture.index),
+                        ));
+                    }
+                    highlights.next_capture.take();
+                }
+            }
+            highlights.captures.set_byte_range(self.range.clone());
+        }
+    }
+
+    pub fn offset(&self) -> usize {
+        self.range.start
+    }
+}
+
+impl<'a> Iterator for HighlightedChunks<'a> {
+    type Item = (&'a str, HighlightId);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let mut next_capture_start = usize::MAX;
+
+        if let Some(highlights) = self.highlights.as_mut() {
+            while let Some((parent_capture_end, _)) = highlights.stack.last() {
+                if *parent_capture_end <= self.range.start {
+                    highlights.stack.pop();
+                } else {
+                    break;
+                }
+            }
+
+            if highlights.next_capture.is_none() {
+                highlights.next_capture = highlights.captures.next();
+            }
+
+            while let Some((mat, capture_ix)) = highlights.next_capture.as_ref() {
+                let capture = mat.captures[*capture_ix as usize];
+                if self.range.start < capture.node.start_byte() {
+                    next_capture_start = capture.node.start_byte();
+                    break;
+                } else {
+                    let style_id = highlights.highlight_map.get(capture.index);
+                    highlights.stack.push((capture.node.end_byte(), style_id));
+                    highlights.next_capture = highlights.captures.next();
+                }
+            }
+        }
+
+        if let Some(chunk) = self.chunks.peek() {
+            let chunk_start = self.range.start;
+            let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start);
+            let mut style_id = HighlightId::default();
+            if let Some((parent_capture_end, parent_style_id)) =
+                self.highlights.as_ref().and_then(|h| h.stack.last())
+            {
+                chunk_end = chunk_end.min(*parent_capture_end);
+                style_id = *parent_style_id;
+            }
+
+            let slice =
+                &chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()];
+            self.range.start = chunk_end;
+            if self.range.start == self.chunks.offset() + chunk.len() {
+                self.chunks.next().unwrap();
+            }
+
+            Some((slice, style_id))
+        } else {
+            None
+        }
+    }
+}
+
+impl QueryCursorHandle {
+    fn new() -> Self {
+        QueryCursorHandle(Some(
+            QUERY_CURSORS
+                .lock()
+                .pop()
+                .unwrap_or_else(|| QueryCursor::new()),
+        ))
+    }
+}
+
+impl Deref for QueryCursorHandle {
+    type Target = QueryCursor;
+
+    fn deref(&self) -> &Self::Target {
+        self.0.as_ref().unwrap()
+    }
+}
+
+impl DerefMut for QueryCursorHandle {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.0.as_mut().unwrap()
+    }
+}
+
+impl Drop for QueryCursorHandle {
+    fn drop(&mut self) {
+        let mut cursor = self.0.take().unwrap();
+        cursor.set_byte_range(0..usize::MAX);
+        cursor.set_point_range(Point::zero().to_ts_point()..Point::MAX.to_ts_point());
+        QUERY_CURSORS.lock().push(cursor)
+    }
+}
+
+trait ToTreeSitterPoint {
+    fn to_ts_point(self) -> tree_sitter::Point;
+    fn from_ts_point(point: tree_sitter::Point) -> Self;
+}
+
+impl ToTreeSitterPoint for Point {
+    fn to_ts_point(self) -> tree_sitter::Point {
+        tree_sitter::Point::new(self.row as usize, self.column as usize)
+    }
+
+    fn from_ts_point(point: tree_sitter::Point) -> Self {
+        Point::new(point.row as u32, point.column as u32)
+    }
+}
+
+fn contiguous_ranges(
+    values: impl IntoIterator<Item = u32>,
+    max_len: usize,
+) -> impl Iterator<Item = Range<u32>> {
+    let mut values = values.into_iter();
+    let mut current_range: Option<Range<u32>> = None;
+    std::iter::from_fn(move || loop {
+        if let Some(value) = values.next() {
+            if let Some(range) = &mut current_range {
+                if value == range.end && range.len() < max_len {
+                    range.end += 1;
+                    continue;
+                }
+            }
+
+            let prev_range = current_range.clone();
+            current_range = Some(value..(value + 1));
+            if prev_range.is_some() {
+                return prev_range;
+            }
+        } else {
+            return current_range.take();
+        }
+    })
+}

crates/buffer/src/tests/syntax.rs → crates/language/src/tests.rs 🔗

@@ -1,7 +1,81 @@
-use crate::*;
+use super::*;
 use gpui::{ModelHandle, MutableAppContext};
+use std::rc::Rc;
 use unindent::Unindent as _;
 
+#[gpui::test]
+fn test_edit_events(cx: &mut gpui::MutableAppContext) {
+    let mut now = Instant::now();
+    let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
+    let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
+
+    let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
+    let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
+    let buffer_ops = buffer1.update(cx, |buffer, cx| {
+        let buffer_1_events = buffer_1_events.clone();
+        cx.subscribe(&buffer1, move |_, _, event, _| {
+            buffer_1_events.borrow_mut().push(event.clone())
+        })
+        .detach();
+        let buffer_2_events = buffer_2_events.clone();
+        cx.subscribe(&buffer2, move |_, _, event, _| {
+            buffer_2_events.borrow_mut().push(event.clone())
+        })
+        .detach();
+
+        // An edit emits an edited event, followed by a dirtied event,
+        // since the buffer was previously in a clean state.
+        buffer.edit(Some(2..4), "XYZ", cx);
+
+        // An empty transaction does not emit any events.
+        buffer.start_transaction(None).unwrap();
+        buffer.end_transaction(None, cx).unwrap();
+
+        // A transaction containing two edits emits one edited event.
+        now += Duration::from_secs(1);
+        buffer.start_transaction_at(None, now).unwrap();
+        buffer.edit(Some(5..5), "u", cx);
+        buffer.edit(Some(6..6), "w", cx);
+        buffer.end_transaction_at(None, now, cx).unwrap();
+
+        // Undoing a transaction emits one edited event.
+        buffer.undo(cx);
+
+        buffer.operations.clone()
+    });
+
+    // Incorporating a set of remote ops emits a single edited event,
+    // followed by a dirtied event.
+    buffer2.update(cx, |buffer, cx| {
+        buffer.apply_ops(buffer_ops, cx).unwrap();
+    });
+
+    let buffer_1_events = buffer_1_events.borrow();
+    assert_eq!(
+        *buffer_1_events,
+        vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
+    );
+
+    let buffer_2_events = buffer_2_events.borrow();
+    assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
+}
+
+#[gpui::test]
+async fn test_apply_diff(mut cx: gpui::TestAppContext) {
+    let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
+    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
+
+    let text = "a\nccc\ndddd\nffffff\n";
+    let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
+    buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
+    cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
+
+    let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
+    let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
+    buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
+    cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
+}
+
 #[gpui::test]
 async fn test_reparse(mut cx: gpui::TestAppContext) {
     let buffer = cx.add_model(|cx| {
@@ -351,6 +425,19 @@ fn test_contiguous_ranges() {
     );
 }
 
+impl Buffer {
+    pub fn enclosing_bracket_point_ranges<T: ToOffset>(
+        &self,
+        range: Range<T>,
+    ) -> Option<(Range<Point>, Range<Point>)> {
+        self.enclosing_bracket_ranges(range).map(|(start, end)| {
+            let point_start = start.start.to_point(self)..start.end.to_point(self);
+            let point_end = end.start.to_point(self)..end.end.to_point(self);
+            (point_start, point_end)
+        })
+    }
+}
+
 fn rust_lang() -> Arc<Language> {
     Arc::new(
         Language::new(

crates/project/Cargo.toml 🔗

@@ -4,7 +4,7 @@ version = "0.1.0"
 edition = "2018"
 
 [features]
-test-support = []
+test-support = ["language/test-support", "buffer/test-support"]
 
 [dependencies]
 buffer = { path = "../buffer" }
@@ -12,6 +12,7 @@ clock = { path = "../clock" }
 fsevent = { path = "../fsevent" }
 fuzzy = { path = "../fuzzy" }
 gpui = { path = "../gpui" }
+language = { path = "../language" }
 client = { path = "../client" }
 sum_tree = { path = "../sum_tree" }
 util = { path = "../util" }
@@ -33,6 +34,8 @@ toml = "0.5"
 
 [dev-dependencies]
 client = { path = "../client", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 rpc = { path = "../rpc", features = ["test-support"] }
 

crates/project/src/lib.rs 🔗

@@ -3,11 +3,11 @@ mod ignore;
 mod worktree;
 
 use anyhow::Result;
-use buffer::LanguageRegistry;
 use client::Client;
 use futures::Future;
 use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
+use language::LanguageRegistry;
 use std::{
     path::Path,
     sync::{atomic::AtomicBool, Arc},
@@ -302,9 +302,9 @@ impl Entity for Project {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use buffer::LanguageRegistry;
     use fs::RealFs;
     use gpui::TestAppContext;
+    use language::LanguageRegistry;
     use serde_json::json;
     use std::{os::unix, path::PathBuf};
     use util::test::temp_tree;

crates/project/src/worktree.rs 🔗

@@ -4,7 +4,6 @@ use super::{
 };
 use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
 use anyhow::{anyhow, Result};
-use buffer::{Buffer, History, LanguageRegistry, Operation, Rope};
 use client::{proto, Client, PeerId, TypedEnvelope};
 use clock::ReplicaId;
 use futures::{Stream, StreamExt};
@@ -13,6 +12,7 @@ use gpui::{
     executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
     Task, UpgradeModelHandle, WeakModelHandle,
 };
+use language::{Buffer, History, LanguageRegistry, Operation, Rope};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use postage::{
@@ -587,54 +587,40 @@ impl Worktree {
             }
         };
 
+        let worktree_handle = cx.handle();
         let mut buffers_to_delete = Vec::new();
         for (buffer_id, buffer) in open_buffers {
             if let Some(buffer) = buffer.upgrade(cx) {
                 buffer.update(cx, |buffer, cx| {
-                    let buffer_is_clean = !buffer.is_dirty();
-
-                    if let Some(file) = buffer.file_mut() {
-                        let mut file_changed = false;
-
-                        if let Some(entry) = file
+                    if let Some(old_file) = buffer.file() {
+                        let new_file = if let Some(entry) = old_file
                             .entry_id()
                             .and_then(|entry_id| self.entry_for_id(entry_id))
                         {
-                            if entry.path != *file.path() {
-                                file.set_path(entry.path.clone());
-                                file_changed = true;
-                            }
-
-                            if entry.mtime != file.mtime() {
-                                file.set_mtime(entry.mtime);
-                                file_changed = true;
-                                if let Some(worktree) = self.as_local() {
-                                    if buffer_is_clean {
-                                        let abs_path = worktree.absolutize(file.path().as_ref());
-                                        refresh_buffer(abs_path, &worktree.fs, cx);
-                                    }
-                                }
+                            File {
+                                entry_id: Some(entry.id),
+                                mtime: entry.mtime,
+                                path: entry.path.clone(),
+                                worktree: worktree_handle.clone(),
                             }
-                        } else if let Some(entry) = self.entry_for_path(file.path().as_ref()) {
-                            file.set_entry_id(Some(entry.id));
-                            file.set_mtime(entry.mtime);
-                            if let Some(worktree) = self.as_local() {
-                                if buffer_is_clean {
-                                    let abs_path = worktree.absolutize(file.path().as_ref());
-                                    refresh_buffer(abs_path, &worktree.fs, cx);
-                                }
+                        } else if let Some(entry) = self.entry_for_path(old_file.path().as_ref()) {
+                            File {
+                                entry_id: Some(entry.id),
+                                mtime: entry.mtime,
+                                path: entry.path.clone(),
+                                worktree: worktree_handle.clone(),
                             }
-                            file_changed = true;
-                        } else if !file.is_deleted() {
-                            if buffer_is_clean {
-                                cx.emit(buffer::Event::Dirtied);
+                        } else {
+                            File {
+                                entry_id: None,
+                                path: old_file.path().clone(),
+                                mtime: old_file.mtime(),
+                                worktree: worktree_handle.clone(),
                             }
-                            file.set_entry_id(None);
-                            file_changed = true;
-                        }
+                        };
 
-                        if file_changed {
-                            cx.emit(buffer::Event::FileHandleChanged);
+                        if let Some(task) = buffer.file_updated(Box::new(new_file), cx) {
+                            task.detach();
                         }
                     }
                 });
@@ -862,7 +848,7 @@ impl LocalWorktree {
                     .update(&mut cx, |this, cx| this.as_local().unwrap().load(&path, cx))
                     .await?;
                 let language = this.read_with(&cx, |this, cx| {
-                    use buffer::File;
+                    use language::File;
 
                     this.languages()
                         .select_language(file.full_path(cx))
@@ -909,7 +895,7 @@ impl LocalWorktree {
                     .insert(buffer.id() as u64, buffer.clone());
 
                 Ok(proto::OpenBufferResponse {
-                    buffer: Some(buffer.update(cx.as_mut(), |buffer, cx| buffer.to_proto(cx))),
+                    buffer: Some(buffer.update(cx.as_mut(), |buffer, _| buffer.to_proto())),
                 })
             })
         })
@@ -1183,24 +1169,6 @@ fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
     Ok(builder.build()?)
 }
 
-pub fn refresh_buffer(abs_path: PathBuf, fs: &Arc<dyn Fs>, cx: &mut ModelContext<Buffer>) {
-    let fs = fs.clone();
-    cx.spawn(|buffer, mut cx| async move {
-        let new_text = fs.load(&abs_path).await;
-        match new_text {
-            Err(error) => log::error!("error refreshing buffer after file changed: {}", error),
-            Ok(new_text) => {
-                buffer
-                    .update(&mut cx, |buffer, cx| {
-                        buffer.set_text_from_disk(new_text.into(), cx)
-                    })
-                    .await;
-            }
-        }
-    })
-    .detach()
-}
-
 impl Deref for LocalWorktree {
     type Target = Snapshot;
 
@@ -1279,7 +1247,7 @@ impl RemoteWorktree {
                     .ok_or_else(|| anyhow!("worktree was closed"))?;
                 let file = File::new(entry.id, this.clone(), entry.path, entry.mtime);
                 let language = this.read_with(&cx, |this, cx| {
-                    use buffer::File;
+                    use language::File;
 
                     this.languages()
                         .select_language(file.full_path(cx))
@@ -1790,7 +1758,7 @@ impl File {
     }
 }
 
-impl buffer::File for File {
+impl language::File for File {
     fn worktree_id(&self) -> usize {
         self.worktree.id()
     }
@@ -1799,26 +1767,14 @@ impl buffer::File for File {
         self.entry_id
     }
 
-    fn set_entry_id(&mut self, entry_id: Option<usize>) {
-        self.entry_id = entry_id;
-    }
-
     fn mtime(&self) -> SystemTime {
         self.mtime
     }
 
-    fn set_mtime(&mut self, mtime: SystemTime) {
-        self.mtime = mtime;
-    }
-
     fn path(&self) -> &Arc<Path> {
         &self.path
     }
 
-    fn set_path(&mut self, path: Arc<Path>) {
-        self.path = path;
-    }
-
     fn full_path(&self, cx: &AppContext) -> PathBuf {
         let worktree = self.worktree.read(cx);
         let mut full_path = PathBuf::new();
@@ -1887,6 +1843,16 @@ impl buffer::File for File {
         })
     }
 
+    fn load_local(&self, cx: &AppContext) -> Option<Task<Result<String>>> {
+        let worktree = self.worktree.read(cx).as_local()?;
+        let abs_path = worktree.absolutize(&self.path);
+        let fs = worktree.fs.clone();
+        Some(
+            cx.background()
+                .spawn(async move { fs.load(&abs_path).await }),
+        )
+    }
+
     fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) {
         self.worktree.update(cx, |worktree, cx| {
             if let Some((rpc, remote_id)) = match worktree {
@@ -1942,7 +1908,7 @@ impl buffer::File for File {
         });
     }
 
-    fn boxed_clone(&self) -> Box<dyn buffer::File> {
+    fn boxed_clone(&self) -> Box<dyn language::File> {
         Box::new(self.clone())
     }
 
@@ -3268,7 +3234,7 @@ mod tests {
             assert!(buffer.is_dirty());
             assert_eq!(
                 *events.borrow(),
-                &[buffer::Event::Edited, buffer::Event::Dirtied]
+                &[language::Event::Edited, language::Event::Dirtied]
             );
             events.borrow_mut().clear();
             buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
@@ -3277,7 +3243,7 @@ mod tests {
         // after saving, the buffer is not dirty, and emits a saved event.
         buffer1.update(&mut cx, |buffer, cx| {
             assert!(!buffer.is_dirty());
-            assert_eq!(*events.borrow(), &[buffer::Event::Saved]);
+            assert_eq!(*events.borrow(), &[language::Event::Saved]);
             events.borrow_mut().clear();
 
             buffer.edit(vec![1..1], "B", cx);
@@ -3291,9 +3257,9 @@ mod tests {
             assert_eq!(
                 *events.borrow(),
                 &[
-                    buffer::Event::Edited,
-                    buffer::Event::Dirtied,
-                    buffer::Event::Edited
+                    language::Event::Edited,
+                    language::Event::Dirtied,
+                    language::Event::Edited
                 ],
             );
             events.borrow_mut().clear();
@@ -3305,7 +3271,7 @@ mod tests {
             assert!(buffer.is_dirty());
         });
 
-        assert_eq!(*events.borrow(), &[buffer::Event::Edited]);
+        assert_eq!(*events.borrow(), &[language::Event::Edited]);
 
         // When a file is deleted, the buffer is considered dirty.
         let events = Rc::new(RefCell::new(Vec::new()));
@@ -3325,7 +3291,7 @@ mod tests {
         buffer2.condition(&cx, |b, _| b.is_dirty()).await;
         assert_eq!(
             *events.borrow(),
-            &[buffer::Event::Dirtied, buffer::Event::FileHandleChanged]
+            &[language::Event::Dirtied, language::Event::FileHandleChanged]
         );
 
         // When a file is already dirty when deleted, we don't emit a Dirtied event.
@@ -3351,7 +3317,7 @@ mod tests {
         buffer3
             .condition(&cx, |_, _| !events.borrow().is_empty())
             .await;
-        assert_eq!(*events.borrow(), &[buffer::Event::FileHandleChanged]);
+        assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
         cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
     }
 
@@ -3442,12 +3408,13 @@ mod tests {
         buffer.update(&mut cx, |buffer, cx| {
             buffer.edit(vec![0..0], " ", cx);
             assert!(buffer.is_dirty());
+            assert!(!buffer.has_conflict());
         });
 
         // Change the file on disk again, adding blank lines to the beginning.
         fs::write(&abs_path, "\n\n\nAAAA\naaa\nBB\nbbbbb\n").unwrap();
 
-        // Becaues the buffer is modified, it doesn't reload from disk, but is
+        // Because the buffer is modified, it doesn't reload from disk, but is
         // marked as having a conflict.
         buffer
             .condition(&cx, |buffer, _| buffer.has_conflict())

crates/server/src/rpc.rs 🔗

@@ -976,13 +976,13 @@ mod tests {
         time::Duration,
     };
     use zed::{
-        buffer::LanguageRegistry,
         client::{
             self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials,
             EstablishConnectionError, UserStore,
         },
         editor::{Editor, EditorSettings, Input},
         fs::{FakeFs, Fs as _},
+        language::LanguageRegistry,
         people_panel::JoinWorktree,
         project::{ProjectPath, Worktree},
         workspace::{Workspace, WorkspaceParams},

crates/workspace/Cargo.toml 🔗

@@ -8,7 +8,7 @@ test-support = [
     "client/test-support",
     "project/test-support",
     "tree-sitter",
-    "tree-sitter-rust"
+    "tree-sitter-rust",
 ]
 
 [dependencies]
@@ -16,6 +16,7 @@ buffer = { path = "../buffer" }
 client = { path = "../client" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
+language = { path = "../language" }
 project = { path = "../project" }
 theme = { path = "../theme" }
 anyhow = "1.0.38"

crates/workspace/src/items.rs 🔗

@@ -1,9 +1,9 @@
 use super::{Item, ItemView};
 use crate::Settings;
 use anyhow::Result;
-use buffer::{Buffer, File as _};
 use editor::{Editor, EditorSettings, Event};
 use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext};
+use language::{Buffer, File as _};
 use postage::watch;
 use project::{ProjectPath, Worktree};
 use std::path::Path;

crates/workspace/src/lib.rs 🔗

@@ -5,7 +5,7 @@ pub mod settings;
 pub mod sidebar;
 
 use anyhow::Result;
-use buffer::{Buffer, LanguageRegistry};
+use language::{Buffer, LanguageRegistry};
 use client::{Authenticate, ChannelList, Client, UserStore};
 use gpui::{
     action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle,
@@ -271,8 +271,8 @@ impl WorkspaceParams {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut MutableAppContext) -> Self {
         let mut languages = LanguageRegistry::new();
-        languages.add(Arc::new(buffer::Language::new(
-            buffer::LanguageConfig {
+        languages.add(Arc::new(language::Language::new(
+            language::LanguageConfig {
                 name: "Rust".to_string(),
                 path_suffixes: vec!["rs".to_string()],
                 ..Default::default()

crates/zed/Cargo.toml 🔗

@@ -18,6 +18,7 @@ test-support = [
     "buffer/test-support",
     "client/test-support",
     "gpui/test-support",
+    "language/test-support",
     "project/test-support",
     "rpc/test-support",
     "tempdir",
@@ -33,6 +34,7 @@ fuzzy = { path = "../fuzzy" }
 editor = { path = "../editor" }
 file_finder = { path = "../file_finder" }
 gpui = { path = "../gpui" }
+language = { path = "../language" }
 people_panel = { path = "../people_panel" }
 project = { path = "../project" }
 project_panel = { path = "../project_panel" }
@@ -85,6 +87,7 @@ url = "2.2"
 buffer = { path = "../buffer", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
 rpc = { path = "../rpc", features = ["test-support"] }
 client = { path = "../client", features = ["test-support"] }

crates/zed/src/language.rs 🔗

@@ -1,4 +1,4 @@
-use buffer::{Language, LanguageRegistry};
+pub use language::{Language, LanguageRegistry};
 use rust_embed::RustEmbed;
 use std::borrow::Cow;
 use std::{str, sync::Arc};

crates/zed/src/lib.rs 🔗

@@ -4,8 +4,7 @@ pub mod menus;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test;
 
-pub use buffer;
-use buffer::LanguageRegistry;
+use self::language::LanguageRegistry;
 use chat_panel::ChatPanel;
 pub use client;
 pub use editor;

crates/zed/src/test.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{assets::Assets, AppState};
-use buffer::LanguageRegistry;
 use client::{http::ServerResponse, test::FakeHttpClient, ChannelList, Client, UserStore};
 use gpui::{AssetSource, MutableAppContext};
+use language::LanguageRegistry;
 use parking_lot::Mutex;
 use postage::watch;
 use project::fs::FakeFs;