//! 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. Load more
//! 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,