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        use itertools::Itertools;
1416
1417        let needed_type_id = TypeId::of::<Tag>();
1418        self.text_highlights
1419            .iter()
1420            .filter(|(key, _)| match key {
1421                HighlightKey::Type(type_id) => type_id == &needed_type_id,
1422                HighlightKey::TypePlus(type_id, _) => type_id == &needed_type_id,
1423            })
1424            .map(|(_, value)| value.clone())
1425            .flat_map(|ranges| {
1426                ranges
1427                    .1
1428                    .iter()
1429                    .flat_map(|range| {
1430                        Some((ranges.0.color?, range.to_point(self.buffer_snapshot())))
1431                    })
1432                    .collect::<Vec<_>>()
1433            })
1434            .sorted_by_key(|(_, range)| range.start)
1435            .collect()
1436    }
1437
1438    #[allow(unused)]
1439    #[cfg(any(test, feature = "test-support"))]
1440    pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
1441        &self,
1442    ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
1443        let type_id = TypeId::of::<Tag>();
1444        self.inlay_highlights.get(&type_id)
1445    }
1446
1447    pub fn buffer_header_height(&self) -> u32 {
1448        self.block_snapshot.buffer_header_height
1449    }
1450
1451    pub fn excerpt_header_height(&self) -> u32 {
1452        self.block_snapshot.excerpt_header_height
1453    }
1454
1455    /// Given a `DisplayPoint`, returns another `DisplayPoint` corresponding to
1456    /// the start of the buffer row that is a given number of buffer rows away
1457    /// from the provided point.
1458    ///
1459    /// This moves by buffer rows instead of display rows, a distinction that is
1460    /// important when soft wrapping is enabled.
1461    pub fn start_of_relative_buffer_row(&self, point: DisplayPoint, times: isize) -> DisplayPoint {
1462        let start = self.display_point_to_fold_point(point, Bias::Left);
1463        let target = start.row() as isize + times;
1464        let new_row = (target.max(0) as u32).min(self.fold_snapshot().max_point().row());
1465
1466        self.clip_point(
1467            self.fold_point_to_display_point(
1468                self.fold_snapshot()
1469                    .clip_point(FoldPoint::new(new_row, 0), Bias::Right),
1470            ),
1471            Bias::Right,
1472        )
1473    }
1474}
1475
1476impl std::ops::Deref for DisplaySnapshot {
1477    type Target = BlockSnapshot;
1478
1479    fn deref(&self) -> &Self::Target {
1480        &self.block_snapshot
1481    }
1482}
1483
1484/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
1485#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
1486pub struct DisplayPoint(BlockPoint);
1487
1488impl Debug for DisplayPoint {
1489    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1490        f.write_fmt(format_args!(
1491            "DisplayPoint({}, {})",
1492            self.row().0,
1493            self.column()
1494        ))
1495    }
1496}
1497
1498impl Add for DisplayPoint {
1499    type Output = Self;
1500
1501    fn add(self, other: Self) -> Self::Output {
1502        DisplayPoint(BlockPoint(self.0.0 + other.0.0))
1503    }
1504}
1505
1506impl Sub for DisplayPoint {
1507    type Output = Self;
1508
1509    fn sub(self, other: Self) -> Self::Output {
1510        DisplayPoint(BlockPoint(self.0.0 - other.0.0))
1511    }
1512}
1513
1514#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
1515#[serde(transparent)]
1516pub struct DisplayRow(pub u32);
1517
1518impl Add<DisplayRow> for DisplayRow {
1519    type Output = Self;
1520
1521    fn add(self, other: Self) -> Self::Output {
1522        DisplayRow(self.0 + other.0)
1523    }
1524}
1525
1526impl Add<u32> for DisplayRow {
1527    type Output = Self;
1528
1529    fn add(self, other: u32) -> Self::Output {
1530        DisplayRow(self.0 + other)
1531    }
1532}
1533
1534impl Sub<DisplayRow> for DisplayRow {
1535    type Output = Self;
1536
1537    fn sub(self, other: Self) -> Self::Output {
1538        DisplayRow(self.0 - other.0)
1539    }
1540}
1541
1542impl Sub<u32> for DisplayRow {
1543    type Output = Self;
1544
1545    fn sub(self, other: u32) -> Self::Output {
1546        DisplayRow(self.0 - other)
1547    }
1548}
1549
1550impl DisplayPoint {
1551    pub fn new(row: DisplayRow, column: u32) -> Self {
1552        Self(BlockPoint(Point::new(row.0, column)))
1553    }
1554
1555    pub fn zero() -> Self {
1556        Self::new(DisplayRow(0), 0)
1557    }
1558
1559    pub fn is_zero(&self) -> bool {
1560        self.0.is_zero()
1561    }
1562
1563    pub fn row(self) -> DisplayRow {
1564        DisplayRow(self.0.row)
1565    }
1566
1567    pub fn column(self) -> u32 {
1568        self.0.column
1569    }
1570
1571    pub fn row_mut(&mut self) -> &mut u32 {
1572        &mut self.0.row
1573    }
1574
1575    pub fn column_mut(&mut self) -> &mut u32 {
1576        &mut self.0.column
1577    }
1578
1579    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
1580        map.display_point_to_point(self, Bias::Left)
1581    }
1582
1583    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
1584        let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
1585        let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
1586        let fold_point = map.tab_snapshot().to_fold_point(tab_point, bias).0;
1587        let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
1588        map.inlay_snapshot()
1589            .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
1590    }
1591}
1592
1593impl ToDisplayPoint for usize {
1594    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1595        map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
1596    }
1597}
1598
1599impl ToDisplayPoint for OffsetUtf16 {
1600    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1601        self.to_offset(map.buffer_snapshot()).to_display_point(map)
1602    }
1603}
1604
1605impl ToDisplayPoint for Point {
1606    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1607        map.point_to_display_point(*self, Bias::Left)
1608    }
1609}
1610
1611impl ToDisplayPoint for Anchor {
1612    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1613        self.to_point(map.buffer_snapshot()).to_display_point(map)
1614    }
1615}
1616
1617#[cfg(test)]
1618pub mod tests {
1619    use super::*;
1620    use crate::{
1621        movement,
1622        test::{marked_display_snapshot, test_font},
1623    };
1624    use Bias::*;
1625    use block_map::BlockPlacement;
1626    use gpui::{
1627        App, AppContext as _, BorrowAppContext, Element, Hsla, Rgba, div, font, observe, px,
1628    };
1629    use language::{
1630        Buffer, Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageConfig,
1631        LanguageMatcher,
1632    };
1633    use lsp::LanguageServerId;
1634
1635    use rand::{Rng, prelude::*};
1636    use settings::{SettingsContent, SettingsStore};
1637    use smol::stream::StreamExt;
1638    use std::{env, sync::Arc};
1639    use text::PointUtf16;
1640    use theme::{LoadThemes, SyntaxTheme};
1641    use unindent::Unindent as _;
1642    use util::test::{marked_text_ranges, sample_text};
1643
1644    #[gpui::test(iterations = 100)]
1645    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1646        cx.background_executor.set_block_on_ticks(0..=50);
1647        let operations = env::var("OPERATIONS")
1648            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1649            .unwrap_or(10);
1650
1651        let mut tab_size = rng.random_range(1..=4);
1652        let buffer_start_excerpt_header_height = rng.random_range(1..=5);
1653        let excerpt_header_height = rng.random_range(1..=5);
1654        let font_size = px(14.0);
1655        let max_wrap_width = 300.0;
1656        let mut wrap_width = if rng.random_bool(0.1) {
1657            None
1658        } else {
1659            Some(px(rng.random_range(0.0..=max_wrap_width)))
1660        };
1661
1662        log::info!("tab size: {}", tab_size);
1663        log::info!("wrap width: {:?}", wrap_width);
1664
1665        cx.update(|cx| {
1666            init_test(cx, |s| {
1667                s.project.all_languages.defaults.tab_size = NonZeroU32::new(tab_size)
1668            });
1669        });
1670
1671        let buffer = cx.update(|cx| {
1672            if rng.random() {
1673                let len = rng.random_range(0..10);
1674                let text = util::RandomCharIter::new(&mut rng)
1675                    .take(len)
1676                    .collect::<String>();
1677                MultiBuffer::build_simple(&text, cx)
1678            } else {
1679                MultiBuffer::build_random(&mut rng, cx)
1680            }
1681        });
1682
1683        let font = test_font();
1684        let map = cx.new(|cx| {
1685            DisplayMap::new(
1686                buffer.clone(),
1687                font,
1688                font_size,
1689                wrap_width,
1690                buffer_start_excerpt_header_height,
1691                excerpt_header_height,
1692                FoldPlaceholder::test(),
1693                DiagnosticSeverity::Warning,
1694                cx,
1695            )
1696        });
1697        let mut notifications = observe(&map, cx);
1698        let mut fold_count = 0;
1699        let mut blocks = Vec::new();
1700
1701        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1702        log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
1703        log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
1704        log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
1705        log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
1706        log::info!("block text: {:?}", snapshot.block_snapshot.text());
1707        log::info!("display text: {:?}", snapshot.text());
1708
1709        for _i in 0..operations {
1710            match rng.random_range(0..100) {
1711                0..=19 => {
1712                    wrap_width = if rng.random_bool(0.2) {
1713                        None
1714                    } else {
1715                        Some(px(rng.random_range(0.0..=max_wrap_width)))
1716                    };
1717                    log::info!("setting wrap width to {:?}", wrap_width);
1718                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1719                }
1720                20..=29 => {
1721                    let mut tab_sizes = vec![1, 2, 3, 4];
1722                    tab_sizes.remove((tab_size - 1) as usize);
1723                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
1724                    log::info!("setting tab size to {:?}", tab_size);
1725                    cx.update(|cx| {
1726                        cx.update_global::<SettingsStore, _>(|store, cx| {
1727                            store.update_user_settings(cx, |s| {
1728                                s.project.all_languages.defaults.tab_size =
1729                                    NonZeroU32::new(tab_size);
1730                            });
1731                        });
1732                    });
1733                }
1734                30..=44 => {
1735                    map.update(cx, |map, cx| {
1736                        if rng.random() || blocks.is_empty() {
1737                            let snapshot = map.snapshot(cx);
1738                            let buffer = snapshot.buffer_snapshot();
1739                            let block_properties = (0..rng.random_range(1..=1))
1740                                .map(|_| {
1741                                    let position = buffer.anchor_after(buffer.clip_offset(
1742                                        rng.random_range(0..=buffer.len()),
1743                                        Bias::Left,
1744                                    ));
1745
1746                                    let placement = if rng.random() {
1747                                        BlockPlacement::Above(position)
1748                                    } else {
1749                                        BlockPlacement::Below(position)
1750                                    };
1751                                    let height = rng.random_range(1..5);
1752                                    log::info!(
1753                                        "inserting block {:?} with height {}",
1754                                        placement.as_ref().map(|p| p.to_point(&buffer)),
1755                                        height
1756                                    );
1757                                    let priority = rng.random_range(1..100);
1758                                    BlockProperties {
1759                                        placement,
1760                                        style: BlockStyle::Fixed,
1761                                        height: Some(height),
1762                                        render: Arc::new(|_| div().into_any()),
1763                                        priority,
1764                                    }
1765                                })
1766                                .collect::<Vec<_>>();
1767                            blocks.extend(map.insert_blocks(block_properties, cx));
1768                        } else {
1769                            blocks.shuffle(&mut rng);
1770                            let remove_count = rng.random_range(1..=4.min(blocks.len()));
1771                            let block_ids_to_remove = (0..remove_count)
1772                                .map(|_| blocks.remove(rng.random_range(0..blocks.len())))
1773                                .collect();
1774                            log::info!("removing block ids {:?}", block_ids_to_remove);
1775                            map.remove_blocks(block_ids_to_remove, cx);
1776                        }
1777                    });
1778                }
1779                45..=79 => {
1780                    let mut ranges = Vec::new();
1781                    for _ in 0..rng.random_range(1..=3) {
1782                        buffer.read_with(cx, |buffer, cx| {
1783                            let buffer = buffer.read(cx);
1784                            let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
1785                            let start = buffer.clip_offset(rng.random_range(0..=end), Left);
1786                            ranges.push(start..end);
1787                        });
1788                    }
1789
1790                    if rng.random() && fold_count > 0 {
1791                        log::info!("unfolding ranges: {:?}", ranges);
1792                        map.update(cx, |map, cx| {
1793                            map.unfold_intersecting(ranges, true, cx);
1794                        });
1795                    } else {
1796                        log::info!("folding ranges: {:?}", ranges);
1797                        map.update(cx, |map, cx| {
1798                            map.fold(
1799                                ranges
1800                                    .into_iter()
1801                                    .map(|range| Crease::simple(range, FoldPlaceholder::test()))
1802                                    .collect(),
1803                                cx,
1804                            );
1805                        });
1806                    }
1807                }
1808                _ => {
1809                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1810                }
1811            }
1812
1813            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1814                notifications.next().await.unwrap();
1815            }
1816
1817            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1818            fold_count = snapshot.fold_count();
1819            log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
1820            log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
1821            log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
1822            log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
1823            log::info!("block text: {:?}", snapshot.block_snapshot.text());
1824            log::info!("display text: {:?}", snapshot.text());
1825
1826            // Line boundaries
1827            let buffer = snapshot.buffer_snapshot();
1828            for _ in 0..5 {
1829                let row = rng.random_range(0..=buffer.max_point().row);
1830                let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
1831                let point = buffer.clip_point(Point::new(row, column), Left);
1832
1833                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1834                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1835
1836                assert!(prev_buffer_bound <= point);
1837                assert!(next_buffer_bound >= point);
1838                assert_eq!(prev_buffer_bound.column, 0);
1839                assert_eq!(prev_display_bound.column(), 0);
1840                if next_buffer_bound < buffer.max_point() {
1841                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1842                }
1843
1844                assert_eq!(
1845                    prev_display_bound,
1846                    prev_buffer_bound.to_display_point(&snapshot),
1847                    "row boundary before {:?}. reported buffer row boundary: {:?}",
1848                    point,
1849                    prev_buffer_bound
1850                );
1851                assert_eq!(
1852                    next_display_bound,
1853                    next_buffer_bound.to_display_point(&snapshot),
1854                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
1855                    point,
1856                    next_buffer_bound
1857                );
1858                assert_eq!(
1859                    prev_buffer_bound,
1860                    prev_display_bound.to_point(&snapshot),
1861                    "row boundary before {:?}. reported display row boundary: {:?}",
1862                    point,
1863                    prev_display_bound
1864                );
1865                assert_eq!(
1866                    next_buffer_bound,
1867                    next_display_bound.to_point(&snapshot),
1868                    "row boundary after {:?}. reported display row boundary: {:?}",
1869                    point,
1870                    next_display_bound
1871                );
1872            }
1873
1874            // Movement
1875            let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1876            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1877            for _ in 0..5 {
1878                let row = rng.random_range(0..=snapshot.max_point().row().0);
1879                let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
1880                let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1881
1882                log::info!("Moving from point {:?}", point);
1883
1884                let moved_right = movement::right(&snapshot, point);
1885                log::info!("Right {:?}", moved_right);
1886                if point < max_point {
1887                    assert!(moved_right > point);
1888                    if point.column() == snapshot.line_len(point.row())
1889                        || snapshot.soft_wrap_indent(point.row()).is_some()
1890                            && point.column() == snapshot.line_len(point.row()) - 1
1891                    {
1892                        assert!(moved_right.row() > point.row());
1893                    }
1894                } else {
1895                    assert_eq!(moved_right, point);
1896                }
1897
1898                let moved_left = movement::left(&snapshot, point);
1899                log::info!("Left {:?}", moved_left);
1900                if point > min_point {
1901                    assert!(moved_left < point);
1902                    if point.column() == 0 {
1903                        assert!(moved_left.row() < point.row());
1904                    }
1905                } else {
1906                    assert_eq!(moved_left, point);
1907                }
1908            }
1909        }
1910    }
1911
1912    #[gpui::test(retries = 5)]
1913    async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1914        cx.background_executor
1915            .set_block_on_ticks(usize::MAX..=usize::MAX);
1916        cx.update(|cx| {
1917            init_test(cx, |_| {});
1918        });
1919
1920        let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
1921        let editor = cx.editor.clone();
1922        let window = cx.window;
1923
1924        _ = cx.update_window(window, |_, window, cx| {
1925            let text_layout_details =
1926                editor.update(cx, |editor, _cx| editor.text_layout_details(window));
1927
1928            let font_size = px(12.0);
1929            let wrap_width = Some(px(96.));
1930
1931            let text = "one two three four five\nsix seven eight";
1932            let buffer = MultiBuffer::build_simple(text, cx);
1933            let map = cx.new(|cx| {
1934                DisplayMap::new(
1935                    buffer.clone(),
1936                    font("Helvetica"),
1937                    font_size,
1938                    wrap_width,
1939                    1,
1940                    1,
1941                    FoldPlaceholder::test(),
1942                    DiagnosticSeverity::Warning,
1943                    cx,
1944                )
1945            });
1946
1947            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1948            assert_eq!(
1949                snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1950                "one two \nthree four \nfive\nsix seven \neight"
1951            );
1952            assert_eq!(
1953                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1954                DisplayPoint::new(DisplayRow(0), 7)
1955            );
1956            assert_eq!(
1957                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1958                DisplayPoint::new(DisplayRow(1), 0)
1959            );
1960            assert_eq!(
1961                movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1962                DisplayPoint::new(DisplayRow(1), 0)
1963            );
1964            assert_eq!(
1965                movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1966                DisplayPoint::new(DisplayRow(0), 7)
1967            );
1968
1969            let x = snapshot
1970                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1971            assert_eq!(
1972                movement::up(
1973                    &snapshot,
1974                    DisplayPoint::new(DisplayRow(1), 10),
1975                    language::SelectionGoal::None,
1976                    false,
1977                    &text_layout_details,
1978                ),
1979                (
1980                    DisplayPoint::new(DisplayRow(0), 7),
1981                    language::SelectionGoal::HorizontalPosition(f64::from(x))
1982                )
1983            );
1984            assert_eq!(
1985                movement::down(
1986                    &snapshot,
1987                    DisplayPoint::new(DisplayRow(0), 7),
1988                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
1989                    false,
1990                    &text_layout_details
1991                ),
1992                (
1993                    DisplayPoint::new(DisplayRow(1), 10),
1994                    language::SelectionGoal::HorizontalPosition(f64::from(x))
1995                )
1996            );
1997            assert_eq!(
1998                movement::down(
1999                    &snapshot,
2000                    DisplayPoint::new(DisplayRow(1), 10),
2001                    language::SelectionGoal::HorizontalPosition(f64::from(x)),
2002                    false,
2003                    &text_layout_details
2004                ),
2005                (
2006                    DisplayPoint::new(DisplayRow(2), 4),
2007                    language::SelectionGoal::HorizontalPosition(f64::from(x))
2008                )
2009            );
2010
2011            let ix = snapshot.buffer_snapshot().text().find("seven").unwrap();
2012            buffer.update(cx, |buffer, cx| {
2013                buffer.edit([(ix..ix, "and ")], None, cx);
2014            });
2015
2016            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2017            assert_eq!(
2018                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2019                "three four \nfive\nsix and \nseven eight"
2020            );
2021
2022            // Re-wrap on font size changes
2023            map.update(cx, |map, cx| {
2024                map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
2025            });
2026
2027            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2028            assert_eq!(
2029                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
2030                "three \nfour five\nsix and \nseven \neight"
2031            )
2032        });
2033    }
2034
2035    #[gpui::test]
2036    fn test_text_chunks(cx: &mut gpui::App) {
2037        init_test(cx, |_| {});
2038
2039        let text = sample_text(6, 6, 'a');
2040        let buffer = MultiBuffer::build_simple(&text, cx);
2041
2042        let font_size = px(14.0);
2043        let map = cx.new(|cx| {
2044            DisplayMap::new(
2045                buffer.clone(),
2046                font("Helvetica"),
2047                font_size,
2048                None,
2049                1,
2050                1,
2051                FoldPlaceholder::test(),
2052                DiagnosticSeverity::Warning,
2053                cx,
2054            )
2055        });
2056
2057        buffer.update(cx, |buffer, cx| {
2058            buffer.edit(
2059                vec![
2060                    (
2061                        MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
2062                        "\t",
2063                    ),
2064                    (
2065                        MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
2066                        "\t",
2067                    ),
2068                    (
2069                        MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
2070                        "\t",
2071                    ),
2072                ],
2073                None,
2074                cx,
2075            )
2076        });
2077
2078        assert_eq!(
2079            map.update(cx, |map, cx| map.snapshot(cx))
2080                .text_chunks(DisplayRow(1))
2081                .collect::<String>()
2082                .lines()
2083                .next(),
2084            Some("    b   bbbbb")
2085        );
2086        assert_eq!(
2087            map.update(cx, |map, cx| map.snapshot(cx))
2088                .text_chunks(DisplayRow(2))
2089                .collect::<String>()
2090                .lines()
2091                .next(),
2092            Some("c   ccccc")
2093        );
2094    }
2095
2096    #[gpui::test]
2097    fn test_inlays_with_newlines_after_blocks(cx: &mut gpui::TestAppContext) {
2098        cx.update(|cx| init_test(cx, |_| {}));
2099
2100        let buffer = cx.new(|cx| Buffer::local("a", cx));
2101        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2102        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2103
2104        let font_size = px(14.0);
2105        let map = cx.new(|cx| {
2106            DisplayMap::new(
2107                buffer.clone(),
2108                font("Helvetica"),
2109                font_size,
2110                None,
2111                1,
2112                1,
2113                FoldPlaceholder::test(),
2114                DiagnosticSeverity::Warning,
2115                cx,
2116            )
2117        });
2118
2119        map.update(cx, |map, cx| {
2120            map.insert_blocks(
2121                [BlockProperties {
2122                    placement: BlockPlacement::Above(
2123                        buffer_snapshot.anchor_before(Point::new(0, 0)),
2124                    ),
2125                    height: Some(2),
2126                    style: BlockStyle::Sticky,
2127                    render: Arc::new(|_| div().into_any()),
2128                    priority: 0,
2129                }],
2130                cx,
2131            );
2132        });
2133        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\na"));
2134
2135        map.update(cx, |map, cx| {
2136            map.splice_inlays(
2137                &[],
2138                vec![Inlay::edit_prediction(
2139                    0,
2140                    buffer_snapshot.anchor_after(0),
2141                    "\n",
2142                )],
2143                cx,
2144            );
2145        });
2146        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\na"));
2147
2148        // Regression test: updating the display map does not crash when a
2149        // block is immediately followed by a multi-line inlay.
2150        buffer.update(cx, |buffer, cx| {
2151            buffer.edit([(1..1, "b")], None, cx);
2152        });
2153        map.update(cx, |m, cx| assert_eq!(m.snapshot(cx).text(), "\n\n\nab"));
2154    }
2155
2156    #[gpui::test]
2157    async fn test_chunks(cx: &mut gpui::TestAppContext) {
2158        let text = r#"
2159            fn outer() {}
2160
2161            mod module {
2162                fn inner() {}
2163            }"#
2164        .unindent();
2165
2166        let theme =
2167            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
2168        let language = Arc::new(
2169            Language::new(
2170                LanguageConfig {
2171                    name: "Test".into(),
2172                    matcher: LanguageMatcher {
2173                        path_suffixes: vec![".test".to_string()],
2174                        ..Default::default()
2175                    },
2176                    ..Default::default()
2177                },
2178                Some(tree_sitter_rust::LANGUAGE.into()),
2179            )
2180            .with_highlights_query(
2181                r#"
2182                (mod_item name: (identifier) body: _ @mod.body)
2183                (function_item name: (identifier) @fn.name)
2184                "#,
2185            )
2186            .unwrap(),
2187        );
2188        language.set_theme(&theme);
2189
2190        cx.update(|cx| {
2191            init_test(cx, |s| {
2192                s.project.all_languages.defaults.tab_size = Some(2.try_into().unwrap())
2193            })
2194        });
2195
2196        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2197        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2198        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2199
2200        let font_size = px(14.0);
2201
2202        let map = cx.new(|cx| {
2203            DisplayMap::new(
2204                buffer,
2205                font("Helvetica"),
2206                font_size,
2207                None,
2208                1,
2209                1,
2210                FoldPlaceholder::test(),
2211                DiagnosticSeverity::Warning,
2212                cx,
2213            )
2214        });
2215        assert_eq!(
2216            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
2217            vec![
2218                ("fn ".to_string(), None),
2219                ("outer".to_string(), Some(Hsla::blue())),
2220                ("() {}\n\nmod module ".to_string(), None),
2221                ("{\n    fn ".to_string(), Some(Hsla::red())),
2222                ("inner".to_string(), Some(Hsla::blue())),
2223                ("() {}\n}".to_string(), Some(Hsla::red())),
2224            ]
2225        );
2226        assert_eq!(
2227            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
2228            vec![
2229                ("    fn ".to_string(), Some(Hsla::red())),
2230                ("inner".to_string(), Some(Hsla::blue())),
2231                ("() {}\n}".to_string(), Some(Hsla::red())),
2232            ]
2233        );
2234
2235        map.update(cx, |map, cx| {
2236            map.fold(
2237                vec![Crease::simple(
2238                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2239                    FoldPlaceholder::test(),
2240                )],
2241                cx,
2242            )
2243        });
2244        assert_eq!(
2245            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
2246            vec![
2247                ("fn ".to_string(), None),
2248                ("out".to_string(), Some(Hsla::blue())),
2249                ("".to_string(), None),
2250                ("  fn ".to_string(), Some(Hsla::red())),
2251                ("inner".to_string(), Some(Hsla::blue())),
2252                ("() {}\n}".to_string(), Some(Hsla::red())),
2253            ]
2254        );
2255    }
2256
2257    #[gpui::test]
2258    async fn test_chunks_with_syntax_highlighting_across_blocks(cx: &mut gpui::TestAppContext) {
2259        cx.background_executor
2260            .set_block_on_ticks(usize::MAX..=usize::MAX);
2261
2262        let text = r#"
2263            const A: &str = "
2264                one
2265                two
2266                three
2267            ";
2268            const B: &str = "four";
2269        "#
2270        .unindent();
2271
2272        let theme = SyntaxTheme::new_test(vec![
2273            ("string", Hsla::red()),
2274            ("punctuation", Hsla::blue()),
2275            ("keyword", Hsla::green()),
2276        ]);
2277        let language = Arc::new(
2278            Language::new(
2279                LanguageConfig {
2280                    name: "Rust".into(),
2281                    ..Default::default()
2282                },
2283                Some(tree_sitter_rust::LANGUAGE.into()),
2284            )
2285            .with_highlights_query(
2286                r#"
2287                (string_literal) @string
2288                "const" @keyword
2289                [":" ";"] @punctuation
2290                "#,
2291            )
2292            .unwrap(),
2293        );
2294        language.set_theme(&theme);
2295
2296        cx.update(|cx| init_test(cx, |_| {}));
2297
2298        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2299        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2300        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2301        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2302
2303        let map = cx.new(|cx| {
2304            DisplayMap::new(
2305                buffer,
2306                font("Courier"),
2307                px(16.0),
2308                None,
2309                1,
2310                1,
2311                FoldPlaceholder::test(),
2312                DiagnosticSeverity::Warning,
2313                cx,
2314            )
2315        });
2316
2317        // Insert two blocks in the middle of a multi-line string literal.
2318        // The second block has zero height.
2319        map.update(cx, |map, cx| {
2320            map.insert_blocks(
2321                [
2322                    BlockProperties {
2323                        placement: BlockPlacement::Below(
2324                            buffer_snapshot.anchor_before(Point::new(1, 0)),
2325                        ),
2326                        height: Some(1),
2327                        style: BlockStyle::Sticky,
2328                        render: Arc::new(|_| div().into_any()),
2329                        priority: 0,
2330                    },
2331                    BlockProperties {
2332                        placement: BlockPlacement::Below(
2333                            buffer_snapshot.anchor_before(Point::new(2, 0)),
2334                        ),
2335                        height: None,
2336                        style: BlockStyle::Sticky,
2337                        render: Arc::new(|_| div().into_any()),
2338                        priority: 0,
2339                    },
2340                ],
2341                cx,
2342            )
2343        });
2344
2345        pretty_assertions::assert_eq!(
2346            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(7), &map, &theme, cx)),
2347            [
2348                ("const".into(), Some(Hsla::green())),
2349                (" A".into(), None),
2350                (":".into(), Some(Hsla::blue())),
2351                (" &str = ".into(), None),
2352                ("\"\n    one\n".into(), Some(Hsla::red())),
2353                ("\n".into(), None),
2354                ("    two\n    three\n\"".into(), Some(Hsla::red())),
2355                (";".into(), Some(Hsla::blue())),
2356                ("\n".into(), None),
2357                ("const".into(), Some(Hsla::green())),
2358                (" B".into(), None),
2359                (":".into(), Some(Hsla::blue())),
2360                (" &str = ".into(), None),
2361                ("\"four\"".into(), Some(Hsla::red())),
2362                (";".into(), Some(Hsla::blue())),
2363                ("\n".into(), None),
2364            ]
2365        );
2366    }
2367
2368    #[gpui::test]
2369    async fn test_chunks_with_diagnostics_across_blocks(cx: &mut gpui::TestAppContext) {
2370        cx.background_executor
2371            .set_block_on_ticks(usize::MAX..=usize::MAX);
2372
2373        let text = r#"
2374            struct A {
2375                b: usize;
2376            }
2377            const c: usize = 1;
2378        "#
2379        .unindent();
2380
2381        cx.update(|cx| init_test(cx, |_| {}));
2382
2383        let buffer = cx.new(|cx| Buffer::local(text, cx));
2384
2385        buffer.update(cx, |buffer, cx| {
2386            buffer.update_diagnostics(
2387                LanguageServerId(0),
2388                DiagnosticSet::new(
2389                    [DiagnosticEntry {
2390                        range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
2391                        diagnostic: Diagnostic {
2392                            severity: lsp::DiagnosticSeverity::ERROR,
2393                            group_id: 1,
2394                            message: "hi".into(),
2395                            ..Default::default()
2396                        },
2397                    }],
2398                    buffer,
2399                ),
2400                cx,
2401            )
2402        });
2403
2404        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2405        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2406
2407        let map = cx.new(|cx| {
2408            DisplayMap::new(
2409                buffer,
2410                font("Courier"),
2411                px(16.0),
2412                None,
2413                1,
2414                1,
2415                FoldPlaceholder::test(),
2416                DiagnosticSeverity::Warning,
2417                cx,
2418            )
2419        });
2420
2421        let black = gpui::black().to_rgb();
2422        let red = gpui::red().to_rgb();
2423
2424        // Insert a block in the middle of a multi-line diagnostic.
2425        map.update(cx, |map, cx| {
2426            map.highlight_text(
2427                HighlightKey::Type(TypeId::of::<usize>()),
2428                vec![
2429                    buffer_snapshot.anchor_before(Point::new(3, 9))
2430                        ..buffer_snapshot.anchor_after(Point::new(3, 14)),
2431                    buffer_snapshot.anchor_before(Point::new(3, 17))
2432                        ..buffer_snapshot.anchor_after(Point::new(3, 18)),
2433                ],
2434                red.into(),
2435                false,
2436                cx,
2437            );
2438            map.insert_blocks(
2439                [BlockProperties {
2440                    placement: BlockPlacement::Below(
2441                        buffer_snapshot.anchor_before(Point::new(1, 0)),
2442                    ),
2443                    height: Some(1),
2444                    style: BlockStyle::Sticky,
2445                    render: Arc::new(|_| div().into_any()),
2446                    priority: 0,
2447                }],
2448                cx,
2449            )
2450        });
2451
2452        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2453        let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
2454        for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
2455            let color = chunk
2456                .highlight_style
2457                .and_then(|style| style.color)
2458                .map_or(black, |color| color.to_rgb());
2459            if let Some((last_chunk, last_severity, last_color)) = chunks.last_mut()
2460                && *last_severity == chunk.diagnostic_severity
2461                && *last_color == color
2462            {
2463                last_chunk.push_str(chunk.text);
2464                continue;
2465            }
2466
2467            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity, color));
2468        }
2469
2470        assert_eq!(
2471            chunks,
2472            [
2473                (
2474                    "struct A {\n    b: usize;\n".into(),
2475                    Some(lsp::DiagnosticSeverity::ERROR),
2476                    black
2477                ),
2478                ("\n".into(), None, black),
2479                ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
2480                ("\nconst c: ".into(), None, black),
2481                ("usize".into(), None, red),
2482                (" = ".into(), None, black),
2483                ("1".into(), None, red),
2484                (";\n".into(), None, black),
2485            ]
2486        );
2487    }
2488
2489    #[gpui::test]
2490    async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
2491        cx.background_executor
2492            .set_block_on_ticks(usize::MAX..=usize::MAX);
2493
2494        cx.update(|cx| init_test(cx, |_| {}));
2495
2496        let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
2497        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2498        let map = cx.new(|cx| {
2499            DisplayMap::new(
2500                buffer.clone(),
2501                font("Courier"),
2502                px(16.0),
2503                None,
2504                1,
2505                1,
2506                FoldPlaceholder::test(),
2507                DiagnosticSeverity::Warning,
2508                cx,
2509            )
2510        });
2511
2512        let snapshot = map.update(cx, |map, cx| {
2513            map.insert_blocks(
2514                [BlockProperties {
2515                    placement: BlockPlacement::Replace(
2516                        buffer_snapshot.anchor_before(Point::new(1, 2))
2517                            ..=buffer_snapshot.anchor_after(Point::new(2, 3)),
2518                    ),
2519                    height: Some(4),
2520                    style: BlockStyle::Fixed,
2521                    render: Arc::new(|_| div().into_any()),
2522                    priority: 0,
2523                }],
2524                cx,
2525            );
2526            map.snapshot(cx)
2527        });
2528
2529        assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
2530
2531        let point_to_display_points = [
2532            (Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
2533            (Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
2534            (Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
2535        ];
2536        for (buffer_point, display_point) in point_to_display_points {
2537            assert_eq!(
2538                snapshot.point_to_display_point(buffer_point, Bias::Left),
2539                display_point,
2540                "point_to_display_point({:?}, Bias::Left)",
2541                buffer_point
2542            );
2543            assert_eq!(
2544                snapshot.point_to_display_point(buffer_point, Bias::Right),
2545                display_point,
2546                "point_to_display_point({:?}, Bias::Right)",
2547                buffer_point
2548            );
2549        }
2550
2551        let display_points_to_points = [
2552            (
2553                DisplayPoint::new(DisplayRow(1), 0),
2554                Point::new(1, 0),
2555                Point::new(2, 5),
2556            ),
2557            (
2558                DisplayPoint::new(DisplayRow(2), 0),
2559                Point::new(1, 0),
2560                Point::new(2, 5),
2561            ),
2562            (
2563                DisplayPoint::new(DisplayRow(3), 0),
2564                Point::new(1, 0),
2565                Point::new(2, 5),
2566            ),
2567            (
2568                DisplayPoint::new(DisplayRow(4), 0),
2569                Point::new(1, 0),
2570                Point::new(2, 5),
2571            ),
2572            (
2573                DisplayPoint::new(DisplayRow(5), 0),
2574                Point::new(3, 0),
2575                Point::new(3, 0),
2576            ),
2577        ];
2578        for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
2579            assert_eq!(
2580                snapshot.display_point_to_point(display_point, Bias::Left),
2581                left_buffer_point,
2582                "display_point_to_point({:?}, Bias::Left)",
2583                display_point
2584            );
2585            assert_eq!(
2586                snapshot.display_point_to_point(display_point, Bias::Right),
2587                right_buffer_point,
2588                "display_point_to_point({:?}, Bias::Right)",
2589                display_point
2590            );
2591        }
2592    }
2593
2594    #[gpui::test]
2595    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
2596        cx.background_executor
2597            .set_block_on_ticks(usize::MAX..=usize::MAX);
2598
2599        let text = r#"
2600            fn outer() {}
2601
2602            mod module {
2603                fn inner() {}
2604            }"#
2605        .unindent();
2606
2607        let theme =
2608            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
2609        let language = Arc::new(
2610            Language::new(
2611                LanguageConfig {
2612                    name: "Test".into(),
2613                    matcher: LanguageMatcher {
2614                        path_suffixes: vec![".test".to_string()],
2615                        ..Default::default()
2616                    },
2617                    ..Default::default()
2618                },
2619                Some(tree_sitter_rust::LANGUAGE.into()),
2620            )
2621            .with_highlights_query(
2622                r#"
2623                (mod_item name: (identifier) body: _ @mod.body)
2624                (function_item name: (identifier) @fn.name)
2625                "#,
2626            )
2627            .unwrap(),
2628        );
2629        language.set_theme(&theme);
2630
2631        cx.update(|cx| init_test(cx, |_| {}));
2632
2633        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2634        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2635        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2636
2637        let font_size = px(16.0);
2638
2639        let map = cx.new(|cx| {
2640            DisplayMap::new(
2641                buffer,
2642                font("Courier"),
2643                font_size,
2644                Some(px(40.0)),
2645                1,
2646                1,
2647                FoldPlaceholder::test(),
2648                DiagnosticSeverity::Warning,
2649                cx,
2650            )
2651        });
2652        assert_eq!(
2653            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
2654            [
2655                ("fn \n".to_string(), None),
2656                ("oute".to_string(), Some(Hsla::blue())),
2657                ("\n".to_string(), None),
2658                ("r".to_string(), Some(Hsla::blue())),
2659                ("() \n{}\n\n".to_string(), None),
2660            ]
2661        );
2662        assert_eq!(
2663            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
2664            [("{}\n\n".to_string(), None)]
2665        );
2666
2667        map.update(cx, |map, cx| {
2668            map.fold(
2669                vec![Crease::simple(
2670                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
2671                    FoldPlaceholder::test(),
2672                )],
2673                cx,
2674            )
2675        });
2676        assert_eq!(
2677            cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
2678            [
2679                ("out".to_string(), Some(Hsla::blue())),
2680                ("\n".to_string(), None),
2681                ("  ".to_string(), Some(Hsla::red())),
2682                ("\n".to_string(), None),
2683                ("fn ".to_string(), Some(Hsla::red())),
2684                ("i".to_string(), Some(Hsla::blue())),
2685                ("\n".to_string(), None)
2686            ]
2687        );
2688    }
2689
2690    #[gpui::test]
2691    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
2692        cx.update(|cx| init_test(cx, |_| {}));
2693
2694        let theme =
2695            SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
2696        let language = Arc::new(
2697            Language::new(
2698                LanguageConfig {
2699                    name: "Test".into(),
2700                    matcher: LanguageMatcher {
2701                        path_suffixes: vec![".test".to_string()],
2702                        ..Default::default()
2703                    },
2704                    ..Default::default()
2705                },
2706                Some(tree_sitter_rust::LANGUAGE.into()),
2707            )
2708            .with_highlights_query(
2709                r#"
2710                ":" @operator
2711                (string_literal) @string
2712                "#,
2713            )
2714            .unwrap(),
2715        );
2716        language.set_theme(&theme);
2717
2718        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»«:» B = "c «d»""#, false);
2719
2720        let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
2721        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
2722
2723        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
2724        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
2725
2726        let font_size = px(16.0);
2727        let map = cx.new(|cx| {
2728            DisplayMap::new(
2729                buffer,
2730                font("Courier"),
2731                font_size,
2732                None,
2733                1,
2734                1,
2735                FoldPlaceholder::test(),
2736                DiagnosticSeverity::Warning,
2737                cx,
2738            )
2739        });
2740
2741        enum MyType {}
2742
2743        let style = HighlightStyle {
2744            color: Some(Hsla::blue()),
2745            ..Default::default()
2746        };
2747
2748        map.update(cx, |map, cx| {
2749            map.highlight_text(
2750                HighlightKey::Type(TypeId::of::<MyType>()),
2751                highlighted_ranges
2752                    .into_iter()
2753                    .map(|range| {
2754                        buffer_snapshot.anchor_before(range.start)
2755                            ..buffer_snapshot.anchor_before(range.end)
2756                    })
2757                    .collect(),
2758                style,
2759                false,
2760                cx,
2761            );
2762        });
2763
2764        assert_eq!(
2765            cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
2766            [
2767                ("const ".to_string(), None, None),
2768                ("a".to_string(), None, Some(Hsla::blue())),
2769                (":".to_string(), Some(Hsla::red()), Some(Hsla::blue())),
2770                (" B = ".to_string(), None, None),
2771                ("\"c ".to_string(), Some(Hsla::green()), None),
2772                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
2773                ("\"".to_string(), Some(Hsla::green()), None),
2774            ]
2775        );
2776    }
2777
2778    #[gpui::test]
2779    fn test_clip_point(cx: &mut gpui::App) {
2780        init_test(cx, |_| {});
2781
2782        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::App) {
2783            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
2784
2785            match bias {
2786                Bias::Left => {
2787                    if shift_right {
2788                        *markers[1].column_mut() += 1;
2789                    }
2790
2791                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
2792                }
2793                Bias::Right => {
2794                    if shift_right {
2795                        *markers[0].column_mut() += 1;
2796                    }
2797
2798                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
2799                }
2800            };
2801        }
2802
2803        use Bias::{Left, Right};
2804        assert("ˇˇα", false, Left, cx);
2805        assert("ˇˇα", true, Left, cx);
2806        assert("ˇˇα", false, Right, cx);
2807        assert("ˇαˇ", true, Right, cx);
2808        assert("ˇˇ✋", false, Left, cx);
2809        assert("ˇˇ✋", true, Left, cx);
2810        assert("ˇˇ✋", false, Right, cx);
2811        assert("ˇ✋ˇ", true, Right, cx);
2812        assert("ˇˇ🍐", false, Left, cx);
2813        assert("ˇˇ🍐", true, Left, cx);
2814        assert("ˇˇ🍐", false, Right, cx);
2815        assert("ˇ🍐ˇ", true, Right, cx);
2816        assert("ˇˇ\t", false, Left, cx);
2817        assert("ˇˇ\t", true, Left, cx);
2818        assert("ˇˇ\t", false, Right, cx);
2819        assert("ˇ\tˇ", true, Right, cx);
2820        assert(" ˇˇ\t", false, Left, cx);
2821        assert(" ˇˇ\t", true, Left, cx);
2822        assert(" ˇˇ\t", false, Right, cx);
2823        assert(" ˇ\tˇ", true, Right, cx);
2824        assert("   ˇˇ\t", false, Left, cx);
2825        assert("   ˇˇ\t", false, Right, cx);
2826    }
2827
2828    #[gpui::test]
2829    fn test_clip_at_line_ends(cx: &mut gpui::App) {
2830        init_test(cx, |_| {});
2831
2832        fn assert(text: &str, cx: &mut gpui::App) {
2833            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
2834            unmarked_snapshot.clip_at_line_ends = true;
2835            assert_eq!(
2836                unmarked_snapshot.clip_point(markers[1], Bias::Left),
2837                markers[0]
2838            );
2839        }
2840
2841        assert("ˇˇ", cx);
2842        assert("ˇaˇ", cx);
2843        assert("aˇbˇ", cx);
2844        assert("aˇαˇ", cx);
2845    }
2846
2847    #[gpui::test]
2848    fn test_creases(cx: &mut gpui::App) {
2849        init_test(cx, |_| {});
2850
2851        let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
2852        let buffer = MultiBuffer::build_simple(text, cx);
2853        let font_size = px(14.0);
2854        cx.new(|cx| {
2855            let mut map = DisplayMap::new(
2856                buffer.clone(),
2857                font("Helvetica"),
2858                font_size,
2859                None,
2860                1,
2861                1,
2862                FoldPlaceholder::test(),
2863                DiagnosticSeverity::Warning,
2864                cx,
2865            );
2866            let snapshot = map.buffer.read(cx).snapshot(cx);
2867            let range =
2868                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
2869
2870            map.crease_map.insert(
2871                [Crease::inline(
2872                    range,
2873                    FoldPlaceholder::test(),
2874                    |_row, _status, _toggle, _window, _cx| div(),
2875                    |_row, _status, _window, _cx| div(),
2876                )],
2877                &map.buffer.read(cx).snapshot(cx),
2878            );
2879
2880            map
2881        });
2882    }
2883
2884    #[gpui::test]
2885    fn test_tabs_with_multibyte_chars(cx: &mut gpui::App) {
2886        init_test(cx, |_| {});
2887
2888        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
2889        let buffer = MultiBuffer::build_simple(text, cx);
2890        let font_size = px(14.0);
2891
2892        let map = cx.new(|cx| {
2893            DisplayMap::new(
2894                buffer.clone(),
2895                font("Helvetica"),
2896                font_size,
2897                None,
2898                1,
2899                1,
2900                FoldPlaceholder::test(),
2901                DiagnosticSeverity::Warning,
2902                cx,
2903            )
2904        });
2905        let map = map.update(cx, |map, cx| map.snapshot(cx));
2906        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
2907        assert_eq!(
2908            map.text_chunks(DisplayRow(0)).collect::<String>(),
2909            "✅       α\nβ   \n🏀β      γ"
2910        );
2911        assert_eq!(
2912            map.text_chunks(DisplayRow(1)).collect::<String>(),
2913            "β   \n🏀β      γ"
2914        );
2915        assert_eq!(
2916            map.text_chunks(DisplayRow(2)).collect::<String>(),
2917            "🏀β      γ"
2918        );
2919
2920        let point = MultiBufferPoint::new(0, "\t\t".len() as u32);
2921        let display_point = DisplayPoint::new(DisplayRow(0), "".len() as u32);
2922        assert_eq!(point.to_display_point(&map), display_point);
2923        assert_eq!(display_point.to_point(&map), point);
2924
2925        let point = MultiBufferPoint::new(1, "β\t".len() as u32);
2926        let display_point = DisplayPoint::new(DisplayRow(1), "β   ".len() as u32);
2927        assert_eq!(point.to_display_point(&map), display_point);
2928        assert_eq!(display_point.to_point(&map), point,);
2929
2930        let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
2931        let display_point = DisplayPoint::new(DisplayRow(2), "🏀β      ".len() as u32);
2932        assert_eq!(point.to_display_point(&map), display_point);
2933        assert_eq!(display_point.to_point(&map), point,);
2934
2935        // Display points inside of expanded tabs
2936        assert_eq!(
2937            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
2938            MultiBufferPoint::new(0, "\t".len() as u32),
2939        );
2940        assert_eq!(
2941            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
2942            MultiBufferPoint::new(0, "".len() as u32),
2943        );
2944
2945        // Clipping display points inside of multi-byte characters
2946        assert_eq!(
2947            map.clip_point(
2948                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
2949                Left
2950            ),
2951            DisplayPoint::new(DisplayRow(0), 0)
2952        );
2953        assert_eq!(
2954            map.clip_point(
2955                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
2956                Bias::Right
2957            ),
2958            DisplayPoint::new(DisplayRow(0), "".len() as u32)
2959        );
2960    }
2961
2962    #[gpui::test]
2963    fn test_max_point(cx: &mut gpui::App) {
2964        init_test(cx, |_| {});
2965
2966        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
2967        let font_size = px(14.0);
2968        let map = cx.new(|cx| {
2969            DisplayMap::new(
2970                buffer.clone(),
2971                font("Helvetica"),
2972                font_size,
2973                None,
2974                1,
2975                1,
2976                FoldPlaceholder::test(),
2977                DiagnosticSeverity::Warning,
2978                cx,
2979            )
2980        });
2981        assert_eq!(
2982            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2983            DisplayPoint::new(DisplayRow(1), 11)
2984        )
2985    }
2986
2987    fn syntax_chunks(
2988        rows: Range<DisplayRow>,
2989        map: &Entity<DisplayMap>,
2990        theme: &SyntaxTheme,
2991        cx: &mut App,
2992    ) -> Vec<(String, Option<Hsla>)> {
2993        chunks(rows, map, theme, cx)
2994            .into_iter()
2995            .map(|(text, color, _)| (text, color))
2996            .collect()
2997    }
2998
2999    fn chunks(
3000        rows: Range<DisplayRow>,
3001        map: &Entity<DisplayMap>,
3002        theme: &SyntaxTheme,
3003        cx: &mut App,
3004    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
3005        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
3006        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
3007        for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
3008            let syntax_color = chunk
3009                .syntax_highlight_id
3010                .and_then(|id| id.style(theme)?.color);
3011            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
3012            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut()
3013                && syntax_color == *last_syntax_color
3014                && highlight_color == *last_highlight_color
3015            {
3016                last_chunk.push_str(chunk.text);
3017                continue;
3018            }
3019            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
3020        }
3021        chunks
3022    }
3023
3024    fn init_test(cx: &mut App, f: impl Fn(&mut SettingsContent)) {
3025        let settings = SettingsStore::test(cx);
3026        cx.set_global(settings);
3027        crate::init(cx);
3028        theme::init(LoadThemes::JustBase, cx);
3029        cx.update_global::<SettingsStore, _>(|store, cx| {
3030            store.update_user_settings(cx, f);
3031        });
3032    }
3033}