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