display_map.rs

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