diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index c678918824..407446e7a2 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,20 @@ pub struct Anchor { pub version: clock::Global, } +pub struct AnchorMap { + pub(crate) version: clock::Global, + pub(crate) entries: Vec<((usize, Bias), T)>, +} + +pub struct AnchorSet(pub(crate) AnchorMap<()>); + +pub struct AnchorRangeMap { + pub(crate) version: clock::Global, + pub(crate) entries: Vec<(Range<(usize, Bias)>, T)>, +} + +pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>); + impl Anchor { pub fn min() -> Self { Self { @@ -62,6 +78,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/lib.rs b/crates/buffer/src/lib.rs index 897e9bda22..4c40ed98d4 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -30,12 +30,11 @@ use std::{ any::Any, cell::RefCell, cmp, - collections::BTreeMap, + collections::{BTreeMap, VecDeque}, convert::{TryFrom, TryInto}, ffi::OsString, hash::BuildHasher, iter::Iterator, - mem, ops::{Deref, DerefMut, Range}, path::{Path, PathBuf}, str, @@ -167,7 +166,7 @@ pub struct Buffer { history: History, file: Option>, language: Option>, - autoindent_requests: Vec, + autoindent_requests: VecDeque, sync_parse_timeout: Duration, syntax_tree: Mutex>, parsing_in_background: bool, @@ -195,11 +194,16 @@ struct SyntaxTree { version: clock::Global, } -#[derive(Clone, Debug)] struct AutoindentRequest { - position: Anchor, - end_position: Option, - prev_suggestion: Option, + before_edit: Snapshot, + edited: AnchorSet, + inserted: AnchorRangeSet, +} + +impl AutoindentRequest { + fn version_after_edit(&self) -> &clock::Global { + self.inserted.version() + } } #[derive(Clone, Debug)] @@ -967,7 +971,7 @@ impl Buffer { language: Arc, cx: &mut ModelContext, ) { - self.perform_autoindent(&tree, language, cx); + self.perform_autoindent(&tree, &version, language, cx); self.parse_count += 1; *self.syntax_tree.lock() = Some(SyntaxTree { tree, version }); cx.emit(Event::Reparsed); @@ -977,160 +981,91 @@ impl Buffer { fn perform_autoindent( &mut self, new_tree: &Tree, + new_version: &clock::Global, language: Arc, cx: &mut ModelContext, ) { - let mut autoindent_requests = mem::take(&mut self.autoindent_requests); - let mut prev_suggestions_by_row = BTreeMap::>::default(); - for request in autoindent_requests.drain(..) { - let start_row = request.position.to_point(&*self).row; - let end_row = if let Some(end_position) = request.end_position { - end_position.to_point(&*self).row - } else { - start_row - }; - for row in start_row..=end_row { - let prev_suggestion = request.prev_suggestion; - prev_suggestions_by_row - .entry(row) - .and_modify(|suggestion| *suggestion = suggestion.or(prev_suggestion)) - .or_insert(request.prev_suggestion); - } - } - self.autoindent_requests = autoindent_requests; - let mut cursor = QueryCursorHandle::new(); - self.start_transaction(None).unwrap(); - let mut prev_suggestions = prev_suggestions_by_row.iter(); - for row_range in contiguous_ranges(prev_suggestions_by_row.keys().copied()) { - let new_suggestions = self - .suggest_autoindents(row_range.clone(), new_tree, &language, &mut cursor) - .collect::>(); - let old_suggestions = prev_suggestions.by_ref().take(row_range.len()); - for ((row, old_suggestion), new_suggestion) in old_suggestions.zip(new_suggestions) { - if *old_suggestion != Some(new_suggestion) { - self.set_indent_column_for_line(*row, new_suggestion, cx); - } - } - } - self.end_transaction(None, cx).unwrap(); - } - - fn suggest_autoindents<'a>( - &'a self, - row_range: Range, - tree: &Tree, - language: &Language, - cursor: &mut QueryCursor, - ) -> impl Iterator + 'a { - 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"); - 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 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()); - } + while let Some(request) = self.autoindent_requests.front() { + if new_version < request.version_after_edit() { + break; } - if let Some((start, end)) = start.zip(end) { - if start.row == end.row { - continue; - } + let request = self.autoindent_requests.pop_front().unwrap(); - 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 old_to_new_rows = request + .edited + .to_points(request.before_edit.content()) + .map(|point| point.row) + .zip( + request + .edited + .to_points(self.content()) + .map(|point| point.row), + ) + .collect::>(); + + let mut old_suggestions = HashMap::default(); + if let Some(old_tree) = &request.before_edit.tree { + let old_edited_ranges = contiguous_ranges(old_to_new_rows.keys().copied()); + for old_edited_range in old_edited_ranges { + let old_content = request.before_edit.content(); + let suggestions = old_content.suggest_autoindents( + old_edited_range.clone(), + old_tree, + &language, + &mut cursor, + ); + 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, + ); } } } - } - let mut prev_row = prev_non_blank_row.unwrap_or(0); - let mut prev_indent_column = - prev_non_blank_row.map_or(0, |prev_row| self.indent_column_for_line(prev_row)); - row_range.map(move |row| { - let row_start = Point::new(row, self.indent_column_for_line(row)); + // 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. - eprintln!( - "autoindent row: {:?}, prev_indent_column: {:?}", - row, prev_indent_column - ); - - 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 { - eprintln!(" indent because of {} {:?}", node_kind, range); - indent_from_prev_row = true; - } - if range.end.row >= prev_row && range.end <= row_start { - eprintln!(" outdent because of {} {:?}", node_kind, range); - outdent_to_row = outdent_to_row.min(range.start.row); + self.start_transaction(None).unwrap(); + let new_edited_row_ranges = contiguous_ranges(old_to_new_rows.values().copied()); + for new_edited_row_range in new_edited_row_ranges { + let new_content = self.content(); + let suggestions = new_content + .suggest_autoindents( + new_edited_row_range.clone(), + &new_tree, + &language, + &mut cursor, + ) + .collect::>(); + for (new_row, suggestion) in new_edited_row_range.zip(suggestions) { + let delta = if suggestion.indent { INDENT_SIZE } else { 0 }; + let new_indentation = self.indent_column_for_line(suggestion.basis_row) + delta; + if old_suggestions + .get(&new_row) + .map_or(true, |old_indentation| new_indentation != *old_indentation) + { + self.set_indent_column_for_line(new_row, new_indentation, cx); + } } } - - let indent_column = if outdent_to_row == prev_row { - prev_indent_column - } else if indent_from_prev_row { - prev_indent_column + INDENT_SIZE - } else if outdent_to_row < prev_row { - self.indent_column_for_line(outdent_to_row) - } else { - prev_indent_column - }; - - prev_indent_column = indent_column; - prev_row = row; - indent_column - }) - } - - fn prev_non_blank_row(&self, mut row: u32) -> Option { - while row > 0 { - row -= 1; - if !self.is_line_blank(row) { - return Some(row); - } + self.end_transaction(None, cx).unwrap(); } - None } 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 + self.content().indent_column_for_line(row) } fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext) { @@ -1163,11 +1098,6 @@ impl Buffer { } } - 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 range_for_syntax_ancestor(&self, range: Range) -> Option> { if let Some(tree) = self.syntax_tree() { let root = tree.root_node(); @@ -1308,18 +1238,18 @@ 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 chars_for_range(&self, range: Range) -> impl Iterator + '_ { @@ -1484,36 +1414,20 @@ impl Buffer { return; } - // When autoindent is enabled, compute the previous indent suggestion for all edited lines. - if autoindent { - if let Some((language, tree)) = self.language.as_ref().zip(self.syntax_tree()) { - let mut cursor = QueryCursorHandle::new(); - let starts_with_newline = new_text.starts_with('\n'); - - let mut autoindent_requests = mem::take(&mut self.autoindent_requests); - let edited_rows = ranges.iter().filter_map(|range| { - let start = range.start.to_point(&*self); - if !starts_with_newline || start.column < self.line_len(start.row) { - Some(start.row) - } else { - None - } - }); - for row_range in contiguous_ranges(edited_rows) { - let suggestions = self - .suggest_autoindents(row_range.clone(), &tree, language, &mut cursor) - .collect::>(); - for (row, suggestion) in row_range.zip(suggestions) { - autoindent_requests.push(AutoindentRequest { - position: self.anchor_before(Point::new(row, 0)), - end_position: None, - prev_suggestion: Some(suggestion), - }) - } + 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)) } - self.autoindent_requests = autoindent_requests; - } - } + })); + Some((before_edit, edited)) + } else { + None + }; let new_text = if new_text.len() > 0 { Some(new_text) @@ -1534,25 +1448,18 @@ impl Buffer { self.last_edit = edit.timestamp.local(); self.version.observe(edit.timestamp.local()); - if autoindent && self.language.is_some() { - let ranges = edit.ranges.iter().map(|range| { - Anchor { - offset: range.start, - bias: Bias::Right, - version: edit.version.clone(), - }..Anchor { - offset: range.end, - bias: Bias::Right, - version: edit.version.clone(), - } - }); - for range in ranges { - self.autoindent_requests.push(AutoindentRequest { - position: range.start, - end_position: Some(range.end), - prev_suggestion: None, - }) - } + if let Some((before_edit, edited)) = autoindent_request { + let inserted = self.content().anchor_range_set( + edit.ranges + .iter() + .map(|range| (range.start, Bias::Left)..(range.end, Bias::Right)), + ); + + self.autoindent_requests.push_back(AutoindentRequest { + before_edit, + edited, + inserted, + }) } self.end_transaction_at(None, Instant::now(), cx).unwrap(); @@ -2134,20 +2041,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; @@ -2160,10 +2067,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 { @@ -2193,7 +2100,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 { @@ -2211,14 +2118,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; @@ -2458,7 +2365,7 @@ impl Clone for Buffer { parsing_in_background: false, sync_parse_timeout: self.sync_parse_timeout, parse_count: self.parse_count, - autoindent_requests: self.autoindent_requests.clone(), + autoindent_requests: Default::default(), deferred_replicas: self.deferred_replicas.clone(), replica_id: self.replica_id, remote_id: self.remote_id.clone(), @@ -2504,6 +2411,10 @@ impl Snapshot { self.content().line_len(row) } + pub fn indent_column_for_line(&self, row: u32) -> u32 { + self.content().indent_column_for_line(row) + } + pub fn text(&self) -> Rope { self.visible_text.clone() } @@ -2644,6 +2555,17 @@ 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 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 { @@ -2654,6 +2576,33 @@ 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 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()) + } + fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary { let cx = Some(anchor.version.clone()); let mut cursor = self.fragments.cursor::<(VersionedOffset, usize)>(); @@ -2670,19 +2619,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(&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(&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(&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 @@ -2705,6 +2769,117 @@ impl<'a> Content<'a> { Err(anyhow!("offset out of bounds")) } } + + fn suggest_autoindents( + &'a self, + row_range: Range, + tree: &Tree, + language: &Language, + cursor: &mut QueryCursor, + ) -> impl Iterator + 'a { + 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"); + 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 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); + } + } + } + } + + eprintln!( + "autoindent {:?}. ranges: {:?}", + row_range, indentation_ranges + ); + + let mut prev_row = prev_non_blank_row.unwrap_or(0); + row_range.map(move |row| { + let row_start = Point::new(row, self.indent_column_for_line(row)); + + eprintln!(" autoindent row: {:?}", 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 { + eprintln!(" indent because of {} {:?}", node_kind, range); + indent_from_prev_row = true; + } + if range.end.row >= prev_row && range.end <= row_start { + eprintln!(" outdent because of {} {:?}", node_kind, range); + 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 + }) + } +} + +struct IndentSuggestion { + basis_row: u32, + indent: bool, } struct RopeBuilder<'a> { @@ -3050,6 +3225,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), diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index a5f4b905ba..ebe01258dc 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -268,6 +268,7 @@ impl<'a> Cursor<'a> { } } + self.offset = end_offset; summary }