2574 lines · 94.1 KB · Rust
RawDownload
//! The inlay map. See the [`display_map`][super] docs for an overview of how the inlay map fits//! into the rest of the [`DisplayMap`][super::DisplayMap]. Much of the documentation for this//! module generalizes to other layers.//!//! The core of this module is the [`InlayMap`] struct, which maintains a vec of [`Inlay`]s, and//! [`InlaySnapshot`], which holds a sum tree of [`Transform`]s. use crate::{    ChunkRenderer, HighlightStyles,    inlays::{Inlay, InlayContent},};use collections::BTreeSet;use language::{Chunk, Edit, LanguageAwareStyling, Point, TextSummary};use multi_buffer::{    MBTextSummary, MultiBufferOffset, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot,    RowInfo, ToOffset,};use project::InlayId;use smallvec::SmallVec;use std::{    cmp, iter,    ops::{Add, AddAssign, Range, Sub, SubAssign},    sync::Arc,};use sum_tree::{Bias, Cursor, Dimensions, SumTree};use text::{ChunkBitmaps, Patch};use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div}; use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId}; /// Decides where the [`Inlay`]s should be displayed.////// See the [`display_map` module documentation](crate::display_map) for more information.pub struct InlayMap {    snapshot: InlaySnapshot,    inlays: Vec<Inlay>,} #[derive(Clone)]pub struct InlaySnapshot {    pub buffer: MultiBufferSnapshot,    transforms: SumTree<Transform>,    pub version: usize,} impl std::ops::Deref for InlaySnapshot {    type Target = MultiBufferSnapshot;     fn deref(&self) -> &Self::Target {        &self.buffer    }} #[derive(Clone, Debug)]enum Transform {    Isomorphic(MBTextSummary),    Inlay(Inlay),} impl sum_tree::Item for Transform {    type Summary = TransformSummary;     #[ztracing::instrument(skip_all)]    fn summary(&self, _: ()) -> Self::Summary {        match self {            Transform::Isomorphic(summary) => TransformSummary {                input: *summary,                output: *summary,            },            Transform::Inlay(inlay) => TransformSummary {                input: MBTextSummary::default(),                output: MBTextSummary::from(inlay.text().summary()),            },        }    }} #[derive(Clone, Debug, Default)]struct TransformSummary {    /// Summary of the text before inlays have been applied.    input: MBTextSummary,    /// Summary of the text after inlays have been applied.    output: MBTextSummary,} impl TransformSummary {    fn has_inlays(&self) -> bool {        self.input.len != self.output.len    }} impl sum_tree::ContextLessSummary for TransformSummary {    fn zero() -> Self {        Default::default()    }     fn add_summary(&mut self, other: &Self) {        self.input += other.input;        self.output += other.output;    }} pub type InlayEdit = Edit<InlayOffset>; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]pub struct InlayOffset(pub MultiBufferOffset); impl Add for InlayOffset {    type Output = Self;     fn add(self, rhs: Self) -> Self::Output {        Self(self.0 + rhs.0)    }} impl Sub for InlayOffset {    type Output = <MultiBufferOffset as Sub>::Output;     fn sub(self, rhs: Self) -> Self::Output {        self.0 - rhs.0    }} impl<T> SubAssign<T> for InlayOffsetwhere    MultiBufferOffset: SubAssign<T>,{    fn sub_assign(&mut self, rhs: T) {        self.0 -= rhs;    }} impl<T> Add<T> for InlayOffsetwhere    MultiBufferOffset: Add<T, Output = MultiBufferOffset>,{    type Output = Self;     fn add(self, rhs: T) -> Self::Output {        Self(self.0 + rhs)    }} impl AddAssign for InlayOffset {    fn add_assign(&mut self, rhs: Self) {        self.0 += rhs.0;    }} impl<T> AddAssign<T> for InlayOffsetwhere    MultiBufferOffset: AddAssign<T>,{    fn add_assign(&mut self, rhs: T) {        self.0 += rhs;    }} impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {    fn zero(_cx: ()) -> Self {        Default::default()    }     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {        self.0 += summary.output.len;    }} #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]pub struct InlayPoint(pub Point); impl Add for InlayPoint {    type Output = Self;     fn add(self, rhs: Self) -> Self::Output {        Self(self.0 + rhs.0)    }} impl Sub for InlayPoint {    type Output = Self;     fn sub(self, rhs: Self) -> Self::Output {        Self(self.0 - rhs.0)    }} impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {    fn zero(_cx: ()) -> Self {        Default::default()    }     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {        self.0 += &summary.output.lines;    }} impl<'a> sum_tree::Dimension<'a, TransformSummary> for MultiBufferOffset {    fn zero(_cx: ()) -> Self {        Default::default()    }     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {        *self += summary.input.len;    }} impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {    fn zero(_cx: ()) -> Self {        Default::default()    }     fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {        *self += &summary.input.lines;    }} #[derive(Clone)]pub struct InlayBufferRows<'a> {    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayPoint, Point>>,    buffer_rows: MultiBufferRows<'a>,    inlay_row: u32,    max_buffer_row: MultiBufferRow,} pub struct InlayChunks<'a> {    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayOffset, MultiBufferOffset>>,    buffer_chunks: CustomHighlightsChunks<'a>,    buffer_chunk: Option<Chunk<'a>>,    inlay_chunks: Option<text::ChunkWithBitmaps<'a>>,    /// text, char bitmap, tabs bitmap    inlay_chunk: Option<ChunkBitmaps<'a>>,    output_offset: InlayOffset,    max_output_offset: InlayOffset,    highlight_styles: HighlightStyles,    highlights: Highlights<'a>,    snapshot: &'a InlaySnapshot,} #[derive(Clone)]pub struct InlayChunk<'a> {    pub chunk: Chunk<'a>,    /// Whether the inlay should be customly rendered.    pub renderer: Option<ChunkRenderer>,} impl InlayChunks<'_> {    #[ztracing::instrument(skip_all)]    pub fn seek(&mut self, new_range: Range<InlayOffset>) {        self.transforms.seek(&new_range.start, Bias::Right);         let buffer_range = self.snapshot.to_buffer_offset(new_range.start)            ..self.snapshot.to_buffer_offset(new_range.end);        self.buffer_chunks.seek(buffer_range);        self.inlay_chunks = None;        self.buffer_chunk = None;        self.output_offset = new_range.start;        self.max_output_offset = new_range.end;    }     pub fn offset(&self) -> InlayOffset {        self.output_offset    }} impl<'a> Iterator for InlayChunks<'a> {    type Item = InlayChunk<'a>;     #[ztracing::instrument(skip_all)]    fn next(&mut self) -> Option<Self::Item> {        if self.output_offset == self.max_output_offset {            return None;        }         let chunk = match self.transforms.item()? {            Transform::Isomorphic(_) => {                let chunk = self                    .buffer_chunk                    .get_or_insert_with(|| self.buffer_chunks.next().unwrap());                if chunk.text.is_empty() {                    *chunk = self.buffer_chunks.next().unwrap();                }                 let desired_bytes = self.transforms.end().0.0 - self.output_offset.0;                 // If we're already at the transform boundary, skip to the next transform                if desired_bytes == 0 {                    self.inlay_chunks = None;                    self.transforms.next();                    return self.next();                }                 // Determine split index handling edge cases                let split_index = if desired_bytes >= chunk.text.len() {                    chunk.text.len()                } else {                    chunk.text.ceil_char_boundary(desired_bytes)                };                 let (prefix, suffix) = chunk.text.split_at(split_index);                self.output_offset.0 += prefix.len();                 let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);                let chars = chunk.chars & mask;                let tabs = chunk.tabs & mask;                let newlines = chunk.newlines & mask;                 chunk.chars = chunk.chars.unbounded_shr(split_index as u32);                chunk.tabs = chunk.tabs.unbounded_shr(split_index as u32);                chunk.newlines = chunk.newlines.unbounded_shr(split_index as u32);                chunk.text = suffix;                 InlayChunk {                    chunk: Chunk {                        text: prefix,                        chars,                        tabs,                        newlines,                        ..chunk.clone()                    },                    renderer: None,                }            }            Transform::Inlay(inlay) => {                let mut inlay_style_and_highlight = None;                if let Some(inlay_highlights) = self.highlights.inlay_highlights {                    for (_, inlay_id_to_data) in inlay_highlights.iter() {                        let style_and_highlight = inlay_id_to_data.get(&inlay.id);                        if style_and_highlight.is_some() {                            inlay_style_and_highlight = style_and_highlight;                            break;                        }                    }                }                 let mut renderer = None;                let mut highlight_style = match inlay.id {                    InlayId::EditPrediction(_) => self.highlight_styles.edit_prediction.map(|s| {                        if inlay.text().chars().all(|c| c.is_whitespace()) {                            s.whitespace                        } else {                            s.insertion                        }                    }),                    InlayId::Hint(_) => self.highlight_styles.inlay_hint,                    InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,                    InlayId::ReplResult(_) => {                        let text = inlay.text().to_string();                        renderer = Some(ChunkRenderer {                            id: ChunkRendererId::Inlay(inlay.id),                            render: Arc::new(move |cx| {                                let colors = cx.theme().colors();                                div()                                    .flex()                                    .flex_row()                                    .items_center()                                    .child(div().w_4())                                    .child(                                        div()                                            .px_1()                                            .rounded_sm()                                            .bg(colors.surface_background)                                            .text_color(colors.text_muted)                                            .text_xs()                                            .child(text.trim().to_string()),                                    )                                    .into_any_element()                            }),                            constrain_width: false,                            measured_width: None,                        });                        self.highlight_styles.inlay_hint                    }                    InlayId::Color(_) => {                        if let InlayContent::Color(color) = inlay.content {                            renderer = Some(ChunkRenderer {                                id: ChunkRendererId::Inlay(inlay.id),                                render: Arc::new(move |cx| {                                    div()                                        .relative()                                        .size_3p5()                                        .child(                                            div()                                                .absolute()                                                .right_1()                                                .size_3()                                                .border_1()                                                .border_color(                                                    if cx.theme().appearance().is_light() {                                                        gpui::black().opacity(0.5)                                                    } else {                                                        gpui::white().opacity(0.5)                                                    },                                                )                                                .bg(color),                                        )                                        .into_any_element()                                }),                                constrain_width: false,                                measured_width: None,                            });                        }                        self.highlight_styles.inlay_hint                    }                };                let next_inlay_highlight_endpoint;                let offset_in_inlay = self.output_offset - self.transforms.start().0;                if let Some((style, highlight)) = inlay_style_and_highlight {                    let range = &highlight.range;                    if offset_in_inlay < range.start {                        next_inlay_highlight_endpoint = range.start - offset_in_inlay;                    } else if offset_in_inlay >= range.end {                        next_inlay_highlight_endpoint = usize::MAX;                    } else {                        next_inlay_highlight_endpoint = range.end - offset_in_inlay;                        highlight_style = highlight_style                            .map(|highlight| highlight.highlight(*style))                            .or_else(|| Some(*style));                    }                } else {                    next_inlay_highlight_endpoint = usize::MAX;                }                 let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {                    let start = offset_in_inlay;                    let end = cmp::min(self.max_output_offset, self.transforms.end().0)                        - self.transforms.start().0;                    let chunks = inlay.text().chunks_in_range(start..end);                    text::ChunkWithBitmaps(chunks)                });                let ChunkBitmaps {                    text: inlay_chunk,                    chars,                    tabs,                    newlines,                } = self                    .inlay_chunk                    .get_or_insert_with(|| inlay_chunks.next().unwrap());                 // Determine split index handling edge cases                let split_index = if next_inlay_highlight_endpoint >= inlay_chunk.len() {                    inlay_chunk.len()                } else if next_inlay_highlight_endpoint == 0 {                    // Need to take at least one character to make progress                    inlay_chunk                        .chars()                        .next()                        .map(|c| c.len_utf8())                        .unwrap_or(1)                } else {                    inlay_chunk.ceil_char_boundary(next_inlay_highlight_endpoint)                };                 let (chunk, remainder) = inlay_chunk.split_at(split_index);                *inlay_chunk = remainder;                 let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);                let new_chars = *chars & mask;                let new_tabs = *tabs & mask;                let new_newlines = *newlines & mask;                 *chars = chars.unbounded_shr(split_index as u32);                *tabs = tabs.unbounded_shr(split_index as u32);                *newlines = newlines.unbounded_shr(split_index as u32);                 if inlay_chunk.is_empty() {                    self.inlay_chunk = None;                }                 self.output_offset.0 += chunk.len();                 InlayChunk {                    chunk: Chunk {                        text: chunk,                        chars: new_chars,                        tabs: new_tabs,                        newlines: new_newlines,                        highlight_style,                        is_inlay: true,                        ..Chunk::default()                    },                    renderer,                }            }        };         if self.output_offset >= self.transforms.end().0 {            self.inlay_chunks = None;            self.transforms.next();        }         Some(chunk)    }} impl InlayBufferRows<'_> {    #[ztracing::instrument(skip_all)]    pub fn seek(&mut self, row: u32) {        let inlay_point = InlayPoint::new(row, 0);        self.transforms.seek(&inlay_point, Bias::Left);

Lines 1–500 of 2574.