diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index a0182902f1ca3af18dbcfddae60eaadc3c780d58..56de434cf49e73c82ac19eefd7be36bdd3f5c71e 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/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() { diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 807049c75c3555cd13f74473cf086b967db67f51..0c07f46ed99e9d3d4fe26cded85bab662781c6ab 100644 --- a/crates/editor/src/display_map.rs +++ b/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{} : 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::(), + 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, map: &ModelHandle, theme: &'a SyntaxTheme, cx: &mut MutableAppContext, ) -> Vec<(String, Option)> { + chunks(rows, map, theme, cx) + .into_iter() + .map(|(text, color, _)| (text, color)) + .collect() + } + + fn chunks<'a>( + rows: Range, + map: &ModelHandle, + theme: &'a SyntaxTheme, + cx: &mut MutableAppContext, + ) -> Vec<(String, Option, Option)> { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); - let mut chunks: Vec<(String, Option)> = Vec::new(); + let mut chunks: Vec<(String, Option, Option)> = 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 } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 489381daef3d7156090847e36d48e4c33b94f6b2..9d1b7a75886b234bbcf54d92a31df9898b568469 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/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, }; diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 586f6e0b04f1547c66b4fe514cead3855555dfe4..2c09244a7df38492b10ad4837f5443e3e8f7c457 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/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::>(); @@ -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)) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0aadc3fab9b19bc864563c4647f28f5b866fcdae..d841024f8880b44579b1aec0df3f1b06b4150675 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1744,134 +1744,135 @@ impl Editor { pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext) { 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.start_transaction(cx); - let mut old_selections = SmallVec::<[_; 32]>::new(); - { - let selections = self.local_selections::(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::(); - - let trailing_whitespace_len = buffer - .chars_at(end) - .take_while(|c| c.is_whitespace() && *c != '\n') - .map(|c| c.len_utf8()) - .sum::(); - - 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::(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::(); + + let trailing_whitespace_len = buffer + .chars_at(end) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); + + 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 = 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 = 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.start_transaction(cx); + self.transact(cx, |this, cx| { + let old_selections = this.local_selections::(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::>() + }; + 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::(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::>() + 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) { @@ -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::>(); - 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::>() @@ -2732,14 +2747,13 @@ impl Editor { } pub fn clear(&mut self, cx: &mut ViewContext) { - 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.start_transaction(cx); let mut selections = self.local_selections::(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) { @@ -2787,75 +2803,76 @@ impl Editor { return; } - self.start_transaction(cx); let tab_size = cx.global::().tab_size; let mut selections = self.local_selections::(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) { @@ -2864,7 +2881,6 @@ impl Editor { return; } - self.start_transaction(cx); let tab_size = cx.global::().tab_size; let selections = self.local_selections::(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::(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::(cx), + Some(Autoscroll::Fit), + cx, + ); + }); } pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) { - self.start_transaction(cx); - let selections = self.local_selections::(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.start_transaction(cx); - let selections = self.local_selections::(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) { @@ -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) { @@ -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.start_transaction(cx); let mut text = String::new(); let mut selections = self.local_selections::(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) { @@ -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) { - if let Some(item) = cx.as_mut().read_from_clipboard() { - let clipboard_text = item.text(); - if let Some(mut clipboard_selections) = item.metadata::>() { - let mut selections = self.local_selections::(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::>() { + let mut selections = this.local_selections::(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) { @@ -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::(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::(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::(cx), - Some(Autoscroll::Fit), - cx, - ); - self.end_transaction(cx); + this.update_selections( + this.local_selections::(cx), + Some(Autoscroll::Fit), + cx, + ); + }); } pub fn select_larger_syntax_node( @@ -4407,6 +4429,7 @@ impl Editor { .flat_map(|(_, ranges)| ranges), ) .collect(); + this.highlight_text::( ranges, HighlightStyle { @@ -4685,13 +4708,13 @@ impl Editor { let start_ix = match self .selections - .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer).unwrap()) + .binary_search_by(|probe| probe.end.cmp(&range.start, &buffer)) { Ok(ix) | Err(ix) => ix, }; let end_ix = match self .selections - .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer).unwrap()) + .binary_search_by(|probe| probe.start.cmp(&range.end, &buffer)) { Ok(ix) => ix + 1, Err(ix) => ix, @@ -4913,8 +4936,7 @@ impl Editor { selections.sort_by(|a, b| { a.start .cmp(&b.start, &*buffer) - .unwrap() - .then_with(|| b.end.cmp(&a.end, &*buffer).unwrap()) + .then_with(|| b.end.cmp(&a.end, &*buffer)) }); // Merge overlapping selections @@ -4923,24 +4945,17 @@ impl Editor { if selections[i - 1] .end .cmp(&selections[i].start, &*buffer) - .unwrap() .is_ge() { let removed = selections.remove(i); if removed .start .cmp(&selections[i - 1].start, &*buffer) - .unwrap() .is_lt() { selections[i - 1].start = removed.start; } - if removed - .end - .cmp(&selections[i - 1].end, &*buffer) - .unwrap() - .is_gt() - { + if removed.end.cmp(&selections[i - 1].end, &*buffer).is_gt() { selections[i - 1].end = removed.end; } } else { @@ -5119,13 +5134,9 @@ impl Editor { cx: &mut ViewContext, update: impl FnOnce(&mut Self, &mut ViewContext), ) { - self.start_transaction(cx); - update(self, cx); - self.end_transaction(cx); - } - - fn start_transaction(&mut self, cx: &mut ViewContext) { self.start_transaction_at(Instant::now(), cx); + update(self, cx); + self.end_transaction_at(Instant::now(), cx); } fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { @@ -5139,10 +5150,6 @@ impl Editor { } } - fn end_transaction(&mut self, cx: &mut ViewContext) { - self.end_transaction_at(Instant::now(), cx); - } - fn end_transaction_at(&mut self, now: Instant, cx: &mut ViewContext) { if let Some(tx_id) = self .buffer @@ -5153,6 +5160,8 @@ impl Editor { } else { log::error!("unexpectedly ended a transaction that wasn't started by this editor"); } + + cx.emit(Event::Edited); } } @@ -5328,11 +5337,13 @@ impl Editor { } pub fn set_text(&mut self, text: impl Into, cx: &mut ViewContext) { - self.buffer - .read(cx) - .as_singleton() - .expect("you can only call set_text on editors for singleton buffers") - .update(cx, |buffer, cx| buffer.set_text(text, cx)); + self.transact(cx, |this, cx| { + this.buffer + .read(cx) + .as_singleton() + .expect("you can only call set_text on editors for singleton buffers") + .update(cx, |buffer, cx| buffer.set_text(text, cx)); + }); } pub fn display_text(&self, cx: &mut MutableAppContext) -> String { @@ -5420,7 +5431,7 @@ impl Editor { let buffer = &display_snapshot.buffer_snapshot; for (color, ranges) in self.background_highlights.values() { let start_ix = match ranges.binary_search_by(|probe| { - let cmp = probe.end.cmp(&search_range.start, &buffer).unwrap(); + let cmp = probe.end.cmp(&search_range.start, &buffer); if cmp.is_gt() { Ordering::Greater } else { @@ -5430,7 +5441,7 @@ impl Editor { Ok(i) | Err(i) => i, }; for range in &ranges[start_ix..] { - if range.start.cmp(&search_range.end, &buffer).unwrap().is_ge() { + if range.start.cmp(&search_range.end, &buffer).is_ge() { break; } let start = range @@ -5535,10 +5546,10 @@ impl Editor { cx: &mut ViewContext, ) { match event { - language::Event::Edited { local } => { + language::Event::Edited => { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); - cx.emit(Event::Edited { local: *local }); + cx.emit(Event::BufferEdited); } language::Event::Dirtied => cx.emit(Event::Dirtied), language::Event::Saved => cx.emit(Event::Saved), @@ -5662,10 +5673,11 @@ fn compute_scroll_position( scroll_position } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Event { Activate, - Edited { local: bool }, + BufferEdited, + Edited, Blurred, Dirtied, Saved, @@ -6144,6 +6156,114 @@ mod tests { use util::test::sample_text; use workspace::FollowableItem; + #[gpui::test] + fn test_edit_events(cx: &mut MutableAppContext) { + populate_settings(cx); + let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx)); + + let events = Rc::new(RefCell::new(Vec::new())); + let (_, editor1) = cx.add_window(Default::default(), { + let events = events.clone(); + |cx| { + cx.subscribe(&cx.handle(), move |_, _, event, _| { + if matches!(event, Event::Edited | Event::BufferEdited | Event::Dirtied) { + events.borrow_mut().push(("editor1", *event)); + } + }) + .detach(); + Editor::for_buffer(buffer.clone(), None, cx) + } + }); + let (_, editor2) = cx.add_window(Default::default(), { + let events = events.clone(); + |cx| { + cx.subscribe(&cx.handle(), move |_, _, event, _| { + if matches!(event, Event::Edited | Event::BufferEdited | Event::Dirtied) { + events.borrow_mut().push(("editor2", *event)); + } + }) + .detach(); + Editor::for_buffer(buffer.clone(), None, cx) + } + }); + assert_eq!(mem::take(&mut *events.borrow_mut()), []); + + // Mutating editor 1 will emit an `Edited` event only for that editor. + editor1.update(cx, |editor, cx| editor.insert("X", cx)); + assert_eq!( + mem::take(&mut *events.borrow_mut()), + [ + ("editor1", Event::Edited), + ("editor1", Event::BufferEdited), + ("editor2", Event::BufferEdited), + ("editor1", Event::Dirtied), + ("editor2", Event::Dirtied) + ] + ); + + // Mutating editor 2 will emit an `Edited` event only for that editor. + editor2.update(cx, |editor, cx| editor.delete(&Delete, cx)); + assert_eq!( + mem::take(&mut *events.borrow_mut()), + [ + ("editor2", Event::Edited), + ("editor1", Event::BufferEdited), + ("editor2", Event::BufferEdited), + ] + ); + + // Undoing on editor 1 will emit an `Edited` event only for that editor. + editor1.update(cx, |editor, cx| editor.undo(&Undo, cx)); + assert_eq!( + mem::take(&mut *events.borrow_mut()), + [ + ("editor1", Event::Edited), + ("editor1", Event::BufferEdited), + ("editor2", Event::BufferEdited), + ] + ); + + // Redoing on editor 1 will emit an `Edited` event only for that editor. + editor1.update(cx, |editor, cx| editor.redo(&Redo, cx)); + assert_eq!( + mem::take(&mut *events.borrow_mut()), + [ + ("editor1", Event::Edited), + ("editor1", Event::BufferEdited), + ("editor2", Event::BufferEdited), + ] + ); + + // Undoing on editor 2 will emit an `Edited` event only for that editor. + editor2.update(cx, |editor, cx| editor.undo(&Undo, cx)); + assert_eq!( + mem::take(&mut *events.borrow_mut()), + [ + ("editor2", Event::Edited), + ("editor1", Event::BufferEdited), + ("editor2", Event::BufferEdited), + ] + ); + + // Redoing on editor 2 will emit an `Edited` event only for that editor. + editor2.update(cx, |editor, cx| editor.redo(&Redo, cx)); + assert_eq!( + mem::take(&mut *events.borrow_mut()), + [ + ("editor2", Event::Edited), + ("editor1", Event::BufferEdited), + ("editor2", Event::BufferEdited), + ] + ); + + // No event is emitted when the mutation is a no-op. + editor2.update(cx, |editor, cx| { + editor.select_ranges([0..0], None, cx); + editor.backspace(&Backspace, cx); + }); + assert_eq!(mem::take(&mut *events.borrow_mut()), []); + } + #[gpui::test] fn test_undo_redo_with_selection_restoration(cx: &mut MutableAppContext) { populate_settings(cx); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 0e77bfa3aa978866135bf9da4b13b8f047b705fd..f10956c125e3d3cc9bc0eab86432a22942a884af 100644 --- a/crates/editor/src/items.rs +++ b/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, diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 594e3fafb7581b7635d0a9e34e5617941f48cb42..af98f3d5896d955872193a29730696aa9a80ed17 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -211,7 +211,7 @@ impl MultiBuffer { pub fn singleton(buffer: ModelHandle, cx: &mut ModelContext) -> 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) -> Vec { - self.buffers - .borrow() + pub fn excerpts_for_buffer( + &self, + buffer: &ModelHandle, + cx: &AppContext, + ) -> Vec<(ExcerptId, Range)> { + let mut excerpts = Vec::new(); + let snapshot = self.read(cx); + let buffers = self.buffers.borrow(); + let mut cursor = snapshot.excerpts.cursor::>(); + 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 { @@ -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); diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 33147ce285f371c97870c1281d25a3015a85fb7b..df080f074cdd5d1295b6a0e4729939819ec71bb0 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/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 { + 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, buffer: &MultiBufferSnapshot) -> Result; + fn cmp(&self, b: &Range, buffer: &MultiBufferSnapshot) -> Ordering; fn to_offset(&self, content: &MultiBufferSnapshot) -> Range; fn to_point(&self, content: &MultiBufferSnapshot) -> Range; } impl AnchorRangeExt for Range { - fn cmp(&self, other: &Range, buffer: &MultiBufferSnapshot) -> Result { - Ok(match self.start.cmp(&other.start, buffer)? { - Ordering::Equal => other.end.cmp(&self.end, buffer)?, + fn cmp(&self, other: &Range, 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 { diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 4908016ef591f6bb1844fa2707b2ff57221e0326..566ca9b2dbc70bb1ff69b72c4bc6d3755dc4de76 100644 --- a/crates/editor/src/test.rs +++ b/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) { 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>) { + 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) +} diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 4656daa4b3c1adbcf9cda5fd92428240c46ae13a..9f0137ef62a25a15eec50ce9d5f84c7c717c3a8b 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -291,7 +291,7 @@ impl FileFinder { cx: &mut ViewContext, ) { 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); diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index ce8ba787a827b5daacb400334a91f6ef2967fcce..109d33097d69cf5f4d251451aa634887774145de 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/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::().ok()); diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 28f2c0510d2da95759bfeee9a6ac7c7c6a45db00..535798083eb6a5de52738e2b85a4829021a3adf9 100644 --- a/crates/language/src/buffer.rs +++ b/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 { 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, ) { if self.edits_since::(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, }; diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index e25551ee3a6b81317aa5c00d90cb70d89cc3954e..490789a8c80c3abe320d54bc12b88ec3529832cc 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/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 { 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(); diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 19b1a1cf73c18ada121fb4768ef9da5ba819b794..98ecf63a4692f6419707a6bc95889ddfb2cbb29f 100644 --- a/crates/language/src/tests.rs +++ b/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::>())) .collect::>(); let expected_remote_selections = active_selections diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 968fceb59c5c8eccd05961164fefd6529c617eef..a626ff89c86e4715345db38229717325c1c7b92c 100644 --- a/crates/outline/src/outline.rs +++ b/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), _ => {} } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fbd3a1f7a6026a0d60361e33e4d801f0d79a6897..2529c976ac15d80cad110b40ab6949ef7b6c6c0e 100644 --- a/crates/project/src/project.rs +++ b/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())); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 5eb04718d7cf604551fd8668a3fd630f76077b53..34d5306d99e78eaf2c056dd1da968e2853ff2c85 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/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), _ => {} } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a1a40c9e391c645b0a9798dc94a7fdbf92d895b6..da9ee0664ba5423f1a8e8fffd2e53d5c1300fb52 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -358,7 +358,7 @@ impl SearchBar { cx: &mut ViewContext, ) { 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, ) { 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), _ => {} } diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index a1e335a0354ffdaaf903ba30c3d32f5d24c90093..9fb4cda8e9a8545b1861ccd9b23327563b06a937 100644 --- a/crates/search/src/search.rs +++ b/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; } diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 28da998d6770f6881ac65fcb439726ae972d139c..e642aa45d3ff06ffd326dcf633df1985ee5d7ef0 100644 --- a/crates/text/src/anchor.rs +++ b/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 { + 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 { fn cmp(&self, other: &Range, buffer: &BufferSnapshot) -> Result { - 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, }) } diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 05cf0af6ec52e9006816ec056939c1d2be713ba4..7961dccd569c8380c3bb32e57e9057481e4371fd 100644 --- a/crates/text/src/tests.rs +++ b/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 ); } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 338a5c0ad1425049a811c43fc81b02281ccbe8d2..b811d08c046c58f8c5d6c020c27c45a430258474 100644 --- a/crates/text/src/text.rs +++ b/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(&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::(); 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>( diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index ebdcc492a9f95af0232ef93b8ab934668d634c97..725319be419b8e196433590bf673f8bc89b936ac 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -204,7 +204,7 @@ impl ThemeSelector { cx: &mut ViewContext, ) { match event { - editor::Event::Edited { .. } => { + editor::Event::BufferEdited { .. } => { self.update_matches(cx); self.select_if_matching(&cx.global::().theme.name); self.show_selected_theme(cx);