Yara 🏳️‍⚧️Rust 1.95 (#55104)32088816 months agoHistory
1748 lines · 64.1 KB · Rust
RawDownload
use super::{    Highlights,    fold_map::{self, Chunk, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},}; use language::{LanguageAwareStyling, Point};use multi_buffer::MultiBufferSnapshot;use std::{cmp, num::NonZeroU32, ops::Range};use sum_tree::Bias; const MAX_EXPANSION_COLUMN: u32 = 256; // Handles a tab width <= 128const SPACES: &[u8; rope::Chunk::MASK_BITS] = &[b' '; _];const MAX_TABS: NonZeroU32 = NonZeroU32::new(SPACES.len() as u32).unwrap(); /// Keeps track of hard tabs in a text buffer.////// See the [`display_map` module documentation](crate::display_map) for more information.pub struct TabMap(TabSnapshot); impl TabMap {    #[ztracing::instrument(skip_all)]    pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {        let snapshot = TabSnapshot {            fold_snapshot,            tab_size: tab_size.min(MAX_TABS),            max_expansion_column: MAX_EXPANSION_COLUMN,            version: 0,        };        (Self(snapshot.clone()), snapshot)    }     #[cfg(test)]    pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot {        self.0.max_expansion_column = column;        self.0.clone()    }     #[ztracing::instrument(skip_all)]    pub fn sync(        &mut self,        fold_snapshot: FoldSnapshot,        mut fold_edits: Vec<FoldEdit>,        tab_size: NonZeroU32,    ) -> (TabSnapshot, Vec<TabEdit>) {        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 old_snapshot = &mut self.0;        let mut new_version = old_snapshot.version;        if old_snapshot.fold_snapshot.version != fold_snapshot.version {            new_version += 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,                LanguageAwareStyling {                    tree_sitter: false,                    diagnostics: 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 < 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;                }                 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;                }            }             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;            }        }         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)    }} #[derive(Clone)]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,} impl std::ops::Deref for TabSnapshot {    type Target = FoldSnapshot;     fn deref(&self) -> &Self::Target {        &self.fold_snapshot    }} impl TabSnapshot {    #[ztracing::instrument(skip_all)]    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {        &self.fold_snapshot.inlay_snapshot.buffer    }     #[ztracing::instrument(skip_all)]    pub fn line_len(&self, row: u32) -> u32 {        let max_point = self.max_point();        if row < max_point.row() {            self.fold_point_to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))                .0                .column        } else {            max_point.column()        }    }     #[ztracing::instrument(skip_all)]    pub fn text_summary(&self) -> TextSummary {        self.text_summary_for_range(TabPoint::zero()..self.max_point())    }     #[ztracing::instrument(skip_all, fields(rows))]    pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {        let input_start = self.tab_point_to_fold_point(range.start, Bias::Left).0;        let input_end = self.tab_point_to_fold_point(range.end, Bias::Right).0;        let input_summary = self            .fold_snapshot            .text_summary_for_range(input_start..input_end);         let line_end = if range.start.row() == range.end.row() {            range.end        } else {            self.max_point()        };        let first_line_chars = self            .chunks(                range.start..line_end,                LanguageAwareStyling {                    tree_sitter: false,                    diagnostics: false,                },                Highlights::default(),            )            .flat_map(|chunk| chunk.text.chars())            .take_while(|&c| c != '\n')            .count() as u32;         let last_line_chars = if range.start.row() == range.end.row() {            first_line_chars        } else {            self.chunks(                TabPoint::new(range.end.row(), 0)..range.end,                LanguageAwareStyling {                    tree_sitter: false,                    diagnostics: false,                },                Highlights::default(),            )            .flat_map(|chunk| chunk.text.chars())            .count() as u32        };         TextSummary {            lines: range.end.0 - range.start.0,            first_line_chars,            last_line_chars,            longest_row: input_summary.longest_row,            longest_row_chars: input_summary.longest_row_chars,        }    }     #[ztracing::instrument(skip_all)]    pub(crate) fn chunks<'a>(        &'a self,        range: Range<TabPoint>,        language_aware: LanguageAwareStyling,        highlights: Highlights<'a>,    ) -> TabChunks<'a> {        let (input_start, expanded_char_column, to_next_stop) =            self.tab_point_to_fold_point(range.start, Bias::Left);        let input_column = input_start.column();        let input_start = input_start.to_offset(&self.fold_snapshot);        let input_end = self            .tab_point_to_fold_point(range.end, Bias::Right)            .0            .to_offset(&self.fold_snapshot);        let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {            range.end.column() - range.start.column()        } else {            to_next_stop        };         TabChunks {            snapshot: self,            fold_chunks: self.fold_snapshot.chunks(                input_start..input_end,                language_aware,                highlights,            ),            input_column,            column: expanded_char_column,            max_expansion_column: self.max_expansion_column,            output_position: range.start.0,            max_output_position: range.end.0,            tab_size: self.tab_size,            chunk: Chunk {                text: unsafe { std::str::from_utf8_unchecked(&SPACES[..to_next_stop as usize]) },                is_tab: true,                chars: 1u128.unbounded_shl(to_next_stop) - 1,                ..Default::default()            },            inside_leading_tab: to_next_stop > 0,        }    }     #[ztracing::instrument(skip_all)]    pub fn rows(&self, row: u32) -> fold_map::FoldRows<'_> {        self.fold_snapshot.row_infos(row)    }     #[cfg(test)]    #[ztracing::instrument(skip_all)]    pub fn text(&self) -> String {        self.chunks(            TabPoint::zero()..self.max_point(),            LanguageAwareStyling {                tree_sitter: false,                diagnostics: false,            },            Highlights::default(),        )        .map(|chunk| chunk.text)        .collect()    }     #[ztracing::instrument(skip_all)]    pub fn max_point(&self) -> TabPoint {        self.fold_point_to_tab_point(self.fold_snapshot.max_point())    }     #[ztracing::instrument(skip_all)]    pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {        self.fold_point_to_tab_point(            self.fold_snapshot                .clip_point(self.tab_point_to_fold_point(point, bias).0, bias),        )    }     #[ztracing::instrument(skip_all)]    pub fn fold_point_to_tab_point(&self, input: FoldPoint) -> TabPoint {        let chunks = self.fold_snapshot.chunks_at(FoldPoint::new(input.row(), 0));        let tab_cursor = TabStopCursor::new(chunks);        let expanded = self.expand_tabs(tab_cursor, input.column());        TabPoint::new(input.row(), expanded)    }     #[ztracing::instrument(skip_all)]    pub fn tab_point_cursor(&self) -> TabPointCursor<'_> {        TabPointCursor { this: self }    }     #[ztracing::instrument(skip_all)]    pub fn tab_point_to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {        let chunks = self            .fold_snapshot            .chunks_at(FoldPoint::new(output.row(), 0));         let tab_cursor = TabStopCursor::new(chunks);        let expanded = output.column();        let (collapsed, expanded_char_column, to_next_stop) =            self.collapse_tabs(tab_cursor, expanded, bias);         (            FoldPoint::new(output.row(), collapsed),            expanded_char_column,            to_next_stop,        )    }     #[ztracing::instrument(skip_all)]    pub fn point_to_tab_point(&self, point: Point, bias: Bias) -> TabPoint {        let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);        let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);        self.fold_point_to_tab_point(fold_point)    }     #[ztracing::instrument(skip_all)]    pub fn tab_point_to_point(&self, point: TabPoint, bias: Bias) -> Point {        let fold_point = self.tab_point_to_fold_point(point, bias).0;        let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);        self.fold_snapshot            .inlay_snapshot            .to_buffer_point(inlay_point)    }     #[ztracing::instrument(skip_all)]    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);        let mut seek_target = end_column;        let mut tab_count = 0;        let mut expanded_tab_len = 0;         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;            expanded_tab_len += tab_len;             seek_target = end_column - cursor.byte_offset;        }         let left_over_char_bytes = if !cursor.is_char_boundary() {            cursor.bytes_until_next_char().unwrap_or(0) as u32        } else {            0        };         let collapsed_bytes = cursor.byte_offset() + left_over_char_bytes;        let expanded_bytes =            cursor.byte_offset() + expanded_tab_len - tab_count + left_over_char_bytes;         expanded_bytes + column.saturating_sub(collapsed_bytes)    }     #[ztracing::instrument(skip_all)]    fn collapse_tabs<'a>(        &self,        mut cursor: TabStopCursor<'a>,        column: u32,        bias: Bias,    ) -> (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_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);            // Increment tab count            tab_count += 1;            // The count of how many spaces we've added to this line in place of tab bytes            expanded_tab_len += tab_len;             // The count of bytes at this point in the iteration while considering tab_count and previous expansions            let expanded_bytes = tab_stop.byte_offset + expanded_tab_len - tab_count;             // Did we expand past the search target?            if expanded_bytes > column {                let mut expanded_chars = tab_stop.char_offset + expanded_tab_len - tab_count;                // We expanded past the search target, so need to account for the offshoot                expanded_chars -= expanded_bytes - column;                return match bias {                    Bias::Left => (                        cursor.byte_offset() - 1,                        expanded_chars,                        expanded_bytes - column,                    ),                    Bias::Right => (cursor.byte_offset(), expanded_chars, 0),                };            } else {                // otherwise we only want to move the cursor collapse column forward                collapsed_column = collapsed_column - tab_len + 1;                seek_target = (collapsed_column - cursor.byte_offset)                    .min(self.max_expansion_column - cursor.byte_offset);            }        }         let collapsed_bytes = cursor.byte_offset();        let expanded_bytes = cursor.byte_offset() + expanded_tab_len - tab_count;        let expanded_chars = cursor.char_offset() + expanded_tab_len - tab_count;        (            collapsed_bytes + column.saturating_sub(expanded_bytes),            expanded_chars,            0,        )    }}

Lines 1–500 of 1748.