Merge branch 'main' into unfold-on-select-match

Nathan Sobo created

Change summary

crates/diagnostics/src/diagnostics.rs         |   5 
crates/editor/src/display_map.rs              | 129 ++
crates/editor/src/display_map/block_map.rs    |   2 
crates/editor/src/display_map/fold_map.rs     |  31 
crates/editor/src/editor.rs                   | 872 ++++++++++----------
crates/editor/src/items.rs                    |   2 
crates/editor/src/multi_buffer.rs             |  77 
crates/editor/src/multi_buffer/anchor.rs      |  23 
crates/editor/src/test.rs                     |  23 
crates/file_finder/src/file_finder.rs         |   2 
crates/go_to_line/src/go_to_line.rs           |   2 
crates/language/src/buffer.rs                 |  31 
crates/language/src/diagnostic_set.rs         |  24 
crates/language/src/tests.rs                  |  14 
crates/outline/src/outline.rs                 |   2 
crates/project/src/project.rs                 |  11 
crates/project_symbols/src/project_symbols.rs |   2 
crates/search/src/buffer_search.rs            |   4 
crates/search/src/search.rs                   |   8 
crates/text/src/anchor.rs                     |  50 
crates/text/src/tests.rs                      |  36 
crates/text/src/text.rs                       |  26 
crates/theme_selector/src/theme_selector.rs   |   2 
23 files changed, 737 insertions(+), 641 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -278,7 +278,7 @@ impl ProjectDiagnosticsEditor {
                             prev_excerpt_id = excerpt_id.clone();
                             first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone());
                             group_state.excerpts.push(excerpt_id.clone());
