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