Extract a language crate

Max Brunsfeld created

Change summary

Cargo.lock                                |   30 
crates/buffer/src/anchor.rs               |    6 
crates/buffer/src/lib.rs                  | 1368 ++--------------------
crates/buffer/src/selection.rs            |    8 
crates/buffer/src/tests.rs                |  660 +++++++++++
crates/buffer/src/tests/buffer.rs         |  733 ------------
crates/editor/Cargo.toml                  |    2 
crates/editor/src/display_map.rs          |    8 
crates/editor/src/display_map/fold_map.rs |    8 
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                |   35 
crates/language/src/highlight_map.rs      |    0 
crates/language/src/language.rs           |    0 
crates/language/src/lib.rs                | 1471 +++++++++++++++++++++++++
crates/language/src/tests.rs              |   76 +
crates/project/Cargo.toml                 |    2 
crates/project/src/lib.rs                 |    4 
crates/project/src/worktree.rs            |   32 
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,487 insertions(+), 2,009 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1624,6 +1624,7 @@ dependencies = [
  "buffer",
  "clock",
  "gpui",
+ "language",
  "lazy_static",
  "log",
  "parking_lot",
@@ -2816,6 +2817,32 @@ dependencies = [
  "log",
 ]
 
+[[package]]
+name = "language"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "arrayvec 0.7.1",
+ "buffer",
+ "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]]
 name = "lazy_static"
 version = "1.4.0"
@@ -3760,6 +3787,7 @@ dependencies = [
  "fuzzy",
  "gpui",
  "ignore",
+ "language",
  "lazy_static",
  "libc",
  "log",
@@ -6106,6 +6134,7 @@ dependencies = [
  "client",
  "editor",
  "gpui",
+ "language",
  "log",
  "postage",
  "project",
@@ -6176,6 +6205,7 @@ dependencies = [
  "ignore",
  "image 0.23.14",
  "indexmap",
+ "language",
  "lazy_static",
  "libc",
  "log",

crates/buffer/src/anchor.rs 🔗

@@ -1,6 +1,6 @@
 use crate::Point;
 
-use super::{Buffer, Content};
+use super::{Content, TextBuffer};
 use anyhow::Result;
 use std::{cmp::Ordering, ops::Range};
 use sum_tree::Bias;
@@ -65,7 +65,7 @@ impl Anchor {
         Ok(offset_comparison.then_with(|| self.bias.cmp(&other.bias)))
     }
 
-    pub fn bias_left(&self, buffer: &Buffer) -> Anchor {
+    pub fn bias_left(&self, buffer: &TextBuffer) -> Anchor {
         if self.bias == Bias::Left {
             self.clone()
         } else {
@@ -73,7 +73,7 @@ impl Anchor {
         }
     }
 
-    pub fn bias_right(&self, buffer: &Buffer) -> Anchor {
+    pub fn bias_right(&self, buffer: &TextBuffer) -> Anchor {
         if self.bias == Bias::Right {
             self.clone()
         } else {

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,13 +11,7 @@ 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::*;
@@ -27,66 +19,17 @@ 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;
-}
 
 #[derive(Clone, Default)]
 struct DeterministicState;
@@ -111,53 +54,6 @@ 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 TextBuffer {
     fragments: SumTree<Fragment>,
@@ -176,42 +72,12 @@ pub struct TextBuffer {
     lamport_clock: clock::Lamport,
 }
 
-pub struct Buffer {
-    buffer: 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>,
-}
-
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct SelectionSet {
     pub selections: Arc<[Selection]>,
     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)]
 pub struct Transaction {
     start: clock::Global,
@@ -485,12 +351,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,
@@ -580,14 +440,6 @@ pub struct UndoOperation {
     version: clock::Global,
 }
 
-impl Deref for Buffer {
-    type Target = TextBuffer;
-
-    fn deref(&self) -> &Self::Target {
-        &self.buffer
-    }
-}
-
 impl TextBuffer {
     pub fn new(replica_id: u16, remote_id: u64, history: History) -> TextBuffer {
         let mut fragments = SumTree::new();
@@ -624,11 +476,69 @@ impl TextBuffer {
         }
     }
 
+    pub fn from_proto(replica_id: u16, message: proto::Buffer) -> Result<Self> {
+        let mut buffer =
+            TextBuffer::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)?;
+        buffer.selections = message
+            .selections
+            .into_iter()
+            .map(|set| {
+                let set_id = clock::Lamport {
+                    replica_id: set.replica_id as ReplicaId,
+                    value: set.local_timestamp,
+                };
+                let selections: Vec<Selection> = set
+                    .selections
+                    .into_iter()
+                    .map(TryFrom::try_from)
+                    .collect::<Result<_, _>>()?;
+                let set = SelectionSet {
+                    selections: Arc::from(selections),
+                    active: set.is_active,
+                };
+                Result::<_, anyhow::Error>::Ok((set_id, set))
+            })
+            .collect::<Result<_, _>>()?;
+        Ok(buffer)
+    }
+
+    pub fn to_proto(&self) -> proto::Buffer {
+        let ops = self.history.ops.values().map(Into::into).collect();
+        proto::Buffer {
+            id: self.remote_id,
+            content: self.history.base_text.to_string(),
+            history: ops,
+            selections: self
+                .selections
+                .iter()
+                .map(|(set_id, set)| proto::SelectionSetSnapshot {
+                    replica_id: set_id.replica_id as u32,
+                    local_timestamp: set_id.value,
+                    selections: set.selections.iter().map(Into::into).collect(),
+                    is_active: set.active,
+                })
+                .collect(),
+        }
+    }
+
     pub fn version(&self) -> clock::Global {
         self.version.clone()
     }
 
-    fn content<'a>(&'a self) -> Content<'a> {
+    pub fn snapshot(&self) -> Snapshot {
+        Snapshot {
+            visible_text: self.visible_text.clone(),
+            fragments: self.fragments.clone(),
+            version: self.version.clone(),
+        }
+    }
+
+    pub fn content<'a>(&'a self) -> Content<'a> {
         self.into()
     }
 
@@ -1250,11 +1160,10 @@ impl TextBuffer {
         &mut self,
         selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
     ) -> Result<()> {
-        self.start_transaction_at(selection_set_ids, Instant::now())?;
-        Ok(())
+        self.start_transaction_at(selection_set_ids, Instant::now())
     }
 
-    fn start_transaction_at(
+    pub fn start_transaction_at(
         &mut self,
         selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
         now: Instant,
@@ -1274,11 +1183,11 @@ impl TextBuffer {
         Ok(())
     }
 
-    fn end_transaction(&mut self, selection_set_ids: impl IntoIterator<Item = SelectionSetId>) {
+    pub fn end_transaction(&mut self, selection_set_ids: impl IntoIterator<Item = SelectionSetId>) {
         self.end_transaction_at(selection_set_ids, Instant::now());
     }
 
-    fn end_transaction_at(
+    pub fn end_transaction_at(
         &mut self,
         selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
         now: Instant,
@@ -1303,12 +1212,12 @@ impl TextBuffer {
         }
     }
 
-    fn remove_peer(&mut self, replica_id: ReplicaId) {
+    pub fn remove_peer(&mut self, replica_id: ReplicaId) {
         self.selections
             .retain(|set_id, _| set_id.replica_id != replica_id)
     }
 
-    fn undo(&mut self) -> Vec<Operation> {
+    pub fn undo(&mut self) -> Vec<Operation> {
         let mut ops = Vec::new();
         if let Some(transaction) = self.history.pop_undo().cloned() {
             let selections = transaction.selections_before.clone();
@@ -1320,7 +1229,7 @@ impl TextBuffer {
         ops
     }
 
-    fn redo(&mut self) -> Vec<Operation> {
+    pub fn redo(&mut self) -> Vec<Operation> {
         let mut ops = Vec::new();
         if let Some(transaction) = self.history.pop_redo().cloned() {
             let selections = transaction.selections_after.clone();
@@ -1458,1078 +1367,107 @@ impl TextBuffer {
     }
 }
 
-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,
-        )
+#[cfg(any(test, feature = "test-support"))]
+impl TextBuffer {
+    fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
+        let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);
+        let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right);
+        start..end
     }
 
-    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,
-        )
+    pub fn randomly_edit<T>(
+        &mut self,
+        rng: &mut T,
+        old_range_count: usize,
+    ) -> (Vec<Range<usize>>, String, Operation)
+    where
+        T: rand::Rng,
+    {
+        let mut old_ranges: Vec<Range<usize>> = Vec::new();
+        for _ in 0..old_range_count {
+            let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1);
+            if last_end > self.len() {
+                break;
+            }
+            old_ranges.push(self.random_byte_range(last_end, rng));
+        }
+        let new_text_len = rng.gen_range(0..10);
+        let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng)
+            .take(new_text_len)
+            .collect();
+        log::info!(
+            "mutating buffer {} at {:?}: {:?}",
+            self.replica_id,
+            old_ranges,
+            new_text
+        );
+        let op = self.edit(old_ranges.iter().cloned(), new_text.as_str());
+        (old_ranges, new_text, Operation::Edit(op))
     }
 
-    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 randomly_mutate<T>(&mut self, rng: &mut T) -> Vec<Operation>
+    where
+        T: rand::Rng,
+    {
+        use rand::prelude::*;
 
-        let mut result = Self {
-            buffer: TextBuffer::new(replica_id, remote_id, history),
-            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,
+        let mut ops = vec![self.randomly_edit(rng, 5).2];
 
-            #[cfg(test)]
-            operations: Default::default(),
-        };
-        result.reparse(cx);
-        result
-    }
+        // Randomly add, remove or mutate selection sets.
+        let replica_selection_sets = &self
+            .selection_sets()
+            .map(|(set_id, _)| *set_id)
+            .filter(|set_id| self.replica_id == set_id.replica_id)
+            .collect::<Vec<_>>();
+        let set_id = replica_selection_sets.choose(rng);
+        if set_id.is_some() && rng.gen_bool(1.0 / 6.0) {
+            ops.push(self.remove_selection_set(*set_id.unwrap()).unwrap());
+        } else {
+            let mut ranges = Vec::new();
+            for _ in 0..5 {
+                ranges.push(self.random_byte_range(0, rng));
+            }
+            let new_selections = self.selections_from_ranges(ranges).unwrap();
 
-    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(),
+            let op = if set_id.is_none() || rng.gen_bool(1.0 / 5.0) {
+                self.add_selection_set(new_selections)
+            } else {
+                self.update_selection_set(*set_id.unwrap(), new_selections)
+                    .unwrap()
+            };
+            ops.push(op);
         }
-    }
 
-    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,
-        );
-        let ops = message
-            .history
-            .into_iter()
-            .map(|op| Operation::Edit(op.into()));
-        buffer.apply_ops(ops, cx)?;
-        buffer.buffer.selections = message
-            .selections
-            .into_iter()
-            .map(|set| {
-                let set_id = clock::Lamport {
-                    replica_id: set.replica_id as ReplicaId,
-                    value: set.local_timestamp,
-                };
-                let selections: Vec<Selection> = set
-                    .selections
-                    .into_iter()
-                    .map(TryFrom::try_from)
-                    .collect::<Result<_, _>>()?;
-                let set = SelectionSet {
-                    selections: Arc::from(selections),
-                    active: set.is_active,
-                };
-                Result::<_, anyhow::Error>::Ok((set_id, set))
-            })
-            .collect::<Result<_, _>>()?;
-        Ok(buffer)
+        ops
     }
 
-    pub fn to_proto(&self, cx: &mut ModelContext<Self>) -> proto::Buffer {
-        let ops = self.history.ops.values().map(Into::into).collect();
-        proto::Buffer {
-            id: cx.model_id() as u64,
-            content: self.history.base_text.to_string(),
-            history: ops,
-            selections: self
-                .selections
-                .iter()
-                .map(|(set_id, set)| proto::SelectionSetSnapshot {
-                    replica_id: set_id.replica_id as u32,
-                    local_timestamp: set_id.value,
-                    selections: set.selections.iter().map(Into::into).collect(),
-                    is_active: set.active,
-                })
-                .collect(),
-        }
-    }
+    pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec<Operation> {
+        use rand::prelude::*;
 
-    pub fn file(&self) -> Option<&dyn File> {
-        self.file.as_deref()
+        let mut ops = Vec::new();
+        for _ in 0..rng.gen_range(1..=5) {
+            if let Some(transaction) = self.history.undo_stack.choose(rng).cloned() {
+                log::info!(
+                    "undoing buffer {} transaction {:?}",
+                    self.replica_id,
+                    transaction
+                );
+                ops.push(self.undo_or_redo(transaction).unwrap());
+            }
+        }
+        ops
     }
 
-    pub fn file_mut(&mut self) -> Option<&mut dyn File> {
-        self.file.as_mut().map(|f| f.deref_mut() as &mut dyn File)
-    }
+    fn selections_from_ranges<I>(&self, ranges: I) -> Result<Vec<Selection>>
+    where
+        I: IntoIterator<Item = Range<usize>>,
+    {
+        use std::sync::atomic::{self, AtomicUsize};
 
-    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))
-        }))
-    }
+        static NEXT_SELECTION_ID: AtomicUsize = AtomicUsize::new(0);
 
-    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.buffer);
-                        if start_point.column == 0 {
-                            let end_point = selection.end.to_point(&self.buffer);
-                            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 = 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 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.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 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();
-                }
-            });
-        })
-    }
-
-    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())?;
-        Ok(())
-    }
-
-    fn start_transaction_at(
-        &mut self,
-        selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
-        now: Instant,
-    ) -> Result<()> {
-        self.buffer.start_transaction_at(selection_set_ids, 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<()> {
-        if let Some(start_version) = self.buffer.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.buffer.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
-                .buffer
-                .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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.randomly_edit(rng, old_range_count);
-    }
-
-    pub fn randomly_mutate<T>(&mut self, rng: &mut T)
-    where
-        T: rand::Rng,
-    {
-        self.buffer.randomly_mutate(rng);
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl TextBuffer {
-    fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
-        let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);
-        let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right);
-        start..end
-    }
-
-    pub fn randomly_edit<T>(
-        &mut self,
-        rng: &mut T,
-        old_range_count: usize,
-    ) -> (Vec<Range<usize>>, String, Operation)
-    where
-        T: rand::Rng,
-    {
-        let mut old_ranges: Vec<Range<usize>> = Vec::new();
-        for _ in 0..old_range_count {
-            let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1);
-            if last_end > self.len() {
-                break;
-            }
-            old_ranges.push(self.random_byte_range(last_end, rng));
-        }
-        let new_text_len = rng.gen_range(0..10);
-        let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng)
-            .take(new_text_len)
-            .collect();
-        log::info!(
-            "mutating buffer {} at {:?}: {:?}",
-            self.replica_id,
-            old_ranges,
-            new_text
-        );
-        let op = self.edit(old_ranges.iter().cloned(), new_text.as_str());
-        (old_ranges, new_text, Operation::Edit(op))
-    }
-
-    pub fn randomly_mutate<T>(&mut self, rng: &mut T) -> Vec<Operation>
-    where
-        T: rand::Rng,
-    {
-        use rand::prelude::*;
-
-        let mut ops = vec![self.randomly_edit(rng, 5).2];
-
-        // Randomly add, remove or mutate selection sets.
-        let replica_selection_sets = &self
-            .selection_sets()
-            .map(|(set_id, _)| *set_id)
-            .filter(|set_id| self.replica_id == set_id.replica_id)
-            .collect::<Vec<_>>();
-        let set_id = replica_selection_sets.choose(rng);
-        if set_id.is_some() && rng.gen_bool(1.0 / 6.0) {
-            ops.push(self.remove_selection_set(*set_id.unwrap()).unwrap());
-        } else {
-            let mut ranges = Vec::new();
-            for _ in 0..5 {
-                ranges.push(self.random_byte_range(0, rng));
-            }
-            let new_selections = self.selections_from_ranges(ranges).unwrap();
-
-            let op = if set_id.is_none() || rng.gen_bool(1.0 / 5.0) {
-                self.add_selection_set(new_selections)
-            } else {
-                self.update_selection_set(*set_id.unwrap(), new_selections)
-                    .unwrap()
-            };
-            ops.push(op);
-        }
-
-        ops
-    }
-
-    pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec<Operation> {
-        use rand::prelude::*;
-
-        let mut ops = Vec::new();
-        for _ in 0..rng.gen_range(1..=5) {
-            if let Some(transaction) = self.history.undo_stack.choose(rng).cloned() {
-                log::info!(
-                    "undoing buffer {} transaction {:?}",
-                    self.replica_id,
-                    transaction
-                );
-                ops.push(self.undo_or_redo(transaction).unwrap());
-            }
-        }
-        ops
-    }
-
-    fn selections_from_ranges<I>(&self, ranges: I) -> Result<Vec<Selection>>
-    where
-        I: IntoIterator<Item = Range<usize>>,
-    {
-        use std::sync::atomic::{self, AtomicUsize};
-
-        static NEXT_SELECTION_ID: AtomicUsize = AtomicUsize::new(0);
-
-        let mut ranges = ranges.into_iter().collect::<Vec<_>>();
-        ranges.sort_unstable_by_key(|range| range.start);
+        let mut ranges = ranges.into_iter().collect::<Vec<_>>();
+        ranges.sort_unstable_by_key(|range| range.start);
 
         let mut selections = Vec::with_capacity(ranges.len());
         for range in ranges {

crates/buffer/src/selection.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _};
+use crate::{Anchor, Point, TextBuffer, ToOffset as _, ToPoint as _};
 use std::{cmp::Ordering, mem, ops::Range};
 
 pub type SelectionSetId = clock::Lamport;
@@ -29,7 +29,7 @@ impl Selection {
         }
     }
 
