diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 8e61a9b633930655e296433711013645ea873dfd..82ab2736b8bc207aa30952ae9f79f161eb9db8db 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -1721,7 +1721,7 @@ impl BufferDiff { if let Some(language_registry) = language_registry { base_text.set_language_registry(language_registry); } - base_text.set_language(language, cx); + base_text.set_language_async(language, cx); base_text.parsing_idle() }); cx.spawn(async move |this, cx| { diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 63e315ab250d5ddbc0ffa9d37cb1c42b3803efac..122ca6f698115c2f5e6c194246f6a378825e5675 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -17,7 +17,7 @@ use multi_buffer::{ }; use project::InlayId; use std::{ - cmp, + cmp, iter, ops::{Add, AddAssign, Range, Sub, SubAssign}, sync::Arc, }; @@ -546,8 +546,11 @@ impl InlayMap { pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) { let version = 0; let snapshot = InlaySnapshot { - buffer: buffer.clone(), - transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), ()), + transforms: SumTree::from_iter( + iter::once(Transform::Isomorphic(buffer.text_summary())), + (), + ), + buffer, version, }; diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 5ff3979e7da848ddba98f5b6f8d1ea26ad990a81..187ed8614e01ddb8dcdae930fd484de9594cf63f 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -44,121 +44,146 @@ impl TabMap { mut fold_edits: Vec, tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { - let old_snapshot = &mut self.0; - let mut new_snapshot = TabSnapshot { - fold_snapshot, - tab_size: tab_size.min(MAX_TABS), - max_expansion_column: old_snapshot.max_expansion_column, - version: old_snapshot.version, - }; - - if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version { - new_snapshot.version += 1; + let tab_size = tab_size.min(MAX_TABS); + + if self.0.tab_size != tab_size { + let old_max_point = self.0.max_point(); + self.0.version += 1; + self.0.fold_snapshot = fold_snapshot; + self.0.tab_size = tab_size; + return ( + self.0.clone(), + vec![TabEdit { + old: TabPoint::zero()..old_max_point, + new: TabPoint::zero()..self.0.max_point(), + }], + ); } - let tab_edits = if old_snapshot.tab_size == new_snapshot.tab_size { - // Expand each edit to include the next tab on the same line as the edit, - // and any subsequent tabs on that line that moved across the tab expansion - // boundary. - for fold_edit in &mut fold_edits { - let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); - let old_end_row_successor_offset = cmp::min( - FoldPoint::new(old_end.row() + 1, 0), - old_snapshot.fold_snapshot.max_point(), - ) - .to_offset(&old_snapshot.fold_snapshot); - let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); + let old_snapshot = &mut self.0; + let mut new_version = old_snapshot.version; + if old_snapshot.fold_snapshot.version != fold_snapshot.version { + new_version += 1; + } - let mut offset_from_edit = 0; - let mut first_tab_offset = None; - let mut last_tab_with_changed_expansion_offset = None; - 'outer: for chunk in old_snapshot.fold_snapshot.chunks( - fold_edit.old.end..old_end_row_successor_offset, - false, - Highlights::default(), - ) { - let mut remaining_tabs = chunk.tabs; - while remaining_tabs != 0 { - let ix = remaining_tabs.trailing_zeros(); - let offset_from_edit = offset_from_edit + ix; - if first_tab_offset.is_none() { - first_tab_offset = Some(offset_from_edit); - } - - let old_column = old_end.column() + offset_from_edit; - let new_column = new_end.column() + offset_from_edit; - let was_expanded = old_column < old_snapshot.max_expansion_column; - let is_expanded = new_column < new_snapshot.max_expansion_column; - if was_expanded != is_expanded { - last_tab_with_changed_expansion_offset = Some(offset_from_edit); - } else if !was_expanded && !is_expanded { - break 'outer; - } - - remaining_tabs &= remaining_tabs - 1; + if fold_edits.is_empty() { + old_snapshot.version = new_version; + old_snapshot.fold_snapshot = fold_snapshot; + old_snapshot.tab_size = tab_size; + return (old_snapshot.clone(), vec![]); + } + // Expand each edit to include the next tab on the same line as the edit, + // and any subsequent tabs on that line that moved across the tab expansion + // boundary. + // + // This is necessary because a tab's display width depends on its column + // position: it expands to fill up to the next tab stop. When an edit + // shifts text on a line, any tab character after the edit may now render + // at a different width even though the tab byte itself wasn't touched. + // Additionally, tabs beyond `max_expansion_column` are rendered as a + // single space instead of expanding to the next tab stop. An edit that + // shifts a tab across that boundary changes its display width, so the + // edit must cover it. We scan forward from the edit end to the end of + // the line, extending the edit to include the first subsequent tab (whose + // rendered width may have changed) and the last tab that crossed the + // expansion boundary (transitioning between expanded and non-expanded). + for fold_edit in &mut fold_edits { + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let old_end_row_successor_offset = cmp::min( + FoldPoint::new(old_end.row() + 1, 0), + old_snapshot.fold_snapshot.max_point(), + ) + .to_offset(&old_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&fold_snapshot); + + let mut offset_from_edit = 0; + let mut first_tab_offset = None; + let mut last_tab_with_changed_expansion_offset = None; + 'outer: for chunk in old_snapshot.fold_snapshot.chunks( + fold_edit.old.end..old_end_row_successor_offset, + false, + Highlights::default(), + ) { + let mut remaining_tabs = chunk.tabs; + while remaining_tabs != 0 { + let ix = remaining_tabs.trailing_zeros(); + let offset_from_edit = offset_from_edit + ix; + if first_tab_offset.is_none() { + first_tab_offset = Some(offset_from_edit); } - offset_from_edit += chunk.text.len() as u32; - if old_end.column() + offset_from_edit >= old_snapshot.max_expansion_column - && new_end.column() + offset_from_edit >= new_snapshot.max_expansion_column - { - break; + let old_column = old_end.column() + offset_from_edit; + let new_column = new_end.column() + offset_from_edit; + let was_expanded = old_column < old_snapshot.max_expansion_column; + let is_expanded = new_column < old_snapshot.max_expansion_column; + if was_expanded != is_expanded { + last_tab_with_changed_expansion_offset = Some(offset_from_edit); + } else if !was_expanded && !is_expanded { + break 'outer; } + + remaining_tabs &= remaining_tabs - 1; } - if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) { - fold_edit.old.end.0 += offset as usize + 1; - fold_edit.new.end.0 += offset as usize + 1; + offset_from_edit += chunk.text.len() as u32; + if old_end.column() + offset_from_edit >= old_snapshot.max_expansion_column + && new_end.column() + offset_from_edit >= old_snapshot.max_expansion_column + { + break; } } - let _old_alloc_ptr = fold_edits.as_ptr(); - // Combine any edits that overlap due to the expansion. - let mut fold_edits = fold_edits.into_iter(); - if let Some(mut first_edit) = fold_edits.next() { - // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them. - #[allow(clippy::filter_map_identity)] - let mut v: Vec<_> = fold_edits - .scan(&mut first_edit, |state, edit| { - if state.old.end >= edit.old.start { - state.old.end = edit.old.end; - state.new.end = edit.new.end; - Some(None) // Skip this edit, it's merged - } else { - let new_state = edit; - let result = Some(Some(state.clone())); // Yield the previous edit - **state = new_state; - result - } - }) - .filter_map(|x| x) - .collect(); - v.push(first_edit); - debug_assert_eq!(v.as_ptr(), _old_alloc_ptr, "Fold edits were reallocated"); - v.into_iter() - .map(|fold_edit| { - let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot); - let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); - let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot); - let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); - TabEdit { - old: old_snapshot.fold_point_to_tab_point(old_start) - ..old_snapshot.fold_point_to_tab_point(old_end), - new: new_snapshot.fold_point_to_tab_point(new_start) - ..new_snapshot.fold_point_to_tab_point(new_end), - } - }) - .collect() - } else { - vec![] + if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) { + fold_edit.old.end.0 += offset as usize + 1; + fold_edit.new.end.0 += offset as usize + 1; } - } else { - new_snapshot.version += 1; - vec![TabEdit { - old: TabPoint::zero()..old_snapshot.max_point(), - new: TabPoint::zero()..new_snapshot.max_point(), - }] + } + + let new_snapshot = TabSnapshot { + fold_snapshot, + tab_size, + max_expansion_column: old_snapshot.max_expansion_column, + version: new_version, }; + + let _old_alloc_ptr = fold_edits.as_ptr(); + // Combine any edits that overlap due to the expansion. + let mut fold_edits = fold_edits.into_iter(); + let mut first_edit = fold_edits.next().unwrap(); + // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them. + #[allow(clippy::filter_map_identity)] + let mut v: Vec<_> = fold_edits + .scan(&mut first_edit, |state, edit| { + if state.old.end >= edit.old.start { + state.old.end = edit.old.end; + state.new.end = edit.new.end; + Some(None) // Skip this edit, it's merged + } else { + let new_state = edit; + let result = Some(Some(state.clone())); // Yield the previous edit + **state = new_state; + result + } + }) + .filter_map(|x| x) + .collect(); + v.push(first_edit); + debug_assert_eq!(v.as_ptr(), _old_alloc_ptr, "Fold edits were reallocated"); + let tab_edits = v + .into_iter() + .map(|fold_edit| { + let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot); + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); + TabEdit { + old: old_snapshot.fold_point_to_tab_point(old_start) + ..old_snapshot.fold_point_to_tab_point(old_end), + new: new_snapshot.fold_point_to_tab_point(new_start) + ..new_snapshot.fold_point_to_tab_point(new_end), + } + }) + .collect(); *old_snapshot = new_snapshot; (old_snapshot.clone(), tab_edits) } @@ -168,6 +193,8 @@ impl TabMap { pub struct TabSnapshot { pub fold_snapshot: FoldSnapshot, pub tab_size: NonZeroU32, + /// The maximum column up to which a tab can expand. + /// Any tab after this column will not expand. pub max_expansion_column: u32, pub version: usize, } @@ -365,10 +392,11 @@ impl TabSnapshot { } #[ztracing::instrument(skip_all)] - fn expand_tabs<'a, I>(&self, mut cursor: TabStopCursor<'a, I>, column: u32) -> u32 - where - I: Iterator>, - { + fn expand_tabs<'a>(&self, mut cursor: TabStopCursor<'a>, column: u32) -> u32 { + // we only ever act on a single row at a time + // so the main difference is that other layers build a transform sumtree, and can then just run through that + // we cant quite do this here, as we need to work with the previous layer chunk to understand the tabs of the corresponding row + // we can still do forward searches for this though, we search for a row, then traverse the column up to where we need to be let tab_size = self.tab_size.get(); let end_column = column.min(self.max_expansion_column); @@ -376,7 +404,7 @@ impl TabSnapshot { let mut tab_count = 0; let mut expanded_tab_len = 0; - while let Some(tab_stop) = cursor.seek(seek_target) { + while let Some(tab_stop) = cursor.seek_forward(seek_target) { let expanded_chars_old = tab_stop.char_offset + expanded_tab_len - tab_count; let tab_len = tab_size - ((expanded_chars_old - 1) % tab_size); tab_count += 1; @@ -399,22 +427,19 @@ impl TabSnapshot { } #[ztracing::instrument(skip_all)] - fn collapse_tabs<'a, I>( + fn collapse_tabs<'a>( &self, - mut cursor: TabStopCursor<'a, I>, + mut cursor: TabStopCursor<'a>, column: u32, bias: Bias, - ) -> (u32, u32, u32) - where - I: Iterator>, - { + ) -> (u32, u32, u32) { let tab_size = self.tab_size.get(); let mut collapsed_column = column; let mut seek_target = column.min(self.max_expansion_column); let mut tab_count = 0; let mut expanded_tab_len = 0; - while let Some(tab_stop) = cursor.seek(seek_target) { + while let Some(tab_stop) = cursor.seek_forward(seek_target) { // Calculate how much we want to expand this tab stop (into spaces) let expanded_chars_old = tab_stop.char_offset + expanded_tab_len - tab_count; let tab_len = tab_size - ((expanded_chars_old - 1) % tab_size); @@ -617,13 +642,7 @@ impl<'a> Iterator for TabChunks<'a> { } } - let first_tab_ix = if self.chunk.tabs != 0 { - self.chunk.tabs.trailing_zeros() as usize - } else { - self.chunk.text.len() - }; - - if first_tab_ix == 0 { + if self.chunk.tabs & 1 != 0 { self.chunk.text = &self.chunk.text[1..]; self.chunk.tabs >>= 1; self.chunk.chars >>= 1; @@ -654,12 +673,46 @@ impl<'a> Iterator for TabChunks<'a> { }); } - let prefix_len = first_tab_ix; + // Fast path: no tabs in the remaining chunk, return it directly + if self.chunk.tabs == 0 { + let chunk = self.chunk.clone(); + self.chunk.text = ""; + self.chunk.tabs = 0; + self.chunk.chars = 0; + self.chunk.newlines = 0; + let chunk_len = chunk.text.len() as u32; + + let newline_count = chunk.newlines.count_ones(); + if newline_count > 0 { + let last_newline_bit = 128 - chunk.newlines.leading_zeros(); + let chars_after_last_newline = + chunk.chars.unbounded_shr(last_newline_bit).count_ones(); + let bytes_after_last_newline = chunk_len - last_newline_bit; + + self.column = chars_after_last_newline; + self.input_column = bytes_after_last_newline; + self.output_position = Point::new( + self.output_position.row + newline_count, + bytes_after_last_newline, + ); + } else { + let char_count = chunk.chars.count_ones(); + self.column += char_count; + if !self.inside_leading_tab { + self.input_column += chunk_len; + } + self.output_position.column += chunk_len; + } + + return Some(chunk); + } + + // Split at the next tab position + let prefix_len = self.chunk.tabs.trailing_zeros() as usize; let (prefix, suffix) = self.chunk.text.split_at(prefix_len); let mask = 1u128.unbounded_shl(prefix_len as u32).wrapping_sub(1); let prefix_chars = self.chunk.chars & mask; - let prefix_tabs = self.chunk.tabs & mask; let prefix_newlines = self.chunk.newlines & mask; self.chunk.text = suffix; @@ -692,13 +745,156 @@ impl<'a> Iterator for TabChunks<'a> { Some(Chunk { text: prefix, chars: prefix_chars, - tabs: prefix_tabs, + tabs: 0, newlines: prefix_newlines, ..self.chunk.clone() }) } } +struct TabStopCursor<'a> { + chunks: FoldChunks<'a>, + byte_offset: u32, + char_offset: u32, + /// Chunk + /// last tab position iterated through + current_chunk: Option<(TabStopChunk<'a>, u32)>, +} + +struct TabStopChunk<'a> { + chars: u128, + text: &'a str, + tabs: u128, +} + +impl<'a> TabStopCursor<'a> { + fn new(chunks: FoldChunks<'a>) -> Self { + Self { + chunks, + byte_offset: 0, + char_offset: 0, + current_chunk: None, + } + } + + fn bytes_until_next_char(&self) -> Option { + self.current_chunk.as_ref().map(|&(ref chunk, idx)| { + let higher_chars = chunk.chars.unbounded_shr(idx + 1); + + if higher_chars != 0 { + higher_chars.trailing_zeros() as usize + 1 + } else { + chunk.text.len() - idx as usize + } + }) + } + + fn is_char_boundary(&self) -> bool { + self.current_chunk + .as_ref() + .is_some_and(|&(ref chunk, idx)| { + (1u128.unbounded_shl(idx) & chunk.chars) != 0 || idx as usize == chunk.text.len() + }) + } + + /// distance: length to move forward while searching for the next tab stop + #[ztracing::instrument(skip_all)] + fn seek_forward(&mut self, distance: u32) -> Option { + if distance == 0 { + return None; + } + + let mut distance_remaining = distance; + + while let Some((mut chunk, chunk_position)) = self.current_chunk.take().or_else(|| { + self.chunks.next().map(|chunk| { + ( + TabStopChunk { + chars: chunk.chars, + text: chunk.text, + tabs: chunk.tabs, + }, + 0, + ) + }) + }) { + let chunk_len = chunk.text.len() as u32; + + if chunk.tabs == 0 { + let chunk_remaining = chunk_len - chunk_position; + if chunk_remaining >= distance_remaining { + let end = chunk_position + distance_remaining; + self.byte_offset += distance_remaining; + self.char_offset += + count_chars_in_byte_range(chunk_position..(end - 1), chunk.chars); + if end < 128 { + self.current_chunk = Some((chunk, end)); + } + return None; + } + + self.byte_offset += chunk_remaining; + self.char_offset += + count_chars_in_byte_range(chunk_position..(chunk_len - 1), chunk.chars); + distance_remaining -= chunk_remaining; + continue; + } + + let tab_end = chunk.tabs.trailing_zeros() + 1; + let bytes_to_tab = tab_end - chunk_position; + + if bytes_to_tab > distance_remaining { + let end = chunk_position + distance_remaining; + self.byte_offset += distance_remaining; + self.char_offset += + count_chars_in_byte_range(chunk_position..(end - 1), chunk.chars); + self.current_chunk = Some((chunk, end)); + return None; + } + + self.byte_offset += bytes_to_tab; + self.char_offset += + count_chars_in_byte_range(chunk_position..(tab_end - 1), chunk.chars); + + let tabstop = TabStop { + char_offset: self.char_offset, + byte_offset: self.byte_offset, + }; + + chunk.tabs = (chunk.tabs - 1) & chunk.tabs; + + if tab_end != chunk_len { + self.current_chunk = Some((chunk, tab_end)); + } + + return Some(tabstop); + } + + None + } + + fn byte_offset(&self) -> u32 { + self.byte_offset + } + + fn char_offset(&self) -> u32 { + self.char_offset + } +} + +#[inline(always)] +fn count_chars_in_byte_range(range: Range, bitmap: u128) -> u32 { + let low_mask = u128::MAX << range.start; + let high_mask = u128::MAX >> (127 - range.end); + (bitmap & low_mask & high_mask).count_ones() +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct TabStop { + char_offset: u32, + byte_offset: u32, +} + #[cfg(test)] mod tests { use std::mem; @@ -814,40 +1010,21 @@ mod tests { #[gpui::test] fn test_expand_tabs(cx: &mut gpui::App) { - let test_values = [ - ("κg🏀 f\nwo🏀❌by🍐❎β🍗c\tβ❎ \ncλ🎉", 17), - (" \twςe", 4), - ("fε", 1), - ("i❎\t", 3), - ]; - let buffer = MultiBuffer::build_simple("", cx); + let input = "A\tBC\tDEF\tG\tHI\tJ\tK\tL\tM"; + + let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); - for (text, column) in test_values { - let mut tabs = 0u128; - let mut chars = 0u128; - for (idx, c) in text.char_indices() { - if c == '\t' { - tabs |= 1 << idx; - } - chars |= 1 << idx; - } - - let chunks = [Chunk { - text, - tabs, - chars, - ..Default::default() - }]; - - let cursor = TabStopCursor::new(chunks); + for (ix, _) in input.char_indices() { + let fold_point = FoldPoint::new(0, ix as u32); assert_eq!( - tab_snapshot.expected_expand_tabs(text.chars(), column), - tab_snapshot.expand_tabs(cursor, column) + tab_snapshot.expected_to_tab_point(fold_point), + tab_snapshot.fold_point_to_tab_point(fold_point), + "Failed with fold_point at column {ix}" ); } } @@ -1263,7 +1440,7 @@ mod tests { Default::default(), ); let mut cursor = TabStopCursor::new(chunks); - assert!(cursor.seek(0).is_none()); + assert!(cursor.seek_forward(0).is_none()); let mut tab_stops = Vec::new(); let mut all_tab_stops = Vec::new(); @@ -1279,7 +1456,7 @@ mod tests { } } - while let Some(tab_stop) = cursor.seek(u32::MAX) { + while let Some(tab_stop) = cursor.seek_forward(u32::MAX) { tab_stops.push(tab_stop); } pretty_assertions::assert_eq!(tab_stops.as_slice(), all_tab_stops.as_slice(),); @@ -1314,7 +1491,7 @@ mod tests { } } - while let Some(tab_stop) = cursor.seek(u32::MAX) { + while let Some(tab_stop) = cursor.seek_forward(u32::MAX) { actual_tab_stops.push(tab_stop); } pretty_assertions::assert_eq!(actual_tab_stops.as_slice(), expected_tab_stops.as_slice(),); @@ -1379,7 +1556,7 @@ mod tests { let mut found_tab_stops = Vec::new(); let mut position = distance; - while let Some(tab_stop) = cursor.seek(position) { + while let Some(tab_stop) = cursor.seek_forward(position) { found_tab_stops.push(tab_stop); position = distance - tab_stop.byte_offset; } @@ -1425,7 +1602,7 @@ mod tests { Default::default(), ); let mut cursor = TabStopCursor::new(chunks); - assert!(cursor.seek(0).is_none()); + assert!(cursor.seek_forward(0).is_none()); let mut expected_tab_stops = Vec::new(); let mut byte_offset = 0; @@ -1441,7 +1618,7 @@ mod tests { } let mut actual_tab_stops = Vec::new(); - while let Some(tab_stop) = cursor.seek(u32::MAX) { + while let Some(tab_stop) = cursor.seek_forward(u32::MAX) { actual_tab_stops.push(tab_stop); } @@ -1487,7 +1664,7 @@ mod tests { let mut found_tab_stops = Vec::new(); let mut position = distance; - while let Some(tab_stop) = cursor.seek(position) { + while let Some(tab_stop) = cursor.seek_forward(position) { found_tab_stops.push(tab_stop); position = distance - tab_stop.byte_offset; } @@ -1520,165 +1697,3 @@ mod tests { } } } - -struct TabStopCursor<'a, I> -where - I: Iterator>, -{ - chunks: I, - byte_offset: u32, - char_offset: u32, - /// Chunk - /// last tab position iterated through - current_chunk: Option<(Chunk<'a>, u32)>, -} - -impl<'a, I> TabStopCursor<'a, I> -where - I: Iterator>, -{ - #[ztracing::instrument(skip_all)] - fn new(chunks: impl IntoIterator, IntoIter = I>) -> Self { - Self { - chunks: chunks.into_iter(), - byte_offset: 0, - char_offset: 0, - current_chunk: None, - } - } - - #[ztracing::instrument(skip_all)] - fn bytes_until_next_char(&self) -> Option { - self.current_chunk.as_ref().and_then(|(chunk, idx)| { - let mut idx = *idx; - let mut diff = 0; - while idx > 0 && chunk.chars & (1u128.unbounded_shl(idx)) == 0 { - idx -= 1; - diff += 1; - } - - if chunk.chars & (1 << idx) != 0 { - Some( - (chunk.text[idx as usize..].chars().next()?) - .len_utf8() - .saturating_sub(diff), - ) - } else { - None - } - }) - } - - #[ztracing::instrument(skip_all)] - fn is_char_boundary(&self) -> bool { - self.current_chunk - .as_ref() - .is_some_and(|(chunk, idx)| (chunk.chars & 1u128.unbounded_shl(*idx)) != 0) - } - - /// distance: length to move forward while searching for the next tab stop - #[ztracing::instrument(skip_all)] - fn seek(&mut self, distance: u32) -> Option { - if distance == 0 { - return None; - } - - let mut distance_traversed = 0; - - while let Some((mut chunk, chunk_position)) = self - .current_chunk - .take() - .or_else(|| self.chunks.next().zip(Some(0))) - { - if chunk.tabs == 0 { - let chunk_distance = chunk.text.len() as u32 - chunk_position; - if chunk_distance + distance_traversed >= distance { - let overshoot = distance_traversed.abs_diff(distance); - - self.byte_offset += overshoot; - self.char_offset += get_char_offset( - chunk_position..(chunk_position + overshoot).saturating_sub(1), - chunk.chars, - ); - - if chunk_position + overshoot < 128 { - self.current_chunk = Some((chunk, chunk_position + overshoot)); - } - - return None; - } - - self.byte_offset += chunk_distance; - self.char_offset += get_char_offset( - chunk_position..(chunk_position + chunk_distance).saturating_sub(1), - chunk.chars, - ); - distance_traversed += chunk_distance; - continue; - } - let tab_position = chunk.tabs.trailing_zeros() + 1; - - if distance_traversed + tab_position - chunk_position > distance { - let cursor_position = distance_traversed.abs_diff(distance); - - self.char_offset += get_char_offset( - chunk_position..(chunk_position + cursor_position - 1), - chunk.chars, - ); - self.current_chunk = Some((chunk, cursor_position + chunk_position)); - self.byte_offset += cursor_position; - - return None; - } - - self.byte_offset += tab_position - chunk_position; - self.char_offset += get_char_offset(chunk_position..(tab_position - 1), chunk.chars); - - let tabstop = TabStop { - char_offset: self.char_offset, - byte_offset: self.byte_offset, - }; - - chunk.tabs = (chunk.tabs - 1) & chunk.tabs; - - if tab_position as usize != chunk.text.len() { - self.current_chunk = Some((chunk, tab_position)); - } - - return Some(tabstop); - } - - None - } - - fn byte_offset(&self) -> u32 { - self.byte_offset - } - - fn char_offset(&self) -> u32 { - self.char_offset - } -} - -#[inline(always)] -fn get_char_offset(range: Range, bit_map: u128) -> u32 { - if range.start == range.end { - return if (1u128 << range.start) & bit_map == 0 { - 0 - } else { - 1 - }; - } - let end_shift: u128 = 127u128 - range.end as u128; - let mut bit_mask = (u128::MAX >> range.start) << range.start; - bit_mask = (bit_mask << end_shift) >> end_shift; - let bit_map = bit_map & bit_mask; - - bit_map.count_ones() -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct TabStop { - char_offset: u32, - byte_offset: u32, -} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6820d598680de438f9244195acca0bcd5ff7476f..25bc9996604773bd67964dcb9f5196c41df6cdce 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3626,7 +3626,7 @@ impl Editor { self.refresh_document_highlights(cx); refresh_linked_ranges(self, window, cx); - self.refresh_selected_text_highlights(false, window, cx); + self.refresh_selected_text_highlights(&display_map, false, window, cx); self.refresh_matching_bracket_highlights(&display_map, cx); self.refresh_outline_symbols_at_cursor(cx); self.update_visible_edit_prediction(window, cx); @@ -7500,7 +7500,7 @@ impl Editor { fn prepare_highlight_query_from_selection( &mut self, - window: &Window, + snapshot: &DisplaySnapshot, cx: &mut Context, ) -> Option<(String, Range)> { if matches!(self.mode, EditorMode::SingleLine) { @@ -7512,7 +7512,6 @@ impl Editor { if self.selections.count() != 1 || self.selections.line_mode() { return None; } - let snapshot = self.snapshot(window, cx); let selection = self.selections.newest::(&snapshot); // If the selection spans multiple rows OR it is empty if selection.start.row != selection.end.row @@ -7534,6 +7533,7 @@ impl Editor { #[ztracing::instrument(skip_all)] fn update_selection_occurrence_highlights( &mut self, + multi_buffer_snapshot: MultiBufferSnapshot, query_text: String, query_range: Range, multi_buffer_range_to_query: Range, @@ -7541,7 +7541,6 @@ impl Editor { window: &mut Window, cx: &mut Context, ) -> Task<()> { - let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); cx.spawn_in(window, async move |editor, cx| { if use_debounce { cx.background_executor() @@ -7557,7 +7556,7 @@ impl Editor { .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()); let mut match_ranges = Vec::new(); let Ok(regex) = project::search::SearchQuery::text( - query_text.clone(), + query_text, false, false, false, @@ -7719,12 +7718,13 @@ impl Editor { #[ztracing::instrument(skip_all)] fn refresh_selected_text_highlights( &mut self, + snapshot: &DisplaySnapshot, on_buffer_edit: bool, window: &mut Window, cx: &mut Context, ) { let Some((query_text, query_range)) = - self.prepare_highlight_query_from_selection(window, cx) + self.prepare_highlight_query_from_selection(snapshot, cx) else { self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx); self.quick_selection_highlight_task.take(); @@ -7756,6 +7756,7 @@ impl Editor { self.quick_selection_highlight_task = Some(( query_range.clone(), self.update_selection_occurrence_highlights( + snapshot.buffer.clone(), query_text.clone(), query_range.clone(), multi_buffer_visible_range, @@ -7781,6 +7782,7 @@ impl Editor { self.debounced_selection_highlight_task = Some(( query_range.clone(), self.update_selection_occurrence_highlights( + snapshot.buffer.clone(), query_text, query_range, multi_buffer_full_range, @@ -24107,7 +24109,7 @@ impl Editor { self.update_lsp_data(Some(buffer_id), window, cx); self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); self.colorize_brackets(false, cx); - self.refresh_selected_text_highlights(true, window, cx); + self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx); cx.emit(EditorEvent::ExcerptsAdded { buffer: buffer.clone(), predecessor: *predecessor, @@ -24166,7 +24168,7 @@ impl Editor { } multi_buffer::Event::Reparsed(buffer_id) => { self.tasks_update_task = Some(self.refresh_runnables(window, cx)); - self.refresh_selected_text_highlights(true, window, cx); + self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx); self.colorize_brackets(true, cx); jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);