WIP: Determine autoindents asynchronously
We still need to insert yield points in `compute_autoindents`. Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
7f5d454b2d
commit
28ffd750ce
2 changed files with 252 additions and 228 deletions
|
@ -12,18 +12,22 @@ pub struct Anchor {
|
||||||
pub version: clock::Global,
|
pub version: clock::Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct AnchorMap<T> {
|
pub struct AnchorMap<T> {
|
||||||
pub(crate) version: clock::Global,
|
pub(crate) version: clock::Global,
|
||||||
pub(crate) entries: Vec<((usize, Bias), T)>,
|
pub(crate) entries: Vec<((usize, Bias), T)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct AnchorSet(pub(crate) AnchorMap<()>);
|
pub struct AnchorSet(pub(crate) AnchorMap<()>);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct AnchorRangeMap<T> {
|
pub struct AnchorRangeMap<T> {
|
||||||
pub(crate) version: clock::Global,
|
pub(crate) version: clock::Global,
|
||||||
pub(crate) entries: Vec<(Range<(usize, Bias)>, T)>,
|
pub(crate) entries: Vec<(Range<(usize, Bias)>, T)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>);
|
pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>);
|
||||||
|
|
||||||
impl Anchor {
|
impl Anchor {
|
||||||
|
|
|
@ -30,9 +30,10 @@ use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp,
|
cmp,
|
||||||
collections::{BTreeMap, VecDeque},
|
collections::BTreeMap,
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
|
future::Future,
|
||||||
hash::BuildHasher,
|
hash::BuildHasher,
|
||||||
iter::Iterator,
|
iter::Iterator,
|
||||||
ops::{Deref, DerefMut, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
|
@ -166,7 +167,8 @@ 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: VecDeque<AutoindentRequest>,
|
autoindent_requests: Vec<Arc<AutoindentRequest>>,
|
||||||
|
pending_autoindent: Option<Task<()>>,
|
||||||
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,
|
||||||
|
@ -194,19 +196,13 @@ struct SyntaxTree {
|
||||||
version: clock::Global,
|
version: clock::Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct AutoindentRequest {
|
struct AutoindentRequest {
|
||||||
before_edit: Snapshot,
|
before_edit: Snapshot,
|
||||||
version_after_edit: clock::Global,
|
|
||||||
edited: AnchorSet,
|
edited: AnchorSet,
|
||||||
inserted: Option<AnchorRangeSet>,
|
inserted: Option<AnchorRangeSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoindentRequest {
|
|
||||||
fn version_after_edit(&self) -> &clock::Global {
|
|
||||||
&self.version_after_edit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Transaction {
|
struct Transaction {
|
||||||
start: clock::Global,
|
start: clock::Global,
|
||||||
|
@ -651,6 +647,7 @@ impl Buffer {
|
||||||
parse_count: 0,
|
parse_count: 0,
|
||||||
sync_parse_timeout: Duration::from_millis(1),
|
sync_parse_timeout: Duration::from_millis(1),
|
||||||
autoindent_requests: Default::default(),
|
autoindent_requests: Default::default(),
|
||||||
|
pending_autoindent: Default::default(),
|
||||||
language,
|
language,
|
||||||
saved_mtime,
|
saved_mtime,
|
||||||
selections: HashMap::default(),
|
selections: HashMap::default(),
|
||||||
|
@ -896,7 +893,7 @@ impl Buffer {
|
||||||
.block_with_timeout(self.sync_parse_timeout, parse_task)
|
.block_with_timeout(self.sync_parse_timeout, parse_task)
|
||||||
{
|
{
|
||||||
Ok(new_tree) => {
|
Ok(new_tree) => {
|
||||||
self.did_finish_parsing(new_tree, parsed_version, language, cx);
|
self.did_finish_parsing(new_tree, parsed_version, cx);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Err(parse_task) => {
|
Err(parse_task) => {
|
||||||
|
@ -910,7 +907,7 @@ impl Buffer {
|
||||||
});
|
});
|
||||||
let parse_again = this.version > parsed_version || language_changed;
|
let parse_again = this.version > parsed_version || language_changed;
|
||||||
this.parsing_in_background = false;
|
this.parsing_in_background = false;
|
||||||
this.did_finish_parsing(new_tree, parsed_version, language, cx);
|
this.did_finish_parsing(new_tree, parsed_version, cx);
|
||||||
|
|
||||||
if parse_again && this.reparse(cx) {
|
if parse_again && this.reparse(cx) {
|
||||||
return;
|
return;
|
||||||
|
@ -969,54 +966,77 @@ impl Buffer {
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
version: clock::Global,
|
version: clock::Global,
|
||||||
language: Arc<Language>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
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 });
|
||||||
|
self.request_autoindent(cx);
|
||||||
cx.emit(Event::Reparsed);
|
cx.emit(Event::Reparsed);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_autoindent(
|
fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
&mut self,
|
if let Some(indent_columns) = self.compute_autoindents() {
|
||||||
new_tree: &Tree,
|
let indent_columns = cx.background().spawn(indent_columns);
|
||||||
new_version: &clock::Global,
|
match cx
|
||||||
language: Arc<Language>,
|
.background()
|
||||||
cx: &mut ModelContext<Self>,
|
.block_with_timeout(Duration::from_micros(500), indent_columns)
|
||||||
) {
|
{
|
||||||
let mut cursor = QueryCursorHandle::new();
|
Ok(indent_columns) => {
|
||||||
while let Some(request) = self.autoindent_requests.front() {
|
log::info!("finished synchronously {:?}", indent_columns);
|
||||||
if new_version < request.version_after_edit() {
|
self.autoindent_requests.clear();
|
||||||
break;
|
self.start_transaction(None).unwrap();
|
||||||
|
for (row, indent_column) in indent_columns {
|
||||||
|
self.set_indent_column_for_line(row, indent_column, cx);
|
||||||
|
}
|
||||||
|
self.end_transaction(None, cx).unwrap();
|
||||||
|
}
|
||||||
|
Err(indent_columns) => {
|
||||||
|
self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move {
|
||||||
|
let indent_columns = indent_columns.await;
|
||||||
|
log::info!("finished ASYNC, {:?}", indent_columns);
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.autoindent_requests.clear();
|
||||||
|
this.start_transaction(None).unwrap();
|
||||||
|
for (row, indent_column) in indent_columns {
|
||||||
|
this.set_indent_column_for_line(row, indent_column, cx);
|
||||||
|
}
|
||||||
|
this.end_transaction(None, cx).unwrap();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let request = self.autoindent_requests.pop_front().unwrap();
|
fn compute_autoindents(&self) -> Option<impl Future<Output = BTreeMap<u32, u32>>> {
|
||||||
|
let snapshot = self.snapshot();
|
||||||
|
if snapshot.language.is_none()
|
||||||
|
|| snapshot.tree.is_none()
|
||||||
|
|| self.autoindent_requests.is_empty()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let old_to_new_rows = request
|
let autoindent_requests = self.autoindent_requests.clone();
|
||||||
.edited
|
Some(async move {
|
||||||
.to_points(request.before_edit.content())
|
let mut indent_columns = BTreeMap::new();
|
||||||
.map(|point| point.row)
|
for request in autoindent_requests {
|
||||||
.zip(
|
let old_to_new_rows = request
|
||||||
request
|
.edited
|
||||||
.edited
|
.to_points(&request.before_edit)
|
||||||
.to_points(self.content())
|
.map(|point| point.row)
|
||||||
.map(|point| point.row),
|
.zip(request.edited.to_points(&snapshot).map(|point| point.row))
|
||||||
)
|
.collect::<BTreeMap<u32, u32>>();
|
||||||
.collect::<BTreeMap<u32, u32>>();
|
|
||||||
|
|
||||||
let mut old_suggestions = HashMap::default();
|
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());
|
let old_edited_ranges = contiguous_ranges(old_to_new_rows.keys().copied());
|
||||||
for old_edited_range in old_edited_ranges {
|
for old_edited_range in old_edited_ranges {
|
||||||
let old_content = request.before_edit.content();
|
let suggestions = request
|
||||||
let suggestions = old_content.suggest_autoindents(
|
.before_edit
|
||||||
old_edited_range.clone(),
|
.suggest_autoindents(old_edited_range.clone())
|
||||||
old_tree,
|
.into_iter()
|
||||||
&language,
|
.flatten();
|
||||||
&mut cursor,
|
|
||||||
);
|
|
||||||
for (old_row, suggestion) in old_edited_range.zip(suggestions) {
|
for (old_row, suggestion) in old_edited_range.zip(suggestions) {
|
||||||
let indentation_basis = old_to_new_rows
|
let indentation_basis = old_to_new_rows
|
||||||
.get(&suggestion.basis_row)
|
.get(&suggestion.basis_row)
|
||||||
|
@ -1033,64 +1053,60 @@ impl Buffer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the
|
// 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.
|
// 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());
|
||||||
self.start_transaction(None).unwrap();
|
for new_edited_row_range in new_edited_row_ranges {
|
||||||
let new_edited_row_ranges = contiguous_ranges(old_to_new_rows.values().copied());
|
let suggestions = snapshot
|
||||||
for new_edited_row_range in new_edited_row_ranges {
|
.suggest_autoindents(new_edited_row_range.clone())
|
||||||
let suggestions = self
|
.into_iter()
|
||||||
.content()
|
.flatten();
|
||||||
.suggest_autoindents(
|
for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
|
||||||
new_edited_row_range.clone(),
|
|
||||||
&new_tree,
|
|
||||||
&language,
|
|
||||||
&mut cursor,
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(inserted) = request.inserted {
|
|
||||||
let inserted_row_ranges = contiguous_ranges(
|
|
||||||
inserted
|
|
||||||
.to_point_ranges(self.content())
|
|
||||||
.flat_map(|range| range.start.row..range.end.row + 1),
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
for inserted_row_range in inserted_row_ranges {
|
|
||||||
let suggestions = self
|
|
||||||
.content()
|
|
||||||
.suggest_autoindents(
|
|
||||||
inserted_row_range.clone(),
|
|
||||||
&new_tree,
|
|
||||||
&language,
|
|
||||||
&mut cursor,
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for (row, suggestion) in inserted_row_range.zip(suggestions) {
|
|
||||||
let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
|
let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
|
||||||
let new_indentation =
|
let new_indentation = indent_columns
|
||||||
self.indent_column_for_line(suggestion.basis_row) + delta;
|
.get(&suggestion.basis_row)
|
||||||
self.set_indent_column_for_line(row, new_indentation, cx);
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
indent_columns
|
||||||
self.end_transaction(None, cx).unwrap();
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn indent_column_for_line(&self, row: u32) -> u32 {
|
pub fn indent_column_for_line(&self, row: u32) -> u32 {
|
||||||
|
@ -1443,6 +1459,7 @@ impl Buffer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.pending_autoindent.take();
|
||||||
let autoindent_request = if autoindent && self.language.is_some() {
|
let autoindent_request = if autoindent && self.language.is_some() {
|
||||||
let before_edit = self.snapshot();
|
let before_edit = self.snapshot();
|
||||||
let edited = self.content().anchor_set(ranges.iter().filter_map(|range| {
|
let edited = self.content().anchor_set(ranges.iter().filter_map(|range| {
|
||||||
|
@ -1466,7 +1483,7 @@ impl Buffer {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
self.start_transaction_at(None, Instant::now()).unwrap();
|
self.start_transaction(None).unwrap();
|
||||||
let timestamp = InsertionTimestamp {
|
let timestamp = InsertionTimestamp {
|
||||||
replica_id: self.replica_id,
|
replica_id: self.replica_id,
|
||||||
local: self.local_clock.tick().value,
|
local: self.local_clock.tick().value,
|
||||||
|
@ -1482,21 +1499,23 @@ impl Buffer {
|
||||||
if let Some((before_edit, edited)) = autoindent_request {
|
if let Some((before_edit, edited)) = autoindent_request {
|
||||||
let mut inserted = None;
|
let mut inserted = None;
|
||||||
if let Some(first_newline_ix) = first_newline_ix {
|
if let Some(first_newline_ix) = first_newline_ix {
|
||||||
|
let mut delta = 0isize;
|
||||||
inserted = Some(self.content().anchor_range_set(ranges.iter().map(|range| {
|
inserted = Some(self.content().anchor_range_set(ranges.iter().map(|range| {
|
||||||
(range.start + first_newline_ix + 1, Bias::Left)
|
let start = (delta + range.start as isize) as usize + first_newline_ix + 1;
|
||||||
..(range.start + new_text_len, Bias::Right)
|
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)
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.autoindent_requests.push_back(AutoindentRequest {
|
self.autoindent_requests.push(Arc::new(AutoindentRequest {
|
||||||
before_edit,
|
before_edit,
|
||||||
version_after_edit: self.version.clone(),
|
|
||||||
edited,
|
edited,
|
||||||
inserted,
|
inserted,
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.end_transaction_at(None, Instant::now(), cx).unwrap();
|
self.end_transaction(None, cx).unwrap();
|
||||||
self.send_operation(Operation::Edit(edit), cx);
|
self.send_operation(Operation::Edit(edit), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1626,6 +1645,8 @@ impl Buffer {
|
||||||
ops: I,
|
ops: I,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
self.pending_autoindent.take();
|
||||||
|
|
||||||
let was_dirty = self.is_dirty();
|
let was_dirty = self.is_dirty();
|
||||||
let old_version = self.version.clone();
|
let old_version = self.version.clone();
|
||||||
|
|
||||||
|
@ -2400,6 +2421,7 @@ impl Clone for Buffer {
|
||||||
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: Default::default(),
|
autoindent_requests: Default::default(),
|
||||||
|
pending_autoindent: 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(),
|
||||||
|
@ -2449,6 +2471,120 @@ impl Snapshot {
|
||||||
self.content().indent_column_for_line(row)
|
self.content().indent_column_for_line(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn suggest_autoindents<'a>(
|
||||||
|
&'a self,
|
||||||
|
row_range: Range<u32>,
|
||||||
|
) -> Option<impl Iterator<Item = IndentSuggestion> + '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<Point>, &'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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<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())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text(&self) -> Rope {
|
pub fn text(&self) -> Rope {
|
||||||
self.visible_text.clone()
|
self.visible_text.clone()
|
||||||
}
|
}
|
||||||
|
@ -2461,11 +2597,16 @@ impl Snapshot {
|
||||||
self.visible_text.max_point()
|
self.visible_text.max_point()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_for_range(&self, range: Range<usize>) -> Chunks {
|
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks {
|
||||||
|
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||||
self.visible_text.chunks_in_range(range)
|
self.visible_text.chunks_in_range(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn highlighted_text_for_range(&mut self, range: Range<usize>) -> HighlightedChunks {
|
pub fn highlighted_text_for_range<T: ToOffset>(
|
||||||
|
&mut self,
|
||||||
|
range: Range<T>,
|
||||||
|
) -> HighlightedChunks {
|
||||||
|
let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
|
||||||
let chunks = self.visible_text.chunks_in_range(range.clone());
|
let chunks = self.visible_text.chunks_in_range(range.clone());
|
||||||
if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
|
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(
|
let captures = self.query_cursor.set_byte_range(range.clone()).captures(
|
||||||
|
@ -2622,21 +2763,6 @@ impl<'a> Content<'a> {
|
||||||
result
|
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)>();
|
||||||
|
@ -2803,112 +2929,6 @@ 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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue