diff --git a/Cargo.lock b/Cargo.lock index d751e25284..0cee1f91d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -759,6 +759,7 @@ dependencies = [ "serde 1.0.125", "similar", "smallvec", + "smol", "sum_tree", "theme", "tree-sitter", diff --git a/crates/buffer/Cargo.toml b/crates/buffer/Cargo.toml index 541c449d46..0cb283aa46 100644 --- a/crates/buffer/Cargo.toml +++ b/crates/buffer/Cargo.toml @@ -22,6 +22,7 @@ seahash = "4.1" serde = { version = "1", features = ["derive"] } similar = "1.3" smallvec = { version = "1.6", features = ["union"] } +smol = "1.2" tree-sitter = "0.19.5" [dev-dependencies] diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index c678918824..1ac82727df 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -1,3 +1,5 @@ +use crate::Point; + use super::{Buffer, Content}; use anyhow::Result; use std::{cmp::Ordering, ops::Range}; @@ -10,6 +12,24 @@ pub struct Anchor { pub version: clock::Global, } +#[derive(Clone)] +pub struct AnchorMap { + pub(crate) version: clock::Global, + pub(crate) entries: Vec<((usize, Bias), T)>, +} + +#[derive(Clone)] +pub struct AnchorSet(pub(crate) AnchorMap<()>); + +#[derive(Clone)] +pub struct AnchorRangeMap { + pub(crate) version: clock::Global, + pub(crate) entries: Vec<(Range<(usize, Bias)>, T)>, +} + +#[derive(Clone)] +pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>); + impl Anchor { pub fn min() -> Self { Self { @@ -62,6 +82,60 @@ impl Anchor { } } +impl AnchorMap { + pub fn to_points<'a>( + &'a self, + content: impl Into> + 'a, + ) -> impl Iterator + 'a { + let content = content.into(); + content + .summaries_for_anchors(self) + .map(move |(sum, value)| (sum.lines, value)) + } + + pub fn version(&self) -> &clock::Global { + &self.version + } +} + +impl AnchorSet { + pub fn to_points<'a>( + &'a self, + content: impl Into> + 'a, + ) -> impl Iterator + 'a { + self.0.to_points(content).map(move |(point, _)| point) + } +} + +impl AnchorRangeMap { + pub fn to_point_ranges<'a>( + &'a self, + content: impl Into> + 'a, + ) -> impl Iterator, &'a T)> + 'a { + let content = content.into(); + content + .summaries_for_anchor_ranges(self) + .map(move |(range, value)| ((range.start.lines..range.end.lines), value)) + } + + pub fn version(&self) -> &clock::Global { + &self.version + } +} + +impl AnchorRangeSet { + pub fn to_point_ranges<'a>( + &'a self, + content: impl Into> + 'a, + ) -> impl Iterator> + 'a { + self.0.to_point_ranges(content).map(|(range, _)| range) + } + + pub fn version(&self) -> &clock::Global { + self.0.version() + } +} + pub trait AnchorRangeExt { fn cmp<'a>(&self, b: &Range, buffer: impl Into>) -> Result; } diff --git a/crates/buffer/src/language.rs b/crates/buffer/src/language.rs index 1a9a29aac5..2260990566 100644 --- a/crates/buffer/src/language.rs +++ b/crates/buffer/src/language.rs @@ -11,20 +11,23 @@ pub use tree_sitter::{Parser, Tree}; pub struct LanguageConfig { pub name: String, pub path_suffixes: Vec, - pub autoclose_pairs: Vec, + pub brackets: Vec, } -#[derive(Clone, Deserialize)] -pub struct AutoclosePair { +#[derive(Clone, Debug, Deserialize)] +pub struct BracketPair { pub start: String, pub end: String, + pub close: bool, + pub newline: bool, } pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Grammar, - pub(crate) highlight_query: Query, + pub(crate) highlights_query: Query, pub(crate) brackets_query: Query, + pub(crate) indents_query: Query, pub(crate) highlight_map: Mutex, } @@ -68,19 +71,25 @@ impl Language { Self { config, brackets_query: Query::new(grammar, "").unwrap(), - highlight_query: Query::new(grammar, "").unwrap(), + highlights_query: Query::new(grammar, "").unwrap(), + indents_query: Query::new(grammar, "").unwrap(), grammar, highlight_map: Default::default(), } } - pub fn with_highlights_query(mut self, highlights_query_source: &str) -> Result { - self.highlight_query = Query::new(self.grammar, highlights_query_source)?; + pub fn with_highlights_query(mut self, source: &str) -> Result { + self.highlights_query = Query::new(self.grammar, source)?; Ok(self) } - pub fn with_brackets_query(mut self, brackets_query_source: &str) -> Result { - self.brackets_query = Query::new(self.grammar, brackets_query_source)?; + pub fn with_brackets_query(mut self, source: &str) -> Result { + self.brackets_query = Query::new(self.grammar, source)?; + Ok(self) + } + + pub fn with_indents_query(mut self, source: &str) -> Result { + self.indents_query = Query::new(self.grammar, source)?; Ok(self) } @@ -88,8 +97,8 @@ impl Language { self.config.name.as_str() } - pub fn autoclose_pairs(&self) -> &[AutoclosePair] { - &self.config.autoclose_pairs + pub fn brackets(&self) -> &[BracketPair] { + &self.config.brackets } pub fn highlight_map(&self) -> HighlightMap { @@ -97,7 +106,8 @@ impl Language { } pub fn set_theme(&self, theme: &SyntaxTheme) { - *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme); + *self.highlight_map.lock() = + HighlightMap::new(self.highlights_query.capture_names(), theme); } } @@ -110,28 +120,22 @@ mod tests { let grammar = tree_sitter_rust::language(); let registry = LanguageRegistry { languages: vec![ - Arc::new(Language { - config: LanguageConfig { + Arc::new(Language::new( + LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], ..Default::default() }, grammar, - highlight_query: Query::new(grammar, "").unwrap(), - brackets_query: Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }), - Arc::new(Language { - config: LanguageConfig { + )), + Arc::new(Language::new( + LanguageConfig { name: "Make".to_string(), path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], ..Default::default() }, grammar, - highlight_query: Query::new(grammar, "").unwrap(), - brackets_query: Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }), + )), ], }; diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index a2f34a5d78..3817b7131a 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -7,6 +7,8 @@ mod point; pub mod random_char_iter; pub mod rope; mod selection; +#[cfg(test)] +mod tests; pub use anchor::*; use anyhow::{anyhow, Result}; @@ -14,7 +16,7 @@ use clock::ReplicaId; use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; pub use highlight_map::{HighlightId, HighlightMap}; use language::Tree; -pub use language::{AutoclosePair, Language, LanguageConfig, LanguageRegistry}; +pub use language::{BracketPair, Language, LanguageConfig, LanguageRegistry}; use lazy_static::lazy_static; use operation_queue::OperationQueue; use parking_lot::Mutex; @@ -26,12 +28,15 @@ use rpc::proto; use seahash::SeaHasher; pub use selection::*; use similar::{ChangeTag, TextDiff}; +use smol::future::yield_now; use std::{ any::Any, cell::RefCell, cmp, + collections::BTreeMap, convert::{TryFrom, TryInto}, ffi::OsString, + future::Future, hash::BuildHasher, iter::Iterator, ops::{Deref, DerefMut, Range}, @@ -114,6 +119,9 @@ lazy_static! { static ref QUERY_CURSORS: Mutex> = Default::default(); } +// TODO - Make this configurable +const INDENT_SIZE: u32 = 4; + struct QueryCursorHandle(Option); impl QueryCursorHandle { @@ -162,6 +170,8 @@ pub struct Buffer { history: History, file: Option>, language: Option>, + autoindent_requests: Vec>, + pending_autoindent: Option>, sync_parse_timeout: Duration, syntax_tree: Mutex>, parsing_in_background: bool, @@ -186,10 +196,17 @@ pub struct SelectionSet { #[derive(Clone)] struct SyntaxTree { tree: Tree, - dirty: bool, version: clock::Global, } +#[derive(Clone)] +struct AutoindentRequest { + selection_set_ids: HashSet, + before_edit: Snapshot, + edited: AnchorSet, + inserted: Option, +} + #[derive(Clone, Debug)] struct Transaction { start: clock::Global, @@ -197,8 +214,8 @@ struct Transaction { buffer_was_dirty: bool, edits: Vec, ranges: Vec>, - selections_before: Option<(SelectionSetId, Arc<[Selection]>)>, - selections_after: Option<(SelectionSetId, Arc<[Selection]>)>, + selections_before: HashMap>, + selections_after: HashMap>, first_edit_at: Instant, last_edit_at: Instant, } @@ -282,7 +299,7 @@ impl History { &mut self, start: clock::Global, buffer_was_dirty: bool, - selections: Option<(SelectionSetId, Arc<[Selection]>)>, + selections_before: HashMap>, now: Instant, ) { self.transaction_depth += 1; @@ -293,8 +310,8 @@ impl History { buffer_was_dirty, edits: Vec::new(), ranges: Vec::new(), - selections_before: selections, - selections_after: None, + selections_before, + selections_after: Default::default(), first_edit_at: now, last_edit_at: now, }); @@ -303,16 +320,21 @@ impl History { fn end_transaction( &mut self, - selections: Option<(SelectionSetId, Arc<[Selection]>)>, + selections_after: HashMap>, now: Instant, ) -> Option<&Transaction> { assert_ne!(self.transaction_depth, 0); self.transaction_depth -= 1; if self.transaction_depth == 0 { - let transaction = self.undo_stack.last_mut().unwrap(); - transaction.selections_after = selections; - transaction.last_edit_at = now; - Some(transaction) + if self.undo_stack.last().unwrap().ranges.is_empty() { + self.undo_stack.pop(); + None + } else { + let transaction = self.undo_stack.last_mut().unwrap(); + transaction.selections_after = selections_after; + transaction.last_edit_at = now; + Some(transaction) + } } else { None } @@ -345,7 +367,9 @@ impl History { if let Some(transaction) = transactions_to_merge.last_mut() { last_transaction.last_edit_at = transaction.last_edit_at; - last_transaction.selections_after = transaction.selections_after.take(); + last_transaction + .selections_after + .extend(transaction.selections_after.drain()); last_transaction.end = transaction.end.clone(); } } @@ -628,6 +652,8 @@ impl Buffer { parsing_in_background: false, parse_count: 0, sync_parse_timeout: Duration::from_millis(1), + autoindent_requests: Default::default(), + pending_autoindent: Default::default(), language, saved_mtime, selections: HashMap::default(), @@ -835,27 +861,9 @@ impl Buffer { self.parse_count } - pub fn syntax_tree(&self) -> Option { + fn syntax_tree(&self) -> Option { if let Some(syntax_tree) = self.syntax_tree.lock().as_mut() { - let mut delta = 0_isize; - for edit in self.edits_since(syntax_tree.version.clone()) { - let start_offset = (edit.old_bytes.start as isize + delta) as usize; - let start_point = self.visible_text.to_point(start_offset); - syntax_tree.tree.edit(&InputEdit { - start_byte: start_offset, - old_end_byte: start_offset + edit.deleted_bytes(), - new_end_byte: start_offset + edit.inserted_bytes(), - start_position: start_point.into(), - old_end_position: (start_point + edit.deleted_lines()).into(), - new_end_position: self - .visible_text - .to_point(start_offset + edit.inserted_bytes()) - .into(), - }); - delta += edit.inserted_bytes() as isize - edit.deleted_bytes() as isize; - syntax_tree.dirty = true; - } - syntax_tree.version = self.version(); + self.interpolate_tree(syntax_tree); Some(syntax_tree.tree.clone()) } else { None @@ -878,14 +886,12 @@ impl Buffer { } if let Some(language) = self.language.clone() { - // The parse tree is out of date, so grab the syntax tree to synchronously - // splice all the edits that have happened since the last parse. let old_tree = self.syntax_tree(); - let parsed_text = self.visible_text.clone(); + let text = self.visible_text.clone(); let parsed_version = self.version(); let parse_task = cx.background().spawn({ let language = language.clone(); - async move { Self::parse_text(&parsed_text, old_tree, &language) } + async move { Self::parse_text(&text, old_tree, &language) } }); match cx @@ -893,14 +899,7 @@ impl Buffer { .block_with_timeout(self.sync_parse_timeout, parse_task) { Ok(new_tree) => { - *self.syntax_tree.lock() = Some(SyntaxTree { - tree: new_tree, - dirty: false, - version: parsed_version, - }); - self.parse_count += 1; - cx.emit(Event::Reparsed); - cx.notify(); + self.did_finish_parsing(new_tree, parsed_version, cx); return true; } Err(parse_task) => { @@ -913,20 +912,12 @@ impl Buffer { !Arc::ptr_eq(curr_language, &language) }); let parse_again = this.version > parsed_version || language_changed; - *this.syntax_tree.lock() = Some(SyntaxTree { - tree: new_tree, - dirty: false, - version: parsed_version, - }); - this.parse_count += 1; this.parsing_in_background = false; + this.did_finish_parsing(new_tree, parsed_version, cx); if parse_again && this.reparse(cx) { return; } - - cx.emit(Event::Reparsed); - cx.notify(); }); }) .detach(); @@ -956,6 +947,242 @@ impl Buffer { }) } + fn interpolate_tree(&self, tree: &mut SyntaxTree) { + let mut delta = 0_isize; + for edit in self.edits_since(tree.version.clone()) { + let start_offset = (edit.old_bytes.start as isize + delta) as usize; + let start_point = self.visible_text.to_point(start_offset); + tree.tree.edit(&InputEdit { + start_byte: start_offset, + old_end_byte: start_offset + edit.deleted_bytes(), + new_end_byte: start_offset + edit.inserted_bytes(), + start_position: start_point.into(), + old_end_position: (start_point + edit.deleted_lines()).into(), + new_end_position: self + .visible_text + .to_point(start_offset + edit.inserted_bytes()) + .into(), + }); + delta += edit.inserted_bytes() as isize - edit.deleted_bytes() as isize; + } + tree.version = self.version(); + } + + fn did_finish_parsing( + &mut self, + tree: Tree, + version: clock::Global, + cx: &mut ModelContext, + ) { + self.parse_count += 1; + *self.syntax_tree.lock() = Some(SyntaxTree { tree, version }); + self.request_autoindent(cx); + cx.emit(Event::Reparsed); + cx.notify(); + } + + fn request_autoindent(&mut self, cx: &mut ModelContext) { + if let Some(indent_columns) = self.compute_autoindents() { + let indent_columns = cx.background().spawn(indent_columns); + match cx + .background() + .block_with_timeout(Duration::from_micros(500), indent_columns) + { + Ok(indent_columns) => self.apply_autoindents(indent_columns, cx), + Err(indent_columns) => { + self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move { + let indent_columns = indent_columns.await; + this.update(&mut cx, |this, cx| { + this.apply_autoindents(indent_columns, cx); + }); + })); + } + } + } + } + + fn compute_autoindents(&self) -> Option>> { + let max_rows_between_yields = 100; + let snapshot = self.snapshot(); + if snapshot.language.is_none() + || snapshot.tree.is_none() + || self.autoindent_requests.is_empty() + { + return None; + } + + let autoindent_requests = self.autoindent_requests.clone(); + Some(async move { + let mut indent_columns = BTreeMap::new(); + for request in autoindent_requests { + let old_to_new_rows = request + .edited + .to_points(&request.before_edit) + .map(|point| point.row) + .zip(request.edited.to_points(&snapshot).map(|point| point.row)) + .collect::>(); + + let mut old_suggestions = HashMap::default(); + let old_edited_ranges = + contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields); + for old_edited_range in old_edited_ranges { + let suggestions = request + .before_edit + .suggest_autoindents(old_edited_range.clone()) + .into_iter() + .flatten(); + for (old_row, suggestion) in old_edited_range.zip(suggestions) { + let indentation_basis = old_to_new_rows + .get(&suggestion.basis_row) + .and_then(|from_row| old_suggestions.get(from_row).copied()) + .unwrap_or_else(|| { + request + .before_edit + .indent_column_for_line(suggestion.basis_row) + }); + let delta = if suggestion.indent { INDENT_SIZE } else { 0 }; + old_suggestions.insert( + *old_to_new_rows.get(&old_row).unwrap(), + indentation_basis + delta, + ); + } + yield_now().await; + } + + // At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the + // buffer before the edit, but keyed by the row for these lines after the edits were applied. + let new_edited_row_ranges = + contiguous_ranges(old_to_new_rows.values().copied(), max_rows_between_yields); + for new_edited_row_range in new_edited_row_ranges { + let suggestions = snapshot + .suggest_autoindents(new_edited_row_range.clone()) + .into_iter() + .flatten(); + for (new_row, suggestion) in new_edited_row_range.zip(suggestions) { + let delta = if suggestion.indent { INDENT_SIZE } else { 0 }; + let new_indentation = indent_columns + .get(&suggestion.basis_row) + .copied() + .unwrap_or_else(|| { + snapshot.indent_column_for_line(suggestion.basis_row) + }) + + delta; + if old_suggestions + .get(&new_row) + .map_or(true, |old_indentation| new_indentation != *old_indentation) + { + indent_columns.insert(new_row, new_indentation); + } + } + yield_now().await; + } + + if let Some(inserted) = request.inserted.as_ref() { + let inserted_row_ranges = contiguous_ranges( + inserted + .to_point_ranges(&snapshot) + .flat_map(|range| range.start.row..range.end.row + 1), + max_rows_between_yields, + ); + for inserted_row_range in inserted_row_ranges { + let suggestions = snapshot + .suggest_autoindents(inserted_row_range.clone()) + .into_iter() + .flatten(); + for (row, suggestion) in inserted_row_range.zip(suggestions) { + let delta = if suggestion.indent { INDENT_SIZE } else { 0 }; + let new_indentation = indent_columns + .get(&suggestion.basis_row) + .copied() + .unwrap_or_else(|| { + snapshot.indent_column_for_line(suggestion.basis_row) + }) + + delta; + indent_columns.insert(row, new_indentation); + } + yield_now().await; + } + } + } + indent_columns + }) + } + + fn apply_autoindents( + &mut self, + indent_columns: BTreeMap, + cx: &mut ModelContext, + ) { + let selection_set_ids = self + .autoindent_requests + .drain(..) + .flat_map(|req| req.selection_set_ids.clone()) + .collect::>(); + + self.start_transaction(selection_set_ids.iter().copied()) + .unwrap(); + for (row, indent_column) in &indent_columns { + self.set_indent_column_for_line(*row, *indent_column, cx); + } + + for selection_set_id in &selection_set_ids { + if let Some(set) = self.selections.get(selection_set_id) { + let new_selections = set + .selections + .iter() + .map(|selection| { + let start_point = selection.start.to_point(&*self); + if start_point.column == 0 { + let end_point = selection.end.to_point(&*self); + let delta = Point::new( + 0, + indent_columns.get(&start_point.row).copied().unwrap_or(0), + ); + if delta.column > 0 { + return Selection { + id: selection.id, + goal: selection.goal, + reversed: selection.reversed, + start: self + .anchor_at(start_point + delta, selection.start.bias), + end: self.anchor_at(end_point + delta, selection.end.bias), + }; + } + } + selection.clone() + }) + .collect::>(); + self.update_selection_set(*selection_set_id, new_selections, cx) + .unwrap(); + } + } + + self.end_transaction(selection_set_ids.iter().copied(), cx) + .unwrap(); + } + + pub fn indent_column_for_line(&self, row: u32) -> u32 { + self.content().indent_column_for_line(row) + } + + fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext) { + let current_column = self.indent_column_for_line(row); + if column > current_column { + let offset = self.visible_text.to_offset(Point::new(row, 0)); + self.edit( + [offset..offset], + " ".repeat((column - current_column) as usize), + cx, + ); + } else if column < current_column { + self.edit( + [Point::new(row, 0)..Point::new(row, current_column - column)], + "", + cx, + ); + } + } + pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { if let Some(tree) = self.syntax_tree() { let root = tree.root_node(); @@ -1096,18 +1323,29 @@ impl Buffer { } pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range) -> Chunks<'a> { - let start = range.start.to_offset(self); - let end = range.end.to_offset(self); - self.visible_text.chunks_in_range(start..end) + self.content().text_for_range(range) } pub fn chars(&self) -> impl Iterator + '_ { self.chars_at(0) } - pub fn chars_at(&self, position: T) -> impl Iterator + '_ { - let offset = position.to_offset(self); - self.visible_text.chars_at(offset) + pub fn chars_at<'a, T: 'a + ToOffset>( + &'a self, + position: T, + ) -> impl Iterator + 'a { + self.content().chars_at(position) + } + + pub fn reversed_chars_at<'a, T: 'a + ToOffset>( + &'a self, + position: T, + ) -> impl Iterator + 'a { + self.content().reversed_chars_at(position) + } + + pub fn chars_for_range(&self, range: Range) -> impl Iterator + '_ { + self.text_for_range(range).flat_map(str::chars) } pub fn bytes_at(&self, position: T) -> impl Iterator + '_ { @@ -1155,20 +1393,28 @@ impl Buffer { self.deferred_ops.len() } - pub fn start_transaction(&mut self, set_id: Option) -> Result<()> { - self.start_transaction_at(set_id, Instant::now()) + pub fn start_transaction( + &mut self, + selection_set_ids: impl IntoIterator, + ) -> Result<()> { + self.start_transaction_at(selection_set_ids, Instant::now()) } - fn start_transaction_at(&mut self, set_id: Option, now: Instant) -> Result<()> { - let selections = if let Some(set_id) = set_id { - let set = self - .selections - .get(&set_id) - .ok_or_else(|| anyhow!("invalid selection set {:?}", set_id))?; - Some((set_id, set.selections.clone())) - } else { - None - }; + fn start_transaction_at( + &mut self, + selection_set_ids: impl IntoIterator, + now: Instant, + ) -> Result<()> { + let selections = selection_set_ids + .into_iter() + .map(|set_id| { + let set = self + .selections + .get(&set_id) + .expect("invalid selection set id"); + (set_id, set.selections.clone()) + }) + .collect(); self.history .start_transaction(self.version.clone(), self.is_dirty(), selections, now); Ok(()) @@ -1176,27 +1422,28 @@ impl Buffer { pub fn end_transaction( &mut self, - set_id: Option, + selection_set_ids: impl IntoIterator, cx: &mut ModelContext, ) -> Result<()> { - self.end_transaction_at(set_id, Instant::now(), cx) + self.end_transaction_at(selection_set_ids, Instant::now(), cx) } fn end_transaction_at( &mut self, - set_id: Option, + selection_set_ids: impl IntoIterator, now: Instant, cx: &mut ModelContext, ) -> Result<()> { - let selections = if let Some(set_id) = set_id { - let set = self - .selections - .get(&set_id) - .ok_or_else(|| anyhow!("invalid selection set {:?}", set_id))?; - Some((set_id, set.selections.clone())) - } else { - None - }; + let selections = selection_set_ids + .into_iter() + .map(|set_id| { + let set = self + .selections + .get(&set_id) + .expect("invalid selection set id"); + (set_id, set.selections.clone()) + }) + .collect(); if let Some(transaction) = self.history.end_transaction(selections, now) { let since = transaction.start.clone(); @@ -1218,20 +1465,41 @@ impl Buffer { I: IntoIterator>, S: ToOffset, T: Into, + { + self.edit_internal(ranges_iter, new_text, false, cx) + } + + pub fn edit_with_autoindent( + &mut self, + ranges_iter: I, + new_text: T, + cx: &mut ModelContext, + ) where + I: IntoIterator>, + S: ToOffset, + T: Into, + { + self.edit_internal(ranges_iter, new_text, true, cx) + } + + pub fn edit_internal( + &mut self, + ranges_iter: I, + new_text: T, + autoindent: bool, + cx: &mut ModelContext, + ) where + I: IntoIterator>, + S: ToOffset, + T: Into, { let new_text = new_text.into(); - let new_text = if new_text.len() > 0 { - Some(new_text) - } else { - None - }; - let has_new_text = new_text.is_some(); // Skip invalid ranges and coalesce contiguous ones. let mut ranges: Vec> = Vec::new(); for range in ranges_iter { let range = range.start.to_offset(&*self)..range.end.to_offset(&*self); - if has_new_text || !range.is_empty() { + if !new_text.is_empty() || !range.is_empty() { if let Some(prev_range) = ranges.last_mut() { if prev_range.end >= range.start { prev_range.end = cmp::max(prev_range.end, range.end); @@ -1243,24 +1511,78 @@ impl Buffer { } } } + if ranges.is_empty() { + return; + } - if !ranges.is_empty() { - self.start_transaction_at(None, Instant::now()).unwrap(); - let timestamp = InsertionTimestamp { - replica_id: self.replica_id, - local: self.local_clock.tick().value, - lamport: self.lamport_clock.tick().value, - }; - let edit = self.apply_local_edit(&ranges, new_text, timestamp); - - self.history.push(edit.clone()); - self.history.push_undo(edit.timestamp.local()); - self.last_edit = edit.timestamp.local(); - self.version.observe(edit.timestamp.local()); - - self.end_transaction_at(None, Instant::now(), cx).unwrap(); - self.send_operation(Operation::Edit(edit), cx); + self.pending_autoindent.take(); + let autoindent_request = if autoindent && self.language.is_some() { + let before_edit = self.snapshot(); + let edited = self.content().anchor_set(ranges.iter().filter_map(|range| { + let start = range.start.to_point(&*self); + if new_text.starts_with('\n') && start.column == self.line_len(start.row) { + None + } else { + Some((range.start, Bias::Left)) + } + })); + Some((before_edit, edited)) + } else { + None }; + + let first_newline_ix = new_text.find('\n'); + let new_text_len = new_text.len(); + let new_text = if new_text_len > 0 { + Some(new_text) + } else { + None + }; + + self.start_transaction(None).unwrap(); + let timestamp = InsertionTimestamp { + replica_id: self.replica_id, + local: self.local_clock.tick().value, + lamport: self.lamport_clock.tick().value, + }; + let edit = self.apply_local_edit(&ranges, new_text, timestamp); + + self.history.push(edit.clone()); + self.history.push_undo(edit.timestamp.local()); + self.last_edit = edit.timestamp.local(); + self.version.observe(edit.timestamp.local()); + + if let Some((before_edit, edited)) = autoindent_request { + let mut inserted = None; + if let Some(first_newline_ix) = first_newline_ix { + let mut delta = 0isize; + inserted = Some(self.content().anchor_range_set(ranges.iter().map(|range| { + let start = (delta + range.start as isize) as usize + first_newline_ix + 1; + let end = (delta + range.start as isize) as usize + new_text_len; + delta += (range.end as isize - range.start as isize) + new_text_len as isize; + (start, Bias::Left)..(end, Bias::Right) + }))); + } + + let selection_set_ids = self + .history + .undo_stack + .last() + .unwrap() + .selections_before + .keys() + .copied() + .collect(); + self.autoindent_requests.push(Arc::new(AutoindentRequest { + selection_set_ids, + before_edit, + edited, + inserted, + })); + } + + self.end_transaction(None, cx).unwrap(); + self.send_operation(Operation::Edit(edit), cx); } fn did_edit(&self, was_dirty: bool, cx: &mut ModelContext) { @@ -1389,6 +1711,8 @@ impl Buffer { ops: I, cx: &mut ModelContext, ) -> Result<()> { + self.pending_autoindent.take(); + let was_dirty = self.is_dirty(); let old_version = self.version.clone(); @@ -1650,7 +1974,7 @@ impl Buffer { if let Some(transaction) = self.history.pop_undo().cloned() { let selections = transaction.selections_before.clone(); self.undo_or_redo(transaction, cx).unwrap(); - if let Some((set_id, selections)) = selections { + for (set_id, selections) in selections { let _ = self.update_selection_set(set_id, selections, cx); } } @@ -1669,7 +1993,7 @@ impl Buffer { if let Some(transaction) = self.history.pop_redo().cloned() { let selections = transaction.selections_after.clone(); self.undo_or_redo(transaction, cx).unwrap(); - if let Some((set_id, selections)) = selections { + for (set_id, selections) in selections { let _ = self.update_selection_set(set_id, selections, cx); } } @@ -1838,20 +2162,20 @@ impl Buffer { let mut new_ropes = RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); - let mut old_fragments = self.fragments.cursor::<(usize, FragmentTextSummary)>(); + let mut old_fragments = self.fragments.cursor::(); let mut new_fragments = old_fragments.slice(&ranges[0].start, Bias::Right, &None); new_ropes.push_tree(new_fragments.summary().text); - let mut fragment_start = old_fragments.start().1.visible; + let mut fragment_start = old_fragments.start().visible; for range in ranges { - let fragment_end = old_fragments.end(&None).1.visible; + let fragment_end = old_fragments.end(&None).visible; // If the current fragment ends before this range, then jump ahead to the first fragment // that extends past the start of this range, reusing any intervening fragments. if fragment_end < range.start { // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().1.visible { + if fragment_start > old_fragments.start().visible { if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end - fragment_start; @@ -1864,10 +2188,10 @@ impl Buffer { let slice = old_fragments.slice(&range.start, Bias::Right, &None); new_ropes.push_tree(slice.summary().text); new_fragments.push_tree(slice, &None); - fragment_start = old_fragments.start().1.visible; + fragment_start = old_fragments.start().visible; } - let full_range_start = range.start + old_fragments.start().1.deleted; + let full_range_start = range.start + old_fragments.start().deleted; // Preserve any portion of the current fragment that precedes this range. if fragment_start < range.start { @@ -1897,7 +2221,7 @@ impl Buffer { // portions as deleted. while fragment_start < range.end { let fragment = old_fragments.item().unwrap(); - let fragment_end = old_fragments.end(&None).1.visible; + let fragment_end = old_fragments.end(&None).visible; let mut intersection = fragment.clone(); let intersection_end = cmp::min(range.end, fragment_end); if fragment.visible { @@ -1915,14 +2239,14 @@ impl Buffer { } } - let full_range_end = range.end + old_fragments.start().1.deleted; + let full_range_end = range.end + old_fragments.start().deleted; edit.ranges.push(full_range_start..full_range_end); } // If the current fragment has been partially consumed, then consume the rest of it // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().1.visible { - let fragment_end = old_fragments.end(&None).1.visible; + if fragment_start > old_fragments.start().visible { + let fragment_end = old_fragments.end(&None).visible; if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end - fragment_start; @@ -2162,6 +2486,8 @@ impl Clone for Buffer { parsing_in_background: false, sync_parse_timeout: self.sync_parse_timeout, parse_count: self.parse_count, + autoindent_requests: Default::default(), + pending_autoindent: Default::default(), deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, remote_id: self.remote_id.clone(), @@ -2207,6 +2533,124 @@ impl Snapshot { self.content().line_len(row) } + pub fn indent_column_for_line(&self, row: u32) -> u32 { + self.content().indent_column_for_line(row) + } + + fn suggest_autoindents<'a>( + &'a self, + row_range: Range, + ) -> Option + 'a> { + let mut query_cursor = QueryCursorHandle::new(); + if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { + let prev_non_blank_row = self.prev_non_blank_row(row_range.start); + + // Get the "indentation ranges" that intersect this row range. + let indent_capture_ix = language.indents_query.capture_index_for_name("indent"); + let end_capture_ix = language.indents_query.capture_index_for_name("end"); + query_cursor.set_point_range( + Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).into() + ..Point::new(row_range.end, 0).into(), + ); + let mut indentation_ranges = Vec::<(Range, &'static str)>::new(); + for mat in query_cursor.matches( + &language.indents_query, + tree.root_node(), + TextProvider(&self.visible_text), + ) { + let mut node_kind = ""; + let mut start: Option = None; + let mut end: Option = None; + for capture in mat.captures { + if Some(capture.index) == indent_capture_ix { + node_kind = capture.node.kind(); + start.get_or_insert(capture.node.start_position().into()); + end.get_or_insert(capture.node.end_position().into()); + } else if Some(capture.index) == end_capture_ix { + end = Some(capture.node.start_position().into()); + } + } + + if let Some((start, end)) = start.zip(end) { + if start.row == end.row { + continue; + } + + let range = start..end; + match indentation_ranges.binary_search_by_key(&range.start, |r| r.0.start) { + Err(ix) => indentation_ranges.insert(ix, (range, node_kind)), + Ok(ix) => { + let prev_range = &mut indentation_ranges[ix]; + prev_range.0.end = prev_range.0.end.max(range.end); + } + } + } + } + + let mut prev_row = prev_non_blank_row.unwrap_or(0); + Some(row_range.map(move |row| { + let row_start = Point::new(row, self.indent_column_for_line(row)); + + let mut indent_from_prev_row = false; + let mut outdent_to_row = u32::MAX; + for (range, _node_kind) in &indentation_ranges { + if range.start.row >= row { + break; + } + + if range.start.row == prev_row && range.end > row_start { + indent_from_prev_row = true; + } + if range.end.row >= prev_row && range.end <= row_start { + outdent_to_row = outdent_to_row.min(range.start.row); + } + } + + let suggestion = if outdent_to_row == prev_row { + IndentSuggestion { + basis_row: prev_row, + indent: false, + } + } else if indent_from_prev_row { + IndentSuggestion { + basis_row: prev_row, + indent: true, + } + } else if outdent_to_row < prev_row { + IndentSuggestion { + basis_row: outdent_to_row, + indent: false, + } + } else { + IndentSuggestion { + basis_row: prev_row, + indent: false, + } + }; + + prev_row = row; + suggestion + })) + } else { + None + } + } + + fn prev_non_blank_row(&self, mut row: u32) -> Option { + while row > 0 { + row -= 1; + if !self.is_line_blank(row) { + return Some(row); + } + } + None + } + + fn is_line_blank(&self, row: u32) -> bool { + self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row))) + .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) + } + pub fn text(&self) -> Rope { self.visible_text.clone() } @@ -2219,15 +2663,20 @@ impl Snapshot { self.visible_text.max_point() } - pub fn text_for_range(&self, range: Range) -> Chunks { + pub fn text_for_range(&self, range: Range) -> Chunks { + let range = range.start.to_offset(self)..range.end.to_offset(self); self.visible_text.chunks_in_range(range) } - pub fn highlighted_text_for_range(&mut self, range: Range) -> HighlightedChunks { + pub fn highlighted_text_for_range( + &mut self, + range: Range, + ) -> HighlightedChunks { + let range = range.start.to_offset(&*self)..range.end.to_offset(&*self); let chunks = self.visible_text.chunks_in_range(range.clone()); if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { let captures = self.query_cursor.set_byte_range(range.clone()).captures( - &language.highlight_query, + &language.highlights_query, tree.root_node(), TextProvider(&self.visible_text), ); @@ -2347,6 +2796,22 @@ impl<'a> Content<'a> { self.fragments.extent::(&None) } + pub fn chars_at(&self, position: T) -> impl Iterator + 'a { + let offset = position.to_offset(self); + self.visible_text.chars_at(offset) + } + + pub fn reversed_chars_at(&self, position: T) -> impl Iterator + 'a { + let offset = position.to_offset(self); + self.visible_text.reversed_chars_at(offset) + } + + pub fn text_for_range(&self, range: Range) -> Chunks<'a> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.chunks_in_range(start..end) + } + fn line_len(&self, row: u32) -> u32 { let row_start_offset = Point::new(row, 0).to_offset(self); let row_end_offset = if row >= self.max_point().row { @@ -2357,6 +2822,18 @@ impl<'a> Content<'a> { (row_end_offset - row_start_offset) as u32 } + pub fn indent_column_for_line(&self, row: u32) -> u32 { + let mut result = 0; + for c in self.chars_at(Point::new(row, 0)) { + if c == ' ' { + result += 1; + } else { + break; + } + } + result + } + fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary { let cx = Some(anchor.version.clone()); let mut cursor = self.fragments.cursor::<(VersionedOffset, usize)>(); @@ -2373,19 +2850,134 @@ impl<'a> Content<'a> { self.visible_text.cursor(range.start).summary(range.end) } + fn summaries_for_anchors( + &self, + map: &'a AnchorMap, + ) -> impl Iterator { + let cx = Some(map.version.clone()); + let mut summary = TextSummary::default(); + let mut rope_cursor = self.visible_text.cursor(0); + let mut cursor = self.fragments.cursor::<(VersionedOffset, usize)>(); + map.entries.iter().map(move |((offset, bias), value)| { + cursor.seek_forward(&VersionedOffset::Offset(*offset), *bias, &cx); + let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { + offset - cursor.start().0.offset() + } else { + 0 + }; + summary += rope_cursor.summary(cursor.start().1 + overshoot); + (summary.clone(), value) + }) + } + + fn summaries_for_anchor_ranges( + &self, + map: &'a AnchorRangeMap, + ) -> impl Iterator, &'a T)> { + let cx = Some(map.version.clone()); + let mut summary = TextSummary::default(); + let mut rope_cursor = self.visible_text.cursor(0); + let mut cursor = self.fragments.cursor::<(VersionedOffset, usize)>(); + map.entries.iter().map(move |(range, value)| { + let Range { + start: (start_offset, start_bias), + end: (end_offset, end_bias), + } = range; + + cursor.seek_forward(&VersionedOffset::Offset(*start_offset), *start_bias, &cx); + let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { + start_offset - cursor.start().0.offset() + } else { + 0 + }; + summary += rope_cursor.summary(cursor.start().1 + overshoot); + let start_summary = summary.clone(); + + cursor.seek_forward(&VersionedOffset::Offset(*end_offset), *end_bias, &cx); + let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) { + end_offset - cursor.start().0.offset() + } else { + 0 + }; + summary += rope_cursor.summary(cursor.start().1 + overshoot); + let end_summary = summary.clone(); + + (start_summary..end_summary, value) + }) + } + fn anchor_at(&self, position: T, bias: Bias) -> Anchor { let offset = position.to_offset(self); let max_offset = self.len(); assert!(offset <= max_offset, "offset is out of range"); - let mut cursor = self.fragments.cursor::<(usize, FragmentTextSummary)>(); + let mut cursor = self.fragments.cursor::(); cursor.seek(&offset, bias, &None); Anchor { - offset: offset + cursor.start().1.deleted, + offset: offset + cursor.start().deleted, bias, version: self.version.clone(), } } + pub fn anchor_map(&self, entries: E) -> AnchorMap + where + E: IntoIterator, + { + let version = self.version.clone(); + let mut cursor = self.fragments.cursor::(); + let entries = entries + .into_iter() + .map(|((offset, bias), value)| { + cursor.seek_forward(&offset, bias, &None); + let full_offset = cursor.start().deleted + offset; + ((full_offset, bias), value) + }) + .collect(); + + AnchorMap { version, entries } + } + + pub fn anchor_range_map(&self, entries: E) -> AnchorRangeMap + where + E: IntoIterator, T)>, + { + let version = self.version.clone(); + let mut cursor = self.fragments.cursor::(); + let entries = entries + .into_iter() + .map(|(range, value)| { + let Range { + start: (start_offset, start_bias), + end: (end_offset, end_bias), + } = range; + cursor.seek_forward(&start_offset, start_bias, &None); + let full_start_offset = cursor.start().deleted + start_offset; + cursor.seek_forward(&end_offset, end_bias, &None); + let full_end_offset = cursor.start().deleted + end_offset; + ( + (full_start_offset, start_bias)..(full_end_offset, end_bias), + value, + ) + }) + .collect(); + + AnchorRangeMap { version, entries } + } + + pub fn anchor_set(&self, entries: E) -> AnchorSet + where + E: IntoIterator, + { + AnchorSet(self.anchor_map(entries.into_iter().map(|range| (range, ())))) + } + + pub fn anchor_range_set(&self, entries: E) -> AnchorRangeSet + where + E: IntoIterator>, + { + AnchorRangeSet(self.anchor_range_map(entries.into_iter().map(|range| (range, ())))) + } + fn full_offset_for_anchor(&self, anchor: &Anchor) -> usize { let cx = Some(anchor.version.clone()); let mut cursor = self @@ -2410,6 +3002,12 @@ impl<'a> Content<'a> { } } +#[derive(Debug)] +struct IndentSuggestion { + basis_row: u32, + indent: bool, +} + struct RopeBuilder<'a> { old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>, @@ -2753,6 +3351,16 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for usize { } } +impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, FragmentTextSummary> for usize { + fn cmp( + &self, + cursor_location: &FragmentTextSummary, + _: &Option, + ) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.visible) + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum VersionedOffset { Offset(usize), @@ -3130,982 +3738,28 @@ impl ToPoint for usize { } } -#[cfg(test)] -mod tests { - use crate::random_char_iter::RandomCharIter; - - use super::*; - use gpui::ModelHandle; - use rand::prelude::*; - use std::{cell::RefCell, cmp::Ordering, env, mem, rc::Rc}; - - #[gpui::test] - fn test_edit(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "abc", cx); - assert_eq!(buffer.text(), "abc"); - buffer.edit(vec![3..3], "def", cx); - assert_eq!(buffer.text(), "abcdef"); - buffer.edit(vec![0..0], "ghi", cx); - assert_eq!(buffer.text(), "ghiabcdef"); - buffer.edit(vec![5..5], "jkl", cx); - assert_eq!(buffer.text(), "ghiabjklcdef"); - buffer.edit(vec![6..7], "", cx); - assert_eq!(buffer.text(), "ghiabjlcdef"); - buffer.edit(vec![4..9], "mno", cx); - assert_eq!(buffer.text(), "ghiamnoef"); - buffer - }); - } - - #[gpui::test] - fn test_edit_events(cx: &mut gpui::MutableAppContext) { - let mut now = Instant::now(); - let buffer_1_events = Rc::new(RefCell::new(Vec::new())); - let buffer_2_events = Rc::new(RefCell::new(Vec::new())); - - let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx)); - let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx)); - let buffer_ops = buffer1.update(cx, |buffer, cx| { - let buffer_1_events = buffer_1_events.clone(); - cx.subscribe(&buffer1, move |_, _, event, _| { - buffer_1_events.borrow_mut().push(event.clone()) - }) - .detach(); - let buffer_2_events = buffer_2_events.clone(); - cx.subscribe(&buffer2, move |_, _, event, _| { - buffer_2_events.borrow_mut().push(event.clone()) - }) - .detach(); - - // An edit emits an edited event, followed by a dirtied event, - // since the buffer was previously in a clean state. - buffer.edit(Some(2..4), "XYZ", cx); - - // An empty transaction does not emit any events. - buffer.start_transaction(None).unwrap(); - buffer.end_transaction(None, cx).unwrap(); - - // A transaction containing two edits emits one edited event. - now += Duration::from_secs(1); - buffer.start_transaction_at(None, now).unwrap(); - buffer.edit(Some(5..5), "u", cx); - buffer.edit(Some(6..6), "w", cx); - buffer.end_transaction_at(None, now, cx).unwrap(); - - // Undoing a transaction emits one edited event. - buffer.undo(cx); - - buffer.operations.clone() - }); - - // Incorporating a set of remote ops emits a single edited event, - // followed by a dirtied event. - buffer2.update(cx, |buffer, cx| { - buffer.apply_ops(buffer_ops, cx).unwrap(); - }); - - let buffer_1_events = buffer_1_events.borrow(); - assert_eq!( - *buffer_1_events, - vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited] - ); - - let buffer_2_events = buffer_2_events.borrow(); - assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]); - } - - #[gpui::test(iterations = 100)] - fn test_random_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); - - let reference_string_len = rng.gen_range(0..3); - let mut reference_string = RandomCharIter::new(&mut rng) - .take(reference_string_len) - .collect::(); - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, reference_string.as_str(), cx); - buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); - let mut buffer_versions = Vec::new(); - log::info!( - "buffer text {:?}, version: {:?}", - buffer.text(), - buffer.version() - ); - - for _i in 0..operations { - let (old_ranges, new_text) = buffer.randomly_mutate(&mut rng, cx); - for old_range in old_ranges.iter().rev() { - reference_string.replace_range(old_range.clone(), &new_text); - } - assert_eq!(buffer.text(), reference_string); - log::info!( - "buffer text {:?}, version: {:?}", - buffer.text(), - buffer.version() - ); - - if rng.gen_bool(0.25) { - buffer.randomly_undo_redo(&mut rng, cx); - reference_string = buffer.text(); - log::info!( - "buffer text {:?}, version: {:?}", - buffer.text(), - buffer.version() - ); - } - - let range = buffer.random_byte_range(0, &mut rng); - assert_eq!( - buffer.text_summary_for_range(range.clone()), - TextSummary::from(&reference_string[range]) - ); - - if rng.gen_bool(0.3) { - buffer_versions.push(buffer.clone()); +fn contiguous_ranges( + values: impl IntoIterator, + max_len: usize, +) -> impl Iterator> { + let mut values = values.into_iter(); + let mut current_range: Option> = None; + std::iter::from_fn(move || loop { + if let Some(value) = values.next() { + if let Some(range) = &mut current_range { + if value == range.end && range.len() < max_len { + range.end += 1; + continue; } } - for mut old_buffer in buffer_versions { - let edits = buffer - .edits_since(old_buffer.version.clone()) - .collect::>(); - - log::info!( - "mutating old buffer version {:?}, text: {:?}, edits since: {:?}", - old_buffer.version(), - old_buffer.text(), - edits, - ); - - let mut delta = 0_isize; - for edit in edits { - let old_start = (edit.old_bytes.start as isize + delta) as usize; - let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect(); - old_buffer.edit( - Some(old_start..old_start + edit.deleted_bytes()), - new_text, - cx, - ); - delta += edit.delta(); - } - assert_eq!(old_buffer.text(), buffer.text()); + let prev_range = current_range.clone(); + current_range = Some(value..(value + 1)); + if prev_range.is_some() { + return prev_range; } - - buffer - }); - } - - #[gpui::test] - fn test_line_len(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "abcd\nefg\nhij", cx); - buffer.edit(vec![12..12], "kl\nmno", cx); - buffer.edit(vec![18..18], "\npqrs\n", cx); - buffer.edit(vec![18..21], "\nPQ", cx); - - assert_eq!(buffer.line_len(0), 4); - assert_eq!(buffer.line_len(1), 3); - assert_eq!(buffer.line_len(2), 5); - assert_eq!(buffer.line_len(3), 3); - assert_eq!(buffer.line_len(4), 4); - assert_eq!(buffer.line_len(5), 0); - buffer - }); - } - - #[gpui::test] - fn test_text_summary_for_range(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", cx); - assert_eq!( - buffer.text_summary_for_range(1..3), - TextSummary { - bytes: 2, - lines: Point::new(1, 0), - first_line_chars: 1, - last_line_chars: 0, - longest_row: 0, - longest_row_chars: 1, - } - ); - assert_eq!( - buffer.text_summary_for_range(1..12), - TextSummary { - bytes: 11, - lines: Point::new(3, 0), - first_line_chars: 1, - last_line_chars: 0, - longest_row: 2, - longest_row_chars: 4, - } - ); - assert_eq!( - buffer.text_summary_for_range(0..20), - TextSummary { - bytes: 20, - lines: Point::new(4, 1), - first_line_chars: 2, - last_line_chars: 1, - longest_row: 3, - longest_row_chars: 6, - } - ); - assert_eq!( - buffer.text_summary_for_range(0..22), - TextSummary { - bytes: 22, - lines: Point::new(4, 3), - first_line_chars: 2, - last_line_chars: 3, - longest_row: 3, - longest_row_chars: 6, - } - ); - assert_eq!( - buffer.text_summary_for_range(7..22), - TextSummary { - bytes: 15, - lines: Point::new(2, 3), - first_line_chars: 4, - last_line_chars: 3, - longest_row: 1, - longest_row_chars: 6, - } - ); - buffer - }); - } - - #[gpui::test] - fn test_chars_at(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "abcd\nefgh\nij", cx); - buffer.edit(vec![12..12], "kl\nmno", cx); - buffer.edit(vec![18..18], "\npqrs", cx); - buffer.edit(vec![18..21], "\nPQ", cx); - - let chars = buffer.chars_at(Point::new(0, 0)); - assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); - - let chars = buffer.chars_at(Point::new(1, 0)); - assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); - - let chars = buffer.chars_at(Point::new(2, 0)); - assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); - - let chars = buffer.chars_at(Point::new(3, 0)); - assert_eq!(chars.collect::(), "mno\nPQrs"); - - let chars = buffer.chars_at(Point::new(4, 0)); - assert_eq!(chars.collect::(), "PQrs"); - - // Regression test: - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", cx); - buffer.edit(vec![60..60], "\n", cx); - - let chars = buffer.chars_at(Point::new(6, 0)); - assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); - - buffer - }); - } - - #[gpui::test] - fn test_anchors(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "abc", cx); - let left_anchor = buffer.anchor_before(2); - let right_anchor = buffer.anchor_after(2); - - buffer.edit(vec![1..1], "def\n", cx); - assert_eq!(buffer.text(), "adef\nbc"); - assert_eq!(left_anchor.to_offset(&buffer), 6); - assert_eq!(right_anchor.to_offset(&buffer), 6); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - - buffer.edit(vec![2..3], "", cx); - assert_eq!(buffer.text(), "adf\nbc"); - assert_eq!(left_anchor.to_offset(&buffer), 5); - assert_eq!(right_anchor.to_offset(&buffer), 5); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - - buffer.edit(vec![5..5], "ghi\n", cx); - assert_eq!(buffer.text(), "adf\nbghi\nc"); - assert_eq!(left_anchor.to_offset(&buffer), 5); - assert_eq!(right_anchor.to_offset(&buffer), 9); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); - - buffer.edit(vec![7..9], "", cx); - assert_eq!(buffer.text(), "adf\nbghc"); - assert_eq!(left_anchor.to_offset(&buffer), 5); - assert_eq!(right_anchor.to_offset(&buffer), 7); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 }); - - // Ensure anchoring to a point is equivalent to anchoring to an offset. - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 0 }), - buffer.anchor_before(0) - ); - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 1 }), - buffer.anchor_before(1) - ); - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 2 }), - buffer.anchor_before(2) - ); - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 3 }), - buffer.anchor_before(3) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 0 }), - buffer.anchor_before(4) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 1 }), - buffer.anchor_before(5) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 2 }), - buffer.anchor_before(6) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 3 }), - buffer.anchor_before(7) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 4 }), - buffer.anchor_before(8) - ); - - // Comparison between anchors. - let anchor_at_offset_0 = buffer.anchor_before(0); - let anchor_at_offset_1 = buffer.anchor_before(1); - let anchor_at_offset_2 = buffer.anchor_before(2); - - assert_eq!( - anchor_at_offset_0 - .cmp(&anchor_at_offset_0, &buffer) - .unwrap(), - Ordering::Equal - ); - assert_eq!( - anchor_at_offset_1 - .cmp(&anchor_at_offset_1, &buffer) - .unwrap(), - Ordering::Equal - ); - assert_eq!( - anchor_at_offset_2 - .cmp(&anchor_at_offset_2, &buffer) - .unwrap(), - Ordering::Equal - ); - - assert_eq!( - anchor_at_offset_0 - .cmp(&anchor_at_offset_1, &buffer) - .unwrap(), - Ordering::Less - ); - assert_eq!( - anchor_at_offset_1 - .cmp(&anchor_at_offset_2, &buffer) - .unwrap(), - Ordering::Less - ); - assert_eq!( - anchor_at_offset_0 - .cmp(&anchor_at_offset_2, &buffer) - .unwrap(), - Ordering::Less - ); - - assert_eq!( - anchor_at_offset_1 - .cmp(&anchor_at_offset_0, &buffer) - .unwrap(), - Ordering::Greater - ); - assert_eq!( - anchor_at_offset_2 - .cmp(&anchor_at_offset_1, &buffer) - .unwrap(), - Ordering::Greater - ); - assert_eq!( - anchor_at_offset_2 - .cmp(&anchor_at_offset_0, &buffer) - .unwrap(), - Ordering::Greater - ); - buffer - }); - } - - #[gpui::test] - fn test_anchors_at_start_and_end(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - let before_start_anchor = buffer.anchor_before(0); - let after_end_anchor = buffer.anchor_after(0); - - buffer.edit(vec![0..0], "abc", cx); - assert_eq!(buffer.text(), "abc"); - assert_eq!(before_start_anchor.to_offset(&buffer), 0); - assert_eq!(after_end_anchor.to_offset(&buffer), 3); - - let after_start_anchor = buffer.anchor_after(0); - let before_end_anchor = buffer.anchor_before(3); - - buffer.edit(vec![3..3], "def", cx); - buffer.edit(vec![0..0], "ghi", cx); - assert_eq!(buffer.text(), "ghiabcdef"); - assert_eq!(before_start_anchor.to_offset(&buffer), 0); - assert_eq!(after_start_anchor.to_offset(&buffer), 3); - assert_eq!(before_end_anchor.to_offset(&buffer), 6); - assert_eq!(after_end_anchor.to_offset(&buffer), 9); - buffer - }); - } - - #[gpui::test] - async fn test_apply_diff(mut cx: gpui::TestAppContext) { - let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n"; - let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - - let text = "a\nccc\ndddd\nffffff\n"; - let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await; - buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx)); - cx.read(|cx| assert_eq!(buffer.read(cx).text(), text)); - - let text = "a\n1\n\nccc\ndd2dd\nffffff\n"; - let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await; - buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx)); - cx.read(|cx| assert_eq!(buffer.read(cx).text(), text)); - } - - #[gpui::test] - fn test_undo_redo(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "1234", cx); - // Set group interval to zero so as to not group edits in the undo stack. - buffer.history.group_interval = Duration::from_secs(0); - - buffer.edit(vec![1..1], "abx", cx); - buffer.edit(vec![3..4], "yzef", cx); - buffer.edit(vec![3..5], "cd", cx); - assert_eq!(buffer.text(), "1abcdef234"); - - let transactions = buffer.history.undo_stack.clone(); - assert_eq!(transactions.len(), 3); - - buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1cdef234"); - buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abcdef234"); - - buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abcdx234"); - buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abx234"); - buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abyzef234"); - buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abcdef234"); - - buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abyzef234"); - buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1yzef234"); - buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1234"); - - buffer - }); - } - - #[gpui::test] - fn test_history(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut now = Instant::now(); - let mut buffer = Buffer::new(0, "123456", cx); - - let set_id = - buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), cx); - buffer.start_transaction_at(Some(set_id), now).unwrap(); - buffer.edit(vec![2..4], "cd", cx); - buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); - assert_eq!(buffer.text(), "12cd56"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); - - buffer.start_transaction_at(Some(set_id), now).unwrap(); - buffer - .update_selection_set( - set_id, - buffer.selections_from_ranges(vec![1..3]).unwrap(), - cx, - ) - .unwrap(); - buffer.edit(vec![4..5], "e", cx); - buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); - assert_eq!(buffer.text(), "12cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); - - now += buffer.history.group_interval + Duration::from_millis(1); - buffer.start_transaction_at(Some(set_id), now).unwrap(); - buffer - .update_selection_set( - set_id, - buffer.selections_from_ranges(vec![2..2]).unwrap(), - cx, - ) - .unwrap(); - buffer.edit(vec![0..1], "a", cx); - buffer.edit(vec![1..1], "b", cx); - buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); - assert_eq!(buffer.text(), "ab2cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); - - // Last transaction happened past the group interval, undo it on its - // own. - buffer.undo(cx); - assert_eq!(buffer.text(), "12cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); - - // First two transactions happened within the group interval, undo them - // together. - buffer.undo(cx); - assert_eq!(buffer.text(), "123456"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); - - // Redo the first two transactions together. - buffer.redo(cx); - assert_eq!(buffer.text(), "12cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); - - // Redo the last transaction on its own. - buffer.redo(cx); - assert_eq!(buffer.text(), "ab2cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); - - buffer - }); - } - - #[gpui::test] - fn test_concurrent_edits(cx: &mut gpui::MutableAppContext) { - let text = "abcdef"; - - let buffer1 = cx.add_model(|cx| Buffer::new(1, text, cx)); - let buffer2 = cx.add_model(|cx| Buffer::new(2, text, cx)); - let buffer3 = cx.add_model(|cx| Buffer::new(3, text, cx)); - - let buf1_op = buffer1.update(cx, |buffer, cx| { - buffer.edit(vec![1..2], "12", cx); - assert_eq!(buffer.text(), "a12cdef"); - buffer.operations.last().unwrap().clone() - }); - let buf2_op = buffer2.update(cx, |buffer, cx| { - buffer.edit(vec![3..4], "34", cx); - assert_eq!(buffer.text(), "abc34ef"); - buffer.operations.last().unwrap().clone() - }); - let buf3_op = buffer3.update(cx, |buffer, cx| { - buffer.edit(vec![5..6], "56", cx); - assert_eq!(buffer.text(), "abcde56"); - buffer.operations.last().unwrap().clone() - }); - - buffer1.update(cx, |buffer, _| { - buffer.apply_op(buf2_op.clone()).unwrap(); - buffer.apply_op(buf3_op.clone()).unwrap(); - }); - buffer2.update(cx, |buffer, _| { - buffer.apply_op(buf1_op.clone()).unwrap(); - buffer.apply_op(buf3_op.clone()).unwrap(); - }); - buffer3.update(cx, |buffer, _| { - buffer.apply_op(buf1_op.clone()).unwrap(); - buffer.apply_op(buf2_op.clone()).unwrap(); - }); - - assert_eq!(buffer1.read(cx).text(), "a12c34e56"); - assert_eq!(buffer2.read(cx).text(), "a12c34e56"); - assert_eq!(buffer3.read(cx).text(), "a12c34e56"); - } - - #[gpui::test(iterations = 100)] - fn test_random_concurrent_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { - let peers = env::var("PEERS") - .map(|i| i.parse().expect("invalid `PEERS` variable")) - .unwrap_or(5); - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); - - let base_text_len = rng.gen_range(0..10); - let base_text = RandomCharIter::new(&mut rng) - .take(base_text_len) - .collect::(); - let mut replica_ids = Vec::new(); - let mut buffers = Vec::new(); - let mut network = Network::new(rng.clone()); - - for i in 0..peers { - let buffer = cx.add_model(|cx| { - let mut buf = Buffer::new(i as ReplicaId, base_text.as_str(), cx); - buf.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); - buf - }); - buffers.push(buffer); - replica_ids.push(i as u16); - network.add_peer(i as u16); + } else { + return current_range.take(); } - - log::info!("initial text: {:?}", base_text); - - let mut mutation_count = operations; - loop { - let replica_index = rng.gen_range(0..peers); - let replica_id = replica_ids[replica_index]; - buffers[replica_index].update(cx, |buffer, cx| match rng.gen_range(0..=100) { - 0..=50 if mutation_count != 0 => { - buffer.randomly_mutate(&mut rng, cx); - network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); - log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); - mutation_count -= 1; - } - 51..=70 if mutation_count != 0 => { - buffer.randomly_undo_redo(&mut rng, cx); - network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); - mutation_count -= 1; - } - 71..=100 if network.has_unreceived(replica_id) => { - let ops = network.receive(replica_id); - if !ops.is_empty() { - log::info!( - "peer {} applying {} ops from the network.", - replica_id, - ops.len() - ); - buffer.apply_ops(ops, cx).unwrap(); - } - } - _ => {} - }); - - if mutation_count == 0 && network.is_idle() { - break; - } - } - - let first_buffer = buffers[0].read(cx); - for buffer in &buffers[1..] { - let buffer = buffer.read(cx); - assert_eq!( - buffer.text(), - first_buffer.text(), - "Replica {} text != Replica 0 text", - buffer.replica_id - ); - assert_eq!( - buffer.selection_sets().collect::>(), - first_buffer.selection_sets().collect::>() - ); - assert_eq!( - buffer.all_selection_ranges().collect::>(), - first_buffer - .all_selection_ranges() - .collect::>() - ); - } - } - - #[gpui::test] - async fn test_reparse(mut cx: gpui::TestAppContext) { - let rust_lang = rust_lang(); - let buffer = cx.add_model(|cx| { - let text = "fn a() {}".into(); - Buffer::from_history(0, History::new(text), None, Some(rust_lang.clone()), cx) - }); - - // Wait for the initial text to parse - buffer - .condition(&cx, |buffer, _| !buffer.is_parsing()) - .await; - assert_eq!( - get_tree_sexp(&buffer, &cx), - concat!( - "(source_file (function_item name: (identifier) ", - "parameters: (parameters) ", - "body: (block)))" - ) - ); - - buffer.update(&mut cx, |buffer, _| { - buffer.set_sync_parse_timeout(Duration::ZERO) - }); - - // Perform some edits (add parameter and variable reference) - // Parsing doesn't begin until the transaction is complete - buffer.update(&mut cx, |buf, cx| { - buf.start_transaction(None).unwrap(); - - let offset = buf.text().find(")").unwrap(); - buf.edit(vec![offset..offset], "b: C", cx); - assert!(!buf.is_parsing()); - - let offset = buf.text().find("}").unwrap(); - buf.edit(vec![offset..offset], " d; ", cx); - assert!(!buf.is_parsing()); - - buf.end_transaction(None, cx).unwrap(); - assert_eq!(buf.text(), "fn a(b: C) { d; }"); - assert!(buf.is_parsing()); - }); - buffer - .condition(&cx, |buffer, _| !buffer.is_parsing()) - .await; - assert_eq!( - get_tree_sexp(&buffer, &cx), - concat!( - "(source_file (function_item name: (identifier) ", - "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", - "body: (block (identifier))))" - ) - ); - - // Perform a series of edits without waiting for the current parse to complete: - // * turn identifier into a field expression - // * turn field expression into a method call - // * add a turbofish to the method call - buffer.update(&mut cx, |buf, cx| { - let offset = buf.text().find(";").unwrap(); - buf.edit(vec![offset..offset], ".e", cx); - assert_eq!(buf.text(), "fn a(b: C) { d.e; }"); - assert!(buf.is_parsing()); - }); - buffer.update(&mut cx, |buf, cx| { - let offset = buf.text().find(";").unwrap(); - buf.edit(vec![offset..offset], "(f)", cx); - assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }"); - assert!(buf.is_parsing()); - }); - buffer.update(&mut cx, |buf, cx| { - let offset = buf.text().find("(f)").unwrap(); - buf.edit(vec![offset..offset], "::", cx); - assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); - assert!(buf.is_parsing()); - }); - buffer - .condition(&cx, |buffer, _| !buffer.is_parsing()) - .await; - assert_eq!( - get_tree_sexp(&buffer, &cx), - concat!( - "(source_file (function_item name: (identifier) ", - "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", - "body: (block (call_expression ", - "function: (generic_function ", - "function: (field_expression value: (identifier) field: (field_identifier)) ", - "type_arguments: (type_arguments (type_identifier))) ", - "arguments: (arguments (identifier))))))", - ) - ); - - buffer.update(&mut cx, |buf, cx| { - buf.undo(cx); - assert_eq!(buf.text(), "fn a() {}"); - assert!(buf.is_parsing()); - }); - buffer - .condition(&cx, |buffer, _| !buffer.is_parsing()) - .await; - assert_eq!( - get_tree_sexp(&buffer, &cx), - concat!( - "(source_file (function_item name: (identifier) ", - "parameters: (parameters) ", - "body: (block)))" - ) - ); - - buffer.update(&mut cx, |buf, cx| { - buf.redo(cx); - assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); - assert!(buf.is_parsing()); - }); - buffer - .condition(&cx, |buffer, _| !buffer.is_parsing()) - .await; - assert_eq!( - get_tree_sexp(&buffer, &cx), - concat!( - "(source_file (function_item name: (identifier) ", - "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", - "body: (block (call_expression ", - "function: (generic_function ", - "function: (field_expression value: (identifier) field: (field_identifier)) ", - "type_arguments: (type_arguments (type_identifier))) ", - "arguments: (arguments (identifier))))))", - ) - ); - - fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> String { - buffer.read_with(cx, |buffer, _| { - buffer.syntax_tree().unwrap().root_node().to_sexp() - }) - } - } - - #[gpui::test] - async fn test_enclosing_bracket_ranges(mut cx: gpui::TestAppContext) { - use unindent::Unindent as _; - - let rust_lang = rust_lang(); - let buffer = cx.add_model(|cx| { - let text = " - mod x { - mod y { - - } - } - " - .unindent() - .into(); - Buffer::from_history(0, History::new(text), None, Some(rust_lang.clone()), cx) - }); - buffer - .condition(&cx, |buffer, _| !buffer.is_parsing()) - .await; - buffer.read_with(&cx, |buf, _| { - assert_eq!( - buf.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)), - Some(( - Point::new(0, 6)..Point::new(0, 7), - Point::new(4, 0)..Point::new(4, 1) - )) - ); - assert_eq!( - buf.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)), - Some(( - Point::new(1, 10)..Point::new(1, 11), - Point::new(3, 4)..Point::new(3, 5) - )) - ); - assert_eq!( - buf.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)), - Some(( - Point::new(1, 10)..Point::new(1, 11), - Point::new(3, 4)..Point::new(3, 5) - )) - ); - }); - } - - #[derive(Clone)] - struct Envelope { - message: T, - sender: ReplicaId, - } - - struct Network { - inboxes: std::collections::BTreeMap>>, - all_messages: Vec, - rng: R, - } - - impl Network { - fn new(rng: R) -> Self { - Network { - inboxes: Default::default(), - all_messages: Vec::new(), - rng, - } - } - - fn add_peer(&mut self, id: ReplicaId) { - self.inboxes.insert(id, Vec::new()); - } - - fn is_idle(&self) -> bool { - self.inboxes.values().all(|i| i.is_empty()) - } - - fn broadcast(&mut self, sender: ReplicaId, messages: Vec) { - for (replica, inbox) in self.inboxes.iter_mut() { - if *replica != sender { - for message in &messages { - let min_index = inbox - .iter() - .enumerate() - .rev() - .find_map(|(index, envelope)| { - if sender == envelope.sender { - Some(index + 1) - } else { - None - } - }) - .unwrap_or(0); - - // Insert one or more duplicates of this message *after* the previous - // message delivered by this replica. - for _ in 0..self.rng.gen_range(1..4) { - let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1); - inbox.insert( - insertion_index, - Envelope { - message: message.clone(), - sender, - }, - ); - } - } - } - } - self.all_messages.extend(messages); - } - - fn has_unreceived(&self, receiver: ReplicaId) -> bool { - !self.inboxes[&receiver].is_empty() - } - - fn receive(&mut self, receiver: ReplicaId) -> Vec { - let inbox = self.inboxes.get_mut(&receiver).unwrap(); - let count = self.rng.gen_range(0..inbox.len() + 1); - inbox - .drain(0..count) - .map(|envelope| envelope.message) - .collect() - } - } - - fn rust_lang() -> Arc { - Arc::new( - Language::new( - LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - ..Default::default() - }, - tree_sitter_rust::language(), - ) - .with_brackets_query(r#" ("{" @open "}" @close) "#) - .unwrap(), - ) - } + }) } diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index a5f4b905ba..a1c5714002 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -115,6 +115,11 @@ impl Rope { self.chunks_in_range(start..self.len()).flat_map(str::chars) } + pub fn reversed_chars_at(&self, start: usize) -> impl Iterator + '_ { + self.reversed_chunks_in_range(0..start) + .flat_map(|chunk| chunk.chars().rev()) + } + pub fn bytes_at(&self, start: usize) -> impl Iterator + '_ { self.chunks_in_range(start..self.len()).flat_map(str::bytes) } @@ -123,8 +128,12 @@ impl Rope { self.chunks_in_range(0..self.len()) } - pub fn chunks_in_range<'a>(&'a self, range: Range) -> Chunks<'a> { - Chunks::new(self, range) + pub fn chunks_in_range(&self, range: Range) -> Chunks { + Chunks::new(self, range, false) + } + + pub fn reversed_chunks_in_range(&self, range: Range) -> Chunks { + Chunks::new(self, range, true) } pub fn to_point(&self, offset: usize) -> Point { @@ -268,6 +277,7 @@ impl<'a> Cursor<'a> { } } + self.offset = end_offset; summary } @@ -283,38 +293,65 @@ impl<'a> Cursor<'a> { pub struct Chunks<'a> { chunks: sum_tree::Cursor<'a, Chunk, usize>, range: Range, + reversed: bool, } impl<'a> Chunks<'a> { - pub fn new(rope: &'a Rope, range: Range) -> Self { + pub fn new(rope: &'a Rope, range: Range, reversed: bool) -> Self { let mut chunks = rope.chunks.cursor(); - chunks.seek(&range.start, Bias::Right, &()); - Self { chunks, range } + if reversed { + chunks.seek(&range.end, Bias::Left, &()); + } else { + chunks.seek(&range.start, Bias::Right, &()); + } + Self { + chunks, + range, + reversed, + } } pub fn offset(&self) -> usize { - self.range.start.max(*self.chunks.start()) + if self.reversed { + self.range.end.min(self.chunks.end(&())) + } else { + self.range.start.max(*self.chunks.start()) + } } pub fn seek(&mut self, offset: usize) { - if offset >= self.chunks.end(&()) { - self.chunks.seek_forward(&offset, Bias::Right, &()); + let bias = if self.reversed { + Bias::Left } else { - self.chunks.seek(&offset, Bias::Right, &()); + Bias::Right + }; + + if offset >= self.chunks.end(&()) { + self.chunks.seek_forward(&offset, bias, &()); + } else { + self.chunks.seek(&offset, bias, &()); + } + + if self.reversed { + self.range.end = offset; + } else { + self.range.start = offset; } - self.range.start = offset; } pub fn peek(&self) -> Option<&'a str> { - if let Some(chunk) = self.chunks.item() { - let offset = *self.chunks.start(); - if self.range.end > offset { - let start = self.range.start.saturating_sub(*self.chunks.start()); - let end = self.range.end - self.chunks.start(); - return Some(&chunk.0[start..chunk.0.len().min(end)]); - } + let chunk = self.chunks.item()?; + if self.reversed && self.range.start >= self.chunks.end(&()) { + return None; } - None + let chunk_start = *self.chunks.start(); + if self.range.end <= chunk_start { + return None; + } + + let start = self.range.start.saturating_sub(chunk_start); + let end = self.range.end - chunk_start; + Some(&chunk.0[start..chunk.0.len().min(end)]) } } @@ -324,7 +361,11 @@ impl<'a> Iterator for Chunks<'a> { fn next(&mut self) -> Option { let result = self.peek(); if result.is_some() { - self.chunks.next(&()); + if self.reversed { + self.chunks.prev(&()); + } else { + self.chunks.next(&()); + } } result } @@ -570,6 +611,16 @@ mod tests { actual.chunks_in_range(start_ix..end_ix).collect::(), &expected[start_ix..end_ix] ); + + assert_eq!( + actual + .reversed_chunks_in_range(start_ix..end_ix) + .collect::>() + .into_iter() + .rev() + .collect::(), + &expected[start_ix..end_ix] + ); } let mut point = Point::new(0, 0); diff --git a/crates/buffer/src/tests.rs b/crates/buffer/src/tests.rs new file mode 100644 index 0000000000..c1b6050de3 --- /dev/null +++ b/crates/buffer/src/tests.rs @@ -0,0 +1,2 @@ +mod buffer; +mod syntax; diff --git a/crates/buffer/src/tests/buffer.rs b/crates/buffer/src/tests/buffer.rs new file mode 100644 index 0000000000..7c627a45ed --- /dev/null +++ b/crates/buffer/src/tests/buffer.rs @@ -0,0 +1,790 @@ +use crate::*; +use clock::ReplicaId; +use rand::prelude::*; +use std::{ + cell::RefCell, + cmp::Ordering, + env, + iter::Iterator, + mem, + rc::Rc, + time::{Duration, Instant}, +}; + +#[gpui::test] +fn test_edit(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let mut buffer = Buffer::new(0, "abc", cx); + assert_eq!(buffer.text(), "abc"); + buffer.edit(vec![3..3], "def", cx); + assert_eq!(buffer.text(), "abcdef"); + buffer.edit(vec![0..0], "ghi", cx); + assert_eq!(buffer.text(), "ghiabcdef"); + buffer.edit(vec![5..5], "jkl", cx); + assert_eq!(buffer.text(), "ghiabjklcdef"); + buffer.edit(vec![6..7], "", cx); + assert_eq!(buffer.text(), "ghiabjlcdef"); + buffer.edit(vec![4..9], "mno", cx); + assert_eq!(buffer.text(), "ghiamnoef"); + buffer + }); +} + +#[gpui::test] +fn test_edit_events(cx: &mut gpui::MutableAppContext) { + let mut now = Instant::now(); + let buffer_1_events = Rc::new(RefCell::new(Vec::new())); + let buffer_2_events = Rc::new(RefCell::new(Vec::new())); + + let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx)); + let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx)); + let buffer_ops = buffer1.update(cx, |buffer, cx| { + let buffer_1_events = buffer_1_events.clone(); + cx.subscribe(&buffer1, move |_, _, event, _| { + buffer_1_events.borrow_mut().push(event.clone()) + }) + .detach(); + let buffer_2_events = buffer_2_events.clone(); + cx.subscribe(&buffer2, move |_, _, event, _| { + buffer_2_events.borrow_mut().push(event.clone()) + }) + .detach(); + + // An edit emits an edited event, followed by a dirtied event, + // since the buffer was previously in a clean state. + buffer.edit(Some(2..4), "XYZ", cx); + + // An empty transaction does not emit any events. + buffer.start_transaction(None).unwrap(); + buffer.end_transaction(None, cx).unwrap(); + + // A transaction containing two edits emits one edited event. + now += Duration::from_secs(1); + buffer.start_transaction_at(None, now).unwrap(); + buffer.edit(Some(5..5), "u", cx); + buffer.edit(Some(6..6), "w", cx); + buffer.end_transaction_at(None, now, cx).unwrap(); + + // Undoing a transaction emits one edited event. + buffer.undo(cx); + + buffer.operations.clone() + }); + + // Incorporating a set of remote ops emits a single edited event, + // followed by a dirtied event. + buffer2.update(cx, |buffer, cx| { + buffer.apply_ops(buffer_ops, cx).unwrap(); + }); + + let buffer_1_events = buffer_1_events.borrow(); + assert_eq!( + *buffer_1_events, + vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited] + ); + + let buffer_2_events = buffer_2_events.borrow(); + assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]); +} + +#[gpui::test(iterations = 100)] +fn test_random_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let reference_string_len = rng.gen_range(0..3); + let mut reference_string = RandomCharIter::new(&mut rng) + .take(reference_string_len) + .collect::(); + cx.add_model(|cx| { + let mut buffer = Buffer::new(0, reference_string.as_str(), cx); + buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); + let mut buffer_versions = Vec::new(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + for _i in 0..operations { + let (old_ranges, new_text) = buffer.randomly_mutate(&mut rng, cx); + for old_range in old_ranges.iter().rev() { + reference_string.replace_range(old_range.clone(), &new_text); + } + assert_eq!(buffer.text(), reference_string); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + if rng.gen_bool(0.25) { + buffer.randomly_undo_redo(&mut rng, cx); + reference_string = buffer.text(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + } + + let range = buffer.random_byte_range(0, &mut rng); + assert_eq!( + buffer.text_summary_for_range(range.clone()), + TextSummary::from(&reference_string[range]) + ); + + if rng.gen_bool(0.3) { + buffer_versions.push(buffer.clone()); + } + } + + for mut old_buffer in buffer_versions { + let edits = buffer + .edits_since(old_buffer.version.clone()) + .collect::>(); + + log::info!( + "mutating old buffer version {:?}, text: {:?}, edits since: {:?}", + old_buffer.version(), + old_buffer.text(), + edits, + ); + + let mut delta = 0_isize; + for edit in edits { + let old_start = (edit.old_bytes.start as isize + delta) as usize; + let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect(); + old_buffer.edit( + Some(old_start..old_start + edit.deleted_bytes()), + new_text, + cx, + ); + delta += edit.delta(); + } + assert_eq!(old_buffer.text(), buffer.text()); + } + + buffer + }); +} + +#[gpui::test] +fn test_line_len(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let mut buffer = Buffer::new(0, "", cx); + buffer.edit(vec![0..0], "abcd\nefg\nhij", cx); + buffer.edit(vec![12..12], "kl\nmno", cx); + buffer.edit(vec![18..18], "\npqrs\n", cx); + buffer.edit(vec![18..21], "\nPQ", cx); + + assert_eq!(buffer.line_len(0), 4); + assert_eq!(buffer.line_len(1), 3); + assert_eq!(buffer.line_len(2), 5); + assert_eq!(buffer.line_len(3), 3); + assert_eq!(buffer.line_len(4), 4); + assert_eq!(buffer.line_len(5), 0); + buffer + }); +} + +#[gpui::test] +fn test_text_summary_for_range(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", cx); + assert_eq!( + buffer.text_summary_for_range(1..3), + TextSummary { + bytes: 2, + lines: Point::new(1, 0), + first_line_chars: 1, + last_line_chars: 0, + longest_row: 0, + longest_row_chars: 1, + } + ); + assert_eq!( + buffer.text_summary_for_range(1..12), + TextSummary { + bytes: 11, + lines: Point::new(3, 0), + first_line_chars: 1, + last_line_chars: 0, + longest_row: 2, + longest_row_chars: 4, + } + ); + assert_eq!( + buffer.text_summary_for_range(0..20), + TextSummary { + bytes: 20, + lines: Point::new(4, 1), + first_line_chars: 2, + last_line_chars: 1, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range(0..22), + TextSummary { + bytes: 22, + lines: Point::new(4, 3), + first_line_chars: 2, + last_line_chars: 3, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range(7..22), + TextSummary { + bytes: 15, + lines: Point::new(2, 3), + first_line_chars: 4, + last_line_chars: 3, + longest_row: 1, + longest_row_chars: 6, + } + ); + buffer + }); +} + +#[gpui::test] +fn test_chars_at(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let mut buffer = Buffer::new(0, "", cx); + buffer.edit(vec![0..0], "abcd\nefgh\nij", cx); + buffer.edit(vec![12..12], "kl\nmno", cx); + buffer.edit(vec![18..18], "\npqrs", cx); + buffer.edit(vec![18..21], "\nPQ", cx); + + let chars = buffer.chars_at(Point::new(0, 0)); + assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(1, 0)); + assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(2, 0)); + assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(3, 0)); + assert_eq!(chars.collect::(), "mno\nPQrs"); + + let chars = buffer.chars_at(Point::new(4, 0)); + assert_eq!(chars.collect::(), "PQrs"); + + // Regression test: + let mut buffer = Buffer::new(0, "", cx); + buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", cx); + buffer.edit(vec![60..60], "\n", cx); + + let chars = buffer.chars_at(Point::new(6, 0)); + assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); + + buffer + }); +} + +#[gpui::test] +fn test_anchors(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let mut buffer = Buffer::new(0, "", cx); + buffer.edit(vec![0..0], "abc", cx); + let left_anchor = buffer.anchor_before(2); + let right_anchor = buffer.anchor_after(2); + + buffer.edit(vec![1..1], "def\n", cx); + assert_eq!(buffer.text(), "adef\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 6); + assert_eq!(right_anchor.to_offset(&buffer), 6); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit(vec![2..3], "", cx); + assert_eq!(buffer.text(), "adf\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 5); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit(vec![5..5], "ghi\n", cx); + assert_eq!(buffer.text(), "adf\nbghi\nc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 9); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); + + buffer.edit(vec![7..9], "", cx); + assert_eq!(buffer.text(), "adf\nbghc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 7); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 }); + + // Ensure anchoring to a point is equivalent to anchoring to an offset. + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 0 }), + buffer.anchor_before(0) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 1 }), + buffer.anchor_before(1) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 2 }), + buffer.anchor_before(2) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 3 }), + buffer.anchor_before(3) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 0 }), + buffer.anchor_before(4) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 1 }), + buffer.anchor_before(5) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 2 }), + buffer.anchor_before(6) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 3 }), + buffer.anchor_before(7) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 4 }), + buffer.anchor_before(8) + ); + + // Comparison between anchors. + let anchor_at_offset_0 = buffer.anchor_before(0); + let anchor_at_offset_1 = buffer.anchor_before(1); + let anchor_at_offset_2 = buffer.anchor_before(2); + + assert_eq!( + anchor_at_offset_0 + .cmp(&anchor_at_offset_0, &buffer) + .unwrap(), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_1 + .cmp(&anchor_at_offset_1, &buffer) + .unwrap(), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_2 + .cmp(&anchor_at_offset_2, &buffer) + .unwrap(), + Ordering::Equal + ); + + assert_eq!( + anchor_at_offset_0 + .cmp(&anchor_at_offset_1, &buffer) + .unwrap(), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_1 + .cmp(&anchor_at_offset_2, &buffer) + .unwrap(), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_0 + .cmp(&anchor_at_offset_2, &buffer) + .unwrap(), + Ordering::Less + ); + + assert_eq!( + anchor_at_offset_1 + .cmp(&anchor_at_offset_0, &buffer) + .unwrap(), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2 + .cmp(&anchor_at_offset_1, &buffer) + .unwrap(), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2 + .cmp(&anchor_at_offset_0, &buffer) + .unwrap(), + Ordering::Greater + ); + buffer + }); +} + +#[gpui::test] +fn test_anchors_at_start_and_end(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let mut buffer = Buffer::new(0, "", cx); + let before_start_anchor = buffer.anchor_before(0); + let after_end_anchor = buffer.anchor_after(0); + + buffer.edit(vec![0..0], "abc", cx); + assert_eq!(buffer.text(), "abc"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_end_anchor.to_offset(&buffer), 3); + + let after_start_anchor = buffer.anchor_after(0); + let before_end_anchor = buffer.anchor_before(3); + + buffer.edit(vec![3..3], "def", cx); + buffer.edit(vec![0..0], "ghi", cx); + assert_eq!(buffer.text(), "ghiabcdef"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_start_anchor.to_offset(&buffer), 3); + assert_eq!(before_end_anchor.to_offset(&buffer), 6); + assert_eq!(after_end_anchor.to_offset(&buffer), 9); + buffer + }); +} + +#[gpui::test] +async fn test_apply_diff(mut cx: gpui::TestAppContext) { + let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n"; + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); + + let text = "a\nccc\ndddd\nffffff\n"; + let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await; + buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx)); + cx.read(|cx| assert_eq!(buffer.read(cx).text(), text)); + + let text = "a\n1\n\nccc\ndd2dd\nffffff\n"; + let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await; + buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx)); + cx.read(|cx| assert_eq!(buffer.read(cx).text(), text)); +} + +#[gpui::test] +fn test_undo_redo(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let mut buffer = Buffer::new(0, "1234", cx); + // Set group interval to zero so as to not group edits in the undo stack. + buffer.history.group_interval = Duration::from_secs(0); + + buffer.edit(vec![1..1], "abx", cx); + buffer.edit(vec![3..4], "yzef", cx); + buffer.edit(vec![3..5], "cd", cx); + assert_eq!(buffer.text(), "1abcdef234"); + + let transactions = buffer.history.undo_stack.clone(); + assert_eq!(transactions.len(), 3); + + buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1cdef234"); + buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1abcdx234"); + buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1abx234"); + buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1yzef234"); + buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); + assert_eq!(buffer.text(), "1234"); + + buffer + }); +} + +#[gpui::test] +fn test_history(cx: &mut gpui::MutableAppContext) { + cx.add_model(|cx| { + let mut now = Instant::now(); + let mut buffer = Buffer::new(0, "123456", cx); + + let set_id = + buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), cx); + buffer.start_transaction_at(Some(set_id), now).unwrap(); + buffer.edit(vec![2..4], "cd", cx); + buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); + assert_eq!(buffer.text(), "12cd56"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); + + buffer.start_transaction_at(Some(set_id), now).unwrap(); + buffer + .update_selection_set( + set_id, + buffer.selections_from_ranges(vec![1..3]).unwrap(), + cx, + ) + .unwrap(); + buffer.edit(vec![4..5], "e", cx); + buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); + + now += buffer.history.group_interval + Duration::from_millis(1); + buffer.start_transaction_at(Some(set_id), now).unwrap(); + buffer + .update_selection_set( + set_id, + buffer.selections_from_ranges(vec![2..2]).unwrap(), + cx, + ) + .unwrap(); + buffer.edit(vec![0..1], "a", cx); + buffer.edit(vec![1..1], "b", cx); + buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); + + // Last transaction happened past the group interval, undo it on its + // own. + buffer.undo(cx); + assert_eq!(buffer.text(), "12cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); + + // First two transactions happened within the group interval, undo them + // together. + buffer.undo(cx); + assert_eq!(buffer.text(), "123456"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); + + // Redo the first two transactions together. + buffer.redo(cx); + assert_eq!(buffer.text(), "12cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); + + // Redo the last transaction on its own. + buffer.redo(cx); + assert_eq!(buffer.text(), "ab2cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); + + buffer.start_transaction_at(None, now).unwrap(); + buffer.end_transaction_at(None, now, cx).unwrap(); + buffer.undo(cx); + assert_eq!(buffer.text(), "12cde6"); + + buffer + }); +} + +#[gpui::test] +fn test_concurrent_edits(cx: &mut gpui::MutableAppContext) { + let text = "abcdef"; + + let buffer1 = cx.add_model(|cx| Buffer::new(1, text, cx)); + let buffer2 = cx.add_model(|cx| Buffer::new(2, text, cx)); + let buffer3 = cx.add_model(|cx| Buffer::new(3, text, cx)); + + let buf1_op = buffer1.update(cx, |buffer, cx| { + buffer.edit(vec![1..2], "12", cx); + assert_eq!(buffer.text(), "a12cdef"); + buffer.operations.last().unwrap().clone() + }); + let buf2_op = buffer2.update(cx, |buffer, cx| { + buffer.edit(vec![3..4], "34", cx); + assert_eq!(buffer.text(), "abc34ef"); + buffer.operations.last().unwrap().clone() + }); + let buf3_op = buffer3.update(cx, |buffer, cx| { + buffer.edit(vec![5..6], "56", cx); + assert_eq!(buffer.text(), "abcde56"); + buffer.operations.last().unwrap().clone() + }); + + buffer1.update(cx, |buffer, _| { + buffer.apply_op(buf2_op.clone()).unwrap(); + buffer.apply_op(buf3_op.clone()).unwrap(); + }); + buffer2.update(cx, |buffer, _| { + buffer.apply_op(buf1_op.clone()).unwrap(); + buffer.apply_op(buf3_op.clone()).unwrap(); + }); + buffer3.update(cx, |buffer, _| { + buffer.apply_op(buf1_op.clone()).unwrap(); + buffer.apply_op(buf2_op.clone()).unwrap(); + }); + + assert_eq!(buffer1.read(cx).text(), "a12c34e56"); + assert_eq!(buffer2.read(cx).text(), "a12c34e56"); + assert_eq!(buffer3.read(cx).text(), "a12c34e56"); +} + +#[gpui::test(iterations = 100)] +fn test_random_concurrent_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + let peers = env::var("PEERS") + .map(|i| i.parse().expect("invalid `PEERS` variable")) + .unwrap_or(5); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let base_text_len = rng.gen_range(0..10); + let base_text = RandomCharIter::new(&mut rng) + .take(base_text_len) + .collect::(); + let mut replica_ids = Vec::new(); + let mut buffers = Vec::new(); + let mut network = Network::new(rng.clone()); + + for i in 0..peers { + let buffer = cx.add_model(|cx| { + let mut buf = Buffer::new(i as ReplicaId, base_text.as_str(), cx); + buf.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); + buf + }); + buffers.push(buffer); + replica_ids.push(i as u16); + network.add_peer(i as u16); + } + + log::info!("initial text: {:?}", base_text); + + let mut mutation_count = operations; + loop { + let replica_index = rng.gen_range(0..peers); + let replica_id = replica_ids[replica_index]; + buffers[replica_index].update(cx, |buffer, cx| match rng.gen_range(0..=100) { + 0..=50 if mutation_count != 0 => { + buffer.randomly_mutate(&mut rng, cx); + network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); + log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); + mutation_count -= 1; + } + 51..=70 if mutation_count != 0 => { + buffer.randomly_undo_redo(&mut rng, cx); + network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); + mutation_count -= 1; + } + 71..=100 if network.has_unreceived(replica_id) => { + let ops = network.receive(replica_id); + if !ops.is_empty() { + log::info!( + "peer {} applying {} ops from the network.", + replica_id, + ops.len() + ); + buffer.apply_ops(ops, cx).unwrap(); + } + } + _ => {} + }); + + if mutation_count == 0 && network.is_idle() { + break; + } + } + + let first_buffer = buffers[0].read(cx); + for buffer in &buffers[1..] { + let buffer = buffer.read(cx); + assert_eq!( + buffer.text(), + first_buffer.text(), + "Replica {} text != Replica 0 text", + buffer.replica_id + ); + assert_eq!( + buffer.selection_sets().collect::>(), + first_buffer.selection_sets().collect::>() + ); + assert_eq!( + buffer.all_selection_ranges().collect::>(), + first_buffer + .all_selection_ranges() + .collect::>() + ); + } +} + +#[derive(Clone)] +struct Envelope { + message: T, + sender: ReplicaId, +} + +struct Network { + inboxes: std::collections::BTreeMap>>, + all_messages: Vec, + rng: R, +} + +impl Network { + fn new(rng: R) -> Self { + Network { + inboxes: Default::default(), + all_messages: Vec::new(), + rng, + } + } + + fn add_peer(&mut self, id: ReplicaId) { + self.inboxes.insert(id, Vec::new()); + } + + fn is_idle(&self) -> bool { + self.inboxes.values().all(|i| i.is_empty()) + } + + fn broadcast(&mut self, sender: ReplicaId, messages: Vec) { + for (replica, inbox) in self.inboxes.iter_mut() { + if *replica != sender { + for message in &messages { + let min_index = inbox + .iter() + .enumerate() + .rev() + .find_map(|(index, envelope)| { + if sender == envelope.sender { + Some(index + 1) + } else { + None + } + }) + .unwrap_or(0); + + // Insert one or more duplicates of this message *after* the previous + // message delivered by this replica. + for _ in 0..self.rng.gen_range(1..4) { + let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1); + inbox.insert( + insertion_index, + Envelope { + message: message.clone(), + sender, + }, + ); + } + } + } + } + self.all_messages.extend(messages); + } + + fn has_unreceived(&self, receiver: ReplicaId) -> bool { + !self.inboxes[&receiver].is_empty() + } + + fn receive(&mut self, receiver: ReplicaId) -> Vec { + let inbox = self.inboxes.get_mut(&receiver).unwrap(); + let count = self.rng.gen_range(0..inbox.len() + 1); + inbox + .drain(0..count) + .map(|envelope| envelope.message) + .collect() + } +} diff --git a/crates/buffer/src/tests/syntax.rs b/crates/buffer/src/tests/syntax.rs new file mode 100644 index 0000000000..4b897dd942 --- /dev/null +++ b/crates/buffer/src/tests/syntax.rs @@ -0,0 +1,380 @@ +use crate::*; +use gpui::{ModelHandle, MutableAppContext}; +use unindent::Unindent as _; + +#[gpui::test] +async fn test_reparse(mut cx: gpui::TestAppContext) { + let buffer = cx.add_model(|cx| { + let text = "fn a() {}".into(); + Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx) + }); + + // Wait for the initial text to parse + buffer + .condition(&cx, |buffer, _| !buffer.is_parsing()) + .await; + assert_eq!( + get_tree_sexp(&buffer, &cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters) ", + "body: (block)))" + ) + ); + + buffer.update(&mut cx, |buffer, _| { + buffer.set_sync_parse_timeout(Duration::ZERO) + }); + + // Perform some edits (add parameter and variable reference) + // Parsing doesn't begin until the transaction is complete + buffer.update(&mut cx, |buf, cx| { + buf.start_transaction(None).unwrap(); + + let offset = buf.text().find(")").unwrap(); + buf.edit(vec![offset..offset], "b: C", cx); + assert!(!buf.is_parsing()); + + let offset = buf.text().find("}").unwrap(); + buf.edit(vec![offset..offset], " d; ", cx); + assert!(!buf.is_parsing()); + + buf.end_transaction(None, cx).unwrap(); + assert_eq!(buf.text(), "fn a(b: C) { d; }"); + assert!(buf.is_parsing()); + }); + buffer + .condition(&cx, |buffer, _| !buffer.is_parsing()) + .await; + assert_eq!( + get_tree_sexp(&buffer, &cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (identifier))))" + ) + ); + + // Perform a series of edits without waiting for the current parse to complete: + // * turn identifier into a field expression + // * turn field expression into a method call + // * add a turbofish to the method call + buffer.update(&mut cx, |buf, cx| { + let offset = buf.text().find(";").unwrap(); + buf.edit(vec![offset..offset], ".e", cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e; }"); + assert!(buf.is_parsing()); + }); + buffer.update(&mut cx, |buf, cx| { + let offset = buf.text().find(";").unwrap(); + buf.edit(vec![offset..offset], "(f)", cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }"); + assert!(buf.is_parsing()); + }); + buffer.update(&mut cx, |buf, cx| { + let offset = buf.text().find("(f)").unwrap(); + buf.edit(vec![offset..offset], "::", cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); + assert!(buf.is_parsing()); + }); + buffer + .condition(&cx, |buffer, _| !buffer.is_parsing()) + .await; + assert_eq!( + get_tree_sexp(&buffer, &cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (call_expression ", + "function: (generic_function ", + "function: (field_expression value: (identifier) field: (field_identifier)) ", + "type_arguments: (type_arguments (type_identifier))) ", + "arguments: (arguments (identifier))))))", + ) + ); + + buffer.update(&mut cx, |buf, cx| { + buf.undo(cx); + assert_eq!(buf.text(), "fn a() {}"); + assert!(buf.is_parsing()); + }); + buffer + .condition(&cx, |buffer, _| !buffer.is_parsing()) + .await; + assert_eq!( + get_tree_sexp(&buffer, &cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters) ", + "body: (block)))" + ) + ); + + buffer.update(&mut cx, |buf, cx| { + buf.redo(cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); + assert!(buf.is_parsing()); + }); + buffer + .condition(&cx, |buffer, _| !buffer.is_parsing()) + .await; + assert_eq!( + get_tree_sexp(&buffer, &cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (call_expression ", + "function: (generic_function ", + "function: (field_expression value: (identifier) field: (field_identifier)) ", + "type_arguments: (type_arguments (type_identifier))) ", + "arguments: (arguments (identifier))))))", + ) + ); + + fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> String { + buffer.read_with(cx, |buffer, _| { + buffer.syntax_tree().unwrap().root_node().to_sexp() + }) + } +} + +#[gpui::test] +fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { + let buffer = cx.add_model(|cx| { + let text = " + mod x { + mod y { + + } + } + " + .unindent() + .into(); + Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx) + }); + let buffer = buffer.read(cx); + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)), + Some(( + Point::new(0, 6)..Point::new(0, 7), + Point::new(4, 0)..Point::new(4, 1) + )) + ); + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)), + Some(( + Point::new(1, 10)..Point::new(1, 11), + Point::new(3, 4)..Point::new(3, 5) + )) + ); + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)), + Some(( + Point::new(1, 10)..Point::new(1, 11), + Point::new(3, 4)..Point::new(3, 5) + )) + ); +} + +#[gpui::test] +fn test_edit_with_autoindent(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = "fn a() {}".into(); + let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx); + + buffer.edit_with_autoindent([8..8], "\n\n", cx); + assert_eq!(buffer.text(), "fn a() {\n \n}"); + + buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx); + assert_eq!(buffer.text(), "fn a() {\n b()\n \n}"); + + buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx); + assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}"); + + buffer + }); +} + +#[gpui::test] +fn test_autoindent_moves_selections(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = History::new("fn a() {}".into()); + let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx); + + let selection_set_id = buffer.add_selection_set(Vec::new(), cx); + buffer.start_transaction(Some(selection_set_id)).unwrap(); + buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx); + buffer + .update_selection_set( + selection_set_id, + vec![ + Selection { + id: 0, + start: buffer.anchor_before(Point::new(1, 0)), + end: buffer.anchor_before(Point::new(1, 0)), + reversed: false, + goal: SelectionGoal::None, + }, + Selection { + id: 1, + start: buffer.anchor_before(Point::new(4, 0)), + end: buffer.anchor_before(Point::new(4, 0)), + reversed: false, + goal: SelectionGoal::None, + }, + ], + cx, + ) + .unwrap(); + assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n"); + + // Ending the transaction runs the auto-indent. The selection + // at the start of the auto-indented row is pushed to the right. + buffer.end_transaction(Some(selection_set_id), cx).unwrap(); + assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n"); + let selection_ranges = buffer + .selection_set(selection_set_id) + .unwrap() + .selections + .iter() + .map(|selection| selection.point_range(&buffer)) + .collect::>(); + + assert_eq!(selection_ranges[0], empty(Point::new(1, 4))); + assert_eq!(selection_ranges[1], empty(Point::new(4, 0))); + + buffer + }); +} + +#[gpui::test] +fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = " + fn a() { + c; + d; + } + " + .unindent() + .into(); + let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx); + + // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, + // their indentation is not adjusted. + buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx); + assert_eq!( + buffer.text(), + " + fn a() { + c(); + d(); + } + " + .unindent() + ); + + // When appending new content after these lines, the indentation is based on the + // preceding lines' actual indentation. + buffer.edit_with_autoindent( + [empty(Point::new(1, 1)), empty(Point::new(2, 1))], + "\n.f\n.g", + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + c + .f + .g(); + d + .f + .g(); + } + " + .unindent() + ); + buffer + }); +} + +#[gpui::test] +fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) { + cx.add_model(|cx| { + let text = History::new( + " + fn a() {} + " + .unindent() + .into(), + ); + let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx); + + buffer.edit_with_autoindent([5..5], "\nb", cx); + assert_eq!( + buffer.text(), + " + fn a( + b) {} + " + .unindent() + ); + + // The indentation suggestion changed because `@end` node (a close paren) + // is now at the beginning of the line. + buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx); + assert_eq!( + buffer.text(), + " + fn a( + ) {} + " + .unindent() + ); + + buffer + }); +} + +#[test] +fn test_contiguous_ranges() { + assert_eq!( + contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12], 100).collect::>(), + &[1..4, 5..7, 9..13] + ); + + // Respects the `max_len` parameter + assert_eq!( + contiguous_ranges([2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31], 3).collect::>(), + &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32], + ); +} + +fn rust_lang() -> Arc { + Arc::new( + Language::new( + LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + tree_sitter_rust::language(), + ) + .with_indents_query( + r#" + (call_expression) @indent + (field_expression) @indent + (_ "(" ")" @end) @indent + (_ "{" "}" @end) @indent + "#, + ) + .unwrap() + .with_brackets_query(r#" ("{" @open "}" @close) "#) + .unwrap(), + ) +} + +fn empty(point: Point) -> Range { + point..point +} diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 856aa37799..a835e5e50c 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -21,7 +21,7 @@ use smol::Timer; use std::{ cell::RefCell, cmp::{self, Ordering}, - mem, + iter, mem, ops::{Range, RangeInclusive}, rc::Rc, sync::Arc, @@ -38,6 +38,8 @@ action!(Cancel); action!(Backspace); action!(Delete); action!(Input, String); +action!(Newline); +action!(Tab); action!(DeleteLine); action!(DeleteToPreviousWordBoundary); action!(DeleteToNextWordBoundary); @@ -95,13 +97,13 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("ctrl-h", Backspace, Some("Editor")), Binding::new("delete", Delete, Some("Editor")), Binding::new("ctrl-d", Delete, Some("Editor")), - Binding::new("enter", Input("\n".into()), Some("Editor && mode == full")), + Binding::new("enter", Newline, Some("Editor && mode == full")), Binding::new( "alt-enter", Input("\n".into()), Some("Editor && mode == auto_height"), ), - Binding::new("tab", Input("\t".into()), Some("Editor")), + Binding::new("tab", Tab, Some("Editor")), Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")), Binding::new( "alt-backspace", @@ -193,8 +195,10 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::select); cx.add_action(Editor::cancel); cx.add_action(Editor::handle_input); + cx.add_action(Editor::newline); cx.add_action(Editor::backspace); cx.add_action(Editor::delete); + cx.add_action(Editor::tab); cx.add_action(Editor::delete_line); cx.add_action(Editor::delete_to_previous_word_boundary); cx.add_action(Editor::delete_to_next_word_boundary); @@ -292,7 +296,7 @@ pub struct Editor { pending_selection: Option, next_selection_id: usize, add_selections_state: Option, - autoclose_stack: Vec, + autoclose_stack: Vec, select_larger_syntax_node_stack: Vec>, scroll_position: Vector2F, scroll_top_anchor: Anchor, @@ -320,9 +324,9 @@ struct AddSelectionsState { stack: Vec, } -struct AutoclosePairState { +struct BracketPairState { ranges: SmallVec<[Range; 32]>, - pair: AutoclosePair, + pair: BracketPair, } #[derive(Serialize, Deserialize)] @@ -750,6 +754,130 @@ impl Editor { } } + pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext) { + self.start_transaction(cx); + let mut old_selections = SmallVec::<[_; 32]>::new(); + { + let selections = self.selections(cx); + let buffer = self.buffer.read(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.to_offset(buffer); + let end = selection.end.to_offset(buffer); + + 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, start..end, indent, insert_extra_newline)); + } + } + + let mut new_selections = Vec::with_capacity(old_selections.len()); + 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); + } + 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 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 mut delta = 0_isize; + new_selections.extend(old_selections.into_iter().map( + |(id, range, indent, insert_extra_newline)| { + let start = (range.start as isize + delta) as usize; + let end = (range.end as isize + delta) as usize; + let text_before_cursor_len = indent as usize + 1; + let anchor = buffer.anchor_before(start + text_before_cursor_len); + let text_len = if insert_extra_newline { + text_before_cursor_len * 2 + } else { + text_before_cursor_len + }; + delta += text_len as isize - (end - start) as isize; + Selection { + id, + start: anchor.clone(), + end: anchor, + reversed: false, + goal: SelectionGoal::None, + } + }, + )) + }); + + self.update_selections(new_selections, true, cx); + self.end_transaction(cx); + + #[derive(Default)] + struct PendingEdit { + indent: u32, + insert_extra_newline: bool, + delta: isize, + ranges: SmallVec<[Range; 32]>, + } + } + fn insert(&mut self, text: &str, cx: &mut ViewContext) { self.start_transaction(cx); let mut old_selections = SmallVec::<[_; 32]>::new(); @@ -766,7 +894,7 @@ impl Editor { let mut new_selections = Vec::new(); self.buffer.update(cx, |buffer, cx| { let edit_ranges = old_selections.iter().map(|(_, range)| range.clone()); - buffer.edit(edit_ranges, text, cx); + buffer.edit_with_autoindent(edit_ranges, text, cx); let text_len = text.len() as isize; let mut delta = 0_isize; new_selections = old_selections @@ -797,7 +925,7 @@ impl Editor { let new_autoclose_pair_state = self.buffer.update(cx, |buffer, cx| { let autoclose_pair = buffer.language().and_then(|language| { let first_selection_start = selections.first().unwrap().start.to_offset(&*buffer); - let pair = language.autoclose_pairs().iter().find(|pair| { + let pair = language.brackets().iter().find(|pair| { buffer.contains_str_at( first_selection_start.saturating_sub(pair.start.len()), &pair.start, @@ -832,7 +960,7 @@ impl Editor { buffer.edit(selection_ranges, &pair.end, cx); if pair.end.len() == 1 { - Some(AutoclosePairState { + Some(BracketPairState { ranges: selections .iter() .map(|selection| { @@ -950,6 +1078,51 @@ impl Editor { self.end_transaction(cx); } + pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { + self.start_transaction(cx); + let tab_size = self.build_settings.borrow()(cx).tab_size; + let mut selections = self.selections(cx).to_vec(); + self.buffer.update(cx, |buffer, cx| { + let mut last_indented_row = None; + for selection in &mut selections { + let mut range = selection.point_range(buffer); + if range.is_empty() { + let char_column = buffer + .chars_for_range(Point::new(range.start.row, 0)..range.start) + .count(); + let chars_to_next_tab_stop = tab_size - (char_column % tab_size); + buffer.edit( + [range.start..range.start], + " ".repeat(chars_to_next_tab_stop), + cx, + ); + range.start.column += chars_to_next_tab_stop as u32; + + let head = buffer.anchor_before(range.start); + selection.start = head.clone(); + selection.end = head; + } else { + for row in range.start.row..=range.end.row { + if last_indented_row != Some(row) { + let char_column = buffer.indent_column_for_line(row) as usize; + let chars_to_next_tab_stop = tab_size - (char_column % tab_size); + let row_start = Point::new(row, 0); + buffer.edit( + [row_start..row_start], + " ".repeat(chars_to_next_tab_stop), + cx, + ); + last_indented_row = Some(row); + } + } + } + } + }); + + self.update_selections(selections, true, cx); + self.end_transaction(cx); + } + pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) { self.start_transaction(cx); @@ -3507,6 +3680,30 @@ mod tests { assert_eq!(buffer.read(cx).text(), "e t te our"); } + #[gpui::test] + fn test_newline(cx: &mut gpui::MutableAppContext) { + let buffer = cx.add_model(|cx| Buffer::new(0, "aaaa\n bbbb\n", cx)); + let settings = EditorSettings::test(&cx); + let (_, view) = cx.add_window(Default::default(), |cx| { + build_editor(buffer.clone(), settings, cx) + }); + + view.update(cx, |view, cx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2), + DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2), + DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6), + ], + cx, + ) + .unwrap(); + + view.newline(&Newline, cx); + assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n"); + }); + } + #[gpui::test] fn test_backspace(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| { @@ -4355,14 +4552,18 @@ mod tests { let settings = cx.read(EditorSettings::test); let language = Arc::new(Language::new( LanguageConfig { - autoclose_pairs: vec![ - AutoclosePair { + brackets: vec![ + BracketPair { start: "{".to_string(), end: "}".to_string(), + close: true, + newline: true, }, - AutoclosePair { + BracketPair { start: "/*".to_string(), end: " */".to_string(), + close: true, + newline: true, }, ], ..Default::default() @@ -4461,6 +4662,76 @@ mod tests { }); } + #[gpui::test] + async fn test_extra_newline_insertion(mut cx: gpui::TestAppContext) { + let settings = cx.read(EditorSettings::test); + let language = Arc::new(Language::new( + LanguageConfig { + brackets: vec![ + BracketPair { + start: "{".to_string(), + end: "}".to_string(), + close: true, + newline: true, + }, + BracketPair { + start: "/* ".to_string(), + end: " */".to_string(), + close: true, + newline: true, + }, + ], + ..Default::default() + }, + tree_sitter_rust::language(), + )); + + let text = concat!( + "{ }\n", // Suppress rustfmt + " x\n", // + " /* */\n", // + "x\n", // + "{{} }\n", // + ); + + let buffer = cx.add_model(|cx| { + let history = History::new(text.into()); + Buffer::from_history(0, history, None, Some(language), cx) + }); + let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); + view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) + .await; + + view.update(&mut cx, |view, cx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3), + DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5), + DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4), + ], + cx, + ) + .unwrap(); + view.newline(&Newline, cx); + + assert_eq!( + view.buffer().read(cx).text(), + concat!( + "{ \n", // Suppress rustfmt + "\n", // + "}\n", // + " x\n", // + " /* \n", // + " \n", // + " */\n", // + "x\n", // + "{{} \n", // + "}\n", // + ) + ); + }); + } + impl Editor { fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec> { self.selections_in_range( diff --git a/crates/zed/languages/rust/config.toml b/crates/zed/languages/rust/config.toml index ece9b57ca2..11b273d137 100644 --- a/crates/zed/languages/rust/config.toml +++ b/crates/zed/languages/rust/config.toml @@ -1,9 +1,10 @@ name = "Rust" path_suffixes = ["rs"] -autoclose_pairs = [ - { start = "{", end = "}" }, - { start = "[", end = "]" }, - { start = "(", end = ")" }, - { start = "\"", end = "\"" }, - { start = "/*", end = " */" }, +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, + { start = "<", end = ">", close = false, newline = true }, + { start = "\"", end = "\"", close = true, newline = false }, + { start = "/*", end = " */", close = true, newline = false }, ] diff --git a/crates/zed/languages/rust/indents.scm b/crates/zed/languages/rust/indents.scm new file mode 100644 index 0000000000..a154dc665b --- /dev/null +++ b/crates/zed/languages/rust/indents.scm @@ -0,0 +1,12 @@ +[ + ((where_clause) _ @end) + (field_expression) + (call_expression) + (assignment_expression) + (let_declaration) +] @indent + +(_ "[" "]" @end) @indent +(_ "<" ">" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 774d8c6502..5a542ffc48 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -22,6 +22,8 @@ fn rust() -> Language { .unwrap() .with_brackets_query(load_query("rust/brackets.scm").as_ref()) .unwrap() + .with_indents_query(load_query("rust/indents.scm").as_ref()) + .unwrap() } fn load_query(path: &str) -> Cow<'static, str> {