4133 lines · 148.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, CompanionViewMut,    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, IndexSet};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::project_settings::DiagnosticSeverity;use project::{InlayId, lsp_store::LspFoldingRange, lsp_store::TokenType};use serde::Deserialize;use smallvec::SmallVec;use sum_tree::{Bias, TreeMap};use text::{BufferId, LineIndent, Patch};use ui::{SharedString, px};use unicode_segmentation::UnicodeSegmentation;use ztracing::instrument; use std::cell::RefCell;use std::collections::hash_map::Entry;use std::{    any::TypeId,    borrow::Cow,    fmt::Debug,    iter,    num::NonZeroU32,    ops::{self, 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,} /// Keys for tagging text highlights.////// Note the order is important as it determines the priority of the highlights, lower means higher priority#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]pub enum HighlightKey {    // Note we want semantic tokens > colorized brackets    // to allow language server highlights to work over brackets.    ColorizeBracket(usize),    SemanticToken,    // below is sorted lexicographically, as there is no relevant ordering for these aside from coming after the above    BufferSearchHighlights,    ConsoleAnsiHighlight(usize),    DebugStackFrameLine,    DocumentHighlightRead,    DocumentHighlightWrite,    EditPredictionHighlight,    Editor,    HighlightOnYank,    HighlightsTreeView(usize),    HoverState,    HoveredLinkState,    InlineAssist,    InputComposition,    MatchingBracket,    PendingInput,    ProjectSearchView,    Rename,    SearchWithinRange,    SelectedTextHighlight,    SyntaxTreeView(usize),    VimExchange,} pub trait ToDisplayPoint {    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;} type TextHighlights = Arc<HashMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>;type SemanticTokensHighlights =    Arc<HashMap<BufferId, (Arc<[SemanticTokenHighlight]>, Arc<HighlightStyleInterner>)>>;type InlayHighlights = TreeMap<HighlightKey, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>; #[derive(Debug)]pub struct CompanionExcerptPatch {    pub patch: Patch<MultiBufferPoint>,    pub edited_range: Range<MultiBufferPoint>,    pub source_excerpt_range: Range<MultiBufferPoint>,    pub target_excerpt_range: Range<MultiBufferPoint>,} pub type ConvertMultiBufferRows = fn(    &HashMap<ExcerptId, ExcerptId>,    &MultiBufferSnapshot,    &MultiBufferSnapshot,    (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),) -> Vec<CompanionExcerptPatch>; /// 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,    /// The semantic tokens from the language server.    pub semantic_token_highlights: SemanticTokensHighlights,    /// 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>)>,    lsp_folding_crease_ids: HashMap<BufferId, Vec<CreaseId>>,} pub(crate) struct Companion {    rhs_display_map_id: EntityId,    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,    rhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,    lhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,} impl Companion {    pub(crate) fn new(        rhs_display_map_id: EntityId,        rhs_rows_to_lhs_rows: ConvertMultiBufferRows,        lhs_rows_to_rhs_rows: ConvertMultiBufferRows,    ) -> Self {        Self {            rhs_display_map_id,            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,            rhs_custom_block_to_balancing_block: Default::default(),            lhs_custom_block_to_balancing_block: Default::default(),        }    }     pub(crate) fn is_rhs(&self, display_map_id: EntityId) -> bool {        self.rhs_display_map_id == display_map_id    }     pub(crate) fn custom_block_to_balancing_block(        &self,        display_map_id: EntityId,    ) -> &RefCell<HashMap<CustomBlockId, CustomBlockId>> {        if self.is_rhs(display_map_id) {            &self.rhs_custom_block_to_balancing_block        } else {            &self.lhs_custom_block_to_balancing_block        }    }     pub(crate) fn convert_rows_to_companion(        &self,        display_map_id: EntityId,        companion_snapshot: &MultiBufferSnapshot,        our_snapshot: &MultiBufferSnapshot,        bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),    ) -> Vec<CompanionExcerptPatch> {        let (excerpt_map, convert_fn) = if self.is_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_point_from_companion(        &self,        display_map_id: EntityId,        our_snapshot: &MultiBufferSnapshot,        companion_snapshot: &MultiBufferSnapshot,        point: MultiBufferPoint,    ) -> Range<MultiBufferPoint> {        let (excerpt_map, convert_fn) = if self.is_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)        };         let excerpt = convert_fn(            excerpt_map,            our_snapshot,            companion_snapshot,            (Bound::Included(point), Bound::Included(point)),        )        .into_iter()        .next();         let Some(excerpt) = excerpt else {            return Point::zero()..our_snapshot.max_point();        };        excerpt.patch.edit_for_old_position(point).new    }     pub(crate) fn convert_point_to_companion(        &self,        display_map_id: EntityId,        our_snapshot: &MultiBufferSnapshot,        companion_snapshot: &MultiBufferSnapshot,        point: MultiBufferPoint,    ) -> Range<MultiBufferPoint> {        let (excerpt_map, convert_fn) = if self.is_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)        };         let excerpt = convert_fn(            excerpt_map,            companion_snapshot,            our_snapshot,            (Bound::Included(point), Bound::Included(point)),        )        .into_iter()        .next();         let Some(excerpt) = excerpt else {            return Point::zero()..companion_snapshot.max_point();        };        excerpt.patch.edit_for_old_position(point).new    }     pub(crate) fn companion_excerpt_to_excerpt(        &self,        display_map_id: EntityId,    ) -> &HashMap<ExcerptId, ExcerptId> {        if self.is_rhs(display_map_id) {            &self.lhs_excerpt_to_rhs_excerpt        } else {            &self.rhs_excerpt_to_lhs_excerpt        }    }     #[cfg(test)]    pub(crate) fn excerpt_mappings(        &self,    ) -> (        &HashMap<ExcerptId, ExcerptId>,        &HashMap<ExcerptId, ExcerptId>,    ) {        (            &self.lhs_excerpt_to_rhs_excerpt,            &self.rhs_excerpt_to_lhs_excerpt,        )    }     fn buffer_to_companion_buffer(&self, display_map_id: EntityId) -> &HashMap<BufferId, BufferId> {        if self.is_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 lhs_to_rhs_buffer(&self, lhs_buffer_id: BufferId) -> Option<BufferId> {        self.lhs_buffer_to_rhs_buffer.get(&lhs_buffer_id).copied()    }     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);    }} #[derive(Default, Debug)]pub struct HighlightStyleInterner {    styles: IndexSet<HighlightStyle>,} impl HighlightStyleInterner {    pub(crate) fn intern(&mut self, style: HighlightStyle) -> HighlightStyleId {        HighlightStyleId(self.styles.insert_full(style).0 as u32)    }} impl ops::Index<HighlightStyleId> for HighlightStyleInterner {    type Output = HighlightStyle;     fn index(&self, index: HighlightStyleId) -> &Self::Output {        &self.styles[index.0 as usize]    }} #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]pub struct HighlightStyleId(u32); /// A `SemanticToken`, but positioned to an offset in a buffer, and stylized.#[derive(Debug, Clone)]pub struct SemanticTokenHighlight {    pub range: Range<Anchor>,    pub style: HighlightStyleId,    pub token_type: TokenType,    pub token_modifiers: u32,    pub server_id: lsp::LanguageServerId,} 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(),            semantic_token_highlights: Default::default(),            clip_at_line_ends: false,            masked: false,            companion: None,            lsp_folding_crease_ids: HashMap::default(),        }    }     pub(crate) fn set_companion(        &mut self,        companion: Option<(Entity<DisplayMap>, Entity<Companion>)>,        cx: &mut Context<Self>,    ) {        let this = cx.weak_entity();        // Reverting to no companion, recompute the block map to clear spacers        // and balancing blocks.        let Some((companion_display_map, companion)) = companion else {            let Some((_, companion)) = self.companion.take() else {                return;

Lines 1–500 of 4133.