3965 lines · 143.7 KB · Rust
RawDownload
//! This module defines where the text should be displayed in an [`Editor`][Editor].//!//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that//! we display as spaces and where to display custom blocks (like diagnostics).//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up//! of several smaller structures that form a hierarchy (starting at the bottom)://! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.//! - [`TabMap`] that keeps track of hard tabs in a buffer.//! - [`WrapMap`] that handles soft wrapping.//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.//! - [`DisplayMap`] that adds background highlights to the regions of text.//!   Each one of those builds on top of preceding map.//!//! ## Structure of the display map layers//!//! Each layer in the map (and the multibuffer itself to some extent) has a few//! structures that are used to implement the public API available to the layer//! above://! - a `Transform` type - this represents a region of text that the layer in//!   question is "managing", that it transforms into a more "processed" text//!   for the layer above. For example, the inlay map has an `enum Transform`//!   that has two variants://!     - `Isomorphic`, representing a region of text that has no inlay hints (i.e.//!       is passed through the map transparently)//!     - `Inlay`, representing a location where an inlay hint is to be inserted.//! - a `TransformSummary` type, which is usually a struct with two fields://!   [`input: TextSummary`][`TextSummary`] and [`output: TextSummary`][`TextSummary`]. Here,//!   `input` corresponds to "text in the layer below", and `output` corresponds to the text//!   exposed to the layer above. So in the inlay map case, a `Transform::Isomorphic`'s summary is//!   just `input = output = summary`, where `summary` is the [`TextSummary`] stored in that//!   variant. Conversely, a `Transform::Inlay` always has an empty `input` summary, because it's//!   not "replacing" any text that exists on disk. The `output` is the summary of the inlay text//!   to be injected. - Various newtype wrappers for co-ordinate spaces (e.g. [`WrapRow`]//!   represents a row index, after soft-wrapping (and all lower layers)).//! - A `Snapshot` type (e.g. [`InlaySnapshot`]) that captures the state of a layer at a specific//!   point in time.//! - various APIs which drill through the layers below to work with the underlying text. Notably://!   - `fn text_summary_for_offset()` returns a [`TextSummary`] for the range in the co-ordinate//!     space that the map in question is responsible for.//!   - `fn <A>_point_to_<B>_point()` converts a point in co-ordinate space `A` into co-ordinate//!     space `B`.//!   - A [`RowInfo`] iterator (e.g. [`InlayBufferRows`]) and a [`Chunk`] iterator//!     (e.g. [`InlayChunks`])//!   - A `sync` function (e.g. [`InlayMap::sync`]) that takes a snapshot and list of [`Edit<T>`]s,//!     and returns a new snapshot and a list of transformed [`Edit<S>`]s. Note that the generic//!     parameter on `Edit` changes, since these methods take in edits in the co-ordinate space of//!     the lower layer, and return edits in their own co-ordinate space. The term "edit" is//!     slightly misleading, since an [`Edit<T>`] doesn't tell you what changed - rather it can be//!     thought of as a "region to invalidate". In theory, it would be correct to always use a//!     single edit that covers the entire range. However, this would lead to lots of unnecessary//!     recalculation.//!//! See the docs for the [`inlay_map`] module for a more in-depth explanation of how a single layer//! works.//!//! [Editor]: crate::Editor//! [EditorElement]: crate::element::EditorElement//! [`TextSummary`]: multi_buffer::MBTextSummary//! [`WrapRow`]: wrap_map::WrapRow//! [`InlayBufferRows`]: inlay_map::InlayBufferRows//! [`InlayChunks`]: inlay_map::InlayChunks//! [`Edit<T>`]: text::Edit//! [`Edit<S>`]: text::Edit//! [`Chunk`]: language::Chunk #[macro_use]mod dimensions; mod block_map;mod crease_map;mod custom_highlights;mod fold_map;mod inlay_map;mod invisibles;mod tab_map;mod wrap_map; pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap};pub use block_map::{    Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,    BlockPoint, BlockProperties, BlockRows, BlockStyle, CompanionView, CustomBlockId,    EditorMargins, RenderBlock, StickyHeaderExcerpt,};pub use crease_map::*;pub use fold_map::{    ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,};pub use inlay_map::{InlayOffset, InlayPoint};pub use invisibles::{is_invisible, replacement};pub use wrap_map::{WrapPoint, WrapRow, WrapSnapshot}; use collections::{HashMap, HashSet};use gpui::{    App, Context, Entity, EntityId, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle,    WeakEntity,};use language::{Point, Subscription as BufferSubscription, language_settings::language_settings};use multi_buffer::{    Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,    MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,};use project::InlayId;use project::project_settings::DiagnosticSeverity;use serde::Deserialize;use sum_tree::{Bias, TreeMap};use text::{BufferId, LineIndent, Patch};use ui::{SharedString, px};use unicode_segmentation::UnicodeSegmentation;use ztracing::instrument; use std::{    any::TypeId,    borrow::Cow,    fmt::Debug,    iter,    num::NonZeroU32,    ops::{Add, Bound, Range, Sub},    sync::Arc,}; use crate::{    EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,};use block_map::{BlockRow, BlockSnapshot};use fold_map::FoldSnapshot;use inlay_map::InlaySnapshot;use tab_map::TabSnapshot;use wrap_map::{WrapMap, WrapPatch}; #[derive(Copy, Clone, Debug, PartialEq, Eq)]pub enum FoldStatus {    Folded,    Foldable,} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]pub enum HighlightKey {    Type(TypeId),    TypePlus(TypeId, usize),} pub trait ToDisplayPoint {    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;} type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>; #[derive(Debug)]pub struct MultiBufferRowMapping {    pub first_group: Option<Range<MultiBufferPoint>>,    pub boundaries: Vec<(MultiBufferPoint, Range<MultiBufferPoint>)>,    pub prev_boundary: Option<(MultiBufferPoint, Range<MultiBufferPoint>)>,    pub source_excerpt_end: MultiBufferPoint,    pub target_excerpt_end: MultiBufferPoint,} pub type ConvertMultiBufferRows = fn(    &HashMap<ExcerptId, ExcerptId>,    &MultiBufferSnapshot,    &MultiBufferSnapshot,    (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),) -> Vec<MultiBufferRowMapping>; /// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.////// See the [module level documentation](self) for more information.pub struct DisplayMap {    entity_id: EntityId,    /// The buffer that we are displaying.    buffer: Entity<MultiBuffer>,    buffer_subscription: BufferSubscription<MultiBufferOffset>,    /// Decides where the [`Inlay`]s should be displayed.    inlay_map: InlayMap,    /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.    fold_map: FoldMap,    /// Keeps track of hard tabs in a buffer.    tab_map: TabMap,    /// Handles soft wrapping.    wrap_map: Entity<WrapMap>,    /// Tracks custom blocks such as diagnostics that should be displayed within buffer.    block_map: BlockMap,    /// Regions of text that should be highlighted.    text_highlights: TextHighlights,    /// Regions of inlays that should be highlighted.    inlay_highlights: InlayHighlights,    /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.    crease_map: CreaseMap,    pub(crate) fold_placeholder: FoldPlaceholder,    pub clip_at_line_ends: bool,    pub(crate) masked: bool,    pub(crate) diagnostics_max_severity: DiagnosticSeverity,    pub(crate) companion: Option<(WeakEntity<DisplayMap>, Entity<Companion>)>,} pub(crate) struct Companion {    rhs_display_map_id: EntityId,    rhs_folded_buffers: HashSet<BufferId>,    rhs_buffer_to_lhs_buffer: HashMap<BufferId, BufferId>,    lhs_buffer_to_rhs_buffer: HashMap<BufferId, BufferId>,    rhs_excerpt_to_lhs_excerpt: HashMap<ExcerptId, ExcerptId>,    lhs_excerpt_to_rhs_excerpt: HashMap<ExcerptId, ExcerptId>,    rhs_rows_to_lhs_rows: ConvertMultiBufferRows,    lhs_rows_to_rhs_rows: ConvertMultiBufferRows,} impl Companion {    pub(crate) fn new(        rhs_display_map_id: EntityId,        rhs_folded_buffers: HashSet<BufferId>,        rhs_rows_to_lhs_rows: ConvertMultiBufferRows,        lhs_rows_to_rhs_rows: ConvertMultiBufferRows,    ) -> Self {        Self {            rhs_display_map_id,            rhs_folded_buffers,            rhs_buffer_to_lhs_buffer: Default::default(),            lhs_buffer_to_rhs_buffer: Default::default(),            rhs_excerpt_to_lhs_excerpt: Default::default(),            lhs_excerpt_to_rhs_excerpt: Default::default(),            rhs_rows_to_lhs_rows,            lhs_rows_to_rhs_rows,        }    }     pub(crate) fn convert_rows_to_companion(        &self,        display_map_id: EntityId,        companion_snapshot: &MultiBufferSnapshot,        our_snapshot: &MultiBufferSnapshot,        bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),    ) -> Vec<MultiBufferRowMapping> {        let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id {            (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)        } else {            (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)        };        convert_fn(excerpt_map, companion_snapshot, our_snapshot, bounds)    }     pub(crate) fn convert_rows_from_companion(        &self,        display_map_id: EntityId,        our_snapshot: &MultiBufferSnapshot,        companion_snapshot: &MultiBufferSnapshot,        bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),    ) -> Vec<MultiBufferRowMapping> {        let (excerpt_map, convert_fn) = if display_map_id == self.rhs_display_map_id {            (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)        } else {            (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)        };        convert_fn(excerpt_map, our_snapshot, companion_snapshot, bounds)    }     pub(crate) fn companion_excerpt_to_excerpt(        &self,        display_map_id: EntityId,    ) -> &HashMap<ExcerptId, ExcerptId> {        if display_map_id == self.rhs_display_map_id {            &self.lhs_excerpt_to_rhs_excerpt        } else {            &self.rhs_excerpt_to_lhs_excerpt        }    }     fn buffer_to_companion_buffer(&self, display_map_id: EntityId) -> &HashMap<BufferId, BufferId> {        if display_map_id == self.rhs_display_map_id {            &self.rhs_buffer_to_lhs_buffer        } else {            &self.lhs_buffer_to_rhs_buffer        }    }     pub(crate) fn add_excerpt_mapping(&mut self, lhs_id: ExcerptId, rhs_id: ExcerptId) {        self.lhs_excerpt_to_rhs_excerpt.insert(lhs_id, rhs_id);        self.rhs_excerpt_to_lhs_excerpt.insert(rhs_id, lhs_id);    }     pub(crate) fn remove_excerpt_mappings(        &mut self,        lhs_ids: impl IntoIterator<Item = ExcerptId>,        rhs_ids: impl IntoIterator<Item = ExcerptId>,    ) {        for id in lhs_ids {            self.lhs_excerpt_to_rhs_excerpt.remove(&id);        }        for id in rhs_ids {            self.rhs_excerpt_to_lhs_excerpt.remove(&id);        }    }     pub(crate) fn add_buffer_mapping(&mut self, lhs_buffer: BufferId, rhs_buffer: BufferId) {        self.lhs_buffer_to_rhs_buffer.insert(lhs_buffer, rhs_buffer);        self.rhs_buffer_to_lhs_buffer.insert(rhs_buffer, lhs_buffer);    }} impl DisplayMap {    pub fn new(        buffer: Entity<MultiBuffer>,        font: Font,        font_size: Pixels,        wrap_width: Option<Pixels>,        buffer_header_height: u32,        excerpt_header_height: u32,        fold_placeholder: FoldPlaceholder,        diagnostics_max_severity: DiagnosticSeverity,        cx: &mut Context<Self>,    ) -> Self {        let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());         let tab_size = Self::tab_size(&buffer, cx);        let buffer_snapshot = buffer.read(cx).snapshot(cx);        let crease_map = CreaseMap::new(&buffer_snapshot);        let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);        let (fold_map, snapshot) = FoldMap::new(snapshot);        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);        let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);         cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();         DisplayMap {            entity_id: cx.entity_id(),            buffer,            buffer_subscription,            fold_map,            inlay_map,            tab_map,            wrap_map,            block_map,            crease_map,            fold_placeholder,            diagnostics_max_severity,            text_highlights: Default::default(),            inlay_highlights: Default::default(),            clip_at_line_ends: false,            masked: false,            companion: None,        }    }     pub(crate) fn set_companion(        &mut self,        companion: Option<(WeakEntity<DisplayMap>, Entity<Companion>)>,        cx: &mut Context<Self>,    ) {        let Some((companion_display_map, companion)) = companion else {            self.companion = None;            let (snapshot, edits) = self.sync_through_wrap(cx);            let edits = edits.compose([text::Edit {                old: WrapRow(0)..snapshot.max_point().row(),                new: WrapRow(0)..snapshot.max_point().row(),            }]);            self.block_map.read(snapshot, edits, None);            return;        };         let rhs_display_map_id = companion.read(cx).rhs_display_map_id;        if self.entity_id != rhs_display_map_id {            let buffer_mapping = companion                .read(cx)                .buffer_to_companion_buffer(rhs_display_map_id);            self.block_map.folded_buffers = companion                .read(cx)                .rhs_folded_buffers                .iter()                .filter_map(|id| buffer_mapping.get(id).copied())                .collect();        }         let snapshot = self.unfold_intersecting([Anchor::min()..Anchor::max()], true, cx);         self.companion = Some((companion_display_map.clone(), companion));         let companion_wrap_data = companion_display_map            .update(cx, |dm, cx| dm.sync_through_wrap(cx))            .ok();        let companion = self.companion.as_ref().map(|(_, c)| c.read(cx));        let companion_view =            companion_wrap_data                .as_ref()                .zip(companion)                .map(|((snapshot, edits), companion)| {                    CompanionView::new(self.entity_id, snapshot, edits, companion)                });         let edits = Patch::new(            [text::Edit {                old: WrapRow(0)..snapshot.max_point().row(),                new: WrapRow(0)..snapshot.max_point().row(),            }]            .into_iter()            .collect(),        );        self.block_map            .read(snapshot.clone(), edits.clone(), companion_view);         if let Some((companion_dm, _)) = &self.companion {            let _ = companion_dm.update(cx, |dm, _cx| {                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(_cx));                    dm.block_map.read(                        companion_snapshot,                        companion_edits,                        their_companion_ref                            .map(|c| CompanionView::new(dm.entity_id, &snapshot, &edits, c)),                    );                }            });        }    }     pub(crate) fn companion(&self) -> Option<&Entity<Companion>> {        self.companion.as_ref().map(|(_, c)| c)    }     pub(crate) fn companion_excerpt_to_my_excerpt(        &self,        their_id: ExcerptId,        cx: &App,    ) -> Option<ExcerptId> {        let (_, companion) = self.companion.as_ref()?;        let c = companion.read(cx);        c.companion_excerpt_to_excerpt(self.entity_id)            .get(&their_id)            .copied()    }     fn sync_through_wrap(&mut self, cx: &mut App) -> (WrapSnapshot, WrapPatch) {        let tab_size = Self::tab_size(&self.buffer, cx);        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);        let edits = self.buffer_subscription.consume().into_inner();         let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);        let (snapshot, edits) = self.fold_map.read(snapshot, edits);        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);        self.wrap_map            .update(cx, |map, cx| map.sync(snapshot, edits, cx))    }     #[instrument(skip_all)]    pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {        let (self_wrap_snapshot, self_wrap_edits) = self.sync_through_wrap(cx);        let companion_wrap_data = self.companion.as_ref().and_then(|(companion_dm, _)| {            companion_dm                .update(cx, |dm, cx| dm.sync_through_wrap(cx))                .ok()        });        let companion_ref = self.companion.as_ref().map(|(_, c)| c.read(cx));        let companion_view = companion_wrap_data.as_ref().zip(companion_ref).map(            |((snapshot, edits), companion)| {                CompanionView::new(self.entity_id, snapshot, edits, companion)            },        );         let block_snapshot = self            .block_map            .read(                self_wrap_snapshot.clone(),                self_wrap_edits.clone(),                companion_view,            )            .snapshot;         if let Some((companion_dm, _)) = &self.companion {            let _ = companion_dm.update(cx, |dm, _cx| {                if let Some((companion_snapshot, companion_edits)) = companion_wrap_data {                    let their_companion_ref = dm.companion.as_ref().map(|(_, c)| c.read(_cx));                    dm.block_map.read(                        companion_snapshot,                        companion_edits,                        their_companion_ref.map(|c| {                            CompanionView::new(                                dm.entity_id,                                &self_wrap_snapshot,                                &self_wrap_edits,                                c,                            )                        }),                    );                }            });        }         let companion_display_snapshot = self.companion.as_ref().and_then(|(companion_dm, _)| {            companion_dm                .update(cx, |dm, cx| Arc::new(dm.snapshot_simple(cx)))                .ok()        });         DisplaySnapshot {            display_map_id: self.entity_id,            companion_display_snapshot,            block_snapshot,

Lines 1–500 of 3965.