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