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