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