display_map.rs

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