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