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