Start on new way of comparing old and new indent suggestions
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
b85ae89b7e
commit
e78a5642fa
3 changed files with 472 additions and 216 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::Point;
|
||||||
|
|
||||||
use super::{Buffer, Content};
|
use super::{Buffer, Content};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::{cmp::Ordering, ops::Range};
|
use std::{cmp::Ordering, ops::Range};
|
||||||
|
@ -10,6 +12,20 @@ pub struct Anchor {
|
||||||
pub version: clock::Global,
|
pub version: clock::Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AnchorMap<T> {
|
||||||
|
pub(crate) version: clock::Global,
|
||||||
|
pub(crate) entries: Vec<((usize, Bias), T)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AnchorSet(pub(crate) AnchorMap<()>);
|
||||||
|
|
||||||
|
pub struct AnchorRangeMap<T> {
|
||||||
|
pub(crate) version: clock::Global,
|
||||||
|
pub(crate) entries: Vec<(Range<(usize, Bias)>, T)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>);
|
||||||
|
|
||||||
impl Anchor {
|
impl Anchor {
|
||||||
pub fn min() -> Self {
|
pub fn min() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -62,6 +78,60 @@ impl Anchor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> AnchorMap<T> {
|
||||||
|
pub fn to_points<'a>(
|
||||||
|
&'a self,
|
||||||
|
content: impl Into<Content<'a>> + 'a,
|
||||||
|
) -> impl Iterator<Item = (Point, &'a T)> + '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<Content<'a>> + 'a,
|
||||||
|
) -> impl Iterator<Item = Point> + 'a {
|
||||||
|
self.0.to_points(content).map(move |(point, _)| point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AnchorRangeMap<T> {
|
||||||
|
pub fn to_point_ranges<'a>(
|
||||||
|
&'a self,
|
||||||
|
content: impl Into<Content<'a>> + 'a,
|
||||||
|
) -> impl Iterator<Item = (Range<Point>, &'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<Content<'a>> + 'a,
|
||||||
|
) -> impl Iterator<Item = Range<Point>> + 'a {
|
||||||
|
self.0.to_point_ranges(content).map(|(range, _)| range)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&self) -> &clock::Global {
|
||||||
|
self.0.version()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait AnchorRangeExt {
|
pub trait AnchorRangeExt {
|
||||||
fn cmp<'a>(&self, b: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering>;
|
fn cmp<'a>(&self, b: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,11 @@ use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp,
|
cmp,
|
||||||
collections::BTreeMap,
|
collections::{BTreeMap, VecDeque},
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
hash::BuildHasher,
|
hash::BuildHasher,
|
||||||
iter::Iterator,
|
iter::Iterator,
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str,
|
str,
|
||||||
|
@ -167,7 +166,7 @@ pub struct Buffer {
|
||||||
history: History,
|
history: History,
|
||||||
file: Option<Box<dyn File>>,
|
file: Option<Box<dyn File>>,
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
autoindent_requests: Vec<AutoindentRequest>,
|
autoindent_requests: VecDeque<AutoindentRequest>,
|
||||||
sync_parse_timeout: Duration,
|
sync_parse_timeout: Duration,
|
||||||
syntax_tree: Mutex<Option<SyntaxTree>>,
|
syntax_tree: Mutex<Option<SyntaxTree>>,
|
||||||
parsing_in_background: bool,
|
parsing_in_background: bool,
|
||||||
|
@ -195,11 +194,16 @@ struct SyntaxTree {
|
||||||
version: clock::Global,
|
version: clock::Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct AutoindentRequest {
|
struct AutoindentRequest {
|
||||||
position: Anchor,
|
before_edit: Snapshot,
|
||||||
end_position: Option<Anchor>,
|
edited: AnchorSet,
|
||||||
prev_suggestion: Option<u32>,
|
inserted: AnchorRangeSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutoindentRequest {
|
||||||
|
fn version_after_edit(&self) -> &clock::Global {
|
||||||
|
self.inserted.version()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -967,7 +971,7 @@ impl Buffer {
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.perform_autoindent(&tree, language, cx);
|
self.perform_autoindent(&tree, &version, language, cx);
|
||||||
self.parse_count += 1;
|
self.parse_count += 1;
|
||||||
*self.syntax_tree.lock() = Some(SyntaxTree { tree, version });
|
*self.syntax_tree.lock() = Some(SyntaxTree { tree, version });
|
||||||
cx.emit(Event::Reparsed);
|
cx.emit(Event::Reparsed);
|
||||||
|
@ -977,160 +981,91 @@ impl Buffer {
|
||||||
fn perform_autoindent(
|
fn perform_autoindent(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_tree: &Tree,
|
new_tree: &Tree,
|
||||||
|
new_version: &clock::Global,
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let mut autoindent_requests = mem::take(&mut self.autoindent_requests);
|
|
||||||
let mut prev_suggestions_by_row = BTreeMap::<u32, Option<u32>>::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();
|
let mut cursor = QueryCursorHandle::new();
|
||||||
self.start_transaction(None).unwrap();
|
while let Some(request) = self.autoindent_requests.front() {
|
||||||
let mut prev_suggestions = prev_suggestions_by_row.iter();
|
if new_version < request.version_after_edit() {
|
||||||
for row_range in contiguous_ranges(prev_suggestions_by_row.keys().copied()) {
|
break;
|
||||||
let new_suggestions = self
|
|
||||||
.suggest_autoindents(row_range.clone(), new_tree, &language, &mut cursor)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
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<u32>,
|
|
||||||
tree: &Tree,
|
|
||||||
language: &Language,
|
|
||||||
cursor: &mut QueryCursor,
|
|
||||||
) -> impl Iterator<Item = u32> + '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<Point>, &'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<Point> = None;
|
|
||||||
let mut end: Option<Point> = None;
|
|
||||||
for capture in mat.captures {
|
|
||||||
if Some(capture.index) == indent_capture_ix {
|
|
||||||
node_kind = capture.node.kind();
|
|
||||||
start.get_or_insert(capture.node.start_position().into());
|
|
||||||
end.get_or_insert(capture.node.end_position().into());
|
|
||||||
} else if Some(capture.index) == end_capture_ix {
|
|
||||||
end = Some(capture.node.start_position().into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((start, end)) = start.zip(end) {
|
let request = self.autoindent_requests.pop_front().unwrap();
|
||||||
if start.row == end.row {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let range = start..end;
|
let old_to_new_rows = request
|
||||||
match indentation_ranges.binary_search_by_key(&range.start, |r| r.0.start) {
|
.edited
|
||||||
Err(ix) => indentation_ranges.insert(ix, (range, node_kind)),
|
.to_points(request.before_edit.content())
|
||||||
Ok(ix) => {
|
.map(|point| point.row)
|
||||||
let prev_range = &mut indentation_ranges[ix];
|
.zip(
|
||||||
prev_range.0.end = prev_range.0.end.max(range.end);
|
request
|
||||||
|
.edited
|
||||||
|
.to_points(self.content())
|
||||||
|
.map(|point| point.row),
|
||||||
|
)
|
||||||
|
.collect::<BTreeMap<u32, u32>>();
|
||||||
|
|
||||||
|
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);
|
// At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the
|
||||||
let mut prev_indent_column =
|
// buffer before the edit, but keyed by the row for these lines after the edits were applied.
|
||||||
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));
|
|
||||||
|
|
||||||
eprintln!(
|
self.start_transaction(None).unwrap();
|
||||||
"autoindent row: {:?}, prev_indent_column: {:?}",
|
let new_edited_row_ranges = contiguous_ranges(old_to_new_rows.values().copied());
|
||||||
row, prev_indent_column
|
for new_edited_row_range in new_edited_row_ranges {
|
||||||
);
|
let new_content = self.content();
|
||||||
|
let suggestions = new_content
|
||||||
let mut indent_from_prev_row = false;
|
.suggest_autoindents(
|
||||||
let mut outdent_to_row = u32::MAX;
|
new_edited_row_range.clone(),
|
||||||
for (range, node_kind) in &indentation_ranges {
|
&new_tree,
|
||||||
if range.start.row >= row {
|
&language,
|
||||||
break;
|
&mut cursor,
|
||||||
}
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
if range.start.row == prev_row && range.end > row_start {
|
for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
|
||||||
eprintln!(" indent because of {} {:?}", node_kind, range);
|
let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
|
||||||
indent_from_prev_row = true;
|
let new_indentation = self.indent_column_for_line(suggestion.basis_row) + delta;
|
||||||
}
|
if old_suggestions
|
||||||
if range.end.row >= prev_row && range.end <= row_start {
|
.get(&new_row)
|
||||||
eprintln!(" outdent because of {} {:?}", node_kind, range);
|
.map_or(true, |old_indentation| new_indentation != *old_indentation)
|
||||||
outdent_to_row = outdent_to_row.min(range.start.row);
|
{
|
||||||
|
self.set_indent_column_for_line(new_row, new_indentation, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.end_transaction(None, cx).unwrap();
|
||||||
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<u32> {
|
|
||||||
while row > 0 {
|
|
||||||
row -= 1;
|
|
||||||
if !self.is_line_blank(row) {
|
|
||||||
return Some(row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn indent_column_for_line(&self, row: u32) -> u32 {
|
pub fn indent_column_for_line(&self, row: u32) -> u32 {
|
||||||
let mut result = 0;
|
self.content().indent_column_for_line(row)
|
||||||
for c in self.chars_at(Point::new(row, 0)) {
|
|
||||||
if c == ' ' {
|
|
||||||
result += 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
|
fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
|
||||||
|
@ -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<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
|
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
|
||||||
if let Some(tree) = self.syntax_tree() {
|
if let Some(tree) = self.syntax_tree() {
|
||||||
let root = tree.root_node();
|
let root = tree.root_node();
|
||||||
|
@ -1308,18 +1238,18 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range<T>) -> Chunks<'a> {
|
pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range<T>) -> Chunks<'a> {
|
||||||
let start = range.start.to_offset(self);
|
self.content().text_for_range(range)
|
||||||
let end = range.end.to_offset(self);
|
|
||||||
self.visible_text.chunks_in_range(start..end)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
|
pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
|
||||||
self.chars_at(0)
|
self.chars_at(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
|
pub fn chars_at<'a, T: 'a + ToOffset>(
|
||||||
let offset = position.to_offset(self);
|
&'a self,
|
||||||
self.visible_text.chars_at(offset)
|
position: T,
|
||||||
|
) -> impl Iterator<Item = char> + 'a {
|
||||||
|
self.content().chars_at(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chars_for_range<T: ToOffset>(&self, range: Range<T>) -> impl Iterator<Item = char> + '_ {
|
pub fn chars_for_range<T: ToOffset>(&self, range: Range<T>) -> impl Iterator<Item = char> + '_ {
|
||||||
|
@ -1484,36 +1414,20 @@ impl Buffer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When autoindent is enabled, compute the previous indent suggestion for all edited lines.
|
let autoindent_request = if autoindent && self.language.is_some() {
|
||||||
if autoindent {
|
let before_edit = self.snapshot();
|
||||||
if let Some((language, tree)) = self.language.as_ref().zip(self.syntax_tree()) {
|
let edited = self.content().anchor_set(ranges.iter().filter_map(|range| {
|
||||||
let mut cursor = QueryCursorHandle::new();
|
let start = range.start.to_point(&*self);
|
||||||
let starts_with_newline = new_text.starts_with('\n');
|
if new_text.starts_with('\n') && start.column == self.line_len(start.row) {
|
||||||
|
None
|
||||||
let mut autoindent_requests = mem::take(&mut self.autoindent_requests);
|
} else {
|
||||||
let edited_rows = ranges.iter().filter_map(|range| {
|
Some((range.start, Bias::Left))
|
||||||
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::<Vec<_>>();
|
|
||||||
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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.autoindent_requests = autoindent_requests;
|
}));
|
||||||
}
|
Some((before_edit, edited))
|
||||||
}
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let new_text = if new_text.len() > 0 {
|
let new_text = if new_text.len() > 0 {
|
||||||
Some(new_text)
|
Some(new_text)
|
||||||
|
@ -1534,25 +1448,18 @@ impl Buffer {
|
||||||
self.last_edit = edit.timestamp.local();
|
self.last_edit = edit.timestamp.local();
|
||||||
self.version.observe(edit.timestamp.local());
|
self.version.observe(edit.timestamp.local());
|
||||||
|
|
||||||
if autoindent && self.language.is_some() {
|
if let Some((before_edit, edited)) = autoindent_request {
|
||||||
let ranges = edit.ranges.iter().map(|range| {
|
let inserted = self.content().anchor_range_set(
|
||||||
Anchor {
|
edit.ranges
|
||||||
offset: range.start,
|
.iter()
|
||||||
bias: Bias::Right,
|
.map(|range| (range.start, Bias::Left)..(range.end, Bias::Right)),
|
||||||
version: edit.version.clone(),
|
);
|
||||||
}..Anchor {
|
|
||||||
offset: range.end,
|
self.autoindent_requests.push_back(AutoindentRequest {
|
||||||
bias: Bias::Right,
|
before_edit,
|
||||||
version: edit.version.clone(),
|
edited,
|
||||||
}
|
inserted,
|
||||||
});
|
})
|
||||||
for range in ranges {
|
|
||||||
self.autoindent_requests.push(AutoindentRequest {
|
|
||||||
position: range.start,
|
|
||||||
end_position: Some(range.end),
|
|
||||||
prev_suggestion: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.end_transaction_at(None, Instant::now(), cx).unwrap();
|
self.end_transaction_at(None, Instant::now(), cx).unwrap();
|
||||||
|
@ -2134,20 +2041,20 @@ impl Buffer {
|
||||||
|
|
||||||
let mut new_ropes =
|
let mut new_ropes =
|
||||||
RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
|
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::<FragmentTextSummary>();
|
||||||
let mut new_fragments = old_fragments.slice(&ranges[0].start, Bias::Right, &None);
|
let mut new_fragments = old_fragments.slice(&ranges[0].start, Bias::Right, &None);
|
||||||
new_ropes.push_tree(new_fragments.summary().text);
|
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 {
|
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
|
// 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.
|
// that extends past the start of this range, reusing any intervening fragments.
|
||||||
if fragment_end < range.start {
|
if fragment_end < range.start {
|
||||||
// If the current fragment has been partially consumed, then consume the rest of it
|
// If the current fragment has been partially consumed, then consume the rest of it
|
||||||
// and advance to the next fragment before slicing.
|
// 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 {
|
if fragment_end > fragment_start {
|
||||||
let mut suffix = old_fragments.item().unwrap().clone();
|
let mut suffix = old_fragments.item().unwrap().clone();
|
||||||
suffix.len = fragment_end - fragment_start;
|
suffix.len = fragment_end - fragment_start;
|
||||||
|
@ -2160,10 +2067,10 @@ impl Buffer {
|
||||||
let slice = old_fragments.slice(&range.start, Bias::Right, &None);
|
let slice = old_fragments.slice(&range.start, Bias::Right, &None);
|
||||||
new_ropes.push_tree(slice.summary().text);
|
new_ropes.push_tree(slice.summary().text);
|
||||||
new_fragments.push_tree(slice, &None);
|
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.
|
// Preserve any portion of the current fragment that precedes this range.
|
||||||
if fragment_start < range.start {
|
if fragment_start < range.start {
|
||||||
|
@ -2193,7 +2100,7 @@ impl Buffer {
|
||||||
// portions as deleted.
|
// portions as deleted.
|
||||||
while fragment_start < range.end {
|
while fragment_start < range.end {
|
||||||
let fragment = old_fragments.item().unwrap();
|
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 mut intersection = fragment.clone();
|
||||||
let intersection_end = cmp::min(range.end, fragment_end);
|
let intersection_end = cmp::min(range.end, fragment_end);
|
||||||
if fragment.visible {
|
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);
|
edit.ranges.push(full_range_start..full_range_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current fragment has been partially consumed, then consume the rest of it
|
// If the current fragment has been partially consumed, then consume the rest of it
|
||||||
// and advance to the next fragment before slicing.
|
// and advance to the next fragment before slicing.
|
||||||
if fragment_start > old_fragments.start().1.visible {
|
if fragment_start > old_fragments.start().visible {
|
||||||
let fragment_end = old_fragments.end(&None).1.visible;
|
let fragment_end = old_fragments.end(&None).visible;
|
||||||
if fragment_end > fragment_start {
|
if fragment_end > fragment_start {
|
||||||
let mut suffix = old_fragments.item().unwrap().clone();
|
let mut suffix = old_fragments.item().unwrap().clone();
|
||||||
suffix.len = fragment_end - fragment_start;
|
suffix.len = fragment_end - fragment_start;
|
||||||
|
@ -2458,7 +2365,7 @@ impl Clone for Buffer {
|
||||||
parsing_in_background: false,
|
parsing_in_background: false,
|
||||||
sync_parse_timeout: self.sync_parse_timeout,
|
sync_parse_timeout: self.sync_parse_timeout,
|
||||||
parse_count: self.parse_count,
|
parse_count: self.parse_count,
|
||||||
autoindent_requests: self.autoindent_requests.clone(),
|
autoindent_requests: Default::default(),
|
||||||
deferred_replicas: self.deferred_replicas.clone(),
|
deferred_replicas: self.deferred_replicas.clone(),
|
||||||
replica_id: self.replica_id,
|
replica_id: self.replica_id,
|
||||||
remote_id: self.remote_id.clone(),
|
remote_id: self.remote_id.clone(),
|
||||||
|
@ -2504,6 +2411,10 @@ impl Snapshot {
|
||||||
self.content().line_len(row)
|
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 {
|
pub fn text(&self) -> Rope {
|
||||||
self.visible_text.clone()
|
self.visible_text.clone()
|
||||||
}
|
}
|
||||||
|
@ -2644,6 +2555,17 @@ impl<'a> Content<'a> {
|
||||||
self.fragments.extent::<usize>(&None)
|
self.fragments.extent::<usize>(&None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + 'a {
|
||||||
|
let offset = position.to_offset(self);
|
||||||
|
self.visible_text.chars_at(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> 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 {
|
fn line_len(&self, row: u32) -> u32 {
|
||||||
let row_start_offset = Point::new(row, 0).to_offset(self);
|
let row_start_offset = Point::new(row, 0).to_offset(self);
|
||||||
let row_end_offset = if row >= self.max_point().row {
|
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
|
(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<u32> {
|
||||||
|
while row > 0 {
|
||||||
|
row -= 1;
|
||||||
|
if !self.is_line_blank(row) {
|
||||||
|
return Some(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_line_blank(&self, row: u32) -> bool {
|
||||||
|
self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
|
||||||
|
.all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
|
||||||
|
}
|
||||||
|
|
||||||
fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary {
|
fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary {
|
||||||
let cx = Some(anchor.version.clone());
|
let cx = Some(anchor.version.clone());
|
||||||
let mut cursor = self.fragments.cursor::<(VersionedOffset, usize)>();
|
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)
|
self.visible_text.cursor(range.start).summary(range.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn summaries_for_anchors<T>(
|
||||||
|
&self,
|
||||||
|
map: &'a AnchorMap<T>,
|
||||||
|
) -> impl Iterator<Item = (TextSummary, &'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 |((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<T>(
|
||||||
|
&self,
|
||||||
|
map: &'a AnchorRangeMap<T>,
|
||||||
|
) -> impl Iterator<Item = (Range<TextSummary>, &'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<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
|
fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
|
||||||
let offset = position.to_offset(self);
|
let offset = position.to_offset(self);
|
||||||
let max_offset = self.len();
|
let max_offset = self.len();
|
||||||
assert!(offset <= max_offset, "offset is out of range");
|
assert!(offset <= max_offset, "offset is out of range");
|
||||||
let mut cursor = self.fragments.cursor::<(usize, FragmentTextSummary)>();
|
let mut cursor = self.fragments.cursor::<FragmentTextSummary>();
|
||||||
cursor.seek(&offset, bias, &None);
|
cursor.seek(&offset, bias, &None);
|
||||||
Anchor {
|
Anchor {
|
||||||
offset: offset + cursor.start().1.deleted,
|
offset: offset + cursor.start().deleted,
|
||||||
bias,
|
bias,
|
||||||
version: self.version.clone(),
|
version: self.version.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn anchor_map<T, E>(&self, entries: E) -> AnchorMap<T>
|
||||||
|
where
|
||||||
|
E: IntoIterator<Item = ((usize, Bias), T)>,
|
||||||
|
{
|
||||||
|
let version = self.version.clone();
|
||||||
|
let mut cursor = self.fragments.cursor::<FragmentTextSummary>();
|
||||||
|
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<T, E>(&self, entries: E) -> AnchorRangeMap<T>
|
||||||
|
where
|
||||||
|
E: IntoIterator<Item = (Range<(usize, Bias)>, T)>,
|
||||||
|
{
|
||||||
|
let version = self.version.clone();
|
||||||
|
let mut cursor = self.fragments.cursor::<FragmentTextSummary>();
|
||||||
|
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<E>(&self, entries: E) -> AnchorSet
|
||||||
|
where
|
||||||
|
E: IntoIterator<Item = (usize, Bias)>,
|
||||||
|
{
|
||||||
|
AnchorSet(self.anchor_map(entries.into_iter().map(|range| (range, ()))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anchor_range_set<E>(&self, entries: E) -> AnchorRangeSet
|
||||||
|
where
|
||||||
|
E: IntoIterator<Item = Range<(usize, Bias)>>,
|
||||||
|
{
|
||||||
|
AnchorRangeSet(self.anchor_range_map(entries.into_iter().map(|range| (range, ()))))
|
||||||
|
}
|
||||||
|
|
||||||
fn full_offset_for_anchor(&self, anchor: &Anchor) -> usize {
|
fn full_offset_for_anchor(&self, anchor: &Anchor) -> usize {
|
||||||
let cx = Some(anchor.version.clone());
|
let cx = Some(anchor.version.clone());
|
||||||
let mut cursor = self
|
let mut cursor = self
|
||||||
|
@ -2705,6 +2769,117 @@ impl<'a> Content<'a> {
|
||||||
Err(anyhow!("offset out of bounds"))
|
Err(anyhow!("offset out of bounds"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn suggest_autoindents(
|
||||||
|
&'a self,
|
||||||
|
row_range: Range<u32>,
|
||||||
|
tree: &Tree,
|
||||||
|
language: &Language,
|
||||||
|
cursor: &mut QueryCursor,
|
||||||
|
) -> impl Iterator<Item = IndentSuggestion> + '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<Point>, &'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<Point> = None;
|
||||||
|
let mut end: Option<Point> = None;
|
||||||
|
for capture in mat.captures {
|
||||||
|
if Some(capture.index) == indent_capture_ix {
|
||||||
|
node_kind = capture.node.kind();
|
||||||
|
start.get_or_insert(capture.node.start_position().into());
|
||||||
|
end.get_or_insert(capture.node.end_position().into());
|
||||||
|
} else if Some(capture.index) == end_capture_ix {
|
||||||
|
end = Some(capture.node.start_position().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((start, end)) = start.zip(end) {
|
||||||
|
if start.row == end.row {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = start..end;
|
||||||
|
match indentation_ranges.binary_search_by_key(&range.start, |r| r.0.start) {
|
||||||
|
Err(ix) => indentation_ranges.insert(ix, (range, node_kind)),
|
||||||
|
Ok(ix) => {
|
||||||
|
let prev_range = &mut indentation_ranges[ix];
|
||||||
|
prev_range.0.end = prev_range.0.end.max(range.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
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<clock::Global>,
|
||||||
|
) -> cmp::Ordering {
|
||||||
|
Ord::cmp(self, &cursor_location.visible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
enum VersionedOffset {
|
enum VersionedOffset {
|
||||||
Offset(usize),
|
Offset(usize),
|
||||||
|
|
|
@ -268,6 +268,7 @@ impl<'a> Cursor<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.offset = end_offset;
|
||||||
summary
|
summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue