display_map.rs

   1//! This module defines where the text should be displayed in an [`Editor`][Editor].
   2//!
   3//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].
   4//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when
   5//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that
   6//! we display as spaces and where to display custom blocks (like diagnostics).
   7//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up
   8//! of several smaller structures that form a hierarchy (starting at the bottom):
   9//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
  10//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.
  11//! - [`TabMap`] that keeps track of hard tabs in a buffer.
  12//! - [`WrapMap`] that handles soft wrapping.
  13//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.
  14//! - [`DisplayMap`] that adds background highlights to the regions of text.
  15//!   Each one of those builds on top of preceding map.
  16//!
  17//! [Editor]: crate::Editor
  18//! [EditorElement]: crate::element::EditorElement
  19
  20#[macro_use]
  21mod dimensions;
  22
  23mod block_map;
  24mod crease_map;
  25mod custom_highlights;
  26mod fold_map;
  27mod inlay_map;
  28mod invisibles;
  29mod tab_map;
  30mod wrap_map;
  31
  32pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap};
  33pub use block_map::{
  34    Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
  35    BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, EditorMargins, RenderBlock,
  36    StickyHeaderExcerpt,
  37};
  38pub use crease_map::*;
  39pub use fold_map::{
  40    ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
  41};
  42#[cfg(any(test, feature = "test-support"))]
  43use gpui::Hsla;
  44pub use inlay_map::{InlayOffset, InlayPoint};
  45pub use invisibles::{is_invisible, replacement};
  46
  47use collections::{HashMap, HashSet};
  48use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
  49use language::{
  50    OffsetUtf16, Point, Subscription as BufferSubscription, language_settings::language_settings,
  51};
  52use multi_buffer::{
  53    Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
  54    RowInfo, ToOffset, ToPoint,
  55};
  56use project::InlayId;
  57use project::project_settings::DiagnosticSeverity;
  58use serde::Deserialize;
  59use sum_tree::{Bias, TreeMap};
  60use text::{BufferId, LineIndent};
  61use ui::{SharedString, px};
  62use unicode_segmentation::UnicodeSegmentation;
  63
  64use std::{
  65    any::TypeId,
  66    borrow::Cow,
  67    fmt::Debug,
  68    iter,
  69    num::NonZeroU32,
  70    ops::{Add, Range, Sub},
  71    sync::Arc,
  72};
  73
  74use crate::{
  75    EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,
  76};
  77use block_map::{BlockRow, BlockSnapshot};
  78use fold_map::FoldSnapshot;
  79use inlay_map::InlaySnapshot;
  80use tab_map::TabSnapshot;
  81use wrap_map::{WrapMap, WrapSnapshot};
  82
  83#[derive(Copy, Clone, Debug, PartialEq, Eq)]
  84pub enum FoldStatus {
  85    Folded,
  86    Foldable,
  87}
  88
  89#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
  90pub enum HighlightKey {
  91    Type(TypeId),
  92    TypePlus(TypeId, usize),
  93}
  94
  95pub trait ToDisplayPoint {
  96    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
  97}
  98
  99type TextHighlights = TreeMap<HighlightKey, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
 100type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
 101
 102/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
 103/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
 104///
 105/// See the [module level documentation](self) for more information.
 106pub struct DisplayMap {
 107    /// The buffer that we are displaying.
 108    buffer: Entity<MultiBuffer>,
 109    buffer_subscription: BufferSubscription,
 110    /// Decides where the [`Inlay`]s should be displayed.
 111    inlay_map: InlayMap,
 112    /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
 113    fold_map: FoldMap,
 114    /// Keeps track of hard tabs in a buffer.
 115    tab_map: TabMap,
 116    /// Handles soft wrapping.
 117    wrap_map: Entity<WrapMap>,
 118    /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
 119    block_map: BlockMap,
 120    /// Regions of text that should be highlighted.
 121    text_highlights: TextHighlights,
 122    /// Regions of inlays that should be highlighted.
 123    inlay_highlights: InlayHighlights,
 124    /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
 125    crease_map: CreaseMap,
 126    pub(crate) fold_placeholder: FoldPlaceholder,
 127    pub clip_at_line_ends: bool,
 128    pub(crate) masked: bool,
 129    pub(crate) diagnostics_max_severity: DiagnosticSeverity,
 130}
 131
 132impl DisplayMap {
 133    pub fn new(
 134        buffer: Entity<MultiBuffer>,
 135        font: Font,
 136        font_size: Pixels,
 137        wrap_width: Option<Pixels>,
 138        buffer_header_height: u32,
 139        excerpt_header_height: u32,
 140        fold_placeholder: FoldPlaceholder,
 141        diagnostics_max_severity: DiagnosticSeverity,
 142        cx: &mut Context<Self>,
 143    ) -> Self {
 144        let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
 145
 146        let tab_size = Self::tab_size(&buffer, cx);
 147        let buffer_snapshot = buffer.read(cx).snapshot(cx);
 148        let crease_map = CreaseMap::new(&buffer_snapshot);
 149        let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
 150        let (fold_map, snapshot) = FoldMap::new(snapshot);
 151        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
 152        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
 153        let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
 154
 155        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
 156
 157        DisplayMap {
 158            buffer,
 159            buffer_subscription,
 160            fold_map,
 161            inlay_map,
 162            tab_map,
 163            wrap_map,
 164            block_map,
 165            crease_map,
 166            fold_placeholder,
 167            diagnostics_max_severity,
 168            text_highlights: Default::default(),
 169            inlay_highlights: Default::default(),
 170            clip_at_line_ends: false,
 171            masked: false,
 172        }
 173    }
 174
 175    pub fn snapshot(&mut self, cx: &mut Context<Self>) -> DisplaySnapshot {
 176        let tab_size = Self::tab_size(&self.buffer, cx);
 177
 178        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 179        let edits = self.buffer_subscription.consume().into_inner();
 180        let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
 181        let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot, edits);
 182        let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot, edits, tab_size);
 183        let (wrap_snapshot, edits) = self
 184            .wrap_map
 185            .update(cx, |map, cx| map.sync(tab_snapshot, edits, cx));
 186        let block_snapshot = self.block_map.read(wrap_snapshot, edits).snapshot;
 187
 188        DisplaySnapshot {
 189            block_snapshot,
 190            diagnostics_max_severity: self.diagnostics_max_severity,
 191            crease_snapshot: self.crease_map.snapshot(),
 192            text_highlights: self.text_highlights.clone(),
 193            inlay_highlights: self.inlay_highlights.clone(),
 194            clip_at_line_ends: self.clip_at_line_ends,
 195            masked: self.masked,
 196            fold_placeholder: self.fold_placeholder.clone(),
 197        }
 198    }
 199
 200    pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut Context<Self>) {
 201        self.fold(
 202            other
 203                .folds_in_range(0..other.buffer_snapshot().len())
 204                .map(|fold| {
 205                    Crease::simple(
 206                        fold.range.to_offset(other.buffer_snapshot()),
 207                        fold.placeholder.clone(),
 208                    )
 209                })
 210                .collect(),
 211            cx,
 212        );
 213    }
 214
 215    /// Creates folds for the given creases.
 216    pub fn fold<T: Clone + ToOffset>(&mut self, creases: Vec<Crease<T>>, cx: &mut Context<Self>) {
 217        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 218        let edits = self.buffer_subscription.consume().into_inner();
 219        let tab_size = Self::tab_size(&self.buffer, cx);
 220        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
 221        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 222        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 223        let (snapshot, edits) = self
 224            .wrap_map
 225            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 226        self.block_map.read(snapshot, edits);
 227
 228        let inline = creases.iter().filter_map(|crease| {
 229            if let Crease::Inline {
 230                range, placeholder, ..
 231            } = crease
 232            {
 233                Some((range.clone(), placeholder.clone()))
 234            } else {
 235                None
 236            }
 237        });
 238        let (snapshot, edits) = fold_map.fold(inline);
 239
 240        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 241        let (snapshot, edits) = self
 242            .wrap_map
 243            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 244        let mut block_map = self.block_map.write(snapshot, edits);
 245        let blocks = creases.into_iter().filter_map(|crease| {
 246            if let Crease::Block {
 247                range,
 248                block_height,
 249                render_block,
 250                block_style,
 251                block_priority,
 252                ..
 253            } = crease
 254            {
 255                Some((
 256                    range,
 257                    render_block,
 258                    block_height,
 259                    block_style,
 260                    block_priority,
 261                ))
 262            } else {
 263                None
 264            }
 265        });
 266        block_map.insert(
 267            blocks
 268                .into_iter()
 269                .map(|(range, render, height, style, priority)| {
 270                    let start = buffer_snapshot.anchor_before(range.start);
 271                    let end = buffer_snapshot.anchor_after(range.end);
 272                    BlockProperties {
 273                        placement: BlockPlacement::Replace(start..=end),
 274                        render,
 275                        height: Some(height),
 276                        style,
 277                        priority,
 278                    }
 279                }),
 280        );
 281    }
 282
 283    /// Removes any folds with the given ranges.
 284    pub fn remove_folds_with_type<T: ToOffset>(
 285        &mut self,
 286        ranges: impl IntoIterator<Item = Range<T>>,
 287        type_id: TypeId,
 288        cx: &mut Context<Self>,
 289    ) {
 290        let snapshot = self.buffer.read(cx).snapshot(cx);
 291        let edits = self.buffer_subscription.consume().into_inner();
 292        let tab_size = Self::tab_size(&self.buffer, cx);
 293        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 294        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 295        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 296        let (snapshot, edits) = self
 297            .wrap_map
 298            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 299        self.block_map.read(snapshot, edits);
 300        let (snapshot, edits) = fold_map.remove_folds(ranges, type_id);
 301        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 302        let (snapshot, edits) = self
 303            .wrap_map
 304            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 305        self.block_map.write(snapshot, edits);
 306    }
 307
 308    /// Removes any folds whose ranges intersect any of the given ranges.
 309    pub fn unfold_intersecting<T: ToOffset>(
 310        &mut self,
 311        ranges: impl IntoIterator<Item = Range<T>>,
 312        inclusive: bool,
 313        cx: &mut Context<Self>,
 314    ) {
 315        let snapshot = self.buffer.read(cx).snapshot(cx);
 316        let offset_ranges = ranges
 317            .into_iter()
 318            .map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot))
 319            .collect::<Vec<_>>();
 320        let edits = self.buffer_subscription.consume().into_inner();
 321        let tab_size = Self::tab_size(&self.buffer, cx);
 322        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 323        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 324        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 325        let (snapshot, edits) = self
 326            .wrap_map
 327            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 328        self.block_map.read(snapshot, edits);
 329
 330        let (snapshot, edits) =
 331            fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
 332        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 333        let (snapshot, edits) = self
 334            .wrap_map
 335            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 336        let mut block_map = self.block_map.write(snapshot, edits);
 337        block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
 338    }
 339
 340    pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
 341        let snapshot = self.buffer.read(cx).snapshot(cx);
 342        let edits = self.buffer_subscription.consume().into_inner();
 343        let tab_size = Self::tab_size(&self.buffer, cx);
 344        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 345        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 346        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 347        let (snapshot, edits) = self
 348            .wrap_map
 349            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 350        let mut block_map = self.block_map.write(snapshot, edits);
 351        block_map.disable_header_for_buffer(buffer_id)
 352    }
 353
 354    pub fn fold_buffers(
 355        &mut self,
 356        buffer_ids: impl IntoIterator<Item = language::BufferId>,
 357        cx: &mut Context<Self>,
 358    ) {
 359        let snapshot = self.buffer.read(cx).snapshot(cx);
 360        let edits = self.buffer_subscription.consume().into_inner();
 361        let tab_size = Self::tab_size(&self.buffer, cx);
 362        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 363        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 364        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 365        let (snapshot, edits) = self
 366            .wrap_map
 367            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 368        let mut block_map = self.block_map.write(snapshot, edits);
 369        block_map.fold_buffers(buffer_ids, self.buffer.read(cx), cx)
 370    }
 371
 372    pub fn unfold_buffers(
 373        &mut self,
 374        buffer_ids: impl IntoIterator<Item = language::BufferId>,
 375        cx: &mut Context<Self>,
 376    ) {
 377        let snapshot = self.buffer.read(cx).snapshot(cx);
 378        let edits = self.buffer_subscription.consume().into_inner();
 379        let tab_size = Self::tab_size(&self.buffer, cx);
 380        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 381        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 382        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 383        let (snapshot, edits) = self
 384            .wrap_map
 385            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 386        let mut block_map = self.block_map.write(snapshot, edits);
 387        block_map.unfold_buffers(buffer_ids, self.buffer.read(cx), cx)
 388    }
 389
 390    pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
 391        self.block_map.folded_buffers.contains(&buffer_id)
 392    }
 393
 394    pub(crate) fn folded_buffers(&self) -> &HashSet<BufferId> {
 395        &self.block_map.folded_buffers
 396    }
 397
 398    pub fn insert_creases(
 399        &mut self,
 400        creases: impl IntoIterator<Item = Crease<Anchor>>,
 401        cx: &mut Context<Self>,
 402    ) -> Vec<CreaseId> {
 403        let snapshot = self.buffer.read(cx).snapshot(cx);
 404        self.crease_map.insert(creases, &snapshot)
 405    }
 406
 407    pub fn remove_creases(
 408        &mut self,
 409        crease_ids: impl IntoIterator<Item = CreaseId>,
 410        cx: &mut Context<Self>,
 411    ) -> Vec<(CreaseId, Range<Anchor>)> {
 412        let snapshot = self.buffer.read(cx).snapshot(cx);
 413        self.crease_map.remove(crease_ids, &snapshot)
 414    }
 415
 416    pub fn insert_blocks(
 417        &mut self,
 418        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
 419        cx: &mut Context<Self>,
 420    ) -> Vec<CustomBlockId> {
 421        let snapshot = self.buffer.read(cx).snapshot(cx);
 422        let edits = self.buffer_subscription.consume().into_inner();
 423        let tab_size = Self::tab_size(&self.buffer, cx);
 424        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 425        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 426        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 427        let (snapshot, edits) = self
 428            .wrap_map
 429            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 430        let mut block_map = self.block_map.write(snapshot, edits);
 431        block_map.insert(blocks)
 432    }
 433
 434    pub fn resize_blocks(&mut self, heights: HashMap<CustomBlockId, u32>, cx: &mut Context<Self>) {
 435        let snapshot = self.buffer.read(cx).snapshot(cx);
 436        let edits = self.buffer_subscription.consume().into_inner();
 437        let tab_size = Self::tab_size(&self.buffer, cx);
 438        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 439        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 440        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 441        let (snapshot, edits) = self
 442            .wrap_map
 443            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 444        let mut block_map = self.block_map.write(snapshot, edits);
 445        block_map.resize(heights);
 446    }
 447
 448    pub fn replace_blocks(&mut self, renderers: HashMap<CustomBlockId, RenderBlock>) {
 449        self.block_map.replace_blocks(renderers);
 450    }
 451
 452    pub fn remove_blocks(&mut self, ids: HashSet<CustomBlockId>, cx: &mut Context<Self>) {
 453        let snapshot = self.buffer.read(cx).snapshot(cx);
 454        let edits = self.buffer_subscription.consume().into_inner();
 455        let tab_size = Self::tab_size(&self.buffer, cx);
 456        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 457        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 458        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 459        let (snapshot, edits) = self
 460            .wrap_map
 461            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 462        let mut block_map = self.block_map.write(snapshot, edits);
 463        block_map.remove(ids);
 464    }
 465
 466    pub fn row_for_block(
 467        &mut self,
 468        block_id: CustomBlockId,
 469        cx: &mut Context<Self>,
 470    ) -> Option<DisplayRow> {
 471        let snapshot = self.buffer.read(cx).snapshot(cx);
 472        let edits = self.buffer_subscription.consume().into_inner();
 473        let tab_size = Self::tab_size(&self.buffer, cx);
 474        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 475        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 476        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 477        let (snapshot, edits) = self
 478            .wrap_map
 479            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 480        let block_map = self.block_map.read(snapshot, edits);
 481        let block_row = block_map.row_for_block(block_id)?;
 482        Some(DisplayRow(block_row.0))
 483    }
 484
 485    pub fn highlight_text(
 486        &mut self,
 487        key: HighlightKey,
 488        ranges: Vec<Range<Anchor>>,
 489        style: HighlightStyle,
 490        merge: bool,
 491        cx: &App,
 492    ) {
 493        let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 494        let to_insert = match self.text_highlights.remove(&key).filter(|_| merge) {
 495            Some(previous) => {
 496                let mut merged_ranges = previous.1.clone();
 497                for new_range in ranges {
 498                    let i = merged_ranges
 499                        .binary_search_by(|probe| {
 500                            probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
 501                        })
 502                        .unwrap_or_else(|i| i);
 503                    merged_ranges.insert(i, new_range);
 504                }
 505                Arc::new((style, merged_ranges))
 506            }
 507            None => Arc::new((style, ranges)),
 508        };
 509        self.text_highlights.insert(key, to_insert);
 510    }
 511
 512    pub(crate) fn highlight_inlays(
 513        &mut self,
 514        type_id: TypeId,
 515        highlights: Vec<InlayHighlight>,
 516        style: HighlightStyle,
 517    ) {
 518        for highlight in highlights {
 519            let update = self.inlay_highlights.update(&type_id, |highlights| {
 520                highlights.insert(highlight.inlay, (style, highlight.clone()))
 521            });
 522            if update.is_none() {
 523                self.inlay_highlights.insert(
 524                    type_id,
 525                    TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
 526                );
 527            }
 528        }
 529    }
 530
 531    pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
 532        let highlights = self.text_highlights.get(&HighlightKey::Type(type_id))?;
 533        Some((highlights.0, &highlights.1))
 534    }
 535
 536    #[cfg(feature = "test-support")]
 537    pub fn all_text_highlights(
 538        &self,
 539    ) -> impl Iterator<Item = &Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
 540        self.text_highlights.values()
 541    }
 542
 543    pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
 544        let mut cleared = self
 545            .text_highlights
 546            .remove(&HighlightKey::Type(type_id))
 547            .is_some();
 548        self.text_highlights.retain(|key, _| {
 549            let retain = if let HighlightKey::TypePlus(key_type_id, _) = key {
 550                key_type_id != &type_id
 551            } else {
 552                true
 553            };
 554            cleared |= !retain;
 555            retain
 556        });
 557        cleared |= self.inlay_highlights.remove(&type_id).is_some();
 558        cleared
 559    }
 560
 561    pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut Context<Self>) -> bool {
 562        self.wrap_map
 563            .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
 564    }
 565
 566    pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut Context<Self>) -> bool {
 567        self.wrap_map
 568            .update(cx, |map, cx| map.set_wrap_width(width, cx))
 569    }
 570
 571    pub fn update_fold_widths(
 572        &mut self,
 573        widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
 574        cx: &mut Context<Self>,
 575    ) -> bool {
 576        let snapshot = self.buffer.read(cx).snapshot(cx);
 577        let edits = self.buffer_subscription.consume().into_inner();
 578        let tab_size = Self::tab_size(&self.buffer, cx);
 579        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 580        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 581        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 582        let (snapshot, edits) = self
 583            .wrap_map
 584            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 585        self.block_map.read(snapshot, edits);
 586
 587        let (snapshot, edits) = fold_map.update_fold_widths(widths);
 588        let widths_changed = !edits.is_empty();
 589        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 590        let (snapshot, edits) = self
 591            .wrap_map
 592            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 593        self.block_map.read(snapshot, edits);
 594
 595        widths_changed
 596    }
 597
 598    pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
 599        self.inlay_map.current_inlays()
 600    }
 601
 602    pub(crate) fn splice_inlays(
 603        &mut self,
 604        to_remove: &[InlayId],
 605        to_insert: Vec<Inlay>,
 606        cx: &mut Context<Self>,
 607    ) {
 608        if to_remove.is_empty() && to_insert.is_empty() {
 609            return;
 610        }
 611        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 612        let edits = self.buffer_subscription.consume().into_inner();
 613        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
 614        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 615        let tab_size = Self::tab_size(&self.buffer, cx);
 616        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 617        let (snapshot, edits) = self
 618            .wrap_map
 619            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 620        self.block_map.read(snapshot, edits);
 621
 622        let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
 623        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 624        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 625        let (snapshot, edits) = self
 626            .wrap_map
 627            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 628        self.block_map.read(snapshot, edits);
 629    }
 630
 631    fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
 632        let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
 633        let language = buffer
 634            .and_then(|buffer| buffer.language())
 635            .map(|l| l.name());
 636        let file = buffer.and_then(|buffer| buffer.file());
 637        language_settings(language, file, cx).tab_size
 638    }
 639
 640    #[cfg(test)]
 641    pub fn is_rewrapping(&self, cx: &gpui::App) -> bool {
 642        self.wrap_map.read(cx).is_rewrapping()
 643    }
 644}
 645
 646#[derive(Debug, Default)]
 647pub(crate) struct Highlights<'a> {
 648    pub text_highlights: Option<&'a TextHighlights>,
 649    pub inlay_highlights: Option<&'a InlayHighlights>,
 650    pub styles: HighlightStyles,
 651}
 652
 653#[derive(Clone, Copy, Debug)]
 654pub struct EditPredictionStyles {
 655    pub insertion: HighlightStyle,
 656    pub whitespace: HighlightStyle,
 657}
 658
 659#[derive(Default, Debug, Clone, Copy)]
 660pub struct HighlightStyles {
 661    pub inlay_hint: Option<HighlightStyle>,
 662    pub edit_prediction: Option<EditPredictionStyles>,
 663}
 664
 665#[derive(Clone)]
 666pub enum ChunkReplacement {
 667    Renderer(ChunkRenderer),
 668    Str(SharedString),
 669}
 670
 671pub struct HighlightedChunk<'a> {
 672    pub text: &'a str,
 673    pub style: Option<HighlightStyle>,
 674    pub is_tab: bool,
 675    pub is_inlay: bool,
 676    pub replacement: Option<ChunkReplacement>,
 677}
 678
 679impl<'a> HighlightedChunk<'a> {
 680    fn highlight_invisibles(
 681        self,
 682        editor_style: &'a EditorStyle,
 683    ) -> impl Iterator<Item = Self> + 'a {
 684        let mut chars = self.text.chars().peekable();
 685        let mut text = self.text;
 686        let style = self.style;
 687        let is_tab = self.is_tab;
 688        let renderer = self.replacement;
 689        let is_inlay = self.is_inlay;
 690        iter::from_fn(move || {
 691            let mut prefix_len = 0;
 692            while let Some(&ch) = chars.peek() {
 693                if !is_invisible(ch) {
 694                    prefix_len += ch.len_utf8();
 695                    chars.next();
 696                    continue;
 697                }
 698                if prefix_len > 0 {
 699                    let (prefix, suffix) = text.split_at(prefix_len);
 700                    text = suffix;
 701                    return Some(HighlightedChunk {
 702                        text: prefix,
 703                        style,
 704                        is_tab,
 705                        is_inlay,
 706                        replacement: renderer.clone(),
 707                    });
 708                }
 709                chars.next();
 710                let (prefix, suffix) = text.split_at(ch.len_utf8());
 711                text = suffix;
 712                if let Some(replacement) = replacement(ch) {
 713                    let invisible_highlight = HighlightStyle {
 714                        background_color: Some(editor_style.status.hint_background),
 715                        underline: Some(UnderlineStyle {
 716                            color: Some(editor_style.status.hint),
 717                            thickness: px(1.),
 718                            wavy: false,
 719                        }),
 720                        ..Default::default()
 721                    };
 722                    let invisible_style = if let Some(style) = style {
 723                        style.highlight(invisible_highlight)
 724                    } else {
 725                        invisible_highlight
 726                    };
 727                    return Some(HighlightedChunk {
 728                        text: prefix,
 729                        style: Some(invisible_style),
 730                        is_tab: false,
 731                        is_inlay,
 732                        replacement: Some(ChunkReplacement::Str(replacement.into())),
 733                    });
 734                } else {
 735                    let invisible_highlight = HighlightStyle {
 736                        background_color: Some(editor_style.status.hint_background),
 737                        underline: Some(UnderlineStyle {
 738                            color: Some(editor_style.status.hint),
 739                            thickness: px(1.),
 740                            wavy: false,
 741                        }),
 742                        ..Default::default()
 743                    };
 744                    let invisible_style = if let Some(style) = style {
 745                        style.highlight(invisible_highlight)
 746                    } else {
 747                        invisible_highlight
 748                    };
 749
 750                    return Some(HighlightedChunk {
 751                        text: prefix,
 752                        style: Some(invisible_style),
 753                        is_tab: false,
 754                        is_inlay,
 755                        replacement: renderer.clone(),
 756                    });
 757                }
 758            }
 759
 760            if !text.is_empty() {
 761                let remainder = text;
 762                text = "";
 763                Some(HighlightedChunk {
 764                    text: remainder,
 765                    style,
 766                    is_tab,
 767                    is_inlay,
 768                    replacement: renderer.clone(),
 769                })
 770            } else {
 771                None
 772            }
 773        })
 774    }
 775}
 776
 777#[derive(Clone)]
 778pub struct DisplaySnapshot {
 779    pub crease_snapshot: CreaseSnapshot,
 780    block_snapshot: BlockSnapshot,
 781    text_highlights: TextHighlights,
 782    inlay_highlights: InlayHighlights,
 783    clip_at_line_ends: bool,
 784    masked: bool,
 785    diagnostics_max_severity: DiagnosticSeverity,
 786    pub(crate) fold_placeholder: FoldPlaceholder,
 787}
 788
 789impl DisplaySnapshot {
 790    pub fn wrap_snapshot(&self) -> &WrapSnapshot {
 791        &self.block_snapshot.wrap_snapshot
 792    }
 793    pub fn tab_snapshot(&self) -> &TabSnapshot {
 794        &self.block_snapshot.wrap_snapshot.tab_snapshot
 795    }
 796
 797    pub fn fold_snapshot(&self) -> &FoldSnapshot {
 798        &self.block_snapshot.wrap_snapshot.tab_snapshot.fold_snapshot
 799    }
 800
 801    pub fn inlay_snapshot(&self) -> &InlaySnapshot {
 802        &self
 803            .block_snapshot
 804            .wrap_snapshot
 805            .tab_snapshot
 806            .fold_snapshot
 807            .inlay_snapshot
 808    }
 809
 810    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
 811        &self
 812            .block_snapshot
 813            .wrap_snapshot
 814            .tab_snapshot
 815            .fold_snapshot
 816            .inlay_snapshot
 817            .buffer
 818    }
 819
 820    #[cfg(test)]
 821    pub fn fold_count(&self) -> usize {
 822        self.fold_snapshot().fold_count()
 823    }
 824
 825    pub fn is_empty(&self) -> bool {
 826        self.buffer_snapshot().len() == 0
 827    }
 828
 829    pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
 830        self.block_snapshot.row_infos(BlockRow(start_row.0))
 831    }
 832
 833    pub fn widest_line_number(&self) -> u32 {
 834        self.buffer_snapshot().widest_line_number()
 835    }
 836
 837    pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
 838        loop {
 839            let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
 840            let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Left);
 841            fold_point.0.column = 0;
 842            inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
 843            point = self.inlay_snapshot().to_buffer_point(inlay_point);
 844
 845            let mut display_point = self.point_to_display_point(point, Bias::Left);
 846            *display_point.column_mut() = 0;
 847            let next_point = self.display_point_to_point(display_point, Bias::Left);
 848            if next_point == point {
 849                return (point, display_point);
 850            }
 851            point = next_point;
 852        }
 853    }
 854
 855    pub fn next_line_boundary(
 856        &self,
 857        mut point: MultiBufferPoint,
 858    ) -> (MultiBufferPoint, DisplayPoint) {
 859        let original_point = point;
 860        loop {
 861            let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
 862            let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Right);
 863            fold_point.0.column = self.fold_snapshot().line_len(fold_point.row());
 864            inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
 865            point = self.inlay_snapshot().to_buffer_point(inlay_point);
 866
 867            let mut display_point = self.point_to_display_point(point, Bias::Right);
 868            *display_point.column_mut() = self.line_len(display_point.row());
 869            let next_point = self.display_point_to_point(display_point, Bias::Right);
 870            if next_point == point || original_point == point || original_point == next_point {
 871                return (point, display_point);
 872            }
 873            point = next_point;
 874        }
 875    }
 876
 877    // used by line_mode selections and tries to match vim behavior
 878    pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
 879        let new_start = MultiBufferPoint::new(range.start.row, 0);
 880        let new_end = if range.end.column > 0 {
 881            MultiBufferPoint::new(
 882                range.end.row,
 883                self.buffer_snapshot()
 884                    .line_len(MultiBufferRow(range.end.row)),
 885            )
 886        } else {
 887            range.end
 888        };
 889
 890        new_start..new_end
 891    }
 892
 893    pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
 894        let inlay_point = self.inlay_snapshot().to_inlay_point(point);
 895        let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
 896        let tab_point = self.tab_snapshot().to_tab_point(fold_point);
 897        let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
 898        let block_point = self.block_snapshot.to_block_point(wrap_point);
 899        DisplayPoint(block_point)
 900    }
 901
 902    pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
 903        self.inlay_snapshot()
 904            .to_buffer_point(self.display_point_to_inlay_point(point, bias))
 905    }
 906
 907    pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
 908        self.inlay_snapshot()
 909            .to_offset(self.display_point_to_inlay_point(point, bias))
 910    }
 911
 912    pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
 913        self.inlay_snapshot()
 914            .to_inlay_offset(anchor.to_offset(self.buffer_snapshot()))
 915    }
 916
 917    pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
 918        self.buffer_snapshot()
 919            .anchor_at(point.to_offset(self, bias), bias)
 920    }
 921
 922    fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
 923        let block_point = point.0;
 924        let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
 925        let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
 926        let fold_point = self.tab_snapshot().to_fold_point(tab_point, bias).0;
 927        fold_point.to_inlay_point(self.fold_snapshot())
 928    }
 929
 930    pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
 931        let block_point = point.0;
 932        let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
 933        let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
 934        self.tab_snapshot().to_fold_point(tab_point, bias).0
 935    }
 936
 937    pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
 938        let tab_point = self.tab_snapshot().to_tab_point(fold_point);
 939        let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
 940        let block_point = self.block_snapshot.to_block_point(wrap_point);
 941        DisplayPoint(block_point)
 942    }
 943
 944    pub fn max_point(&self) -> DisplayPoint {
 945        DisplayPoint(self.block_snapshot.max_point())
 946    }
 947
 948    /// Returns text chunks starting at the given display row until the end of the file
 949    pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
 950        self.block_snapshot
 951            .chunks(
 952                BlockRow(display_row.0)..BlockRow(self.max_point().row().next_row().0),
 953                false,
 954                self.masked,
 955                Highlights::default(),
 956            )
 957            .map(|h| h.text)
 958    }
 959
 960    /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
 961    pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
 962        (0..=display_row.0).rev().flat_map(move |row| {
 963            self.block_snapshot
 964                .chunks(
 965                    BlockRow(row)..BlockRow(row + 1),
 966                    false,
 967                    self.masked,
 968                    Highlights::default(),
 969                )
 970                .map(|h| h.text)
 971                .collect::<Vec<_>>()
 972                .into_iter()
 973                .rev()
 974        })
 975    }
 976
 977    pub fn chunks(
 978        &self,
 979        display_rows: Range<DisplayRow>,
 980        language_aware: bool,
 981        highlight_styles: HighlightStyles,
 982    ) -> DisplayChunks<'_> {
 983        self.block_snapshot.chunks(
 984            BlockRow(display_rows.start.0)..BlockRow(display_rows.end.0),
 985            language_aware,
 986            self.masked,
 987            Highlights {
 988                text_highlights: Some(&self.text_highlights),
 989                inlay_highlights: Some(&self.inlay_highlights),
 990                styles: highlight_styles,
 991            },
 992        )
 993    }
 994
 995    pub fn highlighted_chunks<'a>(
 996        &'a self,
 997        display_rows: Range<DisplayRow>,
 998        language_aware: bool,
 999        editor_style: &'a EditorStyle,
1000    ) -> impl Iterator<Item = HighlightedChunk<'a>> {
1001        self.chunks(
1002            display_rows,
1003            language_aware,
1004            HighlightStyles {
1005                inlay_hint: Some(editor_style.inlay_hints_style),
1006                edit_prediction: Some(editor_style.edit_prediction_styles),
1007            },
1008        )
1009        .flat_map(|chunk| {
1010            let highlight_style = chunk
1011                .syntax_highlight_id
1012                .and_then(|id| id.style(&editor_style.syntax));
1013
1014            let chunk_highlight = chunk.highlight_style.map(|chunk_highlight| {
1015                HighlightStyle {
1016                    // For color inlays, blend the color with the editor background
1017                    // if the color has transparency (alpha < 1.0)
1018                    color: chunk_highlight.color.map(|color| {
1019                        if chunk.is_inlay && !color.is_opaque() {
1020                            editor_style.background.blend(color)
1021                        } else {
1022                            color
1023                        }
1024                    }),
1025                    ..chunk_highlight
1026                }
1027            });
1028
1029            let diagnostic_highlight = chunk
1030                .diagnostic_severity
1031                .filter(|severity| {
1032                    self.diagnostics_max_severity
1033                        .into_lsp()
1034                        .is_some_and(|max_severity| severity <= &max_severity)
1035                })
1036                .map(|severity| HighlightStyle {
1037                    fade_out: chunk
1038                        .is_unnecessary
1039                        .then_some(editor_style.unnecessary_code_fade),
1040                    underline: (chunk.underline
1041                        && editor_style.show_underlines
1042                        && !(chunk.is_unnecessary && severity > lsp::DiagnosticSeverity::WARNING))
1043                        .then(|| {
1044                            let diagnostic_color =
1045                                super::diagnostic_style(severity, &editor_style.status);
1046                            UnderlineStyle {
1047                                color: Some(diagnostic_color),
1048                                thickness: 1.0.into(),
1049                                wavy: true,
1050                            }
1051                        }),
1052                    ..Default::default()
1053                });
1054
1055            let style = [highlight_style, chunk_highlight, diagnostic_highlight]
1056                .into_iter()
1057                .flatten()
1058                .reduce(|acc, highlight| acc.highlight(highlight));
1059
1060            HighlightedChunk {
1061                text: chunk.text,
1062                style,
1063                is_tab: chunk.is_tab,
1064                is_inlay: chunk.is_inlay,
1065                replacement: chunk.renderer.map(ChunkReplacement::Renderer),
1066            }
1067            .highlight_invisibles(editor_style)
1068        })
1069    }
1070
1071    pub fn layout_row(
1072        &self,
1073        display_row: DisplayRow,
1074        TextLayoutDetails {
1075            text_system,
1076            editor_style,
1077            rem_size,
1078            scroll_anchor: _,
1079            visible_rows: _,
1080            vertical_scroll_margin: _,
1081        }: &TextLayoutDetails,
1082    ) -> Arc<LineLayout> {
1083        let mut runs = Vec::new();
1084        let mut line = String::new();
1085
1086        let range = display_row..display_row.next_row();
1087        for chunk in self.highlighted_chunks(range, false, editor_style) {
1088            line.push_str(chunk.text);
1089
1090            let text_style = if let Some(style) = chunk.style {
1091                Cow::Owned(editor_style.text.clone().highlight(style))
1092            } else {
1093                Cow::Borrowed(&editor_style.text)
1094            };
1095
1096            runs.push(text_style.to_run(chunk.text.len()))
1097        }
1098
1099        if line.ends_with('\n') {
1100            line.pop();
1101            if let Some(last_run) = runs.last_mut() {
1102                last_run.len -= 1;
1103                if last_run.len == 0 {
1104                    runs.pop();
1105                }
1106            }
1107        }
1108
1109        let font_size = editor_style.text.font_size.to_pixels(*rem_size);
1110        text_system.layout_line(&line, font_size, &runs, None)
1111    }
1112
1113    pub fn x_for_display_point(
1114        &self,
1115        display_point: DisplayPoint,
1116        text_layout_details: &TextLayoutDetails,
1117    ) -> Pixels {
1118        let line = self.layout_row(display_point.row(), text_layout_details);
1119        line.x_for_index(display_point.column() as usize)
1120    }
1121
1122    pub fn display_column_for_x(
1123        &self,
1124        display_row: DisplayRow,
1125        x: Pixels,
1126        details: &TextLayoutDetails,
1127    ) -> u32 {
1128        let layout_line = self.layout_row(display_row, details);
1129        layout_line.index_for_x(x) as u32
1130    }
1131
1132    pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
1133        point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
1134        let chars = self
1135            .text_chunks(point.row())
1136            .flat_map(str::chars)
1137            .skip_while({
1138                let mut column = 0;
1139                move |char| {
1140                    let at_point = column >= point.column();
1141                    column += char.len_utf8() as u32;
1142                    !at_point
1143                }
1144            })
1145            .take_while({
1146                let mut prev = false;
1147                move |char| {
1148                    let now = char.is_ascii();
1149                    let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
1150                    prev = now;
1151                    !end
1152                }
1153            });
1154        chars.collect::<String>().graphemes(true).next().map(|s| {
1155            if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) {
1156                replacement(invisible).unwrap_or(s).to_owned().into()
1157            } else if s == "\n" {
1158                " ".into()
1159            } else {
1160                s.to_owned().into()
1161            }
1162        })
1163    }
1164
1165    pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
1166        self.buffer_snapshot().chars_at(offset).map(move |ch| {
1167            let ret = (ch, offset);
1168            offset += ch.len_utf8();
1169            ret
1170        })
1171    }
1172
1173    pub fn reverse_buffer_chars_at(
1174        &self,
1175        mut offset: usize,
1176    ) -> impl Iterator<Item = (char, usize)> + '_ {
1177        self.buffer_snapshot()
1178            .reversed_chars_at(offset)
1179            .map(move |ch| {
1180                offset -= ch.len_utf8();
1181                (ch, offset)
1182            })
1183    }
1184
1185    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
1186        let mut clipped = self.block_snapshot.clip_point(point.0, bias);
1187        if self.clip_at_line_ends {
1188            clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
1189        }
1190        DisplayPoint(clipped)
1191    }
1192
1193    pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
1194        DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
1195    }
1196
1197    pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
1198        let mut point = self.display_point_to_point(display_point, Bias::Left);
1199
1200        if point.column != self.buffer_snapshot().line_len(MultiBufferRow(point.row)) {
1201            return display_point;
1202        }
1203        point.column = point.column.saturating_sub(1);
1204        point = self.buffer_snapshot().clip_point(point, Bias::Left);
1205        self.point_to_display_point(point, Bias::Left)
1206    }
1207
1208    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
1209    where
1210        T: ToOffset,
1211    {
1212        self.fold_snapshot().folds_in_range(range)
1213    }
1214
1215    pub fn blocks_in_range(
1216        &self,
1217        rows: Range<DisplayRow>,
1218    ) -> impl Iterator<Item = (DisplayRow, &Block)> {
1219        self.block_snapshot
1220            .blocks_in_range(BlockRow(rows.start.0)..BlockRow(rows.end.0))
1221            .map(|(row, block)| (DisplayRow(row.0), block))
1222    }
1223
1224    pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
1225        self.block_snapshot.sticky_header_excerpt(row)
1226    }
1227
1228    pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
1229        self.block_snapshot.block_for_id(id)
1230    }
1231
1232    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
1233        self.fold_snapshot().intersects_fold(offset)
1234    }
1235
1236    pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
1237        self.block_snapshot.is_line_replaced(buffer_row)
1238            || self.fold_snapshot().is_line_folded(buffer_row)
1239    }
1240
1241    pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
1242        self.block_snapshot.is_block_line(BlockRow(display_row.0))
1243    }
1244
1245    pub fn is_folded_buffer_header(&self, display_row: DisplayRow) -> bool {
1246        self.block_snapshot
1247            .is_folded_buffer_header(BlockRow(display_row.0))
1248    }
1249
1250    pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
1251        let wrap_row = self
1252            .block_snapshot
1253            .to_wrap_point(BlockPoint::new(BlockRow(display_row.0), 0), Bias::Left)
1254            .row();
1255        self.wrap_snapshot().soft_wrap_indent(wrap_row)
1256    }
1257
1258    pub fn text(&self) -> String {
1259        self.text_chunks(DisplayRow(0)).collect()
1260    }
1261
1262    pub fn line(&self, display_row: DisplayRow) -> String {
1263        let mut result = String::new();
1264        for chunk in self.text_chunks(display_row) {
1265            if let Some(ix) = chunk.find('\n') {
1266                result.push_str(&chunk[0..ix]);
1267                break;
1268            } else {
1269                result.push_str(chunk);
1270            }
1271        }
1272        result
1273    }
1274
1275    pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
1276        self.buffer_snapshot().line_indent_for_row(buffer_row)
1277    }
1278
1279    pub fn line_len(&self, row: DisplayRow) -> u32 {
1280        self.block_snapshot.line_len(BlockRow(row.0))
1281    }
1282
1283    pub fn longest_row(&self) -> DisplayRow {
1284        DisplayRow(self.block_snapshot.longest_row().0)
1285    }
1286
1287    pub fn longest_row_in_range(&self, range: Range<DisplayRow>) -> DisplayRow {
1288        let block_range = BlockRow(range.start.0)..BlockRow(range.end.0);
1289        let longest_row = self.block_snapshot.longest_row_in_range(block_range);
1290        DisplayRow(longest_row.0)
1291    }
1292
1293    pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
1294        let max_row = self.buffer_snapshot().max_row();
1295        if buffer_row >= max_row {
1296            return false;
1297        }
1298
1299        let line_indent = self.line_indent_for_buffer_row(buffer_row);
1300        if line_indent.is_line_blank() {
1301            return false;
1302        }
1303
1304        (buffer_row.0 + 1..=max_row.0)
1305            .find_map(|next_row| {
1306                let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
1307                if next_line_indent.raw_len() > line_indent.raw_len() {
1308                    Some(true)
1309                } else if !next_line_indent.is_line_blank() {
1310                    Some(false)
1311                } else {
1312                    None
1313                }
1314            })
1315            .unwrap_or(false)
1316    }
1317
1318    pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
1319        let start =
1320            MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot().line_len(buffer_row));
1321        if let Some(crease) = self
1322            .crease_snapshot
1323            .query_row(buffer_row, self.buffer_snapshot())
1324        {
1325            match crease {
1326                Crease::Inline {
1327                    range,
1328                    placeholder,
1329                    render_toggle,
1330                    render_trailer,
1331                    metadata,
1332                } => Some(Crease::Inline {
1333                    range: range.to_point(self.buffer_snapshot()),
1334                    placeholder: placeholder.clone(),
1335                    render_toggle: render_toggle.clone(),
1336                    render_trailer: render_trailer.clone(),
1337                    metadata: metadata.clone(),
1338                }),
1339                Crease::Block {
1340                    range,
1341                    block_height,
1342                    block_style,
1343                    render_block,
1344                    block_priority,
1345                    render_toggle,
1346                } => Some(Crease::Block {
1347                    range: range.to_point(self.buffer_snapshot()),
1348                    block_height: *block_height,
1349                    block_style: *block_style,
1350                    render_block: render_block.clone(),
1351                    block_priority: *block_priority,
1352                    render_toggle: render_toggle.clone(),
1353                }),
1354            }
1355        } else if self.starts_indent(MultiBufferRow(start.row))
1356            && !self.is_line_folded(MultiBufferRow(start.row))
1357        {
1358            let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
1359            let max_point = self.buffer_snapshot().max_point();
1360            let mut end = None;
1361
1362            for row in (buffer_row.0 + 1)..=max_point.row {
1363                let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
1364                if !line_indent.is_line_blank()
1365                    && line_indent.raw_len() <= start_line_indent.raw_len()
1366                {
1367                    let prev_row = row - 1;
1368                    end = Some(Point::new(
1369                        prev_row,
1370                        self.buffer_snapshot().line_len(MultiBufferRow(prev_row)),
1371                    ));
1372                    break;
1373                }
1374            }
1375
1376            let mut row_before_line_breaks = end.unwrap_or(max_point);
1377            while row_before_line_breaks.row > start.row
1378                && self
1379                    .buffer_snapshot()
1380                    .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
1381            {
1382                row_before_line_breaks.row -= 1;
1383            }
1384
1385            row_before_line_breaks = Point::new(
1386                row_before_line_breaks.row,
1387                self.buffer_snapshot()
1388                    .line_len(MultiBufferRow(row_before_line_breaks.row)),
1389            );
1390
1391            Some(Crease::Inline {
1392                range: start..row_before_line_breaks,
1393                placeholder: self.fold_placeholder.clone(),
1394                render_toggle: None,
1395                render_trailer: None,
1396                metadata: None,
1397            })
1398        } else {
1399            None
1400        }
1401    }
1402
1403    #[cfg(any(test, feature = "test-support"))]
1404    pub fn text_highlight_ranges<Tag: ?Sized + 'static>(
1405        &self,
1406    ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
1407        let type_id = TypeId::of::<Tag>();
1408        self.text_highlights
1409            .get(&HighlightKey::Type(type_id))
1410            .cloned()
1411    }
1412
1413    #[cfg(any(test, feature = "test-support"))]
1414    pub fn all_text_highlight_ranges<Tag: ?Sized + 'static>(&self) -> Vec<(Hsla, Range<Point>)> {
1415        let needed_type_id = TypeId::of::<Tag>();
1416        self.text_highlights
1417            .iter()
1418            .filter(|(key, _)| match key {
1419                HighlightKey::Type(type_id) => type_id == &needed_type_id,
1420                HighlightKey::TypePlus(type_id, _) => type_id == &needed_type_id,
1421            })
1422            .map(|(_, value)| value.clone())
1423            .flat_map(|ranges| {
1424                ranges
1425                    .1
1426                    .iter()
1427                    .flat_map(|range| {
1428                        Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
1429                    })
1430                    .collect::<Vec<_>>()
1431            })
1432            .collect()
1433    }
1434
1435    #[allow(unused)]
1436    #[cfg(any(test, feature = "test-support"))]
1437    pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
1438        &self,
1439    ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
1440        let type_id = TypeId::of::<Tag>();
1441        self.inlay_highlights.get(&type_id)
1442    }
1443
1444    pub fn buffer_header_height(&self) -> u32 {
1445        self.block_snapshot.buffer_header_height
1446    }
1447
1448    pub fn excerpt_header_height(&self) -> u32 {
1449        self.block_snapshot.excerpt_header_height
1450    }
1451
1452    /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
1453    /// the start of the buffer row that is a given number of buffer rows away
1454    /// from the provided point.
1455    ///
1456    /// This moves by buffer rows instead of display rows, a distinction that is
1457    /// important when soft wrapping is enabled.
1458    pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
1459        let start = self.display_point_to_fold_point(point, Bias::Left);
1460        let target = start.row() as isize + times;
1461        let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
1462
1463        self.clip_point(
1464            self.fold_point_to_display_point(
1465                self.fold_snapshot()
1466                    .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
1467            ),
1468            Bias::Right,
1469        )
1470    }
1471}
1472
1473impl std::ops::Deref for DisplaySnapshot {
1474    type Target = BlockSnapshot;
1475
1476    fn deref(&self) -> &Self::Target {
1477        &self.block_snapshot
1478    }
1479}
1480
1481/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
1482#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
1483pub struct DisplayPoint(BlockPoint);
1484
1485impl Debug for DisplayPoint {
1486    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1487        f.write_fmt(format_args!(
1488            "DisplayPoint({}, {})",
1489            self.row().0,
1490            self.column()
1491        ))
1492    }
1493}
1494
1495impl Add for DisplayPoint {
1496    type Output = Self;
1497
1498    fn add(self, other: Self) -> Self::Output {
1499        DisplayPoint(BlockPoint(self.0.0 + other.0.0))
1500    }
1501}
1502
1503impl Sub for DisplayPoint {
1504    type Output = Self;
1505
1506    fn sub(self, other: Self) -> Self::Output {
1507        DisplayPoint(BlockPoint(self.0.0 - other.0.0))
1508    }
1509}
1510
1511#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
1512#[serde(transparent)]
1513pub struct DisplayRow(pub u32);
1514
1515impl Add<DisplayRow> for DisplayRow {
1516    type Output = Self;
1517
1518    fn add(self, other: Self) -> Self::Output {
1519        DisplayRow(self.0 + other.0)
1520    }
1521}
1522
1523impl Add<u32> for DisplayRow {
1524    type Output = Self;
1525
1526    fn add(self, other: u32) -> Self::Output {
1527        DisplayRow(self.0 + other)
1528    }
1529}
1530
1531impl Sub<DisplayRow> for DisplayRow {
1532    type Output = Self;
1533
1534    fn sub(self, other: Self) -> Self::Output {
1535        DisplayRow(self.0 - other.0)
1536    }
1537}
1538
1539impl Sub<u32> for DisplayRow {
1540    type Output = Self;
1541
1542    fn sub(self, other: u32) -> Self::Output {
1543        DisplayRow(self.0 - other)
1544    }
1545}
1546
1547impl DisplayPoint {
1548    pub fn new(row: DisplayRow, column: u32) -> Self {
1549        Self(BlockPoint(Point::new(row.0, column)))
1550    }
1551
1552    pub fn zero() -> Self {
1553        Self::new(DisplayRow(0), 0)
1554    }
1555
1556    pub fn is_zero(&self) -> bool {
1557        self.0.is_zero()
1558    }
1559
1560    pub fn row(self) -> DisplayRow {
1561        DisplayRow(self.0.row)
1562    }
1563
1564    pub fn column(self) -> u32 {
1565        self.0.column
1566    }
1567
1568    pub fn row_mut(&mut self) -> &mut u32 {
1569        &mut self.0.row
1570    }
1571
1572    pub fn column_mut(&mut self) -> &mut u32 {
1573        &mut self.0.column
1574    }
1575
1576    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
1577        map.display_point_to_point(self, Bias::Left)
1578    }
1579
1580    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
1581        let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
1582        let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
1583        let fold_point = map.tab_snapshot().to_fold_point(tab_point, bias).0;
1584        let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
1585        map.inlay_snapshot()
1586            .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
1587    }
1588}
1589
1590impl ToDisplayPoint for usize {
1591    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1592        map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
1593    }
1594}
1595
1596impl ToDisplayPoint for OffsetUtf16 {
1597    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1598        self.to_offset(map.buffer_snapshot()).to_display_point(map)
1599    }
1600}
1601
1602impl ToDisplayPoint for Point {
1603    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1604        map.point_to_display_point(*self, Bias::Left)
1605    }
1606}
1607
1608impl ToDisplayPoint for Anchor {
1609    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1610        self.to_point(map.buffer_snapshot()).to_display_point(map)
1611    }
1612}
1613
1614#[cfg(test)]
1615pub mod tests {
1616    use super::*;
1617    use crate::{
1618        movement,
1619        test::{marked_display_snapshot, test_font},
1620    };
1621    use Bias::*;
1622    use block_map::BlockPlacement;
1623    use gpui::{
1624        App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
1625    };
1626    use language::{
1627        Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
1628        LanguageMatcher,
1629    };
1630    use lsp::LanguageServerId;
1631
1632    use rand::{Rng, prelude::*};
1633    use settings::{SettingsContent, SettingsStore};
1634    use smol::stream::StreamExt;
1635    use std::{env, sync::Arc};
1636    use text::PointUtf16;
1637    use theme::{LoadThemes, SyntaxTheme};
1638    use unindent::Unindent as _;
1639    use util::test::{marked_text_ranges, sample_text};
1640
1641    #[gpui::test(iterations = 100)]
1642    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1643        cx.background_executor.set_block_on_ticks(0..=50);
1644        let operations = env::var("OPERATIONS")
1645            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1646            .unwrap_or(10);
1647
1648        let mut tab_size = rng.random_range(1..=4);
1649        let buffer_start_excerpt_header_height = rng.random_range(1..=5);
1650        let excerpt_header_height = rng.random_range(1..=5);
1651        let font_size = px(14.0);
1652        let max_wrap_width = 300.0;
1653        let mut wrap_width = if rng.random_bool(0.1) {
1654            None
1655        } else {
1656            Some(px(rng.random_range(0.0..=max_wrap_width)))
1657        };
1658
1659        log::info!("tab size: {}", tab_size);
1660        log::info!("wrap width: {:?}", wrap_width);
1661
1662        cx.update(|cx| {
1663            init_test(cx, |s| {
1664                s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
1665            });
1666        });
1667
1668        let buffer = cx.update(|cx| {
1669            if rng.random() {
1670                let len = rng.random_range(0..10);
1671                let text = util::RandomCharIter::new(&mut rng)
1672                    .take(len)
1673                    .collect::<String>();
1674                MultiBuffer::build_simple(&text, cx)
1675            } else {
1676                MultiBuffer::build_random(&mut rng, cx)
1677            }
1678        });
1679
1680        let font = test_font();
1681        let map = cx.new(|cx| {
1682            DisplayMap::new(
1683                buffer.clone(),
1684                font,
1685                font_size,
1686                wrap_width,
1687                buffer_start_excerpt_header_height,
1688                excerpt_header_height,
1689                FoldPlaceholder::test(),
1690                DiagnosticSeverity::Warning,
1691                cx,
1692            )
1693        });
1694        let mut notifications = observe(&map, cx);
1695        let mut fold_count = 0;
1696        let mut blocks = Vec::new();
1697
1698        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1699        log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
1700        log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
1701        log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
1702        log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
1703        log::info!("block text: {:?}", snapshot.block_snapshot.text());
1704        log::info!("display text: {:?}", snapshot.text());
1705
1706        for _i in 0..operations {
1707            match rng.random_range(0..100) {
1708                0..=19 => {
1709                    wrap_width = if rng.random_bool(0.2) {
1710                        None
1711                    } else {
1712                        Some(px(rng.random_range(0.0..=max_wrap_width)))
1713                    };
1714                    log::info!("setting wrap width to {:?}", wrap_width);
1715                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1716                }
1717                20..=29 => {
1718                    let mut tab_sizes = vec![1, 2, 3, 4];
1719                    tab_sizes.remove((tab_size - 1) as usize);
1720                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
1721                    log::info!("setting tab size to {:?}", tab_size);
1722                    cx.update(|cx| {
1723                        cx.update_global::<SettingsStore, _>(|store, cx| {
1724                            store.update_user_settings(cx, |s| {
1725                                s.project.all_languages.defaults.tab_size =
1726                                    NonZeroU32::new(tab_size);
1727                            });
1728                        });
1729                    });
1730                }
1731                30..=44 => {
1732                    map.update(cx, |map, cx| {
1733                        if rng.random() || blocks.is_empty() {
1734                            let snapshot = map.snapshot(cx);
1735                            let buffer = snapshot.buffer_snapshot();
1736                            let block_properties = (0..rng.random_range(1..=1))
1737                                .map(|_| {
1738                                    let position = buffer.anchor_after(buffer.clip_offset(
1739                                        rng.random_range(0..=buffer.len()),
1740                                        Bias::Left,
1741                                    ));
1742
1743                                    let placement = if rng.random() {
1744                                        BlockPlacement::Above(position)
1745                                    } else {
1746                                        BlockPlacement::Below(position)
1747                                    };
1748                                    let height = rng.random_range(1..5);
1749                                    log::info!(
1750                                        "inserting block {:?} with height {}",
1751                                        placement.as_ref().map(|p| p.to_point(&buffer)),
1752                                        height
1753                                    );
1754                                    let priority = rng.random_range(1..100);
1755                                    BlockProperties {
1756                                        placement,
1757                                        style: BlockStyle::Fixed,
1758                                        height: Some(height),
1759                                        render: Arc::new(|_| div().into_any()),
1760                                        priority,
1761                                    }
1762                                })
1763                                .collect::<Vec<_>>();
1764                            blocks.extend(map.insert_blocks(block_properties, cx));
1765                        } else {
1766                            blocks.shuffle(&mut rng);
1767                            let remove_count = rng.random_range(1..=4.min(blocks.len()));
1768                            let block_ids_to_remove = (0..remove_count)
1769                                .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
1770                                .collect();
1771                            log::info!("removing block ids {:?}", block_ids_to_remove);
1772                            map.remove_blocks(block_ids_to_remove, cx);
1773                        }
1774                    });
1775                }
1776                45..=79 => {
1777                    let mut ranges = Vec::new();
1778                    for _ in 0..rng.random_range(1..=3) {
1779                        buffer.read_with(cx, |buffer, cx| {
1780                            let buffer = buffer.read(cx);
1781                            let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
1782                            let start = buffer.clip_offset(rng.random_range(0..=end), Left);
1783                            ranges.push(start..end);
1784                        });
1785                    }
1786
1787                    if rng.random() && fold_count > 0 {
1788                        log::info!("unfolding ranges: {:?}", ranges);
1789                        map.update(cx, |map, cx| {
1790                            map.unfold_intersecting(ranges, true, cx);
1791                        });
1792                    } else {
1793                        log::info!("folding ranges: {:?}", ranges);
1794                        map.update(cx, |map, cx| {
1795                            map.fold(
1796                                ranges
1797                                    .into_iter()
1798                                    .map(|range| Crease::simple(range, FoldPlaceholder::test()))
1799                                    .collect(),
1800                                cx,
1801                            );
1802                        });
1803                    }
1804                }
1805                _ => {
1806                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1807                }
1808            }
1809
1810            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1811                notifications.next().await.unwrap();
1812            }
1813
1814            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1815            fold_count = snapshot.fold_count();
1816            log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
1817            log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
1818            log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
1819            log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
1820            log::info!("block text: {:?}", snapshot.block_snapshot.text());
1821            log::info!("display text: {:?}", snapshot.text());
1822
1823            // Line boundaries
1824            let buffer = snapshot.buffer_snapshot();
1825            for _ in 0..5 {
1826                let row = rng.random_range(0..=buffer.max_point().row);
1827                let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
1828                let point = buffer.clip_point(Point::new(row, column), Left);
1829
1830                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1831                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1832
1833                assert!(prev_buffer_bound <= point);
1834                assert!(next_buffer_bound >= point);
1835                assert_eq!(prev_buffer_bound.column, 0);
1836                assert_eq!(prev_display_bound.column(), 0);
1837                if next_buffer_bound < buffer.max_point() {
1838                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1839                }
1840
1841                assert_eq!(
1842                    prev_display_bound,
1843                    prev_buffer_bound.to_display_point(&snapshot),
1844                    "row boundary before {:?}. reported buffer row boundary: {:?}",
1845                    point,
1846                    prev_buffer_bound
1847                );
1848                assert_eq!(
1849                    next_display_bound,
1850                    next_buffer_bound.to_display_point(&snapshot),
1851                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
1852                    point,
1853                    next_buffer_bound
1854                );
1855                assert_eq!(
1856                    prev_buffer_bound,
1857                    prev_display_bound.to_point(&snapshot),
1858                    "row boundary before {:?}. reported display row boundary: {:?}",
1859                    point,
1860                    prev_display_bound
1861                );
1862                assert_eq!(
1863                    next_buffer_bound,
1864                    next_display_bound.to_point(&snapshot),
1865                    "row boundary after {:?}. reported display row boundary: {:?}",
1866                    point,
1867                    next_display_bound
1868                );
1869            }
1870
1871            // Movement
1872            let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1873            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1874            for _ in 0..5 {
1875                let row = rng.random_range(0..=snapshot.max_point().row().0);
1876                let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
1877                let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1878
1879                log::info!("Moving from point {:?}", point);
1880
1881                let moved_right = movement::right(&snapshot, point);
1882                log::info!("Right {:?}", moved_right);
1883                if point < max_point {
1884                    assert!(moved_right > point);
1885                    if point.column() == snapshot.line_len(point.row())
1886                        || snapshot.soft_wrap_indent(point.row()).is_some()
1887                            && point.column() == snapshot.line_len(point.row()) - 1
1888                    {
1889                        assert!(moved_right.row() > point.row());
1890                    }
1891                } else {
1892                    assert_eq!(moved_right, point);
1893                }
1894
1895                let moved_left = movement::left(&snapshot, point);
1896                log::info!("Left {:?}", moved_left);
1897                if point > min_point {
1898                    assert!(moved_left < point);
1899                    if point.column() == 0 {
1900                        assert!(moved_left.row() < point.row());
1901                    }
1902                } else {
1903                    assert_eq!(moved_left, point);
1904                }
1905            }
1906        }
1907    }
1908
1909    #[gpui::test(retries = 5)]
1910    async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1911        cx.background_executor
1912            .set_block_on_ticks(usize::MAX..=usize::MAX);
1913        cx.update(|cx| {
1914            init_test(cx, |_| {});
1915        });
1916
1917        let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
1918        let editor = cx.editor.clone();
1919        let window = cx.window;
1920
1921        _ = cx.update_window(window, |_, window, cx| {
1922            let text_layout_details =
1923                editor.update(cx, |editor, _cx| editor.text_layout_details(window));
1924
1925            let font_size = px(12.0);
1926            let wrap_width = Some(px(96.));
1927
1928            let text = "one two three four five\nsix seven eight";
1929            let buffer = MultiBuffer::build_simple(text, cx);
1930            let map = cx.new(|cx| {
1931                DisplayMap::new(
1932                    buffer.clone(),
1933                    font("Helvetica"),
1934                    font_size,
1935                    wrap_width,
1936                    1,
1937                    1,
1938                    FoldPlaceholder::test(),
1939                    DiagnosticSeverity::Warning,
1940                    cx,
1941                )
1942            });
1943
1944            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1945            assert_eq!(
1946                snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1947                "one two \nthree four \nfive\nsix seven \neight"
1948            );
1949            assert_eq!(
1950                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1951                DisplayPoint::new(DisplayRow(0), 7)
1952            );
1953            assert_eq!(
1954                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1955                DisplayPoint::new(DisplayRow(1), 0)
1956            );
1957            assert_eq!(
1958                movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1959                DisplayPoint::new(DisplayRow(1), 0)
1960            );
1961            assert_eq!(
1962                movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1963                DisplayPoint::new(DisplayRow(0), 7)
1964            );
1965
1966            let x = snapshot
1967                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1968            assert_eq!(
1969                movement::up(
1970                    &snapshot,
1971                    DisplayPoint::new(DisplayRow(1), 10),
1972                    language::SelectionGoal::None,
1973                    false,
1974                    &text_layout_details,
1975                ),
1976                (
1977                    DisplayPoint::new(DisplayRow(0), 7),
1978                    language::SelectionGoal::HorizontalPosition(f64::from(x))
1979                )
1980            );
1981            assert_eq!(
1982                movement::down(
1983                    &snapshot,
1984                    DisplayPoint::new(DisplayRow(0), 7),
1985                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
1986                    false,
1987                    &text_layout_details
1988                ),
1989                (
1990                    DisplayPoint::new(DisplayRow(1), 10),
1991                    language::SelectionGoal::HorizontalPosition(f64::from(x))
1992                )
1993            );
1994            assert_eq!(
1995                movement::down(
1996                    &snapshot,
1997                    DisplayPoint::new(DisplayRow(1), 10),
1998                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
1999                    false,
2000                    &text_layout_details
2001                ),
2002                (
2003                    DisplayPoint::new(DisplayRow(2), 4),
2004                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2005                )
2006            );
2007
2008            let ix = snapshot.buffer_snapshot().text().find("seven").unwrap();
2009            buffer.update(cx, |buffer, cx| {
2010                buffer.edit([(ix..ix, "and ")], None, cx);
2011            });
2012
2013            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2014            assert_eq!(
2015                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2016                "three four \nfive\nsix and \nseven eight"
2017            );
2018
2019            // Re-wrap on font size changes
2020            map.update(cx, |map, cx| {
2021                map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
2022            });
2023
2024            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2025            assert_eq!(
2026                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2027                "three \nfour five\nsix and \nseven \neight"
2028            )
2029        });
2030    }
2031
2032    #[gpui::test]
2033    fn test_text_chunks(cx: &mut gpui::App) {
2034        init_test(cx, |_| {});
2035
2036        let text = sample_text(6, 6, 'a');
2037        let buffer = MultiBuffer::build_simple(&text, cx);
2038
2039        let font_size = px(14.0);
2040        let map = cx.new(|cx| {
2041            DisplayMap::new(
2042                buffer.clone(),
2043                font("Helvetica"),
2044                font_size,
2045                None,
2046                1,
2047                1,
2048                FoldPlaceholder::test(),
2049                DiagnosticSeverity::Warning,
2050                cx,
2051            )
2052        });
2053
2054        buffer.update(cx, |buffer, cx| {
2055            buffer.edit(
2056                vec![
2057                    (
2058                        MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
2059                        "\t",
2060                    ),
2061                    (
2062                        MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
2063                        "\t",
2064                    ),
2065                    (
2066                        MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
2067                        "\t",
2068                    ),
2069                ],
2070                None,
2071                cx,
2072            )
2073        });
2074
2075        assert_eq!(
2076            map.update(cx, |map, cx| map.snapshot(cx))
2077                .text_chunks(DisplayRow(1))
2078                .collect::<String>()
2079                .lines()
2080                .next(),
2081            Some("    b   bbbbb")
2082        );
2083        assert_eq!(
2084            map.update(cx, |map, cx| map.snapshot(cx))
2085                .text_chunks(DisplayRow(2))
2086                .collect::<String>()
2087                .lines()
2088                .next(),
2089            Some("c   ccccc")
2090        );
2091    }
2092
2093    #[gpui::test]
2094    fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
2095        cx.update(|cx| init_test(cx, |_| {}));
2096
2097        let buffer = cx.new(|cx| Buffer::local("a", cx));
2098        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2099        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2100
2101        let font_size = px(14.0);
2102        let map = cx.new(|cx| {
2103            DisplayMap::new(
2104                buffer.clone(),
2105                font("Helvetica"),
2106                font_size,
2107                None,
2108                1,
2109                1,
2110                FoldPlaceholder::test(),
2111                DiagnosticSeverity::Warning,
2112                cx,
2113            )
2114        });
2115
2116        map.update(cx, |map, cx| {
2117            map.insert_blocks(
2118                [BlockProperties {
2119                    placement: BlockPlacement::Above(
2120                        buffer_snapshot.anchor_before(Point::new(0, 0)),
2121                    ),
2122                    height: Some(2),
2123                    style: BlockStyle::Sticky,
2124                    render: Arc::new(|_| div().into_any()),
2125                    priority: 0,
2126                }],
2127                cx,
2128            );
2129        });
2130        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
2131
2132        map.update(cx, |map, cx| {
2133            map.splice_inlays(
2134                &[],
2135                vec![Inlay::edit_prediction(
2136                    0,
2137                    buffer_snapshot.anchor_after(0),
2138                    "\n",
2139                )],
2140                cx,
2141            );
2142        });
2143        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
2144
2145        // Regression test: updating the display map does not crash when a
2146        // block is immediately followed by a multi-line inlay.
2147        buffer.update(cx, |buffer, cx| {
2148            buffer.edit([(1..1, "b")], None, cx);
2149        });
2150        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
2151    }
2152
2153    #[gpui::test]
2154    async fn test_chunks(cx: &mut gpui::TestAppContext) {
2155        let text = r#"
2156            fn outer() {}
2157
2158            mod module {
2159                fn inner() {}
2160            }"#
2161        .unindent();
2162
2163        let theme =
2164            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
2165        let language = Arc::new(
2166            Language::new(
2167                LanguageConfig {
2168                    name: "Test".into(),
2169                    matcher: LanguageMatcher {
2170                        path_suffixes: vec![".test".to_string()],
2171                        ..Default::default()
2172                    },
2173                    ..Default::default()
2174                },
2175                Some(tree_sitter_rust::LANGUAGE.into()),
2176            )
2177            .with_highlights_query(
2178                r#"
2179                (mod_item name: (identifier) body: _ @mod.body)
2180                (function_item name: (identifier) @fn.name)
2181                "#,
2182            )
2183            .unwrap(),
2184        );
2185        language.set_theme(&theme);
2186
2187        cx.update(|cx| {
2188            init_test(cx, |s| {
2189                s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
2190            })
2191        });
2192
2193        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2194        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2195        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2196
2197        let font_size = px(14.0);
2198
2199        let map = cx.new(|cx| {
2200            DisplayMap::new(
2201                buffer,
2202                font("Helvetica"),
2203                font_size,
2204                None,
2205                1,
2206                1,
2207                FoldPlaceholder::test(),
2208                DiagnosticSeverity::Warning,
2209                cx,
2210            )
2211        });
2212        assert_eq!(
2213            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
2214            vec![
2215                ("fn ".to_string(), None),
2216                ("outer".to_string(), Some(Hsla::blue())),
2217                ("() {}\n\nmod module ".to_string(), None),
2218                ("{\n    fn ".to_string(), Some(Hsla::red())),
2219                ("inner".to_string(), Some(Hsla::blue())),
2220                ("() {}\n}".to_string(), Some(Hsla::red())),
2221            ]
2222        );
2223        assert_eq!(
2224            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
2225            vec![
2226                ("    fn ".to_string(), Some(Hsla::red())),
2227                ("inner".to_string(), Some(Hsla::blue())),
2228                ("() {}\n}".to_string(), Some(Hsla::red())),
2229            ]
2230        );
2231
2232        map.update(cx, |map, cx| {
2233            map.fold(
2234                vec![Crease::simple(
2235                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2236                    FoldPlaceholder::test(),
2237                )],
2238                cx,
2239            )
2240        });
2241        assert_eq!(
2242            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
2243            vec![
2244                ("fn ".to_string(), None),
2245                ("out".to_string(), Some(Hsla::blue())),
2246                ("".to_string(), None),
2247                ("  fn ".to_string(), Some(Hsla::red())),
2248                ("inner".to_string(), Some(Hsla::blue())),
2249                ("() {}\n}".to_string(), Some(Hsla::red())),
2250            ]
2251        );
2252    }
2253
2254    #[gpui::test]
2255    async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
2256        cx.background_executor
2257            .set_block_on_ticks(usize::MAX..=usize::MAX);
2258
2259        let text = r#"
2260            const A: &str = "
2261                one
2262                two
2263                three
2264            ";
2265            const B: &str = "four";
2266        "#
2267        .unindent();
2268
2269        let theme = SyntaxTheme::new_test(vec![
2270            ("string", Hsla::red()),
2271            ("punctuation", Hsla::blue()),
2272            ("keyword", Hsla::green()),
2273        ]);
2274        let language = Arc::new(
2275            Language::new(
2276                LanguageConfig {
2277                    name: "Rust".into(),
2278                    ..Default::default()
2279                },
2280                Some(tree_sitter_rust::LANGUAGE.into()),
2281            )
2282            .with_highlights_query(
2283                r#"
2284                (string_literal) @string
2285                "const" @keyword
2286                [":" ";"] @punctuation
2287                "#,
2288            )
2289            .unwrap(),
2290        );
2291        language.set_theme(&theme);
2292
2293        cx.update(|cx| init_test(cx, |_| {}));
2294
2295        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2296        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2297        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2298        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2299
2300        let map = cx.new(|cx| {
2301            DisplayMap::new(
2302                buffer,
2303                font("Courier"),
2304                px(16.0),
2305                None,
2306                1,
2307                1,
2308                FoldPlaceholder::test(),
2309                DiagnosticSeverity::Warning,
2310                cx,
2311            )
2312        });
2313
2314        // Insert two blocks in the middle of a multi-line string literal.
2315        // The second block has zero height.
2316        map.update(cx, |map, cx| {
2317            map.insert_blocks(
2318                [
2319                    BlockProperties {
2320                        placement: BlockPlacement::Below(
2321                            buffer_snapshot.anchor_before(Point::new(1, 0)),
2322                        ),
2323                        height: Some(1),
2324                        style: BlockStyle::Sticky,
2325                        render: Arc::new(|_| div().into_any()),
2326                        priority: 0,
2327                    },
2328                    BlockProperties {
2329                        placement: BlockPlacement::Below(
2330                            buffer_snapshot.anchor_before(Point::new(2, 0)),
2331                        ),
2332                        height: None,
2333                        style: BlockStyle::Sticky,
2334                        render: Arc::new(|_| div().into_any()),
2335                        priority: 0,
2336                    },
2337                ],
2338                cx,
2339            )
2340        });
2341
2342        pretty_assertions::assert_eq!(
2343            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
2344            [
2345                ("const".into(), Some(Hsla::green())),
2346                (" A".into(), None),
2347                (":".into(), Some(Hsla::blue())),
2348                (" &str = ".into(), None),
2349                ("\"\n    one\n".into(), Some(Hsla::red())),
2350                ("\n".into(), None),
2351                ("    two\n    three\n\"".into(), Some(Hsla::red())),
2352                (";".into(), Some(Hsla::blue())),
2353                ("\n".into(), None),
2354                ("const".into(), Some(Hsla::green())),
2355                (" B".into(), None),
2356                (":".into(), Some(Hsla::blue())),
2357                (" &str = ".into(), None),
2358                ("\"four\"".into(), Some(Hsla::red())),
2359                (";".into(), Some(Hsla::blue())),
2360                ("\n".into(), None),
2361            ]
2362        );
2363    }
2364
2365    #[gpui::test]
2366    async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
2367        cx.background_executor
2368            .set_block_on_ticks(usize::MAX..=usize::MAX);
2369
2370        let text = r#"
2371            struct A {
2372                b: usize;
2373            }
2374            const c: usize = 1;
2375        "#
2376        .unindent();
2377
2378        cx.update(|cx| init_test(cx, |_| {}));
2379
2380        let buffer = cx.new(|cx| Buffer::local(text, cx));
2381
2382        buffer.update(cx, |buffer, cx| {
2383            buffer.update_diagnostics(
2384                LanguageServerId(0),
2385                DiagnosticSet::new(
2386                    [DiagnosticEntry {
2387                        range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
2388                        diagnostic: Diagnostic {
2389                            severity: lsp::DiagnosticSeverity::ERROR,
2390                            group_id: 1,
2391                            message: "hi".into(),
2392                            ..Default::default()
2393                        },
2394                    }],
2395                    buffer,
2396                ),
2397                cx,
2398            )
2399        });
2400
2401        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2402        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2403
2404        let map = cx.new(|cx| {
2405            DisplayMap::new(
2406                buffer,
2407                font("Courier"),
2408                px(16.0),
2409                None,
2410                1,
2411                1,
2412                FoldPlaceholder::test(),
2413                DiagnosticSeverity::Warning,
2414                cx,
2415            )
2416        });
2417
2418        let black = gpui::black().to_rgb();
2419        let red = gpui::red().to_rgb();
2420
2421        // Insert a block in the middle of a multi-line diagnostic.
2422        map.update(cx, |map, cx| {
2423            map.highlight_text(
2424                HighlightKey::Type(TypeId::of::<usize>()),
2425                vec![
2426                    buffer_snapshot.anchor_before(Point::new(3, 9))
2427                        ..buffer_snapshot.anchor_after(Point::new(3, 14)),
2428                    buffer_snapshot.anchor_before(Point::new(3, 17))
2429                        ..buffer_snapshot.anchor_after(Point::new(3, 18)),
2430                ],
2431                red.into(),
2432                false,
2433                cx,
2434            );
2435            map.insert_blocks(
2436                [BlockProperties {
2437                    placement: BlockPlacement::Below(
2438                        buffer_snapshot.anchor_before(Point::new(1, 0)),
2439                    ),
2440                    height: Some(1),
2441                    style: BlockStyle::Sticky,
2442                    render: Arc::new(|_| div().into_any()),
2443                    priority: 0,
2444                }],
2445                cx,
2446            )
2447        });
2448
2449        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2450        let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
2451        for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
2452            let color = chunk
2453                .highlight_style
2454                .and_then(|style| style.color)
2455                .map_or(black, |color| color.to_rgb());
2456            if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
2457                && *last_severity == chunk.diagnostic_severity
2458                && *last_color == color
2459            {
2460                last_chunk.push_str(chunk.text);
2461                continue;
2462            }
2463
2464            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
2465        }
2466
2467        assert_eq!(
2468            chunks,
2469            [
2470                (
2471                    "struct A {\n    b: usize;\n".into(),
2472                    Some(lsp::DiagnosticSeverity::ERROR),
2473                    black
2474                ),
2475                ("\n".into(), None, black),
2476                ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
2477                ("\nconst c: ".into(), None, black),
2478                ("usize".into(), None, red),
2479                (" = ".into(), None, black),
2480                ("1".into(), None, red),
2481                (";\n".into(), None, black),
2482            ]
2483        );
2484    }
2485
2486    #[gpui::test]
2487    async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
2488        cx.background_executor
2489            .set_block_on_ticks(usize::MAX..=usize::MAX);
2490
2491        cx.update(|cx| init_test(cx, |_| {}));
2492
2493        let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
2494        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2495        let map = cx.new(|cx| {
2496            DisplayMap::new(
2497                buffer.clone(),
2498                font("Courier"),
2499                px(16.0),
2500                None,
2501                1,
2502                1,
2503                FoldPlaceholder::test(),
2504                DiagnosticSeverity::Warning,
2505                cx,
2506            )
2507        });
2508
2509        let snapshot = map.update(cx, |map, cx| {
2510            map.insert_blocks(
2511                [BlockProperties {
2512                    placement: BlockPlacement::Replace(
2513                        buffer_snapshot.anchor_before(Point::new(1, 2))
2514                            ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
2515                    ),
2516                    height: Some(4),
2517                    style: BlockStyle::Fixed,
2518                    render: Arc::new(|_| div().into_any()),
2519                    priority: 0,
2520                }],
2521                cx,
2522            );
2523            map.snapshot(cx)
2524        });
2525
2526        assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
2527
2528        let point_to_display_points = [
2529            (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
2530            (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
2531            (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
2532        ];
2533        for (buffer_point, display_point) in point_to_display_points {
2534            assert_eq!(
2535                snapshot.point_to_display_point(buffer_point, Bias::Left),
2536                display_point,
2537                "point_to_display_point({:?}, Bias::Left)",
2538                buffer_point
2539            );
2540            assert_eq!(
2541                snapshot.point_to_display_point(buffer_point, Bias::Right),
2542                display_point,
2543                "point_to_display_point({:?}, Bias::Right)",
2544                buffer_point
2545            );
2546        }
2547
2548        let display_points_to_points = [
2549            (
2550                DisplayPoint::new(DisplayRow(1), 0),
2551                Point::new(1, 0),
2552                Point::new(2, 5),
2553            ),
2554            (
2555                DisplayPoint::new(DisplayRow(2), 0),
2556                Point::new(1, 0),
2557                Point::new(2, 5),
2558            ),
2559            (
2560                DisplayPoint::new(DisplayRow(3), 0),
2561                Point::new(1, 0),
2562                Point::new(2, 5),
2563            ),
2564            (
2565                DisplayPoint::new(DisplayRow(4), 0),
2566                Point::new(1, 0),
2567                Point::new(2, 5),
2568            ),
2569            (
2570                DisplayPoint::new(DisplayRow(5), 0),
2571                Point::new(3, 0),
2572                Point::new(3, 0),
2573            ),
2574        ];
2575        for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
2576            assert_eq!(
2577                snapshot.display_point_to_point(display_point, Bias::Left),
2578                left_buffer_point,
2579                "display_point_to_point({:?}, Bias::Left)",
2580                display_point
2581            );
2582            assert_eq!(
2583                snapshot.display_point_to_point(display_point, Bias::Right),
2584                right_buffer_point,
2585                "display_point_to_point({:?}, Bias::Right)",
2586                display_point
2587            );
2588        }
2589    }
2590
2591    #[gpui::test]
2592    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
2593        cx.background_executor
2594            .set_block_on_ticks(usize::MAX..=usize::MAX);
2595
2596        let text = r#"
2597            fn outer() {}
2598
2599            mod module {
2600                fn inner() {}
2601            }"#
2602        .unindent();
2603
2604        let theme =
2605            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
2606        let language = Arc::new(
2607            Language::new(
2608                LanguageConfig {
2609                    name: "Test".into(),
2610                    matcher: LanguageMatcher {
2611                        path_suffixes: vec![".test".to_string()],
2612                        ..Default::default()
2613                    },
2614                    ..Default::default()
2615                },
2616                Some(tree_sitter_rust::LANGUAGE.into()),
2617            )
2618            .with_highlights_query(
2619                r#"
2620                (mod_item name: (identifier) body: _ @mod.body)
2621                (function_item name: (identifier) @fn.name)
2622                "#,
2623            )
2624            .unwrap(),
2625        );
2626        language.set_theme(&theme);
2627
2628        cx.update(|cx| init_test(cx, |_| {}));
2629
2630        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2631        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2632        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2633
2634        let font_size = px(16.0);
2635
2636        let map = cx.new(|cx| {
2637            DisplayMap::new(
2638                buffer,
2639                font("Courier"),
2640                font_size,
2641                Some(px(40.0)),
2642                1,
2643                1,
2644                FoldPlaceholder::test(),
2645                DiagnosticSeverity::Warning,
2646                cx,
2647            )
2648        });
2649        assert_eq!(
2650            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
2651            [
2652                ("fn \n".to_string(), None),
2653                ("oute".to_string(), Some(Hsla::blue())),
2654                ("\n".to_string(), None),
2655                ("r".to_string(), Some(Hsla::blue())),
2656                ("() \n{}\n\n".to_string(), None),
2657            ]
2658        );
2659        assert_eq!(
2660            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
2661            [("{}\n\n".to_string(), None)]
2662        );
2663
2664        map.update(cx, |map, cx| {
2665            map.fold(
2666                vec![Crease::simple(
2667                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2668                    FoldPlaceholder::test(),
2669                )],
2670                cx,
2671            )
2672        });
2673        assert_eq!(
2674            cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
2675            [
2676                ("out".to_string(), Some(Hsla::blue())),
2677                ("\n".to_string(), None),
2678                ("  ".to_string(), Some(Hsla::red())),
2679                ("\n".to_string(), None),
2680                ("fn ".to_string(), Some(Hsla::red())),
2681                ("i".to_string(), Some(Hsla::blue())),
2682                ("\n".to_string(), None)
2683            ]
2684        );
2685    }
2686
2687    #[gpui::test]
2688    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
2689        cx.update(|cx| init_test(cx, |_| {}));
2690
2691        let theme =
2692            SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
2693        let language = Arc::new(
2694            Language::new(
2695                LanguageConfig {
2696                    name: "Test".into(),
2697                    matcher: LanguageMatcher {
2698                        path_suffixes: vec![".test".to_string()],
2699                        ..Default::default()
2700                    },
2701                    ..Default::default()
2702                },
2703                Some(tree_sitter_rust::LANGUAGE.into()),
2704            )
2705            .with_highlights_query(
2706                r#"
2707                ":" @operator
2708                (string_literal) @string
2709                "#,
2710            )
2711            .unwrap(),
2712        );
2713        language.set_theme(&theme);
2714
2715        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
2716
2717        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2718        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2719
2720        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2721        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2722
2723        let font_size = px(16.0);
2724        let map = cx.new(|cx| {
2725            DisplayMap::new(
2726                buffer,
2727                font("Courier"),
2728                font_size,
2729                None,
2730                1,
2731                1,
2732                FoldPlaceholder::test(),
2733                DiagnosticSeverity::Warning,
2734                cx,
2735            )
2736        });
2737
2738        enum MyType {}
2739
2740        let style = HighlightStyle {
2741            color: Some(Hsla::blue()),
2742            ..Default::default()
2743        };
2744
2745        map.update(cx, |map, cx| {
2746            map.highlight_text(
2747                HighlightKey::Type(TypeId::of::<MyType>()),
2748                highlighted_ranges
2749                    .into_iter()
2750                    .map(|range| {
2751                        buffer_snapshot.anchor_before(range.start)
2752                            ..buffer_snapshot.anchor_before(range.end)
2753                    })
2754                    .collect(),
2755                style,
2756                false,
2757                cx,
2758            );
2759        });
2760
2761        assert_eq!(
2762            cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
2763            [
2764                ("const ".to_string(), None, None),
2765                ("a".to_string(), None, Some(Hsla::blue())),
2766                (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
2767                (" B = ".to_string(), None, None),
2768                ("\"c ".to_string(), Some(Hsla::green()), None),
2769                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
2770                ("\"".to_string(), Some(Hsla::green()), None),
2771            ]
2772        );
2773    }
2774
2775    #[gpui::test]
2776    fn test_clip_point(cx: &mut gpui::App) {
2777        init_test(cx, |_| {});
2778
2779        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
2780            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
2781
2782            match bias {
2783                Bias::Left => {
2784                    if shift_right {
2785                        *markers[1].column_mut() += 1;
2786                    }
2787
2788                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
2789                }
2790                Bias::Right => {
2791                    if shift_right {
2792                        *markers[0].column_mut() += 1;
2793                    }
2794
2795                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
2796                }
2797            };
2798        }
2799
2800        use Bias::{Left, Right};
2801        assert("ˇˇα", false, Left, cx);
2802        assert("ˇˇα", true, Left, cx);
2803        assert("ˇˇα", false, Right, cx);
2804        assert("ˇαˇ", true, Right, cx);
2805        assert("ˇˇ✋", false, Left, cx);
2806        assert("ˇˇ✋", true, Left, cx);
2807        assert("ˇˇ✋", false, Right, cx);
2808        assert("ˇ✋ˇ", true, Right, cx);
2809        assert("ˇˇ🍐", false, Left, cx);
2810        assert("ˇˇ🍐", true, Left, cx);
2811        assert("ˇˇ🍐", false, Right, cx);
2812        assert("ˇ🍐ˇ", true, Right, cx);
2813        assert("ˇˇ\t", false, Left, cx);
2814        assert("ˇˇ\t", true, Left, cx);
2815        assert("ˇˇ\t", false, Right, cx);
2816        assert("ˇ\tˇ", true, Right, cx);
2817        assert(" ˇˇ\t", false, Left, cx);
2818        assert(" ˇˇ\t", true, Left, cx);
2819        assert(" ˇˇ\t", false, Right, cx);
2820        assert(" ˇ\tˇ", true, Right, cx);
2821        assert("   ˇˇ\t", false, Left, cx);
2822        assert("   ˇˇ\t", false, Right, cx);
2823    }
2824
2825    #[gpui::test]
2826    fn test_clip_at_line_ends(cx: &mut gpui::App) {
2827        init_test(cx, |_| {});
2828
2829        fn assert(text: &str, cx: &mut gpui::App) {
2830            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
2831            unmarked_snapshot.clip_at_line_ends = true;
2832            assert_eq!(
2833                unmarked_snapshot.clip_point(markers[1], Bias::Left),
2834                markers[0]
2835            );
2836        }
2837
2838        assert("ˇˇ", cx);
2839        assert("ˇaˇ", cx);
2840        assert("aˇbˇ", cx);
2841        assert("aˇαˇ", cx);
2842    }
2843
2844    #[gpui::test]
2845    fn test_creases(cx: &mut gpui::App) {
2846        init_test(cx, |_| {});
2847
2848        let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
2849        let buffer = MultiBuffer::build_simple(text, cx);
2850        let font_size = px(14.0);
2851        cx.new(|cx| {
2852            let mut map = DisplayMap::new(
2853                buffer.clone(),
2854                font("Helvetica"),
2855                font_size,
2856                None,
2857                1,
2858                1,
2859                FoldPlaceholder::test(),
2860                DiagnosticSeverity::Warning,
2861                cx,
2862            );
2863            let snapshot = map.buffer.read(cx).snapshot(cx);
2864            let range =
2865                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
2866
2867            map.crease_map.insert(
2868                [Crease::inline(
2869                    range,
2870                    FoldPlaceholder::test(),
2871                    |_row, _status, _toggle, _window, _cx| div(),
2872                    |_row, _status, _window, _cx| div(),
2873                )],
2874                &map.buffer.read(cx).snapshot(cx),
2875            );
2876
2877            map
2878        });
2879    }
2880
2881    #[gpui::test]
2882    fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
2883        init_test(cx, |_| {});
2884
2885        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
2886        let buffer = MultiBuffer::build_simple(text, cx);
2887        let font_size = px(14.0);
2888
2889        let map = cx.new(|cx| {
2890            DisplayMap::new(
2891                buffer.clone(),
2892                font("Helvetica"),
2893                font_size,
2894                None,
2895                1,
2896                1,
2897                FoldPlaceholder::test(),
2898                DiagnosticSeverity::Warning,
2899                cx,
2900            )
2901        });
2902        let map = map.update(cx, |map, cx| map.snapshot(cx));
2903        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
2904        assert_eq!(
2905            map.text_chunks(DisplayRow(0)).collect::<String>(),
2906            "✅       α\nβ   \n🏀β      γ"
2907        );
2908        assert_eq!(
2909            map.text_chunks(DisplayRow(1)).collect::<String>(),
2910            "β   \n🏀β      γ"
2911        );
2912        assert_eq!(
2913            map.text_chunks(DisplayRow(2)).collect::<String>(),
2914            "🏀β      γ"
2915        );
2916
2917        let point = MultiBufferPoint::new(0, "\t\t".len() as u32);
2918        let display_point = DisplayPoint::new(DisplayRow(0), "".len() as u32);
2919        assert_eq!(point.to_display_point(&map), display_point);
2920        assert_eq!(display_point.to_point(&map), point);
2921
2922        let point = MultiBufferPoint::new(1, "β\t".len() as u32);
2923        let display_point = DisplayPoint::new(DisplayRow(1), "β   ".len() as u32);
2924        assert_eq!(point.to_display_point(&map), display_point);
2925        assert_eq!(display_point.to_point(&map), point,);
2926
2927        let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
2928        let display_point = DisplayPoint::new(DisplayRow(2), "🏀β      ".len() as u32);
2929        assert_eq!(point.to_display_point(&map), display_point);
2930        assert_eq!(display_point.to_point(&map), point,);
2931
2932        // Display points inside of expanded tabs
2933        assert_eq!(
2934            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
2935            MultiBufferPoint::new(0, "\t".len() as u32),
2936        );
2937        assert_eq!(
2938            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
2939            MultiBufferPoint::new(0, "".len() as u32),
2940        );
2941
2942        // Clipping display points inside of multi-byte characters
2943        assert_eq!(
2944            map.clip_point(
2945                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
2946                Left
2947            ),
2948            DisplayPoint::new(DisplayRow(0), 0)
2949        );
2950        assert_eq!(
2951            map.clip_point(
2952                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
2953                Bias::Right
2954            ),
2955            DisplayPoint::new(DisplayRow(0), "".len() as u32)
2956        );
2957    }
2958
2959    #[gpui::test]
2960    fn test_max_point(cx: &mut gpui::App) {
2961        init_test(cx, |_| {});
2962
2963        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
2964        let font_size = px(14.0);
2965        let map = cx.new(|cx| {
2966            DisplayMap::new(
2967                buffer.clone(),
2968                font("Helvetica"),
2969                font_size,
2970                None,
2971                1,
2972                1,
2973                FoldPlaceholder::test(),
2974                DiagnosticSeverity::Warning,
2975                cx,
2976            )
2977        });
2978        assert_eq!(
2979            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2980            DisplayPoint::new(DisplayRow(1), 11)
2981        )
2982    }
2983
2984    fn syntax_chunks(
2985        rows: Range<DisplayRow>,
2986        map: &Entity<DisplayMap>,
2987        theme: &SyntaxTheme,
2988        cx: &mut App,
2989    ) -> Vec<(String, Option<Hsla>)> {
2990        chunks(rows, map, theme, cx)
2991            .into_iter()
2992            .map(|(text, color, _)| (text, color))
2993            .collect()
2994    }
2995
2996    fn chunks(
2997        rows: Range<DisplayRow>,
2998        map: &Entity<DisplayMap>,
2999        theme: &SyntaxTheme,
3000        cx: &mut App,
3001    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
3002        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3003        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
3004        for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
3005            let syntax_color = chunk
3006                .syntax_highlight_id
3007                .and_then(|id| id.style(theme)?.color);
3008            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
3009            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
3010                && syntax_color == *last_syntax_color
3011                && highlight_color == *last_highlight_color
3012            {
3013                last_chunk.push_str(chunk.text);
3014                continue;
3015            }
3016            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
3017        }
3018        chunks
3019    }
3020
3021    fn init_test(cx: &mut App, f: impl Fn(&mut SettingsContent)) {
3022        let settings = SettingsStore::test(cx);
3023        cx.set_global(settings);
3024        crate::init(cx);
3025        theme::init(LoadThemes::JustBase, cx);
3026        cx.update_global::<SettingsStore, _>(|store, cx| {
3027            store.update_user_settings(cx, f);
3028        });
3029    }
3030}