display_map.rs

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