display_map.rs

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