-                            let header_position = (excerpt_id.clone(), language::Anchor::min());
+                            let header_position = (excerpt_id.clone(), language::Anchor::MIN);
 
                             if is_first_excerpt_for_group {
                                 is_first_excerpt_for_group = false;
@@ -367,8 +367,7 @@ impl ProjectDiagnosticsEditor {
             range_a
                 .start
                 .cmp(&range_b.start, &snapshot)
-                .unwrap()
-                .then_with(|| range_a.end.cmp(&range_b.end, &snapshot).unwrap())
+                .then_with(|| range_a.end.cmp(&range_b.end, &snapshot))
         });
 
         if path_state.diagnostic_groups.is_empty() {

crates/editor/src/display_map.rs 🔗

@@ -490,7 +490,10 @@ impl ToDisplayPoint for Anchor {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::movement;
+    use crate::{
+        movement,
+        test::{marked_text_ranges},
+    };
     use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
     use language::{Buffer, Language, LanguageConfig, RandomCharIter, SelectionGoal};
     use rand::{prelude::*, Rng};
@@ -930,7 +933,7 @@ mod tests {
         let map = cx
             .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
         assert_eq!(
-            cx.update(|cx| chunks(0..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
                 ("outer".to_string(), Some(Color::blue())),
@@ -941,7 +944,7 @@ mod tests {
             ]
         );
         assert_eq!(
-            cx.update(|cx| chunks(3..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
             vec![
                 ("    fn ".to_string(), Some(Color::red())),
                 ("inner".to_string(), Some(Color::blue())),
@@ -953,7 +956,7 @@ mod tests {
             map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
         });
         assert_eq!(
-            cx.update(|cx| chunks(0..2, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(0..2, &map, &theme, cx)),
             vec![
                 ("fn ".to_string(), None),
                 ("out".to_string(), Some(Color::blue())),
@@ -1019,7 +1022,7 @@ mod tests {
             DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), 1, 1, cx)
         });
         assert_eq!(
-            cx.update(|cx| chunks(0..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
             [
                 ("fn \n".to_string(), None),
                 ("oute\nr".to_string(), Some(Color::blue())),
@@ -1027,7 +1030,7 @@ mod tests {
             ]
         );
         assert_eq!(
-            cx.update(|cx| chunks(3..5, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(3..5, &map, &theme, cx)),
             [("{}\n\n".to_string(), None)]
         );
 
@@ -1035,7 +1038,7 @@ mod tests {
             map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx)
         });
         assert_eq!(
-            cx.update(|cx| chunks(1..4, &map, &theme, cx)),
+            cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
             [
                 ("out".to_string(), Some(Color::blue())),
                 ("…\n".to_string(), None),
@@ -1045,6 +1048,89 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
+        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
+
+        let theme = SyntaxTheme::new(vec![
+            ("operator".to_string(), Color::red().into()),
+            ("string".to_string(), Color::green().into()),
+        ]);
+        let language = Arc::new(
+            Language::new(
+                LanguageConfig {
+                    name: "Test".into(),
+                    path_suffixes: vec![".test".to_string()],
+                    ..Default::default()
+                },
+                Some(tree_sitter_rust::language()),
+            )
+            .with_highlights_query(
+                r#"
+                ":" @operator
+                (string_literal) @string
+                "#,
+            )
+            .unwrap(),
+        );
+        language.set_theme(&theme);
+
+        let (text, highlighted_ranges) = marked_text_ranges(
+            r#"const{} <a>: B = "c [d]""#,
+            vec![('{', '}'), ('<', '>'), ('[', ']')],
+        );
+
+        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
+        buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
+
+        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
+
+        let font_cache = cx.font_cache();
+        let tab_size = 4;
+        let family_id = font_cache.load_family(&["Courier"]).unwrap();
+        let font_id = font_cache
+            .select_font(family_id, &Default::default())
+            .unwrap();
+        let font_size = 16.0;
+        let map = cx
+            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
+
+        enum MyType {}
+
+        let style = HighlightStyle {
+            color: Some(Color::blue()),
+            ..Default::default()
+        };
+
+        map.update(cx, |map, _cx| {
+            map.highlight_text(
+                TypeId::of::<MyType>(),
+                highlighted_ranges
+                    .into_iter()
+                    .map(|range| {
+                        buffer_snapshot.anchor_before(range.start)
+                            ..buffer_snapshot.anchor_before(range.end)
+                    })
+                    .collect(),
+                style,
+            );
+        });
+
+        assert_eq!(
+            cx.update(|cx| chunks(0..10, &map, &theme, cx)),
+            [
+                ("const ".to_string(), None, None),
+                ("a".to_string(), None, Some(Color::blue())),
+                (":".to_string(), Some(Color::red()), None),
+                (" B = ".to_string(), None, None),
+                ("\"c ".to_string(), Some(Color::green()), None),
+                ("d".to_string(), Some(Color::green()), Some(Color::blue())),
+                ("\"".to_string(), Some(Color::green()), None),
+            ]
+        );
+    }
+
     #[gpui::test]
     fn test_clip_point(cx: &mut gpui::MutableAppContext) {
         use Bias::{Left, Right};
@@ -1171,27 +1257,38 @@ mod tests {
         )
     }
 
-    fn chunks<'a>(
+    fn syntax_chunks<'a>(
         rows: Range<u32>,
         map: &ModelHandle<DisplayMap>,
         theme: &'a SyntaxTheme,
         cx: &mut MutableAppContext,
     ) -> Vec<(String, Option<Color>)> {
+        chunks(rows, map, theme, cx)
+            .into_iter()
+            .map(|(text, color, _)| (text, color))
+            .collect()
+    }
+
+    fn chunks<'a>(
+        rows: Range<u32>,
+        map: &ModelHandle<DisplayMap>,
+        theme: &'a SyntaxTheme,
+        cx: &mut MutableAppContext,
+    ) -> Vec<(String, Option<Color>, Option<Color>)> {
         let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-        let mut chunks: Vec<(String, Option<Color>)> = Vec::new();
+        let mut chunks: Vec<(String, Option<Color>, Option<Color>)> = Vec::new();
         for chunk in snapshot.chunks(rows, true) {
-            let color = chunk
+            let syntax_color = chunk
                 .syntax_highlight_id
                 .and_then(|id| id.style(theme)?.color);
-            if let Some((last_chunk, last_color)) = chunks.last_mut() {
-                if color == *last_color {
+            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
+            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
+                if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
                     last_chunk.push_str(chunk.text);
-                } else {
-                    chunks.push((chunk.text.to_string(), color));
+                    continue;
                 }
-            } else {
-                chunks.push((chunk.text.to_string(), color));
             }
+            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
         }
         chunks
     }

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

@@ -499,7 +499,7 @@ impl<'a> BlockMapWriter<'a> {
             let block_ix = match self
                 .0
                 .blocks
-                .binary_search_by(|probe| probe.position.cmp(&position, &buffer).unwrap())
+                .binary_search_by(|probe| probe.position.cmp(&position, &buffer))
             {
                 Ok(ix) | Err(ix) => ix,
             };

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

@@ -257,7 +257,7 @@ impl FoldMap {
             let mut folds = self.folds.iter().peekable();
             while let Some(fold) = folds.next() {
                 if let Some(next_fold) = folds.peek() {
-                    let comparison = fold.0.cmp(&next_fold.0, &self.buffer.lock()).unwrap();
+                    let comparison = fold.0.cmp(&next_fold.0, &self.buffer.lock());
                     assert!(comparison.is_le());
                 }
             }
@@ -700,10 +700,7 @@ impl FoldSnapshot {
                             let ranges = &highlights.1;
 
                             let start_ix = match ranges.binary_search_by(|probe| {
-                                let cmp = probe
-                                    .end
-                                    .cmp(&transform_start, &self.buffer_snapshot())
-                                    .unwrap();
+                                let cmp = probe.end.cmp(&transform_start, &self.buffer_snapshot());
                                 if cmp.is_gt() {
                                     Ordering::Greater
                                 } else {
@@ -716,7 +713,6 @@ impl FoldSnapshot {
                                 if range
                                     .start
                                     .cmp(&transform_end, &self.buffer_snapshot)
-                                    .unwrap()
                                     .is_ge()
                                 {
                                     break;
@@ -821,8 +817,8 @@ where
     let start = buffer.anchor_before(range.start.to_offset(buffer));
     let end = buffer.anchor_after(range.end.to_offset(buffer));
     let mut cursor = folds.filter::<_, usize>(move |summary| {
-        let start_cmp = start.cmp(&summary.max_end, buffer).unwrap();
-        let end_cmp = end.cmp(&summary.min_start, buffer).unwrap();
+        let start_cmp = start.cmp(&summary.max_end, buffer);
+        let end_cmp = end.cmp(&summary.min_start, buffer);
 
         if inclusive {
             start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
@@ -963,19 +959,19 @@ impl sum_tree::Summary for FoldSummary {
     type Context = MultiBufferSnapshot;
 
     fn add_summary(&mut self, other: &Self, buffer: &MultiBufferSnapshot) {
-        if other.min_start.cmp(&self.min_start, buffer).unwrap() == Ordering::Less {
+        if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
             self.min_start = other.min_start.clone();
         }
-        if other.max_end.cmp(&self.max_end, buffer).unwrap() == Ordering::Greater {
+        if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater {
             self.max_end = other.max_end.clone();
         }
 
         #[cfg(debug_assertions)]
         {
-            let start_comparison = self.start.cmp(&other.start, buffer).unwrap();
+            let start_comparison = self.start.cmp(&other.start, buffer);
             assert!(start_comparison <= Ordering::Equal);
             if start_comparison == Ordering::Equal {
-                assert!(self.end.cmp(&other.end, buffer).unwrap() >= Ordering::Equal);
+                assert!(self.end.cmp(&other.end, buffer) >= Ordering::Equal);
             }
         }
 
@@ -994,7 +990,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for Fold {
 
 impl<'a> sum_tree::SeekTarget<'a, FoldSummary, Fold> for Fold {
     fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
-        self.0.cmp(&other.0, buffer).unwrap()
+        self.0.cmp(&other.0, buffer)
     }
 }
 
@@ -1157,7 +1153,7 @@ impl Ord for HighlightEndpoint {
     fn cmp(&self, other: &Self) -> Ordering {
         self.offset
             .cmp(&other.offset)
-            .then_with(|| self.is_start.cmp(&other.is_start))
+            .then_with(|| other.is_start.cmp(&self.is_start))
     }
 }
 
@@ -1606,9 +1602,8 @@ mod tests {
                     .filter(|fold| {
                         let start = buffer_snapshot.anchor_before(start);
                         let end = buffer_snapshot.anchor_after(end);
-                        start.cmp(&fold.0.end, &buffer_snapshot).unwrap() == Ordering::Less
-                            && end.cmp(&fold.0.start, &buffer_snapshot).unwrap()
-                                == Ordering::Greater
+                        start.cmp(&fold.0.end, &buffer_snapshot) == Ordering::Less
+                            && end.cmp(&fold.0.start, &buffer_snapshot) == Ordering::Greater
                     })
                     .map(|fold| fold.0)
                     .collect::<Vec<_>>();
@@ -1686,7 +1681,7 @@ mod tests {
             let buffer = self.buffer.lock().clone();
             let mut folds = self.folds.items(&buffer);
             // Ensure sorting doesn't change how folds get merged and displayed.
-            folds.sort_by(|a, b| a.0.cmp(&b.0, &buffer).unwrap());
+            folds.sort_by(|a, b| a.0.cmp(&b.0, &buffer));
             let mut fold_ranges = folds
                 .iter()
                 .map(|fold| fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer))

crates/editor/src/editor.rs 🔗

@@ -1744,134 +1744,135 @@ impl Editor {
     pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext<Self>) {
         let text = action.0.as_ref();
         if !self.skip_autoclose_end(text, cx) {
-            self.start_transaction(cx);
-            if !self.surround_with_bracket_pair(text, cx) {
-                self.insert(text, cx);
-                self.autoclose_bracket_pairs(cx);
-            }
-            self.end_transaction(cx);
+            self.transact(cx, |this, cx| {
+                if !this.surround_with_bracket_pair(text, cx) {
+                    this.insert(text, cx);
+                    this.autoclose_bracket_pairs(cx);
+                }
+            });
             self.trigger_completion_on_input(text, cx);
         }
     }
 
     pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
-        self.start_transaction(cx);
-        let mut old_selections = SmallVec::<[_; 32]>::new();
-        {
-            let selections = self.local_selections::<usize>(cx);
-            let buffer = self.buffer.read(cx).snapshot(cx);
-            for selection in selections.iter() {
-                let start_point = selection.start.to_point(&buffer);
-                let indent = buffer
-                    .indent_column_for_line(start_point.row)
-                    .min(start_point.column);
-                let start = selection.start;
-                let end = selection.end;
-
-                let mut insert_extra_newline = false;
-                if let Some(language) = buffer.language() {
-                    let leading_whitespace_len = buffer
-                        .reversed_chars_at(start)
-                        .take_while(|c| c.is_whitespace() && *c != '\n')
-                        .map(|c| c.len_utf8())
-                        .sum::<usize>();
-
-                    let trailing_whitespace_len = buffer
-                        .chars_at(end)
-                        .take_while(|c| c.is_whitespace() && *c != '\n')
-                        .map(|c| c.len_utf8())
-                        .sum::<usize>();
-
-                    insert_extra_newline = language.brackets().iter().any(|pair| {
-                        let pair_start = pair.start.trim_end();
-                        let pair_end = pair.end.trim_start();
-
-                        pair.newline
-                            && buffer.contains_str_at(end + trailing_whitespace_len, pair_end)
-                            && buffer.contains_str_at(
-                                (start - leading_whitespace_len).saturating_sub(pair_start.len()),
-                                pair_start,
-                            )
-                    });
-                }
+        self.transact(cx, |this, cx| {
+            let mut old_selections = SmallVec::<[_; 32]>::new();
+            {
+                let selections = this.local_selections::<usize>(cx);
+                let buffer = this.buffer.read(cx).snapshot(cx);
+                for selection in selections.iter() {
+                    let start_point = selection.start.to_point(&buffer);
+                    let indent = buffer
+                        .indent_column_for_line(start_point.row)
+                        .min(start_point.column);
+                    let start = selection.start;
+                    let end = selection.end;
+
+                    let mut insert_extra_newline = false;
+                    if let Some(language) = buffer.language() {
+                        let leading_whitespace_len = buffer
+                            .reversed_chars_at(start)
+                            .take_while(|c| c.is_whitespace() && *c != '\n')
+                            .map(|c| c.len_utf8())
+                            .sum::<usize>();
+
+                        let trailing_whitespace_len = buffer
+                            .chars_at(end)
+                            .take_while(|c| c.is_whitespace() && *c != '\n')
+                            .map(|c| c.len_utf8())
+                            .sum::<usize>();
+
+                        insert_extra_newline = language.brackets().iter().any(|pair| {
+                            let pair_start = pair.start.trim_end();
+                            let pair_end = pair.end.trim_start();
+
+                            pair.newline
+                                && buffer.contains_str_at(end + trailing_whitespace_len, pair_end)
+                                && buffer.contains_str_at(
+                                    (start - leading_whitespace_len)
+                                        .saturating_sub(pair_start.len()),
+                                    pair_start,
+                                )
+                        });
+                    }
 
-                old_selections.push((
-                    selection.id,
-                    buffer.anchor_after(end),
-                    start..end,
-                    indent,
-                    insert_extra_newline,
-                ));
+                    old_selections.push((
+                        selection.id,
+                        buffer.anchor_after(end),
+                        start..end,
+                        indent,
+                        insert_extra_newline,
+                    ));
+                }
             }
-        }
 
-        self.buffer.update(cx, |buffer, cx| {
-            let mut delta = 0_isize;
-            let mut pending_edit: Option<PendingEdit> = None;
-            for (_, _, range, indent, insert_extra_newline) in &old_selections {
-                if pending_edit.as_ref().map_or(false, |pending| {
-                    pending.indent != *indent
-                        || pending.insert_extra_newline != *insert_extra_newline
-                }) {
-                    let pending = pending_edit.take().unwrap();
-                    let mut new_text = String::with_capacity(1 + pending.indent as usize);
-                    new_text.push('\n');
-                    new_text.extend(iter::repeat(' ').take(pending.indent as usize));
-                    if pending.insert_extra_newline {
-                        new_text = new_text.repeat(2);
+            this.buffer.update(cx, |buffer, cx| {
+                let mut delta = 0_isize;
+                let mut pending_edit: Option<PendingEdit> = None;
+                for (_, _, range, indent, insert_extra_newline) in &old_selections {
+                    if pending_edit.as_ref().map_or(false, |pending| {
+                        pending.indent != *indent
+                            || pending.insert_extra_newline != *insert_extra_newline
+                    }) {
+                        let pending = pending_edit.take().unwrap();
+                        let mut new_text = String::with_capacity(1 + pending.indent as usize);
+                        new_text.push('\n');
+                        new_text.extend(iter::repeat(' ').take(pending.indent as usize));
+                        if pending.insert_extra_newline {
+                            new_text = new_text.repeat(2);
+                        }
+                        buffer.edit_with_autoindent(pending.ranges, new_text, cx);
+                        delta += pending.delta;
                     }
-                    buffer.edit_with_autoindent(pending.ranges, new_text, cx);
-                    delta += pending.delta;
-                }
 
-                let start = (range.start as isize + delta) as usize;
-                let end = (range.end as isize + delta) as usize;
-                let mut text_len = *indent as usize + 1;
-                if *insert_extra_newline {
-                    text_len *= 2;
+                    let start = (range.start as isize + delta) as usize;
+                    let end = (range.end as isize + delta) as usize;
+                    let mut text_len = *indent as usize + 1;
+                    if *insert_extra_newline {
+                        text_len *= 2;
+                    }
+
+                    let pending = pending_edit.get_or_insert_with(Default::default);
+                    pending.delta += text_len as isize - (end - start) as isize;
+                    pending.indent = *indent;
+                    pending.insert_extra_newline = *insert_extra_newline;
+                    pending.ranges.push(start..end);
                 }
 
-                let pending = pending_edit.get_or_insert_with(Default::default);
-                pending.delta += text_len as isize - (end - start) as isize;
-                pending.indent = *indent;
-                pending.insert_extra_newline = *insert_extra_newline;
-                pending.ranges.push(start..end);
-            }
+                let pending = pending_edit.unwrap();
+                let mut new_text = String::with_capacity(1 + pending.indent as usize);
+                new_text.push('\n');
+                new_text.extend(iter::repeat(' ').take(pending.indent as usize));
+                if pending.insert_extra_newline {
+                    new_text = new_text.repeat(2);
+                }
+                buffer.edit_with_autoindent(pending.ranges, new_text, cx);
 
-            let pending = pending_edit.unwrap();
-            let mut new_text = String::with_capacity(1 + pending.indent as usize);
-            new_text.push('\n');
-            new_text.extend(iter::repeat(' ').take(pending.indent as usize));
-            if pending.insert_extra_newline {
-                new_text = new_text.repeat(2);
-            }
-            buffer.edit_with_autoindent(pending.ranges, new_text, cx);
+                let buffer = buffer.read(cx);
+                this.selections = this
+                    .selections
+                    .iter()
+                    .cloned()
+                    .zip(old_selections)
+                    .map(
+                        |(mut new_selection, (_, end_anchor, _, _, insert_extra_newline))| {
+                            let mut cursor = end_anchor.to_point(&buffer);
+                            if insert_extra_newline {
+                                cursor.row -= 1;
+                                cursor.column = buffer.line_len(cursor.row);
+                            }
+                            let anchor = buffer.anchor_after(cursor);
+                            new_selection.start = anchor.clone();
+                            new_selection.end = anchor;
+                            new_selection
+                        },
+                    )
+                    .collect();
+            });
 
-            let buffer = buffer.read(cx);
-            self.selections = self
-                .selections
-                .iter()
-                .cloned()
-                .zip(old_selections)
-                .map(
-                    |(mut new_selection, (_, end_anchor, _, _, insert_extra_newline))| {
-                        let mut cursor = end_anchor.to_point(&buffer);
-                        if insert_extra_newline {
-                            cursor.row -= 1;
-                            cursor.column = buffer.line_len(cursor.row);
-                        }
-                        let anchor = buffer.anchor_after(cursor);
-                        new_selection.start = anchor.clone();
-                        new_selection.end = anchor;
-                        new_selection
-                    },
-                )
-                .collect();
+            this.request_autoscroll(Autoscroll::Fit, cx);
         });
 
-        self.request_autoscroll(Autoscroll::Fit, cx);
-        self.end_transaction(cx);
-
         #[derive(Default)]
         struct PendingEdit {
             indent: u32,
@@ -1882,40 +1883,39 @@ impl Editor {
     }
 
     pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-        self.start_transaction(cx);
+        self.transact(cx, |this, cx| {
+            let old_selections = this.local_selections::<usize>(cx);
+            let selection_anchors = this.buffer.update(cx, |buffer, cx| {
+                let anchors = {
+                    let snapshot = buffer.read(cx);
+                    old_selections
+                        .iter()
+                        .map(|s| (s.id, s.goal, snapshot.anchor_after(s.end)))
+                        .collect::<Vec<_>>()
+                };
+                let edit_ranges = old_selections.iter().map(|s| s.start..s.end);
+                buffer.edit_with_autoindent(edit_ranges, text, cx);
+                anchors
+            });
 
-        let old_selections = self.local_selections::<usize>(cx);
-        let selection_anchors = self.buffer.update(cx, |buffer, cx| {
-            let anchors = {
-                let snapshot = buffer.read(cx);
-                old_selections
-                    .iter()
-                    .map(|s| (s.id, s.goal, snapshot.anchor_after(s.end)))
-                    .collect::<Vec<_>>()
+            let selections = {
+                let snapshot = this.buffer.read(cx).read(cx);
+                selection_anchors
+                    .into_iter()
+                    .map(|(id, goal, position)| {
+                        let position = position.to_offset(&snapshot);
+                        Selection {
+                            id,
+                            start: position,
+                            end: position,
+                            goal,
+                            reversed: false,
+                        }
+                    })
+                    .collect()
             };
-            let edit_ranges = old_selections.iter().map(|s| s.start..s.end);
-            buffer.edit_with_autoindent(edit_ranges, text, cx);
-            anchors
+            this.update_selections(selections, Some(Autoscroll::Fit), cx);
         });
-
-        let selections = {
-            let snapshot = self.buffer.read(cx).read(cx);
-            selection_anchors
-                .into_iter()
-                .map(|(id, goal, position)| {
-                    let position = position.to_offset(&snapshot);
-                    Selection {
-                        id,
-                        start: position,
-                        end: position,
-                        goal,
-                        reversed: false,
-                    }
-                })
-                .collect()
-        };
-        self.update_selections(selections, Some(Autoscroll::Fit), cx);
-        self.end_transaction(cx);
     }
 
     fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
@@ -2284,21 +2284,21 @@ impl Editor {
         }
         let text = &text[common_prefix_len..];
 
-        self.start_transaction(cx);
-        if let Some(mut snippet) = snippet {
-            snippet.text = text.to_string();
-            for tabstop in snippet.tabstops.iter_mut().flatten() {
-                tabstop.start -= common_prefix_len as isize;
-                tabstop.end -= common_prefix_len as isize;
-            }
+        self.transact(cx, |this, cx| {
+            if let Some(mut snippet) = snippet {
+                snippet.text = text.to_string();
+                for tabstop in snippet.tabstops.iter_mut().flatten() {
+                    tabstop.start -= common_prefix_len as isize;
+                    tabstop.end -= common_prefix_len as isize;
+                }
 
-            self.insert_snippet(&ranges, snippet, cx).log_err();
-        } else {
-            self.buffer.update(cx, |buffer, cx| {
-                buffer.edit_with_autoindent(ranges, text, cx);
-            });
-        }
-        self.end_transaction(cx);
+                this.insert_snippet(&ranges, snippet, cx).log_err();
+            } else {
+                this.buffer.update(cx, |buffer, cx| {
+                    buffer.edit_with_autoindent(ranges, text, cx);
+                });
+            }
+        });
 
         let project = self.project.clone()?;
         let apply_edits = project.update(cx, |project, cx| {
@@ -2397,7 +2397,7 @@ impl Editor {
     ) -> Result<()> {
         let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx));
 
-        // If the code action's edits are all contained within this editor, then
+        // If the project transaction's edits are all contained within this editor, then
         // avoid opening a new editor to display them.
         let mut entries = transaction.0.iter();
         if let Some((buffer, transaction)) = entries.next() {
@@ -2519,7 +2519,6 @@ impl Editor {
                     }
 
                     let buffer_id = cursor_position.buffer_id;
-                    let excerpt_id = cursor_position.excerpt_id.clone();
                     let style = this.style(cx);
                     let read_background = style.document_highlight_read_background;
                     let write_background = style.document_highlight_write_background;
@@ -2531,22 +2530,39 @@ impl Editor {
                         return;
                     }
 
+                    let cursor_buffer_snapshot = cursor_buffer.read(cx);
                     let mut write_ranges = Vec::new();
                     let mut read_ranges = Vec::new();
                     for highlight in highlights {
-                        let range = Anchor {
-                            buffer_id,
-                            excerpt_id: excerpt_id.clone(),
-                            text_anchor: highlight.range.start,
-                        }..Anchor {
-                            buffer_id,
-                            excerpt_id: excerpt_id.clone(),
-                            text_anchor: highlight.range.end,
-                        };
-                        if highlight.kind == lsp::DocumentHighlightKind::WRITE {
-                            write_ranges.push(range);
-                        } else {
-                            read_ranges.push(range);
+                        for (excerpt_id, excerpt_range) in
+                            buffer.excerpts_for_buffer(&cursor_buffer, cx)
+                        {
+                            let start = highlight
+                                .range
+                                .start
+                                .max(&excerpt_range.start, cursor_buffer_snapshot);
+                            let end = highlight
+                                .range
+                                .end
+                                .min(&excerpt_range.end, cursor_buffer_snapshot);
+                            if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
+                                continue;
+                            }
+
+                            let range = Anchor {
+                                buffer_id,
+                                excerpt_id: excerpt_id.clone(),
+                                text_anchor: start,
+                            }..Anchor {
+                                buffer_id,
+                                excerpt_id,
+                                text_anchor: end,
+                            };
+                            if highlight.kind == lsp::DocumentHighlightKind::WRITE {
+                                write_ranges.push(range);
+                            } else {
+                                read_ranges.push(range);
+                            }
                         }
                     }
 
@@ -2656,8 +2672,7 @@ impl Editor {
                             })
                         })
                         .collect::<Vec<_>>();
-                    tabstop_ranges
-                        .sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot).unwrap());
+                    tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
                     tabstop_ranges
                 })
                 .collect::<Vec<_>>()
@@ -2732,14 +2747,13 @@ impl Editor {
     }
 
     pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
-        self.start_transaction(cx);
-        self.select_all(&SelectAll, cx);
-        self.insert("", cx);
-        self.end_transaction(cx);
+        self.transact(cx, |this, cx| {
+            this.select_all(&SelectAll, cx);
+            this.insert("", cx);
+        });
     }
 
     pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
-        self.start_transaction(cx);
         let mut selections = self.local_selections::<Point>(cx);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         for selection in &mut selections {
@@ -2765,9 +2779,11 @@ impl Editor {
                 selection.set_head(new_head, SelectionGoal::None);
             }
         }
-        self.update_selections(selections, Some(Autoscroll::Fit), cx);
-        self.insert("", cx);
-        self.end_transaction(cx);
+
+        self.transact(cx, |this, cx| {
+            this.update_selections(selections, Some(Autoscroll::Fit), cx);
+            this.insert("", cx);
+        });
     }
 
     pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
@@ -2787,75 +2803,76 @@ impl Editor {
             return;
         }
 
-        self.start_transaction(cx);
         let tab_size = cx.global::<Settings>().tab_size;
         let mut selections = self.local_selections::<Point>(cx);
-        let mut last_indent = None;
-        self.buffer.update(cx, |buffer, cx| {
-            for selection in &mut selections {
-                if selection.is_empty() {
-                    let char_column = buffer
-                        .read(cx)
-                        .text_for_range(Point::new(selection.start.row, 0)..selection.start)
-                        .flat_map(str::chars)
-                        .count();
-                    let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
-                    buffer.edit(
-                        [selection.start..selection.start],
-                        " ".repeat(chars_to_next_tab_stop),
-                        cx,
-                    );
-                    selection.start.column += chars_to_next_tab_stop as u32;
-                    selection.end = selection.start;
-                } else {
-                    let mut start_row = selection.start.row;
-                    let mut end_row = selection.end.row + 1;
-
-                    // If a selection ends at the beginning of a line, don't indent
-                    // that last line.
-                    if selection.end.column == 0 {
-                        end_row -= 1;
-                    }
-
-                    // Avoid re-indenting a row that has already been indented by a
-                    // previous selection, but still update this selection's column
-                    // to reflect that indentation.
-                    if let Some((last_indent_row, last_indent_len)) = last_indent {
-                        if last_indent_row == selection.start.row {
-                            selection.start.column += last_indent_len;
-                            start_row += 1;
-                        }
-                        if last_indent_row == selection.end.row {
-                            selection.end.column += last_indent_len;
-                        }
-                    }
-
-                    for row in start_row..end_row {
-                        let indent_column = buffer.read(cx).indent_column_for_line(row) as usize;
-                        let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
-                        let row_start = Point::new(row, 0);
+        self.transact(cx, |this, cx| {
+            let mut last_indent = None;
+            this.buffer.update(cx, |buffer, cx| {
+                for selection in &mut selections {
+                    if selection.is_empty() {
+                        let char_column = buffer
+                            .read(cx)
+                            .text_for_range(Point::new(selection.start.row, 0)..selection.start)
+                            .flat_map(str::chars)
+                            .count();
+                        let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
                         buffer.edit(
-                            [row_start..row_start],
-                            " ".repeat(columns_to_next_tab_stop),
+                            [selection.start..selection.start],
+                            " ".repeat(chars_to_next_tab_stop),
                             cx,
                         );
+                        selection.start.column += chars_to_next_tab_stop as u32;
+                        selection.end = selection.start;
+                    } else {
+                        let mut start_row = selection.start.row;
+                        let mut end_row = selection.end.row + 1;
 
-                        // Update this selection's endpoints to reflect the indentation.
-                        if row == selection.start.row {
-                            selection.start.column += columns_to_next_tab_stop as u32;
+                        // If a selection ends at the beginning of a line, don't indent
+                        // that last line.
+                        if selection.end.column == 0 {
+                            end_row -= 1;
                         }
-                        if row == selection.end.row {
-                            selection.end.column += columns_to_next_tab_stop as u32;
+
+                        // Avoid re-indenting a row that has already been indented by a
+                        // previous selection, but still update this selection's column
+                        // to reflect that indentation.
+                        if let Some((last_indent_row, last_indent_len)) = last_indent {
+                            if last_indent_row == selection.start.row {
+                                selection.start.column += last_indent_len;
+                                start_row += 1;
+                            }
+                            if last_indent_row == selection.end.row {
+                                selection.end.column += last_indent_len;
+                            }
                         }
 
-                        last_indent = Some((row, columns_to_next_tab_stop as u32));
+                        for row in start_row..end_row {
+                            let indent_column =
+                                buffer.read(cx).indent_column_for_line(row) as usize;
+                            let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
+                            let row_start = Point::new(row, 0);
+                            buffer.edit(
+                                [row_start..row_start],
+                                " ".repeat(columns_to_next_tab_stop),
+                                cx,
+                            );
+
+                            // Update this selection's endpoints to reflect the indentation.
+                            if row == selection.start.row {
+                                selection.start.column += columns_to_next_tab_stop as u32;
+                            }
+                            if row == selection.end.row {
+                                selection.end.column += columns_to_next_tab_stop as u32;
+                            }
+
+                            last_indent = Some((row, columns_to_next_tab_stop as u32));
+                        }
                     }
                 }
-            }
-        });
+            });
 
-        self.update_selections(selections, Some(Autoscroll::Fit), cx);
-        self.end_transaction(cx);
+            this.update_selections(selections, Some(Autoscroll::Fit), cx);
+        });
     }
 
     pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
@@ -2864,7 +2881,6 @@ impl Editor {
             return;
         }
 
-        self.start_transaction(cx);
         let tab_size = cx.global::<Settings>().tab_size;
         let selections = self.local_selections::<Point>(cx);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -2896,21 +2912,20 @@ impl Editor {
                 }
             }
         }
-        self.buffer.update(cx, |buffer, cx| {
-            buffer.edit(deletion_ranges, "", cx);
-        });
 
-        self.update_selections(
-            self.local_selections::<usize>(cx),
-            Some(Autoscroll::Fit),
-            cx,
-        );
-        self.end_transaction(cx);
+        self.transact(cx, |this, cx| {
+            this.buffer.update(cx, |buffer, cx| {
+                buffer.edit(deletion_ranges, "", cx);
+            });
+            this.update_selections(
+                this.local_selections::<usize>(cx),
+                Some(Autoscroll::Fit),
+                cx,
+            );
+        });
     }
 
     pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
-        self.start_transaction(cx);
-
         let selections = self.local_selections::<Point>(cx);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx).snapshot(cx);
@@ -2960,30 +2975,29 @@ impl Editor {
             edit_ranges.push(edit_start..edit_end);
         }
 
-        let buffer = self.buffer.update(cx, |buffer, cx| {
-            buffer.edit(edit_ranges, "", cx);
-            buffer.snapshot(cx)
+        self.transact(cx, |this, cx| {
+            let buffer = this.buffer.update(cx, |buffer, cx| {
+                buffer.edit(edit_ranges, "", cx);
+                buffer.snapshot(cx)
+            });
+            let new_selections = new_cursors
+                .into_iter()
+                .map(|(id, cursor)| {
+                    let cursor = cursor.to_point(&buffer);
+                    Selection {
+                        id,
+                        start: cursor,
+                        end: cursor,
+                        reversed: false,
+                        goal: SelectionGoal::None,
+                    }
+                })
+                .collect();
+            this.update_selections(new_selections, Some(Autoscroll::Fit), cx);
         });
-        let new_selections = new_cursors
-            .into_iter()
-            .map(|(id, cursor)| {
-                let cursor = cursor.to_point(&buffer);
-                Selection {
-                    id,
-                    start: cursor,
-                    end: cursor,
-                    reversed: false,
-                    goal: SelectionGoal::None,
-                }
-            })
-            .collect();
-        self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
-        self.end_transaction(cx);
     }
 
     pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
-        self.start_transaction(cx);
-
         let selections = self.local_selections::<Point>(cx);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = &display_map.buffer_snapshot;
@@ -3014,14 +3028,15 @@ impl Editor {
             edits.push((start, text, rows.len() as u32));
         }
 
-        self.buffer.update(cx, |buffer, cx| {
-            for (point, text, _) in edits.into_iter().rev() {
-                buffer.edit(Some(point..point), text, cx);
-            }
-        });
+        self.transact(cx, |this, cx| {
+            this.buffer.update(cx, |buffer, cx| {
+                for (point, text, _) in edits.into_iter().rev() {
+                    buffer.edit(Some(point..point), text, cx);
+                }
+            });
 
-        self.request_autoscroll(Autoscroll::Fit, cx);
-        self.end_transaction(cx);
+            this.request_autoscroll(Autoscroll::Fit, cx);
+        });
     }
 
     pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
@@ -3122,16 +3137,16 @@ impl Editor {
             new_selections.extend(contiguous_row_selections.drain(..));
         }
 
-        self.start_transaction(cx);
-        self.unfold_ranges(unfold_ranges, true, cx);
-        self.buffer.update(cx, |buffer, cx| {
-            for (range, text) in edits {
-                buffer.edit([range], text, cx);
-            }
+        self.transact(cx, |this, cx| {
+            this.unfold_ranges(unfold_ranges, true, cx);
+            this.buffer.update(cx, |buffer, cx| {
+                for (range, text) in edits {
+                    buffer.edit([range], text, cx);
+                }
+            });
+            this.fold_ranges(refold_ranges, cx);
+            this.update_selections(new_selections, Some(Autoscroll::Fit), cx);
         });
-        self.fold_ranges(refold_ranges, cx);
-        self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
-        self.end_transaction(cx);
     }
 
     pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
@@ -3225,20 +3240,19 @@ impl Editor {
             new_selections.extend(contiguous_row_selections.drain(..));
         }
 
-        self.start_transaction(cx);
-        self.unfold_ranges(unfold_ranges, true, cx);
-        self.buffer.update(cx, |buffer, cx| {
-            for (range, text) in edits {
-                buffer.edit([range], text, cx);
-            }
+        self.transact(cx, |this, cx| {
+            this.unfold_ranges(unfold_ranges, true, cx);
+            this.buffer.update(cx, |buffer, cx| {
+                for (range, text) in edits {
+                    buffer.edit([range], text, cx);
+                }
+            });
+            this.fold_ranges(refold_ranges, cx);
+            this.update_selections(new_selections, Some(Autoscroll::Fit), cx);
         });
-        self.fold_ranges(refold_ranges, cx);
-        self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
-        self.end_transaction(cx);
     }
 
     pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
-        self.start_transaction(cx);
         let mut text = String::new();
         let mut selections = self.local_selections::<Point>(cx);
         let mut clipboard_selections = Vec::with_capacity(selections.len());
@@ -3263,12 +3277,12 @@ impl Editor {
                 });
             }
         }
-        self.update_selections(selections, Some(Autoscroll::Fit), cx);
-        self.insert("", cx);
-        self.end_transaction(cx);
 
-        cx.as_mut()
-            .write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
+        self.transact(cx, |this, cx| {
+            this.update_selections(selections, Some(Autoscroll::Fit), cx);
+            this.insert("", cx);
+            cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
+        });
     }
 
     pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
@@ -3298,63 +3312,65 @@ impl Editor {
             }
         }
 
-        cx.as_mut()
-            .write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
+        cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
     }
 
     pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
-        if let Some(item) = cx.as_mut().read_from_clipboard() {
-            let clipboard_text = item.text();
-            if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
-                let mut selections = self.local_selections::<usize>(cx);
-                let all_selections_were_entire_line =
-                    clipboard_selections.iter().all(|s| s.is_entire_line);
-                if clipboard_selections.len() != selections.len() {
-                    clipboard_selections.clear();
-                }
-
-                let mut delta = 0_isize;
-                let mut start_offset = 0;
-                for (i, selection) in selections.iter_mut().enumerate() {
-                    let to_insert;
-                    let entire_line;
-                    if let Some(clipboard_selection) = clipboard_selections.get(i) {
-                        let end_offset = start_offset + clipboard_selection.len;
-                        to_insert = &clipboard_text[start_offset..end_offset];
-                        entire_line = clipboard_selection.is_entire_line;
-                        start_offset = end_offset
-                    } else {
-                        to_insert = clipboard_text.as_str();
-                        entire_line = all_selections_were_entire_line;
+        self.transact(cx, |this, cx| {
+            if let Some(item) = cx.as_mut().read_from_clipboard() {
+                let clipboard_text = item.text();
+                if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
+                    let mut selections = this.local_selections::<usize>(cx);
+                    let all_selections_were_entire_line =
+                        clipboard_selections.iter().all(|s| s.is_entire_line);
+                    if clipboard_selections.len() != selections.len() {
+                        clipboard_selections.clear();
                     }
 
-                    selection.start = (selection.start as isize + delta) as usize;
-                    selection.end = (selection.end as isize + delta) as usize;
-
-                    self.buffer.update(cx, |buffer, cx| {
-                        // If the corresponding selection was empty when this slice of the
-                        // clipboard text was written, then the entire line containing the
-                        // selection was copied. If this selection is also currently empty,
-                        // then paste the line before the current line of the buffer.
-                        let range = if selection.is_empty() && entire_line {
-                            let column = selection.start.to_point(&buffer.read(cx)).column as usize;
-                            let line_start = selection.start - column;
-                            line_start..line_start
+                    let mut delta = 0_isize;
+                    let mut start_offset = 0;
+                    for (i, selection) in selections.iter_mut().enumerate() {
+                        let to_insert;
+                        let entire_line;
+                        if let Some(clipboard_selection) = clipboard_selections.get(i) {
+                            let end_offset = start_offset + clipboard_selection.len;
+                            to_insert = &clipboard_text[start_offset..end_offset];
+                            entire_line = clipboard_selection.is_entire_line;
+                            start_offset = end_offset
                         } else {
-                            selection.start..selection.end
-                        };
+                            to_insert = clipboard_text.as_str();
+                            entire_line = all_selections_were_entire_line;
+                        }
 
-                        delta += to_insert.len() as isize - range.len() as isize;
-                        buffer.edit([range], to_insert, cx);
-                        selection.start += to_insert.len();
-                        selection.end = selection.start;
-                    });
+                        selection.start = (selection.start as isize + delta) as usize;
+                        selection.end = (selection.end as isize + delta) as usize;
+
+                        this.buffer.update(cx, |buffer, cx| {
+                            // If the corresponding selection was empty when this slice of the
+                            // clipboard text was written, then the entire line containing the
+                            // selection was copied. If this selection is also currently empty,
+                            // then paste the line before the current line of the buffer.
+                            let range = if selection.is_empty() && entire_line {
+                                let column =
+                                    selection.start.to_point(&buffer.read(cx)).column as usize;
+                                let line_start = selection.start - column;
+                                line_start..line_start
+                            } else {
+                                selection.start..selection.end
+                            };
+
+                            delta += to_insert.len() as isize - range.len() as isize;
+                            buffer.edit([range], to_insert, cx);
+                            selection.start += to_insert.len();
+                            selection.end = selection.start;
+                        });
+                    }
+                    this.update_selections(selections, Some(Autoscroll::Fit), cx);
+                } else {
+                    this.insert(clipboard_text, cx);
                 }
-                self.update_selections(selections, Some(Autoscroll::Fit), cx);
-            } else {
-                self.insert(clipboard_text, cx);
             }
-        }
+        });
     }
 
     pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
@@ -3363,6 +3379,7 @@ impl Editor {
                 self.set_selections(selections, None, true, cx);
             }
             self.request_autoscroll(Autoscroll::Fit, cx);
+            cx.emit(Event::Edited);
         }
     }
 
@@ -3372,6 +3389,7 @@ impl Editor {
                 self.set_selections(selections, None, true, cx);
             }
             self.request_autoscroll(Autoscroll::Fit, cx);
+            cx.emit(Event::Edited);
         }
     }
 
@@ -3964,90 +3982,94 @@ impl Editor {
         let comment_prefix = full_comment_prefix.trim_end_matches(' ');
         let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
 
-        self.start_transaction(cx);
-        let mut selections = self.local_selections::<Point>(cx);
-        let mut all_selection_lines_are_comments = true;
-        let mut edit_ranges = Vec::new();
-        let mut last_toggled_row = None;
-        self.buffer.update(cx, |buffer, cx| {
-            for selection in &mut selections {
-                edit_ranges.clear();
-                let snapshot = buffer.snapshot(cx);
+        self.transact(cx, |this, cx| {
+            let mut selections = this.local_selections::<Point>(cx);
+            let mut all_selection_lines_are_comments = true;
+            let mut edit_ranges = Vec::new();
+            let mut last_toggled_row = None;
+            this.buffer.update(cx, |buffer, cx| {
+                for selection in &mut selections {
+                    edit_ranges.clear();
+                    let snapshot = buffer.snapshot(cx);
 
-                let end_row =
-                    if selection.end.row > selection.start.row && selection.end.column == 0 {
-                        selection.end.row
-                    } else {
-                        selection.end.row + 1
-                    };
+                    let end_row =
+                        if selection.end.row > selection.start.row && selection.end.column == 0 {
+                            selection.end.row
+                        } else {
+                            selection.end.row + 1
+                        };
 
-                for row in selection.start.row..end_row {
-                    // If multiple selections contain a given row, avoid processing that
-                    // row more than once.
-                    if last_toggled_row == Some(row) {
-                        continue;
-                    } else {
-                        last_toggled_row = Some(row);
-                    }
+                    for row in selection.start.row..end_row {
+                        // If multiple selections contain a given row, avoid processing that
+                        // row more than once.
+                        if last_toggled_row == Some(row) {
+                            continue;
+                        } else {
+                            last_toggled_row = Some(row);
+                        }
 
-                    if snapshot.is_line_blank(row) {
-                        continue;
-                    }
+                        if snapshot.is_line_blank(row) {
+                            continue;
+                        }
 
-                    let start = Point::new(row, snapshot.indent_column_for_line(row));
-                    let mut line_bytes = snapshot
-                        .bytes_in_range(start..snapshot.max_point())
-                        .flatten()
-                        .copied();
-
-                    // If this line currently begins with the line comment prefix, then record
-                    // the range containing the prefix.
-                    if all_selection_lines_are_comments
-                        && line_bytes
-                            .by_ref()
-                            .take(comment_prefix.len())
-                            .eq(comment_prefix.bytes())
-                    {
-                        // Include any whitespace that matches the comment prefix.
-                        let matching_whitespace_len = line_bytes
-                            .zip(comment_prefix_whitespace.bytes())
-                            .take_while(|(a, b)| a == b)
-                            .count() as u32;
-                        let end = Point::new(
-                            row,
-                            start.column + comment_prefix.len() as u32 + matching_whitespace_len,
-                        );
-                        edit_ranges.push(start..end);
-                    }
-                    // If this line does not begin with the line comment prefix, then record
-                    // the position where the prefix should be inserted.
-                    else {
-                        all_selection_lines_are_comments = false;
-                        edit_ranges.push(start..start);
+                        let start = Point::new(row, snapshot.indent_column_for_line(row));
+                        let mut line_bytes = snapshot
+                            .bytes_in_range(start..snapshot.max_point())
+                            .flatten()
+                            .copied();
+
+                        // If this line currently begins with the line comment prefix, then record
+                        // the range containing the prefix.
+                        if all_selection_lines_are_comments
+                            && line_bytes
+                                .by_ref()
+                                .take(comment_prefix.len())
+                                .eq(comment_prefix.bytes())
+                        {
+                            // Include any whitespace that matches the comment prefix.
+                            let matching_whitespace_len = line_bytes
+                                .zip(comment_prefix_whitespace.bytes())
+                                .take_while(|(a, b)| a == b)
+                                .count()
+                                as u32;
+                            let end = Point::new(
+                                row,
+                                start.column
+                                    + comment_prefix.len() as u32
+                                    + matching_whitespace_len,
+                            );
+                            edit_ranges.push(start..end);
+                        }
+                        // If this line does not begin with the line comment prefix, then record
+                        // the position where the prefix should be inserted.
+                        else {
+                            all_selection_lines_are_comments = false;
+                            edit_ranges.push(start..start);
+                        }
                     }
-                }
 
-                if !edit_ranges.is_empty() {
-                    if all_selection_lines_are_comments {
-                        buffer.edit(edit_ranges.iter().cloned(), "", cx);
-                    } else {
-                        let min_column = edit_ranges.iter().map(|r| r.start.column).min().unwrap();
-                        let edit_ranges = edit_ranges.iter().map(|range| {
-                            let position = Point::new(range.start.row, min_column);
-                            position..position
-                        });
-                        buffer.edit(edit_ranges, &full_comment_prefix, cx);
+                    if !edit_ranges.is_empty() {
+                        if all_selection_lines_are_comments {
+                            buffer.edit(edit_ranges.iter().cloned(), "", cx);
+                        } else {
+                            let min_column =
+                                edit_ranges.iter().map(|r| r.start.column).min().unwrap();
+                            let edit_ranges = edit_ranges.iter().map(|range| {
+                                let position = Point::new(range.start.row, min_column);
+                                position..position
+                            });
+                            buffer.edit(edit_ranges, &full_comment_prefix, cx);
+                        }
                     }
                 }
-            }
-        });
+            });
 
-        self.update_selections(
-            self.local_selections::<usize>(cx),
-            Some(Autoscroll::Fit),
-            cx,
-        );
-        self.end_transaction(cx);
+            this.update_selections(
+                this.local_selections::<usize>(cx),
+                Some(Autoscroll::Fit),
+                cx,
+            );
+        });
     }
 
     pub fn select_larger_syntax_node(

crates/editor/src/items.rs 🔗

@@ -198,7 +198,7 @@ impl FollowableItem for Editor {
 
     fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool {
         match event {
-            Event::Edited { local } => *local,
+            Event::Edited => true,
             Event::SelectionsChanged { local } => *local,
             Event::ScrollPositionChanged { local } => *local,
             _ => false,

crates/editor/src/multi_buffer.rs 🔗

@@ -211,7 +211,7 @@ impl MultiBuffer {
     pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
         let mut this = Self::new(buffer.read(cx).replica_id());
         this.singleton = true;
-        this.push_excerpts(buffer, [text::Anchor::min()..text::Anchor::max()], cx);
+        this.push_excerpts(buffer, [text::Anchor::MIN..text::Anchor::MAX], cx);
         this.snapshot.borrow_mut().singleton = true;
         this
     }
@@ -522,24 +522,14 @@ impl MultiBuffer {
             self.buffers.borrow()[&buffer_id]
                 .buffer
                 .update(cx, |buffer, cx| {
-                    selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap());
+                    selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer));
                     let mut selections = selections.into_iter().peekable();
                     let merged_selections = Arc::from_iter(iter::from_fn(|| {
                         let mut selection = selections.next()?;
                         while let Some(next_selection) = selections.peek() {
-                            if selection
-                                .end
-                                .cmp(&next_selection.start, buffer)
-                                .unwrap()
-                                .is_ge()
-                            {
+                            if selection.end.cmp(&next_selection.start, buffer).is_ge() {
                                 let next_selection = selections.next().unwrap();
-                                if next_selection
-                                    .end
-                                    .cmp(&selection.end, buffer)
-                                    .unwrap()
-                                    .is_ge()
-                                {
+                                if next_selection.end.cmp(&selection.end, buffer).is_ge() {
                                     selection.end = next_selection.end;
                                 }
                             } else {
@@ -814,11 +804,30 @@ impl MultiBuffer {
         cx.notify();
     }
 
-    pub fn excerpt_ids_for_buffer(&self, buffer: &ModelHandle<Buffer>) -> Vec<ExcerptId> {
-        self.buffers
-            .borrow()
+    pub fn excerpts_for_buffer(
+        &self,
+        buffer: &ModelHandle<Buffer>,
+        cx: &AppContext,
+    ) -> Vec<(ExcerptId, Range<text::Anchor>)> {
+        let mut excerpts = Vec::new();
+        let snapshot = self.read(cx);
+        let buffers = self.buffers.borrow();
+        let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
+        for excerpt_id in buffers
             .get(&buffer.id())
-            .map_or(Vec::new(), |state| state.excerpts.clone())
+            .map(|state| &state.excerpts)
+            .into_iter()
+            .flatten()
+        {
+            cursor.seek_forward(&Some(excerpt_id), Bias::Left, &());
+            if let Some(excerpt) = cursor.item() {
+                if excerpt.id == *excerpt_id {
+                    excerpts.push((excerpt.id.clone(), excerpt.range.clone()));
+                }
+            }
+        }
+
+        excerpts
     }
 
     pub fn excerpt_ids(&self) -> Vec<ExcerptId> {
@@ -1917,11 +1926,7 @@ impl MultiBufferSnapshot {
                             .range
                             .start
                             .bias(anchor.text_anchor.bias, &excerpt.buffer);
-                        if text_anchor
-                            .cmp(&excerpt.range.end, &excerpt.buffer)
-                            .unwrap()
-                            .is_gt()
-                        {
+                        if text_anchor.cmp(&excerpt.range.end, &excerpt.buffer).is_gt() {
                             text_anchor = excerpt.range.end.clone();
                         }
                         Anchor {
@@ -1936,7 +1941,6 @@ impl MultiBufferSnapshot {
                             .bias(anchor.text_anchor.bias, &excerpt.buffer);
                         if text_anchor
                             .cmp(&excerpt.range.start, &excerpt.buffer)
-                            .unwrap()
                             .is_lt()
                         {
                             text_anchor = excerpt.range.start.clone();
@@ -1956,7 +1960,7 @@ impl MultiBufferSnapshot {
                 result.push((anchor_ix, anchor, kept_position));
             }
         }
-        result.sort_unstable_by(|a, b| a.1.cmp(&b.1, self).unwrap());
+        result.sort_unstable_by(|a, b| a.1.cmp(&b.1, self));
         result
     }
 
@@ -2303,10 +2307,10 @@ impl MultiBufferSnapshot {
                                 excerpt_id: excerpt.id.clone(),
                                 text_anchor: selection.end.clone(),
                             };
-                            if range.start.cmp(&start, self).unwrap().is_gt() {
+                            if range.start.cmp(&start, self).is_gt() {
                                 start = range.start.clone();
                             }
-                            if range.end.cmp(&end, self).unwrap().is_lt() {
+                            if range.end.cmp(&end, self).is_lt() {
                                 end = range.end.clone();
                             }
 
@@ -2530,17 +2534,9 @@ impl Excerpt {
     }
 
     fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor {
-        if text_anchor
-            .cmp(&self.range.start, &self.buffer)
-            .unwrap()
-            .is_lt()
-        {
+        if text_anchor.cmp(&self.range.start, &self.buffer).is_lt() {
             self.range.start.clone()
-        } else if text_anchor
-            .cmp(&self.range.end, &self.buffer)
-            .unwrap()
-            .is_gt()
-        {
+        } else if text_anchor.cmp(&self.range.end, &self.buffer).is_gt() {
             self.range.end.clone()
         } else {
             text_anchor
@@ -2553,13 +2549,11 @@ impl Excerpt {
                 .range
                 .start
                 .cmp(&anchor.text_anchor, &self.buffer)
-                .unwrap()
                 .is_le()
             && self
                 .range
                 .end
                 .cmp(&anchor.text_anchor, &self.buffer)
-                .unwrap()
                 .is_ge()
     }
 }
@@ -3070,7 +3064,8 @@ mod tests {
         );
 
         let snapshot = multibuffer.update(cx, |multibuffer, cx| {
-            let buffer_2_excerpt_id = multibuffer.excerpt_ids_for_buffer(&buffer_2)[0].clone();
+            let (buffer_2_excerpt_id, _) =
+                multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone();
             multibuffer.remove_excerpts(&[buffer_2_excerpt_id], cx);
             multibuffer.snapshot(cx)
         });
@@ -3365,7 +3360,7 @@ mod tests {
                     let bias = if rng.gen() { Bias::Left } else { Bias::Right };
                     log::info!("Creating anchor at {} with bias {:?}", offset, bias);
                     anchors.push(multibuffer.anchor_at(offset, bias));
-                    anchors.sort_by(|a, b| a.cmp(&b, &multibuffer).unwrap());
+                    anchors.sort_by(|a, b| a.cmp(&b, &multibuffer));
                 }
                 40..=44 if !anchors.is_empty() => {
                     let multibuffer = multibuffer.read(cx).read(cx);

crates/editor/src/multi_buffer/anchor.rs 🔗

@@ -1,5 +1,4 @@
 use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
-use anyhow::Result;
 use std::{
     cmp::Ordering,
     ops::{Range, Sub},
@@ -19,7 +18,7 @@ impl Anchor {
         Self {
             buffer_id: None,
             excerpt_id: ExcerptId::min(),
-            text_anchor: text::Anchor::min(),
+            text_anchor: text::Anchor::MIN,
         }
     }
 
@@ -27,7 +26,7 @@ impl Anchor {
         Self {
             buffer_id: None,
             excerpt_id: ExcerptId::max(),
-            text_anchor: text::Anchor::max(),
+            text_anchor: text::Anchor::MAX,
         }
     }
 
@@ -35,18 +34,18 @@ impl Anchor {
         &self.excerpt_id
     }
 
-    pub fn cmp<'a>(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Result<Ordering> {
+    pub fn cmp<'a>(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
         let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id);
         if excerpt_id_cmp.is_eq() {
             if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
-                Ok(Ordering::Equal)
+                Ordering::Equal
             } else if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) {
                 self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
             } else {
-                Ok(Ordering::Equal)
+                Ordering::Equal
             }
         } else {
-            Ok(excerpt_id_cmp)
+            excerpt_id_cmp
         }
     }
 
@@ -97,17 +96,17 @@ impl ToPoint for Anchor {
 }
 
 pub trait AnchorRangeExt {
-    fn cmp(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Result<Ordering>;
+    fn cmp(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
     fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
     fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
 }
 
 impl AnchorRangeExt for Range<Anchor> {
-    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Result<Ordering> {
-        Ok(match self.start.cmp(&other.start, buffer)? {
-            Ordering::Equal => other.end.cmp(&self.end, buffer)?,
+    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
+        match self.start.cmp(&other.start, buffer) {
+            Ordering::Equal => other.end.cmp(&self.end, buffer),
             ord @ _ => ord,
-        })
+        }
     }
 
     fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize> {

crates/editor/src/test.rs 🔗

@@ -1,3 +1,5 @@
+use std::ops::Range;
+
 use collections::HashMap;
 
 #[cfg(test)]
@@ -31,3 +33,24 @@ pub fn marked_text(marked_text: &str) -> (String, Vec<usize>) {
     let (unmarked_text, mut markers) = marked_text_by(marked_text, vec!['|']);
     (unmarked_text, markers.remove(&'|').unwrap_or_else(Vec::new))
 }
+
+pub fn marked_text_ranges(
+    marked_text: &str,
+    range_markers: Vec<(char, char)>,
+) -> (String, Vec<Range<usize>>) {
+    let mut marker_chars = Vec::new();
+    for (start, end) in range_markers.iter() {
+        marker_chars.push(*start);
+        marker_chars.push(*end);
+    }
+    let (unmarked_text, markers) = marked_text_by(marked_text, marker_chars);
+    let ranges = range_markers
+        .iter()
+        .map(|(start_marker, end_marker)| {
+            let start = markers.get(start_marker).unwrap()[0];
+            let end = markers.get(end_marker).unwrap()[0];
+            start..end
+        })
+        .collect();
+    (unmarked_text, ranges)
+}

crates/file_finder/src/file_finder.rs 🔗

@@ -291,7 +291,7 @@ impl FileFinder {
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            editor::Event::Edited { .. } => {
+            editor::Event::BufferEdited { .. } => {
                 let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
                 if query.is_empty() {
                     self.latest_search_id = post_inc(&mut self.search_count);

crates/go_to_line/src/go_to_line.rs 🔗

@@ -102,7 +102,7 @@ impl GoToLine {
     ) {
         match event {
             editor::Event::Blurred => cx.emit(Event::Dismissed),
-            editor::Event::Edited { .. } => {
+            editor::Event::BufferEdited { .. } => {
                 let line_editor = self.line_editor.read(cx).buffer().read(cx).read(cx).text();
                 let mut components = line_editor.trim().split(&[',', ':'][..]);
                 let row = components.next().and_then(|row| row.parse::<u32>().ok());

crates/language/src/buffer.rs 🔗

@@ -142,7 +142,7 @@ pub enum Operation {
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Event {
     Operation(Operation),
-    Edited { local: bool },
+    Edited,
     Dirtied,
     Saved,
     FileHandleChanged,
@@ -967,7 +967,7 @@ impl Buffer {
     ) -> Option<TransactionId> {
         if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) {
             let was_dirty = start_version != self.saved_version;
-            self.did_edit(&start_version, was_dirty, true, cx);
+            self.did_edit(&start_version, was_dirty, cx);
             Some(transaction_id)
         } else {
             None
@@ -1160,7 +1160,6 @@ impl Buffer {
         &mut self,
         old_version: &clock::Global,
         was_dirty: bool,
-        local: bool,
         cx: &mut ModelContext<Self>,
     ) {
         if self.edits_since::<usize>(old_version).next().is_none() {
@@ -1169,7 +1168,7 @@ impl Buffer {
 
         self.reparse(cx);
 
-        cx.emit(Event::Edited { local });
+        cx.emit(Event::Edited);
         if !was_dirty {
             cx.emit(Event::Dirtied);
         }
@@ -1206,7 +1205,7 @@ impl Buffer {
         self.text.apply_ops(buffer_ops)?;
         self.deferred_ops.insert(deferred_ops);
         self.flush_deferred_ops(cx);
-        self.did_edit(&old_version, was_dirty, false, cx);
+        self.did_edit(&old_version, was_dirty, cx);
         // Notify independently of whether the buffer was edited as the operations could include a
         // selection update.
         cx.notify();
@@ -1321,7 +1320,7 @@ impl Buffer {
 
         if let Some((transaction_id, operation)) = self.text.undo() {
             self.send_operation(Operation::Buffer(operation), cx);
-            self.did_edit(&old_version, was_dirty, true, cx);
+            self.did_edit(&old_version, was_dirty, cx);
             Some(transaction_id)
         } else {
             None
@@ -1342,7 +1341,7 @@ impl Buffer {
             self.send_operation(Operation::Buffer(operation), cx);
         }
         if undone {
-            self.did_edit(&old_version, was_dirty, true, cx)
+            self.did_edit(&old_version, was_dirty, cx)
         }
         undone
     }
@@ -1353,7 +1352,7 @@ impl Buffer {
 
         if let Some((transaction_id, operation)) = self.text.redo() {
             self.send_operation(Operation::Buffer(operation), cx);
-            self.did_edit(&old_version, was_dirty, true, cx);
+            self.did_edit(&old_version, was_dirty, cx);
             Some(transaction_id)
         } else {
             None
@@ -1374,7 +1373,7 @@ impl Buffer {
             self.send_operation(Operation::Buffer(operation), cx);
         }
         if redone {
-            self.did_edit(&old_version, was_dirty, true, cx)
+            self.did_edit(&old_version, was_dirty, cx)
         }
         redone
     }
@@ -1440,7 +1439,7 @@ impl Buffer {
         if !ops.is_empty() {
             for op in ops {
                 self.send_operation(Operation::Buffer(op), cx);
-                self.did_edit(&old_version, was_dirty, true, cx);
+                self.did_edit(&old_version, was_dirty, cx);
             }
         }
     }
@@ -1821,20 +1820,12 @@ impl BufferSnapshot {
             })
             .map(move |(replica_id, set)| {
                 let start_ix = match set.selections.binary_search_by(|probe| {
-                    probe
-                        .end
-                        .cmp(&range.start, self)
-                        .unwrap()
-                        .then(Ordering::Greater)
+                    probe.end.cmp(&range.start, self).then(Ordering::Greater)
                 }) {
                     Ok(ix) | Err(ix) => ix,
                 };
                 let end_ix = match set.selections.binary_search_by(|probe| {
-                    probe
-                        .start
-                        .cmp(&range.end, self)
-                        .unwrap()
-                        .then(Ordering::Less)
+                    probe.start.cmp(&range.end, self).then(Ordering::Less)
                 }) {
                     Ok(ix) | Err(ix) => ix,
                 };

crates/language/src/diagnostic_set.rs 🔗

@@ -81,8 +81,8 @@ impl DiagnosticSet {
         let range = buffer.anchor_before(range.start)..buffer.anchor_at(range.end, end_bias);
         let mut cursor = self.diagnostics.filter::<_, ()>({
             move |summary: &Summary| {
-                let start_cmp = range.start.cmp(&summary.max_end, buffer).unwrap();
-                let end_cmp = range.end.cmp(&summary.min_start, buffer).unwrap();
+                let start_cmp = range.start.cmp(&summary.max_end, buffer);
+                let end_cmp = range.end.cmp(&summary.min_start, buffer);
                 if inclusive {
                     start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
                 } else {
@@ -123,7 +123,7 @@ impl DiagnosticSet {
 
         let start_ix = output.len();
         output.extend(groups.into_values().filter_map(|mut entries| {
-            entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start, buffer).unwrap());
+            entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start, buffer));
             entries
                 .iter()
                 .position(|entry| entry.diagnostic.is_primary)
@@ -137,7 +137,6 @@ impl DiagnosticSet {
                 .range
                 .start
                 .cmp(&b.entries[b.primary_ix].range.start, buffer)
-                .unwrap()
         });
     }
 
@@ -187,10 +186,10 @@ impl DiagnosticEntry<Anchor> {
 impl Default for Summary {
     fn default() -> Self {
         Self {
-            start: Anchor::min(),
-            end: Anchor::max(),
-            min_start: Anchor::max(),
-            max_end: Anchor::min(),
+            start: Anchor::MIN,
+            end: Anchor::MAX,
+            min_start: Anchor::MAX,
+            max_end: Anchor::MIN,
             count: 0,
         }
     }
@@ -200,15 +199,10 @@ impl sum_tree::Summary for Summary {
     type Context = text::BufferSnapshot;
 
     fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
-        if other
-            .min_start
-            .cmp(&self.min_start, buffer)
-            .unwrap()
-            .is_lt()
-        {
+        if other.min_start.cmp(&self.min_start, buffer).is_lt() {
             self.min_start = other.min_start.clone();
         }
-        if other.max_end.cmp(&self.max_end, buffer).unwrap().is_gt() {
+        if other.max_end.cmp(&self.max_end, buffer).is_gt() {
             self.max_end = other.max_end.clone();
         }
         self.start = other.start.clone();

crates/language/src/tests.rs 🔗

@@ -122,19 +122,11 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) {
     let buffer_1_events = buffer_1_events.borrow();
     assert_eq!(
         *buffer_1_events,
-        vec![
-            Event::Edited { local: true },
-            Event::Dirtied,
-            Event::Edited { local: true },
-            Event::Edited { local: true }
-        ]
+        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 { local: false }, Event::Dirtied]
-    );
+    assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
 }
 
 #[gpui::test]
@@ -827,7 +819,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
     for buffer in &buffers {
         let buffer = buffer.read(cx).snapshot();
         let actual_remote_selections = buffer
-            .remote_selections_in_range(Anchor::min()..Anchor::max())
+            .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
             .map(|(replica_id, selections)| (replica_id, selections.collect::<Vec<_>>()))
             .collect::<Vec<_>>();
         let expected_remote_selections = active_selections

crates/outline/src/outline.rs 🔗

@@ -224,7 +224,7 @@ impl OutlineView {
     ) {
         match event {
             editor::Event::Blurred => cx.emit(Event::Dismissed),
-            editor::Event::Edited { .. } => self.update_matches(cx),
+            editor::Event::BufferEdited { .. } => self.update_matches(cx),
             _ => {}
         }
     }

crates/project/src/project.rs 🔗

@@ -6229,10 +6229,7 @@ mod tests {
             assert!(buffer.is_dirty());
             assert_eq!(
                 *events.borrow(),
-                &[
-                    language::Event::Edited { local: true },
-                    language::Event::Dirtied
-                ]
+                &[language::Event::Edited, language::Event::Dirtied]
             );
             events.borrow_mut().clear();
             buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
@@ -6255,9 +6252,9 @@ mod tests {
             assert_eq!(
                 *events.borrow(),
                 &[
-                    language::Event::Edited { local: true },
+                    language::Event::Edited,
                     language::Event::Dirtied,
-                    language::Event::Edited { local: true },
+                    language::Event::Edited,
                 ],
             );
             events.borrow_mut().clear();
@@ -6269,7 +6266,7 @@ mod tests {
             assert!(buffer.is_dirty());
         });
 
-        assert_eq!(*events.borrow(), &[language::Event::Edited { local: true }]);
+        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()));

crates/project_symbols/src/project_symbols.rs 🔗

@@ -328,7 +328,7 @@ impl ProjectSymbolsView {
     ) {
         match event {
             editor::Event::Blurred => cx.emit(Event::Dismissed),
-            editor::Event::Edited { .. } => self.update_matches(cx),
+            editor::Event::BufferEdited { .. } => self.update_matches(cx),
             _ => {}
         }
     }

crates/search/src/buffer_search.rs 🔗

@@ -358,7 +358,7 @@ impl SearchBar {
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            editor::Event::Edited { .. } => {
+            editor::Event::BufferEdited { .. } => {
                 self.query_contains_error = false;
                 self.clear_matches(cx);
                 self.update_matches(true, cx);
@@ -375,7 +375,7 @@ impl SearchBar {
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            editor::Event::Edited { .. } => self.update_matches(false, cx),
+            editor::Event::BufferEdited { .. } => self.update_matches(false, cx),
             editor::Event::SelectionsChanged { .. } => self.update_match_index(cx),
             _ => {}
         }

crates/search/src/search.rs 🔗

@@ -39,9 +39,9 @@ pub(crate) fn active_match_index(
         None
     } else {
         match ranges.binary_search_by(|probe| {
-            if probe.end.cmp(&cursor, &*buffer).unwrap().is_lt() {
+            if probe.end.cmp(&cursor, &*buffer).is_lt() {
                 Ordering::Less
-            } else if probe.start.cmp(&cursor, &*buffer).unwrap().is_gt() {
+            } else if probe.start.cmp(&cursor, &*buffer).is_gt() {
                 Ordering::Greater
             } else {
                 Ordering::Equal
@@ -59,7 +59,7 @@ pub(crate) fn match_index_for_direction(
     direction: Direction,
     buffer: &MultiBufferSnapshot,
 ) -> usize {
-    if ranges[index].start.cmp(&cursor, &buffer).unwrap().is_gt() {
+    if ranges[index].start.cmp(&cursor, &buffer).is_gt() {
         if direction == Direction::Prev {
             if index == 0 {
                 index = ranges.len() - 1;
@@ -67,7 +67,7 @@ pub(crate) fn match_index_for_direction(
                 index -= 1;
             }
         }
-    } else if ranges[index].end.cmp(&cursor, &buffer).unwrap().is_lt() {
+    } else if ranges[index].end.cmp(&cursor, &buffer).is_lt() {
         if direction == Direction::Next {
             index = 0;
         }

crates/text/src/anchor.rs 🔗

@@ -12,23 +12,19 @@ pub struct Anchor {
 }
 
 impl Anchor {
-    pub fn min() -> Self {
-        Self {
-            timestamp: clock::Local::MIN,
-            offset: usize::MIN,
-            bias: Bias::Left,
-        }
-    }
+    pub const MIN: Self = Self {
+        timestamp: clock::Local::MIN,
+        offset: usize::MIN,
+        bias: Bias::Left,
+    };
 
-    pub fn max() -> Self {
-        Self {
-            timestamp: clock::Local::MAX,
-            offset: usize::MAX,
-            bias: Bias::Right,
-        }
-    }
+    pub const MAX: Self = Self {
+        timestamp: clock::Local::MAX,
+        offset: usize::MAX,
+        bias: Bias::Right,
+    };
 
-    pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Result<Ordering> {
+    pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering {
         let fragment_id_comparison = if self.timestamp == other.timestamp {
             Ordering::Equal
         } else {
@@ -37,9 +33,25 @@ impl Anchor {
                 .cmp(&buffer.fragment_id_for_anchor(other))
         };
 
-        Ok(fragment_id_comparison
+        fragment_id_comparison
             .then_with(|| self.offset.cmp(&other.offset))
-            .then_with(|| self.bias.cmp(&other.bias)))
+            .then_with(|| self.bias.cmp(&other.bias))
+    }
+
+    pub fn min(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
+        if self.cmp(other, buffer).is_le() {
+            self.clone()
+        } else {
+            other.clone()
+        }
+    }
+
+    pub fn max(&self, other: &Self, buffer: &BufferSnapshot) -> Self {
+        if self.cmp(other, buffer).is_ge() {
+            self.clone()
+        } else {
+            other.clone()
+        }
     }
 
     pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor {
@@ -105,8 +117,8 @@ pub trait AnchorRangeExt {
 
 impl AnchorRangeExt for Range<Anchor> {
     fn cmp(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> Result<Ordering> {
-        Ok(match self.start.cmp(&other.start, buffer)? {
-            Ordering::Equal => other.end.cmp(&self.end, buffer)?,
+        Ok(match self.start.cmp(&other.start, buffer) {
+            Ordering::Equal => other.end.cmp(&self.end, buffer),
             ord @ _ => ord,
         })
     }

crates/text/src/tests.rs 🔗

@@ -340,59 +340,41 @@ fn test_anchors() {
     let anchor_at_offset_2 = buffer.anchor_before(2);
 
     assert_eq!(
-        anchor_at_offset_0
-            .cmp(&anchor_at_offset_0, &buffer)
-            .unwrap(),
+        anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer),
         Ordering::Equal
     );
     assert_eq!(
-        anchor_at_offset_1
-            .cmp(&anchor_at_offset_1, &buffer)
-            .unwrap(),
+        anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer),
         Ordering::Equal
     );
     assert_eq!(
-        anchor_at_offset_2
-            .cmp(&anchor_at_offset_2, &buffer)
-            .unwrap(),
+        anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer),
         Ordering::Equal
     );
 
     assert_eq!(
-        anchor_at_offset_0
-            .cmp(&anchor_at_offset_1, &buffer)
-            .unwrap(),
+        anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer),
         Ordering::Less
     );
     assert_eq!(
-        anchor_at_offset_1
-            .cmp(&anchor_at_offset_2, &buffer)
-            .unwrap(),
+        anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer),
         Ordering::Less
     );
     assert_eq!(
-        anchor_at_offset_0
-            .cmp(&anchor_at_offset_2, &buffer)
-            .unwrap(),
+        anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer),
         Ordering::Less
     );
 
     assert_eq!(
-        anchor_at_offset_1
-            .cmp(&anchor_at_offset_0, &buffer)
-            .unwrap(),
+        anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer),
         Ordering::Greater
     );
     assert_eq!(
-        anchor_at_offset_2
-            .cmp(&anchor_at_offset_1, &buffer)
-            .unwrap(),
+        anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer),
         Ordering::Greater
     );
     assert_eq!(
-        anchor_at_offset_2
-            .cmp(&anchor_at_offset_0, &buffer)
-            .unwrap(),
+        anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer),
         Ordering::Greater
     );
 }

crates/text/src/text.rs 🔗

@@ -1318,8 +1318,8 @@ impl Buffer {
         let mut futures = Vec::new();
         for anchor in anchors {
             if !self.version.observed(anchor.timestamp)
-                && *anchor != Anchor::max()
-                && *anchor != Anchor::min()
+                && *anchor != Anchor::MAX
+                && *anchor != Anchor::MIN
             {
                 let (tx, rx) = oneshot::channel();
                 self.edit_id_resolvers
@@ -1638,9 +1638,9 @@ impl BufferSnapshot {
         let mut position = D::default();
 
         anchors.map(move |anchor| {
-            if *anchor == Anchor::min() {
+            if *anchor == Anchor::MIN {
                 return D::default();
-            } else if *anchor == Anchor::max() {
+            } else if *anchor == Anchor::MAX {
                 return D::from_text_summary(&self.visible_text.summary());
             }
 
@@ -1680,9 +1680,9 @@ impl BufferSnapshot {
     where
         D: TextDimension,
     {
-        if *anchor == Anchor::min() {
+        if *anchor == Anchor::MIN {
             D::default()
-        } else if *anchor == Anchor::max() {
+        } else if *anchor == Anchor::MAX {
             D::from_text_summary(&self.visible_text.summary())
         } else {
             let anchor_key = InsertionFragmentKey {
@@ -1718,9 +1718,9 @@ impl BufferSnapshot {
     }
 
     fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator {
-        if *anchor == Anchor::min() {
+        if *anchor == Anchor::MIN {
             &locator::MIN
-        } else if *anchor == Anchor::max() {
+        } else if *anchor == Anchor::MAX {
             &locator::MAX
         } else {
             let anchor_key = InsertionFragmentKey {
@@ -1758,9 +1758,9 @@ impl BufferSnapshot {
     pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
         let offset = position.to_offset(self);
         if bias == Bias::Left && offset == 0 {
-            Anchor::min()
+            Anchor::MIN
         } else if bias == Bias::Right && offset == self.len() {
-            Anchor::max()
+            Anchor::MAX
         } else {
             let mut fragment_cursor = self.fragments.cursor::<usize>();
             fragment_cursor.seek(&offset, bias, &None);
@@ -1775,9 +1775,7 @@ impl BufferSnapshot {
     }
 
     pub fn can_resolve(&self, anchor: &Anchor) -> bool {
-        *anchor == Anchor::min()
-            || *anchor == Anchor::max()
-            || self.version.observed(anchor.timestamp)
+        *anchor == Anchor::MIN || *anchor == Anchor::MAX || self.version.observed(anchor.timestamp)
     }
 
     pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
@@ -1799,7 +1797,7 @@ impl BufferSnapshot {
     where
         D: TextDimension + Ord,
     {
-        self.edits_since_in_range(since, Anchor::min()..Anchor::max())
+        self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX)
     }
 
     pub fn edited_ranges_for_transaction<'a, D>(

crates/theme_selector/src/theme_selector.rs 🔗

@@ -204,7 +204,7 @@ impl ThemeSelector {
         cx: &mut ViewContext<Self>,
     ) {
         match event {
-            editor::Event::Edited { .. } => {
+            editor::Event::BufferEdited { .. } => {
                 self.update_matches(cx);
                 self.select_if_matching(&cx.global::<Settings>().theme.name);
                 self.show_selected_theme(cx);