-    pub fn set_head(&mut self, buffer: &Buffer, cursor: Anchor) {
+    pub fn set_head(&mut self, buffer: &TextBuffer, cursor: Anchor) {
         if cursor.cmp(self.tail(), buffer).unwrap() < Ordering::Equal {
             if !self.reversed {
                 mem::swap(&mut self.start, &mut self.end);
@@ -53,7 +53,7 @@ impl Selection {
         }
     }
 
-    pub fn point_range(&self, buffer: &Buffer) -> Range<Point> {
+    pub fn point_range(&self, buffer: &TextBuffer) -> Range<Point> {
         let start = self.start.to_point(buffer);
         let end = self.end.to_point(buffer);
         if self.reversed {
@@ -63,7 +63,7 @@ impl Selection {
         }
     }
 
-    pub fn offset_range(&self, buffer: &Buffer) -> Range<usize> {
+    pub fn offset_range(&self, buffer: &TextBuffer) -> Range<usize> {
         let start = self.start.to_offset(buffer);
         let end = self.end.to_offset(buffer);
         if self.reversed {

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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::new(1, 0, History::new(text.into()));
+    let mut buffer2 = TextBuffer::new(2, 0, History::new(text.into()));
+    let mut buffer3 = TextBuffer::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 = TextBuffer::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,733 +0,0 @@
-use crate::*;
-use clock::ReplicaId;
-use rand::prelude::*;
-use std::{
-    cell::RefCell,
-    cmp::Ordering,
-    env,
-    iter::Iterator,
-    rc::Rc,
-    time::{Duration, Instant},
-};
-
-#[test]
-fn test_edit() {
-    let mut buffer = TextBuffer::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]
-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(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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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 = TextBuffer::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);
-}
-
-#[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));
-}
-
-#[test]
-fn test_undo_redo() {
-    let mut buffer = TextBuffer::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 = TextBuffer::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 = TextBuffer::new(1, 0, History::new(text.into()));
-    let mut buffer2 = TextBuffer::new(2, 0, History::new(text.into()));
-    let mut buffer3 = TextBuffer::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 = TextBuffer::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/editor/Cargo.toml 🔗

@@ -10,6 +10,7 @@ test-support = ["buffer/test-support", "gpui/test-support"]
 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 +25,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_edit(&mut rng, 5));
+                    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, AnchorRangeExt, Buffer, HighlightId, Point, TextSummary, ToOffset};
 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,7 +1331,7 @@ 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);

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));
+                    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,35 @@
+[package]
+name = "language"
+version = "0.1.0"
+edition = "2018"
+
+[features]
+test-support = ["rand"]
+
+[dependencies]
+buffer = { path = "../buffer" }
+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"
+smallvec = { version = "1.6", features = ["union"] }
+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,1471 @@
+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::*;
+use clock::ReplicaId;
+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 sum_tree::Bias;
+use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
+
+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 {
+    buffer: 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 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;
+}
+
+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 {
+            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.buffer.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 file_mut(&mut self) -> Option<&mut dyn File> {
+        self.file.as_mut().map(|f| f.deref_mut() as &mut dyn File)
+    }
+
+    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,
+        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.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.into(),
+                old_end_position: (start_point + edit.deleted_lines()).into(),
+                new_end_position: self
+                    .as_rope()
+                    .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::<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.buffer);
+                        if start_point.column == 0 {
+                            let end_point = selection.end.to_point(&self.buffer);
+                            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,
+            }
+        })
+    }
+
+    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();
+                }
+            });
+        })
+    }
+
+    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.buffer.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.buffer.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.buffer.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
+                .buffer
+                .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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.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.buffer.randomly_edit(rng, old_range_count);
+    }
+
+    pub fn randomly_mutate<T>(&mut self, rng: &mut T)
+    where
+        T: rand::Rng,
+    {
+        self.buffer.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 {
+            buffer: self.buffer.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.buffer
+    }
+}
+
+impl<'a> From<&'a Buffer> for Content<'a> {
+    fn from(buffer: &'a Buffer) -> Self {
+        Self::from(&buffer.buffer)
+    }
+}
+
+impl<'a> From<&'a mut Buffer> for Content<'a> {
+    fn from(buffer: &'a mut Buffer) -> Self {
+        Self::from(&buffer.buffer)
+    }
+}
+
+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).into()
+                    ..Point::new(row_range.end, 0).into(),
+            );
+            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(capture.node.start_position().into());
+                        end.get_or_insert(capture.node.end_position().into());
+                    } else if Some(capture.index) == end_capture_ix {
+                        end = Some(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().into()..Point::MAX.into());
+        QUERY_CURSORS.lock().push(cursor)
+    }
+}
+
+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| {

crates/project/Cargo.toml 🔗

@@ -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,7 @@ toml = "0.5"
 
 [dev-dependencies]
 client = { path = "../client", 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::{
@@ -627,14 +627,14 @@ impl Worktree {
                             file_changed = true;
                         } else if !file.is_deleted() {
                             if buffer_is_clean {
-                                cx.emit(buffer::Event::Dirtied);
+                                cx.emit(language::Event::Dirtied);
                             }
                             file.set_entry_id(None);
                             file_changed = true;
                         }
 
                         if file_changed {
-                            cx.emit(buffer::Event::FileHandleChanged);
+                            cx.emit(language::Event::FileHandleChanged);
                         }
                     }
                 });
@@ -862,7 +862,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 +909,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())),
                 })
             })
         })
@@ -1279,7 +1279,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 +1790,7 @@ impl File {
     }
 }
 
-impl buffer::File for File {
+impl language::File for File {
     fn worktree_id(&self) -> usize {
         self.worktree.id()
     }
@@ -1942,7 +1942,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 +3268,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 +3277,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 +3291,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 +3305,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 +3325,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 +3351,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()));
     }
 

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;