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 fold_map;
  23mod inlay_map;
  24mod tab_map;
  25mod wrap_map;
  26
  27use crate::{
  28    hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
  29};
  30pub use block_map::{
  31    BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
  32    BlockMap, BlockPoint, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
  33    TransformBlockId,
  34};
  35use block_map::{BlockRow, BlockSnapshot};
  36use collections::{HashMap, HashSet};
  37pub use crease_map::*;
  38pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
  39use fold_map::{FoldMap, FoldSnapshot};
  40use gpui::{
  41    AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
  42};
  43pub(crate) use inlay_map::Inlay;
  44use inlay_map::{InlayMap, InlaySnapshot};
  45pub use inlay_map::{InlayOffset, InlayPoint};
  46use language::{
  47    language_settings::language_settings, ChunkRenderer, OffsetUtf16, Point,
  48    Subscription as BufferSubscription,
  49};
  50use lsp::DiagnosticSeverity;
  51use multi_buffer::{
  52    Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
  53    ToOffset, ToPoint,
  54};
  55use serde::Deserialize;
  56use std::{
  57    any::TypeId,
  58    borrow::Cow,
  59    fmt::Debug,
  60    num::NonZeroU32,
  61    ops::{Add, Range, Sub},
  62    sync::Arc,
  63};
  64use sum_tree::{Bias, TreeMap};
  65use tab_map::{TabMap, TabSnapshot};
  66use text::LineIndent;
  67use ui::WindowContext;
  68use wrap_map::{WrapMap, WrapSnapshot};
  69
  70#[derive(Copy, Clone, Debug, PartialEq, Eq)]
  71pub enum FoldStatus {
  72    Folded,
  73    Foldable,
  74}
  75
  76pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut WindowContext) -> AnyElement>;
  77
  78const UNNECESSARY_CODE_FADE: f32 = 0.3;
  79
  80pub trait ToDisplayPoint {
  81    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
  82}
  83
  84type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
  85type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
  86
  87/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
  88/// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
  89///
  90/// See the [module level documentation](self) for more information.
  91pub struct DisplayMap {
  92    /// The buffer that we are displaying.
  93    buffer: Model<MultiBuffer>,
  94    buffer_subscription: BufferSubscription,
  95    /// Decides where the [`Inlay`]s should be displayed.
  96    inlay_map: InlayMap,
  97    /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
  98    fold_map: FoldMap,
  99    /// Keeps track of hard tabs in a buffer.
 100    tab_map: TabMap,
 101    /// Handles soft wrapping.
 102    wrap_map: Model<WrapMap>,
 103    /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
 104    block_map: BlockMap,
 105    /// Regions of text that should be highlighted.
 106    text_highlights: TextHighlights,
 107    /// Regions of inlays that should be highlighted.
 108    inlay_highlights: InlayHighlights,
 109    /// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
 110    crease_map: CreaseMap,
 111    fold_placeholder: FoldPlaceholder,
 112    pub clip_at_line_ends: bool,
 113}
 114
 115impl DisplayMap {
 116    #[allow(clippy::too_many_arguments)]
 117    pub fn new(
 118        buffer: Model<MultiBuffer>,
 119        font: Font,
 120        font_size: Pixels,
 121        wrap_width: Option<Pixels>,
 122        show_excerpt_controls: bool,
 123        buffer_header_height: u8,
 124        excerpt_header_height: u8,
 125        excerpt_footer_height: u8,
 126        fold_placeholder: FoldPlaceholder,
 127        cx: &mut ModelContext<Self>,
 128    ) -> Self {
 129        let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
 130
 131        let tab_size = Self::tab_size(&buffer, cx);
 132        let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
 133        let (fold_map, snapshot) = FoldMap::new(snapshot);
 134        let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
 135        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
 136        let block_map = BlockMap::new(
 137            snapshot,
 138            show_excerpt_controls,
 139            buffer_header_height,
 140            excerpt_header_height,
 141            excerpt_footer_height,
 142        );
 143        let crease_map = CreaseMap::default();
 144
 145        cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
 146
 147        DisplayMap {
 148            buffer,
 149            buffer_subscription,
 150            fold_map,
 151            inlay_map,
 152            tab_map,
 153            wrap_map,
 154            block_map,
 155            crease_map,
 156            fold_placeholder,
 157            text_highlights: Default::default(),
 158            inlay_highlights: Default::default(),
 159            clip_at_line_ends: false,
 160        }
 161    }
 162
 163    pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
 164        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 165        let edits = self.buffer_subscription.consume().into_inner();
 166        let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
 167        let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
 168        let tab_size = Self::tab_size(&self.buffer, cx);
 169        let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
 170        let (wrap_snapshot, edits) = self
 171            .wrap_map
 172            .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
 173        let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits).snapshot;
 174
 175        DisplaySnapshot {
 176            buffer_snapshot: self.buffer.read(cx).snapshot(cx),
 177            fold_snapshot,
 178            inlay_snapshot,
 179            tab_snapshot,
 180            wrap_snapshot,
 181            block_snapshot,
 182            crease_snapshot: self.crease_map.snapshot(),
 183            text_highlights: self.text_highlights.clone(),
 184            inlay_highlights: self.inlay_highlights.clone(),
 185            clip_at_line_ends: self.clip_at_line_ends,
 186            fold_placeholder: self.fold_placeholder.clone(),
 187        }
 188    }
 189
 190    pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut ModelContext<Self>) {
 191        self.fold(
 192            other
 193                .folds_in_range(0..other.buffer_snapshot.len())
 194                .map(|fold| {
 195                    (
 196                        fold.range.to_offset(&other.buffer_snapshot),
 197                        fold.placeholder.clone(),
 198                    )
 199                }),
 200            cx,
 201        );
 202    }
 203
 204    pub fn fold<T: ToOffset>(
 205        &mut self,
 206        ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
 207        cx: &mut ModelContext<Self>,
 208    ) {
 209        let snapshot = self.buffer.read(cx).snapshot(cx);
 210        let edits = self.buffer_subscription.consume().into_inner();
 211        let tab_size = Self::tab_size(&self.buffer, cx);
 212        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 213        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 214        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 215        let (snapshot, edits) = self
 216            .wrap_map
 217            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 218        self.block_map.read(snapshot, edits);
 219        let (snapshot, edits) = fold_map.fold(ranges);
 220        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 221        let (snapshot, edits) = self
 222            .wrap_map
 223            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 224        self.block_map.read(snapshot, edits);
 225    }
 226
 227    pub fn unfold<T: ToOffset>(
 228        &mut self,
 229        ranges: impl IntoIterator<Item = Range<T>>,
 230        inclusive: bool,
 231        cx: &mut ModelContext<Self>,
 232    ) {
 233        let snapshot = self.buffer.read(cx).snapshot(cx);
 234        let edits = self.buffer_subscription.consume().into_inner();
 235        let tab_size = Self::tab_size(&self.buffer, cx);
 236        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 237        let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
 238        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 239        let (snapshot, edits) = self
 240            .wrap_map
 241            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 242        self.block_map.read(snapshot, edits);
 243        let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
 244        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 245        let (snapshot, edits) = self
 246            .wrap_map
 247            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 248        self.block_map.read(snapshot, edits);
 249    }
 250
 251    pub fn insert_creases(
 252        &mut self,
 253        creases: impl IntoIterator<Item = Crease>,
 254        cx: &mut ModelContext<Self>,
 255    ) -> Vec<CreaseId> {
 256        let snapshot = self.buffer.read(cx).snapshot(cx);
 257        self.crease_map.insert(creases, &snapshot)
 258    }
 259
 260    pub fn remove_creases(
 261        &mut self,
 262        crease_ids: impl IntoIterator<Item = CreaseId>,
 263        cx: &mut ModelContext<Self>,
 264    ) {
 265        let snapshot = self.buffer.read(cx).snapshot(cx);
 266        self.crease_map.remove(crease_ids, &snapshot)
 267    }
 268
 269    pub fn insert_blocks(
 270        &mut self,
 271        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
 272        cx: &mut ModelContext<Self>,
 273    ) -> Vec<BlockId> {
 274        let snapshot = self.buffer.read(cx).snapshot(cx);
 275        let edits = self.buffer_subscription.consume().into_inner();
 276        let tab_size = Self::tab_size(&self.buffer, cx);
 277        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 278        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 279        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 280        let (snapshot, edits) = self
 281            .wrap_map
 282            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 283        let mut block_map = self.block_map.write(snapshot, edits);
 284        block_map.insert(blocks)
 285    }
 286
 287    pub fn replace_blocks(
 288        &mut self,
 289        heights_and_renderers: HashMap<BlockId, (Option<u8>, RenderBlock)>,
 290        cx: &mut ModelContext<Self>,
 291    ) {
 292        //
 293        // Note: previous implementation of `replace_blocks` simply called
 294        // `self.block_map.replace(styles)` which just modified the render by replacing
 295        // the `RenderBlock` with the new one.
 296        //
 297        // ```rust
 298        //  for block in &self.blocks {
 299        //           if let Some(render) = renderers.remove(&block.id) {
 300        //               *block.render.lock() = render;
 301        //           }
 302        //       }
 303        // ```
 304        //
 305        // If height changes however, we need to update the tree. There's a performance
 306        // cost to this, so we'll split the replace blocks into handling the old behavior
 307        // directly and the new behavior separately.
 308        //
 309        //
 310        let mut only_renderers = HashMap::<BlockId, RenderBlock>::default();
 311        let mut full_replace = HashMap::<BlockId, (u8, RenderBlock)>::default();
 312        for (id, (height, render)) in heights_and_renderers {
 313            if let Some(height) = height {
 314                full_replace.insert(id, (height, render));
 315            } else {
 316                only_renderers.insert(id, render);
 317            }
 318        }
 319        self.block_map.replace_renderers(only_renderers);
 320
 321        if full_replace.is_empty() {
 322            return;
 323        }
 324
 325        let snapshot = self.buffer.read(cx).snapshot(cx);
 326        let edits = self.buffer_subscription.consume().into_inner();
 327        let tab_size = Self::tab_size(&self.buffer, cx);
 328        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 329        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 330        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 331        let (snapshot, edits) = self
 332            .wrap_map
 333            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 334        let mut block_map = self.block_map.write(snapshot, edits);
 335        block_map.replace(full_replace);
 336    }
 337
 338    pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
 339        let snapshot = self.buffer.read(cx).snapshot(cx);
 340        let edits = self.buffer_subscription.consume().into_inner();
 341        let tab_size = Self::tab_size(&self.buffer, cx);
 342        let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
 343        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 344        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 345        let (snapshot, edits) = self
 346            .wrap_map
 347            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 348        let mut block_map = self.block_map.write(snapshot, edits);
 349        block_map.remove(ids);
 350    }
 351
 352    pub fn row_for_block(
 353        &mut self,
 354        block_id: BlockId,
 355        cx: &mut ModelContext<Self>,
 356    ) -> Option<DisplayRow> {
 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 block_map = self.block_map.read(snapshot, edits);
 367        let block_row = block_map.row_for_block(block_id)?;
 368        Some(DisplayRow(block_row.0))
 369    }
 370
 371    pub fn highlight_text(
 372        &mut self,
 373        type_id: TypeId,
 374        ranges: Vec<Range<Anchor>>,
 375        style: HighlightStyle,
 376    ) {
 377        self.text_highlights
 378            .insert(Some(type_id), Arc::new((style, ranges)));
 379    }
 380
 381    pub(crate) fn highlight_inlays(
 382        &mut self,
 383        type_id: TypeId,
 384        highlights: Vec<InlayHighlight>,
 385        style: HighlightStyle,
 386    ) {
 387        for highlight in highlights {
 388            let update = self.inlay_highlights.update(&type_id, |highlights| {
 389                highlights.insert(highlight.inlay, (style, highlight.clone()))
 390            });
 391            if update.is_none() {
 392                self.inlay_highlights.insert(
 393                    type_id,
 394                    TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]),
 395                );
 396            }
 397        }
 398    }
 399
 400    pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
 401        let highlights = self.text_highlights.get(&Some(type_id))?;
 402        Some((highlights.0, &highlights.1))
 403    }
 404    pub fn clear_highlights(&mut self, type_id: TypeId) -> bool {
 405        let mut cleared = self.text_highlights.remove(&Some(type_id)).is_some();
 406        cleared |= self.inlay_highlights.remove(&type_id).is_some();
 407        cleared
 408    }
 409
 410    pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
 411        self.wrap_map
 412            .update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
 413    }
 414
 415    pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
 416        self.wrap_map
 417            .update(cx, |map, cx| map.set_wrap_width(width, cx))
 418    }
 419
 420    pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
 421        self.inlay_map.current_inlays()
 422    }
 423
 424    pub(crate) fn splice_inlays(
 425        &mut self,
 426        to_remove: Vec<InlayId>,
 427        to_insert: Vec<Inlay>,
 428        cx: &mut ModelContext<Self>,
 429    ) {
 430        if to_remove.is_empty() && to_insert.is_empty() {
 431            return;
 432        }
 433        let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
 434        let edits = self.buffer_subscription.consume().into_inner();
 435        let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
 436        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 437        let tab_size = Self::tab_size(&self.buffer, cx);
 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        self.block_map.read(snapshot, edits);
 443
 444        let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
 445        let (snapshot, edits) = self.fold_map.read(snapshot, edits);
 446        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
 447        let (snapshot, edits) = self
 448            .wrap_map
 449            .update(cx, |map, cx| map.sync(snapshot, edits, cx));
 450        self.block_map.read(snapshot, edits);
 451    }
 452
 453    fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
 454        let language = buffer
 455            .read(cx)
 456            .as_singleton()
 457            .and_then(|buffer| buffer.read(cx).language());
 458        language_settings(language, None, cx).tab_size
 459    }
 460
 461    #[cfg(test)]
 462    pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
 463        self.wrap_map.read(cx).is_rewrapping()
 464    }
 465
 466    pub fn show_excerpt_controls(&self) -> bool {
 467        self.block_map.show_excerpt_controls()
 468    }
 469}
 470
 471#[derive(Debug, Default)]
 472pub(crate) struct Highlights<'a> {
 473    pub text_highlights: Option<&'a TextHighlights>,
 474    pub inlay_highlights: Option<&'a InlayHighlights>,
 475    pub styles: HighlightStyles,
 476}
 477
 478#[derive(Default, Debug, Clone, Copy)]
 479pub struct HighlightStyles {
 480    pub inlay_hint: Option<HighlightStyle>,
 481    pub suggestion: Option<HighlightStyle>,
 482}
 483
 484pub struct HighlightedChunk<'a> {
 485    pub text: &'a str,
 486    pub style: Option<HighlightStyle>,
 487    pub is_tab: bool,
 488    pub renderer: Option<ChunkRenderer>,
 489}
 490
 491#[derive(Clone)]
 492pub struct DisplaySnapshot {
 493    pub buffer_snapshot: MultiBufferSnapshot,
 494    pub fold_snapshot: FoldSnapshot,
 495    pub crease_snapshot: CreaseSnapshot,
 496    inlay_snapshot: InlaySnapshot,
 497    tab_snapshot: TabSnapshot,
 498    wrap_snapshot: WrapSnapshot,
 499    block_snapshot: BlockSnapshot,
 500    text_highlights: TextHighlights,
 501    inlay_highlights: InlayHighlights,
 502    clip_at_line_ends: bool,
 503    pub(crate) fold_placeholder: FoldPlaceholder,
 504}
 505
 506impl DisplaySnapshot {
 507    #[cfg(test)]
 508    pub fn fold_count(&self) -> usize {
 509        self.fold_snapshot.fold_count()
 510    }
 511
 512    pub fn is_empty(&self) -> bool {
 513        self.buffer_snapshot.len() == 0
 514    }
 515
 516    pub fn buffer_rows(
 517        &self,
 518        start_row: DisplayRow,
 519    ) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
 520        self.block_snapshot
 521            .buffer_rows(BlockRow(start_row.0))
 522            .map(|row| row.map(|row| MultiBufferRow(row.0)))
 523    }
 524
 525    pub fn max_buffer_row(&self) -> MultiBufferRow {
 526        self.buffer_snapshot.max_buffer_row()
 527    }
 528
 529    pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
 530        loop {
 531            let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
 532            let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
 533            fold_point.0.column = 0;
 534            inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
 535            point = self.inlay_snapshot.to_buffer_point(inlay_point);
 536
 537            let mut display_point = self.point_to_display_point(point, Bias::Left);
 538            *display_point.column_mut() = 0;
 539            let next_point = self.display_point_to_point(display_point, Bias::Left);
 540            if next_point == point {
 541                return (point, display_point);
 542            }
 543            point = next_point;
 544        }
 545    }
 546
 547    pub fn next_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
 548        loop {
 549            let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
 550            let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
 551            fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
 552            inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
 553            point = self.inlay_snapshot.to_buffer_point(inlay_point);
 554
 555            let mut display_point = self.point_to_display_point(point, Bias::Right);
 556            *display_point.column_mut() = self.line_len(display_point.row());
 557            let next_point = self.display_point_to_point(display_point, Bias::Right);
 558            if next_point == point {
 559                return (point, display_point);
 560            }
 561            point = next_point;
 562        }
 563    }
 564
 565    // used by line_mode selections and tries to match vim behaviour
 566    pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
 567        let new_start = if range.start.row == 0 {
 568            MultiBufferPoint::new(0, 0)
 569        } else if range.start.row == self.max_buffer_row().0
 570            || (range.end.column > 0 && range.end.row == self.max_buffer_row().0)
 571        {
 572            MultiBufferPoint::new(
 573                range.start.row - 1,
 574                self.buffer_snapshot
 575                    .line_len(MultiBufferRow(range.start.row - 1)),
 576            )
 577        } else {
 578            self.prev_line_boundary(range.start).0
 579        };
 580
 581        let new_end = if range.end.column == 0 {
 582            range.end
 583        } else if range.end.row < self.max_buffer_row().0 {
 584            self.buffer_snapshot
 585                .clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
 586        } else {
 587            self.buffer_snapshot.max_point()
 588        };
 589
 590        new_start..new_end
 591    }
 592
 593    fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
 594        let inlay_point = self.inlay_snapshot.to_inlay_point(point);
 595        let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
 596        let tab_point = self.tab_snapshot.to_tab_point(fold_point);
 597        let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
 598        let block_point = self.block_snapshot.to_block_point(wrap_point);
 599        DisplayPoint(block_point)
 600    }
 601
 602    fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
 603        self.inlay_snapshot
 604            .to_buffer_point(self.display_point_to_inlay_point(point, bias))
 605    }
 606
 607    pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
 608        self.inlay_snapshot
 609            .to_offset(self.display_point_to_inlay_point(point, bias))
 610    }
 611
 612    pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
 613        self.inlay_snapshot
 614            .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
 615    }
 616
 617    pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
 618        self.buffer_snapshot
 619            .anchor_at(point.to_offset(&self, bias), bias)
 620    }
 621
 622    fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
 623        let block_point = point.0;
 624        let wrap_point = self.block_snapshot.to_wrap_point(block_point);
 625        let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
 626        let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
 627        fold_point.to_inlay_point(&self.fold_snapshot)
 628    }
 629
 630    pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
 631        let block_point = point.0;
 632        let wrap_point = self.block_snapshot.to_wrap_point(block_point);
 633        let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
 634        self.tab_snapshot.to_fold_point(tab_point, bias).0
 635    }
 636
 637    pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
 638        let tab_point = self.tab_snapshot.to_tab_point(fold_point);
 639        let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
 640        let block_point = self.block_snapshot.to_block_point(wrap_point);
 641        DisplayPoint(block_point)
 642    }
 643
 644    pub fn max_point(&self) -> DisplayPoint {
 645        DisplayPoint(self.block_snapshot.max_point())
 646    }
 647
 648    /// Returns text chunks starting at the given display row until the end of the file
 649    pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
 650        self.block_snapshot
 651            .chunks(
 652                display_row.0..self.max_point().row().next_row().0,
 653                false,
 654                Highlights::default(),
 655            )
 656            .map(|h| h.text)
 657    }
 658
 659    /// Returns text chunks starting at the end of the given display row in reverse until the start of the file
 660    pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator<Item = &str> {
 661        (0..=display_row.0).rev().flat_map(|row| {
 662            self.block_snapshot
 663                .chunks(row..row + 1, false, Highlights::default())
 664                .map(|h| h.text)
 665                .collect::<Vec<_>>()
 666                .into_iter()
 667                .rev()
 668        })
 669    }
 670
 671    pub fn chunks(
 672        &self,
 673        display_rows: Range<DisplayRow>,
 674        language_aware: bool,
 675        highlight_styles: HighlightStyles,
 676    ) -> DisplayChunks<'_> {
 677        self.block_snapshot.chunks(
 678            display_rows.start.0..display_rows.end.0,
 679            language_aware,
 680            Highlights {
 681                text_highlights: Some(&self.text_highlights),
 682                inlay_highlights: Some(&self.inlay_highlights),
 683                styles: highlight_styles,
 684            },
 685        )
 686    }
 687
 688    pub fn highlighted_chunks<'a>(
 689        &'a self,
 690        display_rows: Range<DisplayRow>,
 691        language_aware: bool,
 692        editor_style: &'a EditorStyle,
 693    ) -> impl Iterator<Item = HighlightedChunk<'a>> {
 694        self.chunks(
 695            display_rows,
 696            language_aware,
 697            HighlightStyles {
 698                inlay_hint: Some(editor_style.inlay_hints_style),
 699                suggestion: Some(editor_style.suggestions_style),
 700            },
 701        )
 702        .map(|chunk| {
 703            let mut highlight_style = chunk
 704                .syntax_highlight_id
 705                .and_then(|id| id.style(&editor_style.syntax));
 706
 707            if let Some(chunk_highlight) = chunk.highlight_style {
 708                if let Some(highlight_style) = highlight_style.as_mut() {
 709                    highlight_style.highlight(chunk_highlight);
 710                } else {
 711                    highlight_style = Some(chunk_highlight);
 712                }
 713            }
 714
 715            let mut diagnostic_highlight = HighlightStyle::default();
 716
 717            if chunk.is_unnecessary {
 718                diagnostic_highlight.fade_out = Some(UNNECESSARY_CODE_FADE);
 719            }
 720
 721            if let Some(severity) = chunk.diagnostic_severity {
 722                // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
 723                if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
 724                    let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
 725                    diagnostic_highlight.underline = Some(UnderlineStyle {
 726                        color: Some(diagnostic_color),
 727                        thickness: 1.0.into(),
 728                        wavy: true,
 729                    });
 730                }
 731            }
 732
 733            if let Some(highlight_style) = highlight_style.as_mut() {
 734                highlight_style.highlight(diagnostic_highlight);
 735            } else {
 736                highlight_style = Some(diagnostic_highlight);
 737            }
 738
 739            HighlightedChunk {
 740                text: chunk.text,
 741                style: highlight_style,
 742                is_tab: chunk.is_tab,
 743                renderer: chunk.renderer,
 744            }
 745        })
 746    }
 747
 748    pub fn layout_row(
 749        &self,
 750        display_row: DisplayRow,
 751        TextLayoutDetails {
 752            text_system,
 753            editor_style,
 754            rem_size,
 755            scroll_anchor: _,
 756            visible_rows: _,
 757            vertical_scroll_margin: _,
 758        }: &TextLayoutDetails,
 759    ) -> Arc<LineLayout> {
 760        let mut runs = Vec::new();
 761        let mut line = String::new();
 762
 763        let range = display_row..display_row.next_row();
 764        for chunk in self.highlighted_chunks(range, false, &editor_style) {
 765            line.push_str(chunk.text);
 766
 767            let text_style = if let Some(style) = chunk.style {
 768                Cow::Owned(editor_style.text.clone().highlight(style))
 769            } else {
 770                Cow::Borrowed(&editor_style.text)
 771            };
 772
 773            runs.push(text_style.to_run(chunk.text.len()))
 774        }
 775
 776        if line.ends_with('\n') {
 777            line.pop();
 778            if let Some(last_run) = runs.last_mut() {
 779                last_run.len -= 1;
 780                if last_run.len == 0 {
 781                    runs.pop();
 782                }
 783            }
 784        }
 785
 786        let font_size = editor_style.text.font_size.to_pixels(*rem_size);
 787        text_system
 788            .layout_line(&line, font_size, &runs)
 789            .expect("we expect the font to be loaded because it's rendered by the editor")
 790    }
 791
 792    pub fn x_for_display_point(
 793        &self,
 794        display_point: DisplayPoint,
 795        text_layout_details: &TextLayoutDetails,
 796    ) -> Pixels {
 797        let line = self.layout_row(display_point.row(), text_layout_details);
 798        line.x_for_index(display_point.column() as usize)
 799    }
 800
 801    pub fn display_column_for_x(
 802        &self,
 803        display_row: DisplayRow,
 804        x: Pixels,
 805        details: &TextLayoutDetails,
 806    ) -> u32 {
 807        let layout_line = self.layout_row(display_row, details);
 808        layout_line.closest_index_for_x(x) as u32
 809    }
 810
 811    pub fn display_chars_at(
 812        &self,
 813        mut point: DisplayPoint,
 814    ) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
 815        point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
 816        self.text_chunks(point.row())
 817            .flat_map(str::chars)
 818            .skip_while({
 819                let mut column = 0;
 820                move |char| {
 821                    let at_point = column >= point.column();
 822                    column += char.len_utf8() as u32;
 823                    !at_point
 824                }
 825            })
 826            .map(move |ch| {
 827                let result = (ch, point);
 828                if ch == '\n' {
 829                    *point.row_mut() += 1;
 830                    *point.column_mut() = 0;
 831                } else {
 832                    *point.column_mut() += ch.len_utf8() as u32;
 833                }
 834                result
 835            })
 836    }
 837
 838    pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
 839        self.buffer_snapshot.chars_at(offset).map(move |ch| {
 840            let ret = (ch, offset);
 841            offset += ch.len_utf8();
 842            ret
 843        })
 844    }
 845
 846    pub fn reverse_buffer_chars_at(
 847        &self,
 848        mut offset: usize,
 849    ) -> impl Iterator<Item = (char, usize)> + '_ {
 850        self.buffer_snapshot
 851            .reversed_chars_at(offset)
 852            .map(move |ch| {
 853                offset -= ch.len_utf8();
 854                (ch, offset)
 855            })
 856    }
 857
 858    pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
 859        let mut clipped = self.block_snapshot.clip_point(point.0, bias);
 860        if self.clip_at_line_ends {
 861            clipped = self.clip_at_line_end(DisplayPoint(clipped)).0
 862        }
 863        DisplayPoint(clipped)
 864    }
 865
 866    pub fn clip_ignoring_line_ends(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
 867        DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
 868    }
 869
 870    pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
 871        let mut point = point.0;
 872        if point.column == self.line_len(DisplayRow(point.row)) {
 873            point.column = point.column.saturating_sub(1);
 874            point = self.block_snapshot.clip_point(point, Bias::Left);
 875        }
 876        DisplayPoint(point)
 877    }
 878
 879    pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
 880    where
 881        T: ToOffset,
 882    {
 883        self.fold_snapshot.folds_in_range(range)
 884    }
 885
 886    pub fn blocks_in_range(
 887        &self,
 888        rows: Range<DisplayRow>,
 889    ) -> impl Iterator<Item = (DisplayRow, &TransformBlock)> {
 890        self.block_snapshot
 891            .blocks_in_range(rows.start.0..rows.end.0)
 892            .map(|(row, block)| (DisplayRow(row), block))
 893    }
 894
 895    pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
 896        self.fold_snapshot.intersects_fold(offset)
 897    }
 898
 899    pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
 900        self.fold_snapshot.is_line_folded(buffer_row)
 901    }
 902
 903    pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
 904        self.block_snapshot.is_block_line(BlockRow(display_row.0))
 905    }
 906
 907    pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
 908        let wrap_row = self
 909            .block_snapshot
 910            .to_wrap_point(BlockPoint::new(display_row.0, 0))
 911            .row();
 912        self.wrap_snapshot.soft_wrap_indent(wrap_row)
 913    }
 914
 915    pub fn text(&self) -> String {
 916        self.text_chunks(DisplayRow(0)).collect()
 917    }
 918
 919    pub fn line(&self, display_row: DisplayRow) -> String {
 920        let mut result = String::new();
 921        for chunk in self.text_chunks(display_row) {
 922            if let Some(ix) = chunk.find('\n') {
 923                result.push_str(&chunk[0..ix]);
 924                break;
 925            } else {
 926                result.push_str(chunk);
 927            }
 928        }
 929        result
 930    }
 931
 932    pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
 933        let (buffer, range) = self
 934            .buffer_snapshot
 935            .buffer_line_for_row(buffer_row)
 936            .unwrap();
 937
 938        buffer.line_indent_for_row(range.start.row)
 939    }
 940
 941    pub fn line_len(&self, row: DisplayRow) -> u32 {
 942        self.block_snapshot.line_len(BlockRow(row.0))
 943    }
 944
 945    pub fn longest_row(&self) -> DisplayRow {
 946        DisplayRow(self.block_snapshot.longest_row())
 947    }
 948
 949    pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
 950        let max_row = self.buffer_snapshot.max_buffer_row();
 951        if buffer_row >= max_row {
 952            return false;
 953        }
 954
 955        let line_indent = self.line_indent_for_buffer_row(buffer_row);
 956        if line_indent.is_line_blank() {
 957            return false;
 958        }
 959
 960        (buffer_row.0 + 1..=max_row.0)
 961            .find_map(|next_row| {
 962                let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
 963                if next_line_indent.raw_len() > line_indent.raw_len() {
 964                    Some(true)
 965                } else if !next_line_indent.is_line_blank() {
 966                    Some(false)
 967                } else {
 968                    None
 969                }
 970            })
 971            .unwrap_or(false)
 972    }
 973
 974    pub fn foldable_range(
 975        &self,
 976        buffer_row: MultiBufferRow,
 977    ) -> Option<(Range<Point>, FoldPlaceholder)> {
 978        let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
 979        if let Some(crease) = self
 980            .crease_snapshot
 981            .query_row(buffer_row, &self.buffer_snapshot)
 982        {
 983            Some((
 984                crease.range.to_point(&self.buffer_snapshot),
 985                crease.placeholder.clone(),
 986            ))
 987        } else if self.starts_indent(MultiBufferRow(start.row))
 988            && !self.is_line_folded(MultiBufferRow(start.row))
 989        {
 990            let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
 991            let max_point = self.buffer_snapshot.max_point();
 992            let mut end = None;
 993
 994            for row in (buffer_row.0 + 1)..=max_point.row {
 995                let line_indent = self.line_indent_for_buffer_row(MultiBufferRow(row));
 996                if !line_indent.is_line_blank()
 997                    && line_indent.raw_len() <= start_line_indent.raw_len()
 998                {
 999                    let prev_row = row - 1;
1000                    end = Some(Point::new(
1001                        prev_row,
1002                        self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
1003                    ));
1004                    break;
1005                }
1006            }
1007
1008            let mut row_before_line_breaks = end.unwrap_or(max_point);
1009            while row_before_line_breaks.row > start.row
1010                && self
1011                    .buffer_snapshot
1012                    .is_line_blank(MultiBufferRow(row_before_line_breaks.row))
1013            {
1014                row_before_line_breaks.row -= 1;
1015            }
1016
1017            row_before_line_breaks = Point::new(
1018                row_before_line_breaks.row,
1019                self.buffer_snapshot
1020                    .line_len(MultiBufferRow(row_before_line_breaks.row)),
1021            );
1022
1023            Some((start..row_before_line_breaks, self.fold_placeholder.clone()))
1024        } else {
1025            None
1026        }
1027    }
1028
1029    #[cfg(any(test, feature = "test-support"))]
1030    pub fn text_highlight_ranges<Tag: ?Sized + 'static>(
1031        &self,
1032    ) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
1033        let type_id = TypeId::of::<Tag>();
1034        self.text_highlights.get(&Some(type_id)).cloned()
1035    }
1036
1037    #[allow(unused)]
1038    #[cfg(any(test, feature = "test-support"))]
1039    pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
1040        &self,
1041    ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
1042        let type_id = TypeId::of::<Tag>();
1043        self.inlay_highlights.get(&type_id)
1044    }
1045}
1046
1047#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
1048pub struct DisplayPoint(BlockPoint);
1049
1050impl Debug for DisplayPoint {
1051    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1052        f.write_fmt(format_args!(
1053            "DisplayPoint({}, {})",
1054            self.row().0,
1055            self.column()
1056        ))
1057    }
1058}
1059
1060impl Add for DisplayPoint {
1061    type Output = Self;
1062
1063    fn add(self, other: Self) -> Self::Output {
1064        DisplayPoint(BlockPoint(self.0 .0 + other.0 .0))
1065    }
1066}
1067
1068impl Sub for DisplayPoint {
1069    type Output = Self;
1070
1071    fn sub(self, other: Self) -> Self::Output {
1072        DisplayPoint(BlockPoint(self.0 .0 - other.0 .0))
1073    }
1074}
1075
1076#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
1077#[serde(transparent)]
1078pub struct DisplayRow(pub u32);
1079
1080impl Add for DisplayRow {
1081    type Output = Self;
1082
1083    fn add(self, other: Self) -> Self::Output {
1084        DisplayRow(self.0 + other.0)
1085    }
1086}
1087
1088impl Sub for DisplayRow {
1089    type Output = Self;
1090
1091    fn sub(self, other: Self) -> Self::Output {
1092        DisplayRow(self.0 - other.0)
1093    }
1094}
1095
1096impl DisplayPoint {
1097    pub fn new(row: DisplayRow, column: u32) -> Self {
1098        Self(BlockPoint(Point::new(row.0, column)))
1099    }
1100
1101    pub fn zero() -> Self {
1102        Self::new(DisplayRow(0), 0)
1103    }
1104
1105    pub fn is_zero(&self) -> bool {
1106        self.0.is_zero()
1107    }
1108
1109    pub fn row(self) -> DisplayRow {
1110        DisplayRow(self.0.row)
1111    }
1112
1113    pub fn column(self) -> u32 {
1114        self.0.column
1115    }
1116
1117    pub fn row_mut(&mut self) -> &mut u32 {
1118        &mut self.0.row
1119    }
1120
1121    pub fn column_mut(&mut self) -> &mut u32 {
1122        &mut self.0.column
1123    }
1124
1125    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
1126        map.display_point_to_point(self, Bias::Left)
1127    }
1128
1129    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
1130        let wrap_point = map.block_snapshot.to_wrap_point(self.0);
1131        let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
1132        let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
1133        let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
1134        map.inlay_snapshot
1135            .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
1136    }
1137}
1138
1139impl ToDisplayPoint for usize {
1140    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1141        map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
1142    }
1143}
1144
1145impl ToDisplayPoint for OffsetUtf16 {
1146    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1147        self.to_offset(&map.buffer_snapshot).to_display_point(map)
1148    }
1149}
1150
1151impl ToDisplayPoint for Point {
1152    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1153        map.point_to_display_point(*self, Bias::Left)
1154    }
1155}
1156
1157impl ToDisplayPoint for Anchor {
1158    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
1159        self.to_point(&map.buffer_snapshot).to_display_point(map)
1160    }
1161}
1162
1163#[cfg(test)]
1164pub mod tests {
1165    use super::*;
1166    use crate::{movement, test::marked_display_snapshot};
1167    use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla};
1168    use language::{
1169        language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
1170        Buffer, Language, LanguageConfig, LanguageMatcher,
1171    };
1172    use project::Project;
1173    use rand::{prelude::*, Rng};
1174    use settings::SettingsStore;
1175    use smol::stream::StreamExt;
1176    use std::{env, sync::Arc};
1177    use theme::{LoadThemes, SyntaxTheme};
1178    use util::test::{marked_text_ranges, sample_text};
1179    use Bias::*;
1180
1181    #[gpui::test(iterations = 100)]
1182    async fn test_random_display_map(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1183        cx.background_executor.set_block_on_ticks(0..=50);
1184        let operations = env::var("OPERATIONS")
1185            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1186            .unwrap_or(10);
1187
1188        let mut tab_size = rng.gen_range(1..=4);
1189        let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
1190        let excerpt_header_height = rng.gen_range(1..=5);
1191        let font_size = px(14.0);
1192        let max_wrap_width = 300.0;
1193        let mut wrap_width = if rng.gen_bool(0.1) {
1194            None
1195        } else {
1196            Some(px(rng.gen_range(0.0..=max_wrap_width)))
1197        };
1198
1199        log::info!("tab size: {}", tab_size);
1200        log::info!("wrap width: {:?}", wrap_width);
1201
1202        cx.update(|cx| {
1203            init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
1204        });
1205
1206        let buffer = cx.update(|cx| {
1207            if rng.gen() {
1208                let len = rng.gen_range(0..10);
1209                let text = util::RandomCharIter::new(&mut rng)
1210                    .take(len)
1211                    .collect::<String>();
1212                MultiBuffer::build_simple(&text, cx)
1213            } else {
1214                MultiBuffer::build_random(&mut rng, cx)
1215            }
1216        });
1217
1218        let map = cx.new_model(|cx| {
1219            DisplayMap::new(
1220                buffer.clone(),
1221                font("Helvetica"),
1222                font_size,
1223                wrap_width,
1224                true,
1225                buffer_start_excerpt_header_height,
1226                excerpt_header_height,
1227                0,
1228                FoldPlaceholder::test(),
1229                cx,
1230            )
1231        });
1232        let mut notifications = observe(&map, cx);
1233        let mut fold_count = 0;
1234        let mut blocks = Vec::new();
1235
1236        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1237        log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1238        log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1239        log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1240        log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1241        log::info!("block text: {:?}", snapshot.block_snapshot.text());
1242        log::info!("display text: {:?}", snapshot.text());
1243
1244        for _i in 0..operations {
1245            match rng.gen_range(0..100) {
1246                0..=19 => {
1247                    wrap_width = if rng.gen_bool(0.2) {
1248                        None
1249                    } else {
1250                        Some(px(rng.gen_range(0.0..=max_wrap_width)))
1251                    };
1252                    log::info!("setting wrap width to {:?}", wrap_width);
1253                    map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1254                }
1255                20..=29 => {
1256                    let mut tab_sizes = vec![1, 2, 3, 4];
1257                    tab_sizes.remove((tab_size - 1) as usize);
1258                    tab_size = *tab_sizes.choose(&mut rng).unwrap();
1259                    log::info!("setting tab size to {:?}", tab_size);
1260                    cx.update(|cx| {
1261                        cx.update_global::<SettingsStore, _>(|store, cx| {
1262                            store.update_user_settings::<AllLanguageSettings>(cx, |s| {
1263                                s.defaults.tab_size = NonZeroU32::new(tab_size);
1264                            });
1265                        });
1266                    });
1267                }
1268                30..=44 => {
1269                    map.update(cx, |map, cx| {
1270                        if rng.gen() || blocks.is_empty() {
1271                            let buffer = map.snapshot(cx).buffer_snapshot;
1272                            let block_properties = (0..rng.gen_range(1..=1))
1273                                .map(|_| {
1274                                    let position =
1275                                        buffer.anchor_after(buffer.clip_offset(
1276                                            rng.gen_range(0..=buffer.len()),
1277                                            Bias::Left,
1278                                        ));
1279
1280                                    let disposition = if rng.gen() {
1281                                        BlockDisposition::Above
1282                                    } else {
1283                                        BlockDisposition::Below
1284                                    };
1285                                    let height = rng.gen_range(1..5);
1286                                    log::info!(
1287                                        "inserting block {:?} {:?} with height {}",
1288                                        disposition,
1289                                        position.to_point(&buffer),
1290                                        height
1291                                    );
1292                                    BlockProperties {
1293                                        style: BlockStyle::Fixed,
1294                                        position,
1295                                        height,
1296                                        disposition,
1297                                        render: Box::new(|_| div().into_any()),
1298                                    }
1299                                })
1300                                .collect::<Vec<_>>();
1301                            blocks.extend(map.insert_blocks(block_properties, cx));
1302                        } else {
1303                            blocks.shuffle(&mut rng);
1304                            let remove_count = rng.gen_range(1..=4.min(blocks.len()));
1305                            let block_ids_to_remove = (0..remove_count)
1306                                .map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
1307                                .collect();
1308                            log::info!("removing block ids {:?}", block_ids_to_remove);
1309                            map.remove_blocks(block_ids_to_remove, cx);
1310                        }
1311                    });
1312                }
1313                45..=79 => {
1314                    let mut ranges = Vec::new();
1315                    for _ in 0..rng.gen_range(1..=3) {
1316                        buffer.read_with(cx, |buffer, cx| {
1317                            let buffer = buffer.read(cx);
1318                            let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1319                            let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1320                            ranges.push(start..end);
1321                        });
1322                    }
1323
1324                    if rng.gen() && fold_count > 0 {
1325                        log::info!("unfolding ranges: {:?}", ranges);
1326                        map.update(cx, |map, cx| {
1327                            map.unfold(ranges, true, cx);
1328                        });
1329                    } else {
1330                        log::info!("folding ranges: {:?}", ranges);
1331                        map.update(cx, |map, cx| {
1332                            map.fold(
1333                                ranges
1334                                    .into_iter()
1335                                    .map(|range| (range, FoldPlaceholder::test())),
1336                                cx,
1337                            );
1338                        });
1339                    }
1340                }
1341                _ => {
1342                    buffer.update(cx, |buffer, cx| buffer.randomly_mutate(&mut rng, 5, cx));
1343                }
1344            }
1345
1346            if map.read_with(cx, |map, cx| map.is_rewrapping(cx)) {
1347                notifications.next().await.unwrap();
1348            }
1349
1350            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1351            fold_count = snapshot.fold_count();
1352            log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
1353            log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
1354            log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
1355            log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
1356            log::info!("block text: {:?}", snapshot.block_snapshot.text());
1357            log::info!("display text: {:?}", snapshot.text());
1358
1359            // Line boundaries
1360            let buffer = &snapshot.buffer_snapshot;
1361            for _ in 0..5 {
1362                let row = rng.gen_range(0..=buffer.max_point().row);
1363                let column = rng.gen_range(0..=buffer.line_len(MultiBufferRow(row)));
1364                let point = buffer.clip_point(Point::new(row, column), Left);
1365
1366                let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
1367                let (next_buffer_bound, next_display_bound) = snapshot.next_line_boundary(point);
1368
1369                assert!(prev_buffer_bound <= point);
1370                assert!(next_buffer_bound >= point);
1371                assert_eq!(prev_buffer_bound.column, 0);
1372                assert_eq!(prev_display_bound.column(), 0);
1373                if next_buffer_bound < buffer.max_point() {
1374                    assert_eq!(buffer.chars_at(next_buffer_bound).next(), Some('\n'));
1375                }
1376
1377                assert_eq!(
1378                    prev_display_bound,
1379                    prev_buffer_bound.to_display_point(&snapshot),
1380                    "row boundary before {:?}. reported buffer row boundary: {:?}",
1381                    point,
1382                    prev_buffer_bound
1383                );
1384                assert_eq!(
1385                    next_display_bound,
1386                    next_buffer_bound.to_display_point(&snapshot),
1387                    "display row boundary after {:?}. reported buffer row boundary: {:?}",
1388                    point,
1389                    next_buffer_bound
1390                );
1391                assert_eq!(
1392                    prev_buffer_bound,
1393                    prev_display_bound.to_point(&snapshot),
1394                    "row boundary before {:?}. reported display row boundary: {:?}",
1395                    point,
1396                    prev_display_bound
1397                );
1398                assert_eq!(
1399                    next_buffer_bound,
1400                    next_display_bound.to_point(&snapshot),
1401                    "row boundary after {:?}. reported display row boundary: {:?}",
1402                    point,
1403                    next_display_bound
1404                );
1405            }
1406
1407            // Movement
1408            let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
1409            let max_point = snapshot.clip_point(snapshot.max_point(), Right);
1410            for _ in 0..5 {
1411                let row = rng.gen_range(0..=snapshot.max_point().row().0);
1412                let column = rng.gen_range(0..=snapshot.line_len(DisplayRow(row)));
1413                let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
1414
1415                log::info!("Moving from point {:?}", point);
1416
1417                let moved_right = movement::right(&snapshot, point);
1418                log::info!("Right {:?}", moved_right);
1419                if point < max_point {
1420                    assert!(moved_right > point);
1421                    if point.column() == snapshot.line_len(point.row())
1422                        || snapshot.soft_wrap_indent(point.row()).is_some()
1423                            && point.column() == snapshot.line_len(point.row()) - 1
1424                    {
1425                        assert!(moved_right.row() > point.row());
1426                    }
1427                } else {
1428                    assert_eq!(moved_right, point);
1429                }
1430
1431                let moved_left = movement::left(&snapshot, point);
1432                log::info!("Left {:?}", moved_left);
1433                if point > min_point {
1434                    assert!(moved_left < point);
1435                    if point.column() == 0 {
1436                        assert!(moved_left.row() < point.row());
1437                    }
1438                } else {
1439                    assert_eq!(moved_left, point);
1440                }
1441            }
1442        }
1443    }
1444
1445    #[cfg(target_os = "macos")]
1446    #[gpui::test(retries = 5)]
1447    async fn test_soft_wraps(cx: &mut gpui::TestAppContext) {
1448        cx.background_executor
1449            .set_block_on_ticks(usize::MAX..=usize::MAX);
1450        cx.update(|cx| {
1451            init_test(cx, |_| {});
1452        });
1453
1454        let mut cx = crate::test::editor_test_context::EditorTestContext::new(cx).await;
1455        let editor = cx.editor.clone();
1456        let window = cx.window;
1457
1458        _ = cx.update_window(window, |_, cx| {
1459            let text_layout_details =
1460                editor.update(cx, |editor, cx| editor.text_layout_details(cx));
1461
1462            let font_size = px(12.0);
1463            let wrap_width = Some(px(64.));
1464
1465            let text = "one two three four five\nsix seven eight";
1466            let buffer = MultiBuffer::build_simple(text, cx);
1467            let map = cx.new_model(|cx| {
1468                DisplayMap::new(
1469                    buffer.clone(),
1470                    font("Helvetica"),
1471                    font_size,
1472                    wrap_width,
1473                    true,
1474                    1,
1475                    1,
1476                    0,
1477                    FoldPlaceholder::test(),
1478                    cx,
1479                )
1480            });
1481
1482            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1483            assert_eq!(
1484                snapshot.text_chunks(DisplayRow(0)).collect::<String>(),
1485                "one two \nthree four \nfive\nsix seven \neight"
1486            );
1487            assert_eq!(
1488                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Left),
1489                DisplayPoint::new(DisplayRow(0), 7)
1490            );
1491            assert_eq!(
1492                snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 8), Bias::Right),
1493                DisplayPoint::new(DisplayRow(1), 0)
1494            );
1495            assert_eq!(
1496                movement::right(&snapshot, DisplayPoint::new(DisplayRow(0), 7)),
1497                DisplayPoint::new(DisplayRow(1), 0)
1498            );
1499            assert_eq!(
1500                movement::left(&snapshot, DisplayPoint::new(DisplayRow(1), 0)),
1501                DisplayPoint::new(DisplayRow(0), 7)
1502            );
1503
1504            let x = snapshot
1505                .x_for_display_point(DisplayPoint::new(DisplayRow(1), 10), &text_layout_details);
1506            assert_eq!(
1507                movement::up(
1508                    &snapshot,
1509                    DisplayPoint::new(DisplayRow(1), 10),
1510                    language::SelectionGoal::None,
1511                    false,
1512                    &text_layout_details,
1513                ),
1514                (
1515                    DisplayPoint::new(DisplayRow(0), 7),
1516                    language::SelectionGoal::HorizontalPosition(x.0)
1517                )
1518            );
1519            assert_eq!(
1520                movement::down(
1521                    &snapshot,
1522                    DisplayPoint::new(DisplayRow(0), 7),
1523                    language::SelectionGoal::HorizontalPosition(x.0),
1524                    false,
1525                    &text_layout_details
1526                ),
1527                (
1528                    DisplayPoint::new(DisplayRow(1), 10),
1529                    language::SelectionGoal::HorizontalPosition(x.0)
1530                )
1531            );
1532            assert_eq!(
1533                movement::down(
1534                    &snapshot,
1535                    DisplayPoint::new(DisplayRow(1), 10),
1536                    language::SelectionGoal::HorizontalPosition(x.0),
1537                    false,
1538                    &text_layout_details
1539                ),
1540                (
1541                    DisplayPoint::new(DisplayRow(2), 4),
1542                    language::SelectionGoal::HorizontalPosition(x.0)
1543                )
1544            );
1545
1546            let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
1547            buffer.update(cx, |buffer, cx| {
1548                buffer.edit([(ix..ix, "and ")], None, cx);
1549            });
1550
1551            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1552            assert_eq!(
1553                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1554                "three four \nfive\nsix and \nseven eight"
1555            );
1556
1557            // Re-wrap on font size changes
1558            map.update(cx, |map, cx| {
1559                map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
1560            });
1561
1562            let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
1563            assert_eq!(
1564                snapshot.text_chunks(DisplayRow(1)).collect::<String>(),
1565                "three \nfour five\nsix and \nseven \neight"
1566            )
1567        });
1568    }
1569
1570    #[gpui::test]
1571    fn test_text_chunks(cx: &mut gpui::AppContext) {
1572        init_test(cx, |_| {});
1573
1574        let text = sample_text(6, 6, 'a');
1575        let buffer = MultiBuffer::build_simple(&text, cx);
1576
1577        let font_size = px(14.0);
1578        let map = cx.new_model(|cx| {
1579            DisplayMap::new(
1580                buffer.clone(),
1581                font("Helvetica"),
1582                font_size,
1583                None,
1584                true,
1585                1,
1586                1,
1587                0,
1588                FoldPlaceholder::test(),
1589                cx,
1590            )
1591        });
1592
1593        buffer.update(cx, |buffer, cx| {
1594            buffer.edit(
1595                vec![
1596                    (
1597                        MultiBufferPoint::new(1, 0)..MultiBufferPoint::new(1, 0),
1598                        "\t",
1599                    ),
1600                    (
1601                        MultiBufferPoint::new(1, 1)..MultiBufferPoint::new(1, 1),
1602                        "\t",
1603                    ),
1604                    (
1605                        MultiBufferPoint::new(2, 1)..MultiBufferPoint::new(2, 1),
1606                        "\t",
1607                    ),
1608                ],
1609                None,
1610                cx,
1611            )
1612        });
1613
1614        assert_eq!(
1615            map.update(cx, |map, cx| map.snapshot(cx))
1616                .text_chunks(DisplayRow(1))
1617                .collect::<String>()
1618                .lines()
1619                .next(),
1620            Some("    b   bbbbb")
1621        );
1622        assert_eq!(
1623            map.update(cx, |map, cx| map.snapshot(cx))
1624                .text_chunks(DisplayRow(2))
1625                .collect::<String>()
1626                .lines()
1627                .next(),
1628            Some("c   ccccc")
1629        );
1630    }
1631
1632    #[gpui::test]
1633    async fn test_chunks(cx: &mut gpui::TestAppContext) {
1634        use unindent::Unindent as _;
1635
1636        let text = r#"
1637            fn outer() {}
1638
1639            mod module {
1640                fn inner() {}
1641            }"#
1642        .unindent();
1643
1644        let theme =
1645            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1646        let language = Arc::new(
1647            Language::new(
1648                LanguageConfig {
1649                    name: "Test".into(),
1650                    matcher: LanguageMatcher {
1651                        path_suffixes: vec![".test".to_string()],
1652                        ..Default::default()
1653                    },
1654                    ..Default::default()
1655                },
1656                Some(tree_sitter_rust::language()),
1657            )
1658            .with_highlights_query(
1659                r#"
1660                (mod_item name: (identifier) body: _ @mod.body)
1661                (function_item name: (identifier) @fn.name)
1662                "#,
1663            )
1664            .unwrap(),
1665        );
1666        language.set_theme(&theme);
1667
1668        cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
1669
1670        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1671        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1672        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1673
1674        let font_size = px(14.0);
1675
1676        let map = cx.new_model(|cx| {
1677            DisplayMap::new(
1678                buffer,
1679                font("Helvetica"),
1680                font_size,
1681                None,
1682                true,
1683                1,
1684                1,
1685                1,
1686                FoldPlaceholder::test(),
1687                cx,
1688            )
1689        });
1690        assert_eq!(
1691            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1692            vec![
1693                ("fn ".to_string(), None),
1694                ("outer".to_string(), Some(Hsla::blue())),
1695                ("() {}\n\nmod module ".to_string(), None),
1696                ("{\n    fn ".to_string(), Some(Hsla::red())),
1697                ("inner".to_string(), Some(Hsla::blue())),
1698                ("() {}\n}".to_string(), Some(Hsla::red())),
1699            ]
1700        );
1701        assert_eq!(
1702            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1703            vec![
1704                ("    fn ".to_string(), Some(Hsla::red())),
1705                ("inner".to_string(), Some(Hsla::blue())),
1706                ("() {}\n}".to_string(), Some(Hsla::red())),
1707            ]
1708        );
1709
1710        map.update(cx, |map, cx| {
1711            map.fold(
1712                vec![(
1713                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1714                    FoldPlaceholder::test(),
1715                )],
1716                cx,
1717            )
1718        });
1719        assert_eq!(
1720            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(2), &map, &theme, cx)),
1721            vec![
1722                ("fn ".to_string(), None),
1723                ("out".to_string(), Some(Hsla::blue())),
1724                ("".to_string(), None),
1725                ("  fn ".to_string(), Some(Hsla::red())),
1726                ("inner".to_string(), Some(Hsla::blue())),
1727                ("() {}\n}".to_string(), Some(Hsla::red())),
1728            ]
1729        );
1730    }
1731
1732    // todo(linux) fails due to pixel differences in text rendering
1733    #[cfg(target_os = "macos")]
1734    #[gpui::test]
1735    async fn test_chunks_with_soft_wrapping(cx: &mut gpui::TestAppContext) {
1736        use unindent::Unindent as _;
1737
1738        cx.background_executor
1739            .set_block_on_ticks(usize::MAX..=usize::MAX);
1740
1741        let text = r#"
1742            fn outer() {}
1743
1744            mod module {
1745                fn inner() {}
1746            }"#
1747        .unindent();
1748
1749        let theme =
1750            SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]);
1751        let language = Arc::new(
1752            Language::new(
1753                LanguageConfig {
1754                    name: "Test".into(),
1755                    matcher: LanguageMatcher {
1756                        path_suffixes: vec![".test".to_string()],
1757                        ..Default::default()
1758                    },
1759                    ..Default::default()
1760                },
1761                Some(tree_sitter_rust::language()),
1762            )
1763            .with_highlights_query(
1764                r#"
1765                (mod_item name: (identifier) body: _ @mod.body)
1766                (function_item name: (identifier) @fn.name)
1767                "#,
1768            )
1769            .unwrap(),
1770        );
1771        language.set_theme(&theme);
1772
1773        cx.update(|cx| init_test(cx, |_| {}));
1774
1775        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1776        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1777        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1778
1779        let font_size = px(16.0);
1780
1781        let map = cx.new_model(|cx| {
1782            DisplayMap::new(
1783                buffer,
1784                font("Courier"),
1785                font_size,
1786                Some(px(40.0)),
1787                true,
1788                1,
1789                1,
1790                0,
1791                FoldPlaceholder::test(),
1792                cx,
1793            )
1794        });
1795        assert_eq!(
1796            cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
1797            [
1798                ("fn \n".to_string(), None),
1799                ("oute\nr".to_string(), Some(Hsla::blue())),
1800                ("() \n{}\n\n".to_string(), None),
1801            ]
1802        );
1803        assert_eq!(
1804            cx.update(|cx| syntax_chunks(DisplayRow(3)..DisplayRow(5), &map, &theme, cx)),
1805            [("{}\n\n".to_string(), None)]
1806        );
1807
1808        map.update(cx, |map, cx| {
1809            map.fold(
1810                vec![(
1811                    MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
1812                    FoldPlaceholder::test(),
1813                )],
1814                cx,
1815            )
1816        });
1817        assert_eq!(
1818            cx.update(|cx| syntax_chunks(DisplayRow(1)..DisplayRow(4), &map, &theme, cx)),
1819            [
1820                ("out".to_string(), Some(Hsla::blue())),
1821                ("\n".to_string(), None),
1822                ("  \nfn ".to_string(), Some(Hsla::red())),
1823                ("i\n".to_string(), Some(Hsla::blue()))
1824            ]
1825        );
1826    }
1827
1828    #[gpui::test]
1829    async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
1830        cx.update(|cx| init_test(cx, |_| {}));
1831
1832        let theme =
1833            SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]);
1834        let language = Arc::new(
1835            Language::new(
1836                LanguageConfig {
1837                    name: "Test".into(),
1838                    matcher: LanguageMatcher {
1839                        path_suffixes: vec![".test".to_string()],
1840                        ..Default::default()
1841                    },
1842                    ..Default::default()
1843                },
1844                Some(tree_sitter_rust::language()),
1845            )
1846            .with_highlights_query(
1847                r#"
1848                ":" @operator
1849                (string_literal) @string
1850                "#,
1851            )
1852            .unwrap(),
1853        );
1854        language.set_theme(&theme);
1855
1856        let (text, highlighted_ranges) = marked_text_ranges(r#"constˇ «a»: B = "c «d»""#, false);
1857
1858        let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
1859        cx.condition(&buffer, |buf, _| !buf.is_parsing()).await;
1860
1861        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
1862        let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1863
1864        let font_size = px(16.0);
1865        let map = cx.new_model(|cx| {
1866            DisplayMap::new(
1867                buffer,
1868                font("Courier"),
1869                font_size,
1870                None,
1871                true,
1872                1,
1873                1,
1874                1,
1875                FoldPlaceholder::test(),
1876                cx,
1877            )
1878        });
1879
1880        enum MyType {}
1881
1882        let style = HighlightStyle {
1883            color: Some(Hsla::blue()),
1884            ..Default::default()
1885        };
1886
1887        map.update(cx, |map, _cx| {
1888            map.highlight_text(
1889                TypeId::of::<MyType>(),
1890                highlighted_ranges
1891                    .into_iter()
1892                    .map(|range| {
1893                        buffer_snapshot.anchor_before(range.start)
1894                            ..buffer_snapshot.anchor_before(range.end)
1895                    })
1896                    .collect(),
1897                style,
1898            );
1899        });
1900
1901        assert_eq!(
1902            cx.update(|cx| chunks(DisplayRow(0)..DisplayRow(10), &map, &theme, cx)),
1903            [
1904                ("const ".to_string(), None, None),
1905                ("a".to_string(), None, Some(Hsla::blue())),
1906                (":".to_string(), Some(Hsla::red()), None),
1907                (" B = ".to_string(), None, None),
1908                ("\"c ".to_string(), Some(Hsla::green()), None),
1909                ("d".to_string(), Some(Hsla::green()), Some(Hsla::blue())),
1910                ("\"".to_string(), Some(Hsla::green()), None),
1911            ]
1912        );
1913    }
1914
1915    #[gpui::test]
1916    fn test_clip_point(cx: &mut gpui::AppContext) {
1917        init_test(cx, |_| {});
1918
1919        fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
1920            let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
1921
1922            match bias {
1923                Bias::Left => {
1924                    if shift_right {
1925                        *markers[1].column_mut() += 1;
1926                    }
1927
1928                    assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
1929                }
1930                Bias::Right => {
1931                    if shift_right {
1932                        *markers[0].column_mut() += 1;
1933                    }
1934
1935                    assert_eq!(unmarked_snapshot.clip_point(markers[0], bias), markers[1])
1936                }
1937            };
1938        }
1939
1940        use Bias::{Left, Right};
1941        assert("ˇˇα", false, Left, cx);
1942        assert("ˇˇα", true, Left, cx);
1943        assert("ˇˇα", false, Right, cx);
1944        assert("ˇαˇ", true, Right, cx);
1945        assert("ˇˇ✋", false, Left, cx);
1946        assert("ˇˇ✋", true, Left, cx);
1947        assert("ˇˇ✋", false, Right, cx);
1948        assert("ˇ✋ˇ", true, Right, cx);
1949        assert("ˇˇ🍐", false, Left, cx);
1950        assert("ˇˇ🍐", true, Left, cx);
1951        assert("ˇˇ🍐", false, Right, cx);
1952        assert("ˇ🍐ˇ", true, Right, cx);
1953        assert("ˇˇ\t", false, Left, cx);
1954        assert("ˇˇ\t", true, Left, cx);
1955        assert("ˇˇ\t", false, Right, cx);
1956        assert("ˇ\tˇ", true, Right, cx);
1957        assert(" ˇˇ\t", false, Left, cx);
1958        assert(" ˇˇ\t", true, Left, cx);
1959        assert(" ˇˇ\t", false, Right, cx);
1960        assert(" ˇ\tˇ", true, Right, cx);
1961        assert("   ˇˇ\t", false, Left, cx);
1962        assert("   ˇˇ\t", false, Right, cx);
1963    }
1964
1965    #[gpui::test]
1966    fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
1967        init_test(cx, |_| {});
1968
1969        fn assert(text: &str, cx: &mut gpui::AppContext) {
1970            let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
1971            unmarked_snapshot.clip_at_line_ends = true;
1972            assert_eq!(
1973                unmarked_snapshot.clip_point(markers[1], Bias::Left),
1974                markers[0]
1975            );
1976        }
1977
1978        assert("ˇˇ", cx);
1979        assert("ˇaˇ", cx);
1980        assert("aˇbˇ", cx);
1981        assert("aˇαˇ", cx);
1982    }
1983
1984    #[gpui::test]
1985    fn test_creases(cx: &mut gpui::AppContext) {
1986        init_test(cx, |_| {});
1987
1988        let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
1989        let buffer = MultiBuffer::build_simple(text, cx);
1990        let font_size = px(14.0);
1991        cx.new_model(|cx| {
1992            let mut map = DisplayMap::new(
1993                buffer.clone(),
1994                font("Helvetica"),
1995                font_size,
1996                None,
1997                true,
1998                1,
1999                1,
2000                0,
2001                FoldPlaceholder::test(),
2002                cx,
2003            );
2004            let snapshot = map.buffer.read(cx).snapshot(cx);
2005            let range =
2006                snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
2007
2008            map.crease_map.insert(
2009                [Crease::new(
2010                    range,
2011                    FoldPlaceholder::test(),
2012                    |_row, _status, _toggle, _cx| div(),
2013                    |_row, _status, _cx| div(),
2014                )],
2015                &map.buffer.read(cx).snapshot(cx),
2016            );
2017
2018            map
2019        });
2020    }
2021
2022    #[gpui::test]
2023    fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
2024        init_test(cx, |_| {});
2025
2026        let text = "\t\tα\nβ\t\n🏀β\t\tγ";
2027        let buffer = MultiBuffer::build_simple(text, cx);
2028        let font_size = px(14.0);
2029
2030        let map = cx.new_model(|cx| {
2031            DisplayMap::new(
2032                buffer.clone(),
2033                font("Helvetica"),
2034                font_size,
2035                None,
2036                true,
2037                1,
2038                1,
2039                0,
2040                FoldPlaceholder::test(),
2041                cx,
2042            )
2043        });
2044        let map = map.update(cx, |map, cx| map.snapshot(cx));
2045        assert_eq!(map.text(), "✅       α\nβ   \n🏀β      γ");
2046        assert_eq!(
2047            map.text_chunks(DisplayRow(0)).collect::<String>(),
2048            "✅       α\nβ   \n🏀β      γ"
2049        );
2050        assert_eq!(
2051            map.text_chunks(DisplayRow(1)).collect::<String>(),
2052            "β   \n🏀β      γ"
2053        );
2054        assert_eq!(
2055            map.text_chunks(DisplayRow(2)).collect::<String>(),
2056            "🏀β      γ"
2057        );
2058
2059        let point = MultiBufferPoint::new(0, "\t\t".len() as u32);
2060        let display_point = DisplayPoint::new(DisplayRow(0), "".len() as u32);
2061        assert_eq!(point.to_display_point(&map), display_point);
2062        assert_eq!(display_point.to_point(&map), point);
2063
2064        let point = MultiBufferPoint::new(1, "β\t".len() as u32);
2065        let display_point = DisplayPoint::new(DisplayRow(1), "β   ".len() as u32);
2066        assert_eq!(point.to_display_point(&map), display_point);
2067        assert_eq!(display_point.to_point(&map), point,);
2068
2069        let point = MultiBufferPoint::new(2, "🏀β\t\t".len() as u32);
2070        let display_point = DisplayPoint::new(DisplayRow(2), "🏀β      ".len() as u32);
2071        assert_eq!(point.to_display_point(&map), display_point);
2072        assert_eq!(display_point.to_point(&map), point,);
2073
2074        // Display points inside of expanded tabs
2075        assert_eq!(
2076            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
2077            MultiBufferPoint::new(0, "\t".len() as u32),
2078        );
2079        assert_eq!(
2080            DisplayPoint::new(DisplayRow(0), "".len() as u32).to_point(&map),
2081            MultiBufferPoint::new(0, "".len() as u32),
2082        );
2083
2084        // Clipping display points inside of multi-byte characters
2085        assert_eq!(
2086            map.clip_point(
2087                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
2088                Left
2089            ),
2090            DisplayPoint::new(DisplayRow(0), 0)
2091        );
2092        assert_eq!(
2093            map.clip_point(
2094                DisplayPoint::new(DisplayRow(0), "".len() as u32 - 1),
2095                Bias::Right
2096            ),
2097            DisplayPoint::new(DisplayRow(0), "".len() as u32)
2098        );
2099    }
2100
2101    #[gpui::test]
2102    fn test_max_point(cx: &mut gpui::AppContext) {
2103        init_test(cx, |_| {});
2104
2105        let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
2106        let font_size = px(14.0);
2107        let map = cx.new_model(|cx| {
2108            DisplayMap::new(
2109                buffer.clone(),
2110                font("Helvetica"),
2111                font_size,
2112                None,
2113                true,
2114                1,
2115                1,
2116                0,
2117                FoldPlaceholder::test(),
2118                cx,
2119            )
2120        });
2121        assert_eq!(
2122            map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
2123            DisplayPoint::new(DisplayRow(1), 11)
2124        )
2125    }
2126
2127    fn syntax_chunks(
2128        rows: Range<DisplayRow>,
2129        map: &Model<DisplayMap>,
2130        theme: &SyntaxTheme,
2131        cx: &mut AppContext,
2132    ) -> Vec<(String, Option<Hsla>)> {
2133        chunks(rows, map, theme, cx)
2134            .into_iter()
2135            .map(|(text, color, _)| (text, color))
2136            .collect()
2137    }
2138
2139    fn chunks(
2140        rows: Range<DisplayRow>,
2141        map: &Model<DisplayMap>,
2142        theme: &SyntaxTheme,
2143        cx: &mut AppContext,
2144    ) -> Vec<(String, Option<Hsla>, Option<Hsla>)> {
2145        let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
2146        let mut chunks: Vec<(String, Option<Hsla>, Option<Hsla>)> = Vec::new();
2147        for chunk in snapshot.chunks(rows, true, HighlightStyles::default()) {
2148            let syntax_color = chunk
2149                .syntax_highlight_id
2150                .and_then(|id| id.style(theme)?.color);
2151            let highlight_color = chunk.highlight_style.and_then(|style| style.color);
2152            if let Some((last_chunk, last_syntax_color, last_highlight_color)) = chunks.last_mut() {
2153                if syntax_color == *last_syntax_color && highlight_color == *last_highlight_color {
2154                    last_chunk.push_str(chunk.text);
2155                    continue;
2156                }
2157            }
2158            chunks.push((chunk.text.to_string(), syntax_color, highlight_color));
2159        }
2160        chunks
2161    }
2162
2163    fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2164        let settings = SettingsStore::test(cx);
2165        cx.set_global(settings);
2166        language::init(cx);
2167        crate::init(cx);
2168        Project::init_settings(cx);
2169        theme::init(LoadThemes::JustBase, cx);
2170        cx.update_global::<SettingsStore, _>(|store, cx| {
2171            store.update_user_settings::<AllLanguageSettings>(cx, f);
2172        });
2173    }
2174}