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