element.rs

   1use crate::{
   2    blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
   3    display_map::{
   4        BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
   5        TransformBlock,
   6    },
   7    editor_settings::{DoubleClickInMultibuffer, MultiCursorModifier, ShowScrollbar},
   8    git::{blame::GitBlame, diff_hunk_to_display, DisplayDiffHunk},
   9    hover_popover::{
  10        self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
  11    },
  12    items::BufferSearchHighlights,
  13    mouse_context_menu::{self, MouseContextMenu},
  14    scroll::scroll_amount::ScrollAmount,
  15    CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
  16    EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, GutterDimensions, HalfPageDown,
  17    HalfPageUp, HoveredCursor, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
  18    SelectPhase, Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
  19};
  20use anyhow::Result;
  21use collections::{BTreeMap, HashMap};
  22use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
  23use gpui::{
  24    anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
  25    transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
  26    ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementContext,
  27    ElementInputHandler, Entity, Hitbox, Hsla, InteractiveElement, IntoElement,
  28    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
  29    ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful,
  30    StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
  31    ViewContext, WeakView, WindowContext,
  32};
  33use itertools::Itertools;
  34use language::language_settings::ShowWhitespaceSetting;
  35use lsp::DiagnosticSeverity;
  36use multi_buffer::Anchor;
  37use project::{
  38    project_settings::{GitGutterSetting, ProjectSettings},
  39    ProjectPath,
  40};
  41use settings::Settings;
  42use smallvec::SmallVec;
  43use std::{
  44    any::TypeId,
  45    borrow::Cow,
  46    cmp::{self, max, Ordering},
  47    fmt::Write,
  48    iter, mem,
  49    ops::Range,
  50    sync::Arc,
  51};
  52use sum_tree::Bias;
  53use theme::{ActiveTheme, PlayerColor};
  54use ui::prelude::*;
  55use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
  56use util::ResultExt;
  57use workspace::{item::Item, Workspace};
  58
  59struct SelectionLayout {
  60    head: DisplayPoint,
  61    cursor_shape: CursorShape,
  62    is_newest: bool,
  63    is_local: bool,
  64    range: Range<DisplayPoint>,
  65    active_rows: Range<u32>,
  66    user_name: Option<SharedString>,
  67}
  68
  69impl SelectionLayout {
  70    fn new<T: ToPoint + ToDisplayPoint + Clone>(
  71        selection: Selection<T>,
  72        line_mode: bool,
  73        cursor_shape: CursorShape,
  74        map: &DisplaySnapshot,
  75        is_newest: bool,
  76        is_local: bool,
  77        user_name: Option<SharedString>,
  78    ) -> Self {
  79        let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
  80        let display_selection = point_selection.map(|p| p.to_display_point(map));
  81        let mut range = display_selection.range();
  82        let mut head = display_selection.head();
  83        let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
  84            ..map.next_line_boundary(point_selection.end).1.row();
  85
  86        // vim visual line mode
  87        if line_mode {
  88            let point_range = map.expand_to_line(point_selection.range());
  89            range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
  90        }
  91
  92        // any vim visual mode (including line mode)
  93        if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed {
  94            if head.column() > 0 {
  95                head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
  96            } else if head.row() > 0 && head != map.max_point() {
  97                head = map.clip_point(
  98                    DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
  99                    Bias::Left,
 100                );
 101                // updating range.end is a no-op unless you're cursor is
 102                // on the newline containing a multi-buffer divider
 103                // in which case the clip_point may have moved the head up
 104                // an additional row.
 105                range.end = DisplayPoint::new(head.row() + 1, 0);
 106                active_rows.end = head.row();
 107            }
 108        }
 109
 110        Self {
 111            head,
 112            cursor_shape,
 113            is_newest,
 114            is_local,
 115            range,
 116            active_rows,
 117            user_name,
 118        }
 119    }
 120}
 121
 122pub struct EditorElement {
 123    editor: View<Editor>,
 124    style: EditorStyle,
 125}
 126
 127impl EditorElement {
 128    pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
 129        Self {
 130            editor: editor.clone(),
 131            style,
 132        }
 133    }
 134
 135    fn register_actions(&self, cx: &mut WindowContext) {
 136        let view = &self.editor;
 137        view.update(cx, |editor, cx| {
 138            for action in editor.editor_actions.iter() {
 139                (action)(cx)
 140            }
 141        });
 142
 143        crate::rust_analyzer_ext::apply_related_actions(view, cx);
 144        register_action(view, cx, Editor::move_left);
 145        register_action(view, cx, Editor::move_right);
 146        register_action(view, cx, Editor::move_down);
 147        register_action(view, cx, Editor::move_down_by_lines);
 148        register_action(view, cx, Editor::select_down_by_lines);
 149        register_action(view, cx, Editor::move_up);
 150        register_action(view, cx, Editor::move_up_by_lines);
 151        register_action(view, cx, Editor::select_up_by_lines);
 152        register_action(view, cx, Editor::cancel);
 153        register_action(view, cx, Editor::newline);
 154        register_action(view, cx, Editor::newline_above);
 155        register_action(view, cx, Editor::newline_below);
 156        register_action(view, cx, Editor::backspace);
 157        register_action(view, cx, Editor::delete);
 158        register_action(view, cx, Editor::tab);
 159        register_action(view, cx, Editor::tab_prev);
 160        register_action(view, cx, Editor::indent);
 161        register_action(view, cx, Editor::outdent);
 162        register_action(view, cx, Editor::delete_line);
 163        register_action(view, cx, Editor::join_lines);
 164        register_action(view, cx, Editor::sort_lines_case_sensitive);
 165        register_action(view, cx, Editor::sort_lines_case_insensitive);
 166        register_action(view, cx, Editor::reverse_lines);
 167        register_action(view, cx, Editor::shuffle_lines);
 168        register_action(view, cx, Editor::convert_to_upper_case);
 169        register_action(view, cx, Editor::convert_to_lower_case);
 170        register_action(view, cx, Editor::convert_to_title_case);
 171        register_action(view, cx, Editor::convert_to_snake_case);
 172        register_action(view, cx, Editor::convert_to_kebab_case);
 173        register_action(view, cx, Editor::convert_to_upper_camel_case);
 174        register_action(view, cx, Editor::convert_to_lower_camel_case);
 175        register_action(view, cx, Editor::delete_to_previous_word_start);
 176        register_action(view, cx, Editor::delete_to_previous_subword_start);
 177        register_action(view, cx, Editor::delete_to_next_word_end);
 178        register_action(view, cx, Editor::delete_to_next_subword_end);
 179        register_action(view, cx, Editor::delete_to_beginning_of_line);
 180        register_action(view, cx, Editor::delete_to_end_of_line);
 181        register_action(view, cx, Editor::cut_to_end_of_line);
 182        register_action(view, cx, Editor::duplicate_line_up);
 183        register_action(view, cx, Editor::duplicate_line_down);
 184        register_action(view, cx, Editor::move_line_up);
 185        register_action(view, cx, Editor::move_line_down);
 186        register_action(view, cx, Editor::transpose);
 187        register_action(view, cx, Editor::cut);
 188        register_action(view, cx, Editor::copy);
 189        register_action(view, cx, Editor::paste);
 190        register_action(view, cx, Editor::undo);
 191        register_action(view, cx, Editor::redo);
 192        register_action(view, cx, Editor::move_page_up);
 193        register_action(view, cx, Editor::move_page_down);
 194        register_action(view, cx, Editor::next_screen);
 195        register_action(view, cx, Editor::scroll_cursor_top);
 196        register_action(view, cx, Editor::scroll_cursor_center);
 197        register_action(view, cx, Editor::scroll_cursor_bottom);
 198        register_action(view, cx, |editor, _: &LineDown, cx| {
 199            editor.scroll_screen(&ScrollAmount::Line(1.), cx)
 200        });
 201        register_action(view, cx, |editor, _: &LineUp, cx| {
 202            editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
 203        });
 204        register_action(view, cx, |editor, _: &HalfPageDown, cx| {
 205            editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
 206        });
 207        register_action(view, cx, |editor, _: &HalfPageUp, cx| {
 208            editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
 209        });
 210        register_action(view, cx, |editor, _: &PageDown, cx| {
 211            editor.scroll_screen(&ScrollAmount::Page(1.), cx)
 212        });
 213        register_action(view, cx, |editor, _: &PageUp, cx| {
 214            editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
 215        });
 216        register_action(view, cx, Editor::move_to_previous_word_start);
 217        register_action(view, cx, Editor::move_to_previous_subword_start);
 218        register_action(view, cx, Editor::move_to_next_word_end);
 219        register_action(view, cx, Editor::move_to_next_subword_end);
 220        register_action(view, cx, Editor::move_to_beginning_of_line);
 221        register_action(view, cx, Editor::move_to_end_of_line);
 222        register_action(view, cx, Editor::move_to_start_of_paragraph);
 223        register_action(view, cx, Editor::move_to_end_of_paragraph);
 224        register_action(view, cx, Editor::move_to_beginning);
 225        register_action(view, cx, Editor::move_to_end);
 226        register_action(view, cx, Editor::select_up);
 227        register_action(view, cx, Editor::select_down);
 228        register_action(view, cx, Editor::select_left);
 229        register_action(view, cx, Editor::select_right);
 230        register_action(view, cx, Editor::select_to_previous_word_start);
 231        register_action(view, cx, Editor::select_to_previous_subword_start);
 232        register_action(view, cx, Editor::select_to_next_word_end);
 233        register_action(view, cx, Editor::select_to_next_subword_end);
 234        register_action(view, cx, Editor::select_to_beginning_of_line);
 235        register_action(view, cx, Editor::select_to_end_of_line);
 236        register_action(view, cx, Editor::select_to_start_of_paragraph);
 237        register_action(view, cx, Editor::select_to_end_of_paragraph);
 238        register_action(view, cx, Editor::select_to_beginning);
 239        register_action(view, cx, Editor::select_to_end);
 240        register_action(view, cx, Editor::select_all);
 241        register_action(view, cx, |editor, action, cx| {
 242            editor.select_all_matches(action, cx).log_err();
 243        });
 244        register_action(view, cx, Editor::select_line);
 245        register_action(view, cx, Editor::split_selection_into_lines);
 246        register_action(view, cx, Editor::add_selection_above);
 247        register_action(view, cx, Editor::add_selection_below);
 248        register_action(view, cx, |editor, action, cx| {
 249            editor.select_next(action, cx).log_err();
 250        });
 251        register_action(view, cx, |editor, action, cx| {
 252            editor.select_previous(action, cx).log_err();
 253        });
 254        register_action(view, cx, Editor::toggle_comments);
 255        register_action(view, cx, Editor::select_larger_syntax_node);
 256        register_action(view, cx, Editor::select_smaller_syntax_node);
 257        register_action(view, cx, Editor::move_to_enclosing_bracket);
 258        register_action(view, cx, Editor::undo_selection);
 259        register_action(view, cx, Editor::redo_selection);
 260        if !view.read(cx).is_singleton(cx) {
 261            register_action(view, cx, Editor::expand_excerpts);
 262        }
 263        register_action(view, cx, Editor::go_to_diagnostic);
 264        register_action(view, cx, Editor::go_to_prev_diagnostic);
 265        register_action(view, cx, Editor::go_to_hunk);
 266        register_action(view, cx, Editor::go_to_prev_hunk);
 267        register_action(view, cx, |editor, a, cx| {
 268            editor.go_to_definition(a, cx).detach_and_log_err(cx);
 269        });
 270        register_action(view, cx, |editor, a, cx| {
 271            editor.go_to_definition_split(a, cx).detach_and_log_err(cx);
 272        });
 273        register_action(view, cx, |editor, a, cx| {
 274            editor.go_to_implementation(a, cx).detach_and_log_err(cx);
 275        });
 276        register_action(view, cx, |editor, a, cx| {
 277            editor
 278                .go_to_implementation_split(a, cx)
 279                .detach_and_log_err(cx);
 280        });
 281        register_action(view, cx, |editor, a, cx| {
 282            editor.go_to_type_definition(a, cx).detach_and_log_err(cx);
 283        });
 284        register_action(view, cx, |editor, a, cx| {
 285            editor
 286                .go_to_type_definition_split(a, cx)
 287                .detach_and_log_err(cx);
 288        });
 289        register_action(view, cx, Editor::open_url);
 290        register_action(view, cx, Editor::fold);
 291        register_action(view, cx, Editor::fold_at);
 292        register_action(view, cx, Editor::unfold_lines);
 293        register_action(view, cx, Editor::unfold_at);
 294        register_action(view, cx, Editor::fold_selected_ranges);
 295        register_action(view, cx, Editor::show_completions);
 296        register_action(view, cx, Editor::toggle_code_actions);
 297        register_action(view, cx, Editor::open_excerpts);
 298        register_action(view, cx, Editor::open_excerpts_in_split);
 299        register_action(view, cx, Editor::toggle_soft_wrap);
 300        register_action(view, cx, Editor::toggle_line_numbers);
 301        register_action(view, cx, Editor::toggle_inlay_hints);
 302        register_action(view, cx, hover_popover::hover);
 303        register_action(view, cx, Editor::reveal_in_finder);
 304        register_action(view, cx, Editor::copy_path);
 305        register_action(view, cx, Editor::copy_relative_path);
 306        register_action(view, cx, Editor::copy_highlight_json);
 307        register_action(view, cx, Editor::copy_permalink_to_line);
 308        register_action(view, cx, Editor::open_permalink_to_line);
 309        register_action(view, cx, Editor::toggle_git_blame);
 310        register_action(view, cx, Editor::toggle_git_blame_inline);
 311        register_action(view, cx, |editor, action, cx| {
 312            if let Some(task) = editor.format(action, cx) {
 313                task.detach_and_log_err(cx);
 314            } else {
 315                cx.propagate();
 316            }
 317        });
 318        register_action(view, cx, Editor::restart_language_server);
 319        register_action(view, cx, Editor::show_character_palette);
 320        register_action(view, cx, |editor, action, cx| {
 321            if let Some(task) = editor.confirm_completion(action, cx) {
 322                task.detach_and_log_err(cx);
 323            } else {
 324                cx.propagate();
 325            }
 326        });
 327        register_action(view, cx, |editor, action, cx| {
 328            if let Some(task) = editor.confirm_code_action(action, cx) {
 329                task.detach_and_log_err(cx);
 330            } else {
 331                cx.propagate();
 332            }
 333        });
 334        register_action(view, cx, |editor, action, cx| {
 335            if let Some(task) = editor.rename(action, cx) {
 336                task.detach_and_log_err(cx);
 337            } else {
 338                cx.propagate();
 339            }
 340        });
 341        register_action(view, cx, |editor, action, cx| {
 342            if let Some(task) = editor.confirm_rename(action, cx) {
 343                task.detach_and_log_err(cx);
 344            } else {
 345                cx.propagate();
 346            }
 347        });
 348        register_action(view, cx, |editor, action, cx| {
 349            if let Some(task) = editor.find_all_references(action, cx) {
 350                task.detach_and_log_err(cx);
 351            } else {
 352                cx.propagate();
 353            }
 354        });
 355        register_action(view, cx, Editor::next_inline_completion);
 356        register_action(view, cx, Editor::previous_inline_completion);
 357        register_action(view, cx, Editor::show_inline_completion);
 358        register_action(view, cx, Editor::context_menu_first);
 359        register_action(view, cx, Editor::context_menu_prev);
 360        register_action(view, cx, Editor::context_menu_next);
 361        register_action(view, cx, Editor::context_menu_last);
 362        register_action(view, cx, Editor::display_cursor_names);
 363        register_action(view, cx, Editor::unique_lines_case_insensitive);
 364        register_action(view, cx, Editor::unique_lines_case_sensitive);
 365        register_action(view, cx, Editor::accept_partial_inline_completion);
 366        register_action(view, cx, Editor::revert_selected_hunks);
 367        register_action(view, cx, Editor::open_active_item_in_terminal)
 368    }
 369
 370    fn register_key_listeners(&self, cx: &mut ElementContext, layout: &EditorLayout) {
 371        let position_map = layout.position_map.clone();
 372        cx.on_key_event({
 373            let editor = self.editor.clone();
 374            let text_hitbox = layout.text_hitbox.clone();
 375            move |event: &ModifiersChangedEvent, phase, cx| {
 376                if phase != DispatchPhase::Bubble {
 377                    return;
 378                }
 379
 380                editor.update(cx, |editor, cx| {
 381                    Self::modifiers_changed(editor, event, &position_map, &text_hitbox, cx)
 382                })
 383            }
 384        });
 385    }
 386
 387    fn modifiers_changed(
 388        editor: &mut Editor,
 389        event: &ModifiersChangedEvent,
 390        position_map: &PositionMap,
 391        text_hitbox: &Hitbox,
 392        cx: &mut ViewContext<Editor>,
 393    ) {
 394        let mouse_position = cx.mouse_position();
 395        if !text_hitbox.is_hovered(cx) {
 396            return;
 397        }
 398
 399        editor.update_hovered_link(
 400            position_map.point_for_position(text_hitbox.bounds, mouse_position),
 401            &position_map.snapshot,
 402            event.modifiers,
 403            cx,
 404        )
 405    }
 406
 407    fn mouse_left_down(
 408        editor: &mut Editor,
 409        event: &MouseDownEvent,
 410        position_map: &PositionMap,
 411        text_hitbox: &Hitbox,
 412        gutter_hitbox: &Hitbox,
 413        cx: &mut ViewContext<Editor>,
 414    ) {
 415        if cx.default_prevented() {
 416            return;
 417        }
 418
 419        let mut click_count = event.click_count;
 420        let mut modifiers = event.modifiers;
 421
 422        if gutter_hitbox.is_hovered(cx) {
 423            click_count = 3; // Simulate triple-click when clicking the gutter to select lines
 424        } else if !text_hitbox.is_hovered(cx) {
 425            return;
 426        }
 427
 428        if click_count == 2 && !editor.buffer().read(cx).is_singleton() {
 429            match EditorSettings::get_global(cx).double_click_in_multibuffer {
 430                DoubleClickInMultibuffer::Select => {
 431                    // do nothing special on double click, all selection logic is below
 432                }
 433                DoubleClickInMultibuffer::Open => {
 434                    if modifiers.alt {
 435                        // if double click is made with alt, pretend it's a regular double click without opening and alt,
 436                        // and run the selection logic.
 437                        modifiers.alt = false;
 438                    } else {
 439                        // if double click is made without alt, open the corresponding excerp
 440                        editor.open_excerpts(&OpenExcerpts, cx);
 441                        return;
 442                    }
 443                }
 444            }
 445        }
 446
 447        let point_for_position =
 448            position_map.point_for_position(text_hitbox.bounds, event.position);
 449        let position = point_for_position.previous_valid;
 450        if modifiers.shift && modifiers.alt {
 451            editor.select(
 452                SelectPhase::BeginColumnar {
 453                    position,
 454                    goal_column: point_for_position.exact_unclipped.column(),
 455                },
 456                cx,
 457            );
 458        } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.secondary()
 459        {
 460            editor.select(
 461                SelectPhase::Extend {
 462                    position,
 463                    click_count,
 464                },
 465                cx,
 466            );
 467        } else {
 468            let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
 469            let multi_cursor_modifier = match multi_cursor_setting {
 470                MultiCursorModifier::Alt => modifiers.alt,
 471                MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
 472            };
 473            editor.select(
 474                SelectPhase::Begin {
 475                    position,
 476                    add: multi_cursor_modifier,
 477                    click_count,
 478                },
 479                cx,
 480            );
 481        }
 482
 483        cx.stop_propagation();
 484    }
 485
 486    fn mouse_right_down(
 487        editor: &mut Editor,
 488        event: &MouseDownEvent,
 489        position_map: &PositionMap,
 490        text_hitbox: &Hitbox,
 491        cx: &mut ViewContext<Editor>,
 492    ) {
 493        if !text_hitbox.is_hovered(cx) {
 494            return;
 495        }
 496        let point_for_position =
 497            position_map.point_for_position(text_hitbox.bounds, event.position);
 498        mouse_context_menu::deploy_context_menu(
 499            editor,
 500            event.position,
 501            point_for_position.previous_valid,
 502            cx,
 503        );
 504        cx.stop_propagation();
 505    }
 506
 507    fn mouse_middle_down(
 508        editor: &mut Editor,
 509        event: &MouseDownEvent,
 510        position_map: &PositionMap,
 511        text_hitbox: &Hitbox,
 512        cx: &mut ViewContext<Editor>,
 513    ) {
 514        if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
 515            return;
 516        }
 517
 518        if let Some(item) = cx.read_from_primary() {
 519            let point_for_position =
 520                position_map.point_for_position(text_hitbox.bounds, event.position);
 521            let position = point_for_position.previous_valid;
 522
 523            editor.select(
 524                SelectPhase::Begin {
 525                    position,
 526                    add: false,
 527                    click_count: 1,
 528                },
 529                cx,
 530            );
 531            editor.insert(item.text(), cx);
 532        }
 533    }
 534
 535    fn mouse_up(
 536        editor: &mut Editor,
 537        event: &MouseUpEvent,
 538        position_map: &PositionMap,
 539        text_hitbox: &Hitbox,
 540        cx: &mut ViewContext<Editor>,
 541    ) {
 542        let end_selection = editor.has_pending_selection();
 543        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
 544
 545        if end_selection {
 546            editor.select(SelectPhase::End, cx);
 547        }
 548
 549        let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
 550        let multi_cursor_modifier = match multi_cursor_setting {
 551            MultiCursorModifier::Alt => event.modifiers.secondary(),
 552            MultiCursorModifier::CmdOrCtrl => event.modifiers.alt,
 553        };
 554
 555        if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(cx) {
 556            let point = position_map.point_for_position(text_hitbox.bounds, event.position);
 557            editor.handle_click_hovered_link(point, event.modifiers, cx);
 558
 559            cx.stop_propagation();
 560        } else if end_selection {
 561            cx.stop_propagation();
 562        }
 563    }
 564
 565    fn mouse_dragged(
 566        editor: &mut Editor,
 567        event: &MouseMoveEvent,
 568        position_map: &PositionMap,
 569        text_bounds: Bounds<Pixels>,
 570        cx: &mut ViewContext<Editor>,
 571    ) {
 572        if !editor.has_pending_selection() {
 573            return;
 574        }
 575
 576        let point_for_position = position_map.point_for_position(text_bounds, event.position);
 577        let mut scroll_delta = gpui::Point::<f32>::default();
 578        let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
 579        let top = text_bounds.origin.y + vertical_margin;
 580        let bottom = text_bounds.lower_left().y - vertical_margin;
 581        if event.position.y < top {
 582            scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
 583        }
 584        if event.position.y > bottom {
 585            scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
 586        }
 587
 588        let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
 589        let left = text_bounds.origin.x + horizontal_margin;
 590        let right = text_bounds.upper_right().x - horizontal_margin;
 591        if event.position.x < left {
 592            scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
 593        }
 594        if event.position.x > right {
 595            scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
 596        }
 597
 598        editor.select(
 599            SelectPhase::Update {
 600                position: point_for_position.previous_valid,
 601                goal_column: point_for_position.exact_unclipped.column(),
 602                scroll_delta,
 603            },
 604            cx,
 605        );
 606    }
 607
 608    fn mouse_moved(
 609        editor: &mut Editor,
 610        event: &MouseMoveEvent,
 611        position_map: &PositionMap,
 612        text_hitbox: &Hitbox,
 613        gutter_hitbox: &Hitbox,
 614        cx: &mut ViewContext<Editor>,
 615    ) {
 616        let modifiers = event.modifiers;
 617        let gutter_hovered = gutter_hitbox.is_hovered(cx);
 618        editor.set_gutter_hovered(gutter_hovered, cx);
 619
 620        // Don't trigger hover popover if mouse is hovering over context menu
 621        if text_hitbox.is_hovered(cx) {
 622            let point_for_position =
 623                position_map.point_for_position(text_hitbox.bounds, event.position);
 624
 625            editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx);
 626
 627            if let Some(point) = point_for_position.as_valid() {
 628                hover_at(editor, Some(point), cx);
 629                Self::update_visible_cursor(editor, point, position_map, cx);
 630            } else {
 631                hover_at(editor, None, cx);
 632            }
 633        } else {
 634            editor.hide_hovered_link(cx);
 635            hover_at(editor, None, cx);
 636            if gutter_hovered {
 637                cx.stop_propagation();
 638            }
 639        }
 640    }
 641
 642    fn update_visible_cursor(
 643        editor: &mut Editor,
 644        point: DisplayPoint,
 645        position_map: &PositionMap,
 646        cx: &mut ViewContext<Editor>,
 647    ) {
 648        let snapshot = &position_map.snapshot;
 649        let Some(hub) = editor.collaboration_hub() else {
 650            return;
 651        };
 652        let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
 653            ..DisplayPoint::new(
 654                point.row(),
 655                (point.column() + 1).min(snapshot.line_len(point.row())),
 656            );
 657
 658        let range = snapshot
 659            .buffer_snapshot
 660            .anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
 661            ..snapshot
 662                .buffer_snapshot
 663                .anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
 664
 665        let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
 666            return;
 667        };
 668        let key = crate::HoveredCursor {
 669            replica_id: selection.replica_id,
 670            selection_id: selection.selection.id,
 671        };
 672        editor.hovered_cursors.insert(
 673            key.clone(),
 674            cx.spawn(|editor, mut cx| async move {
 675                cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
 676                editor
 677                    .update(&mut cx, |editor, cx| {
 678                        editor.hovered_cursors.remove(&key);
 679                        cx.notify();
 680                    })
 681                    .ok();
 682            }),
 683        );
 684        cx.notify()
 685    }
 686
 687    fn layout_selections(
 688        &self,
 689        start_anchor: Anchor,
 690        end_anchor: Anchor,
 691        snapshot: &EditorSnapshot,
 692        start_row: u32,
 693        end_row: u32,
 694        cx: &mut ElementContext,
 695    ) -> (
 696        Vec<(PlayerColor, Vec<SelectionLayout>)>,
 697        BTreeMap<u32, bool>,
 698        Option<DisplayPoint>,
 699    ) {
 700        let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
 701        let mut active_rows = BTreeMap::new();
 702        let mut newest_selection_head = None;
 703        let editor = self.editor.read(cx);
 704
 705        if editor.show_local_selections {
 706            let mut local_selections: Vec<Selection<Point>> = editor
 707                .selections
 708                .disjoint_in_range(start_anchor..end_anchor, cx);
 709            local_selections.extend(editor.selections.pending(cx));
 710            let mut layouts = Vec::new();
 711            let newest = editor.selections.newest(cx);
 712            for selection in local_selections.drain(..) {
 713                let is_empty = selection.start == selection.end;
 714                let is_newest = selection == newest;
 715
 716                let layout = SelectionLayout::new(
 717                    selection,
 718                    editor.selections.line_mode,
 719                    editor.cursor_shape,
 720                    &snapshot.display_snapshot,
 721                    is_newest,
 722                    editor.leader_peer_id.is_none(),
 723                    None,
 724                );
 725                if is_newest {
 726                    newest_selection_head = Some(layout.head);
 727                }
 728
 729                for row in cmp::max(layout.active_rows.start, start_row)
 730                    ..=cmp::min(layout.active_rows.end, end_row)
 731                {
 732                    let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
 733                    *contains_non_empty_selection |= !is_empty;
 734                }
 735                layouts.push(layout);
 736            }
 737
 738            let player = if editor.read_only(cx) {
 739                cx.theme().players().read_only()
 740            } else {
 741                self.style.local_player
 742            };
 743
 744            selections.push((player, layouts));
 745        }
 746
 747        if let Some(collaboration_hub) = &editor.collaboration_hub {
 748            // When following someone, render the local selections in their color.
 749            if let Some(leader_id) = editor.leader_peer_id {
 750                if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
 751                    if let Some(participant_index) = collaboration_hub
 752                        .user_participant_indices(cx)
 753                        .get(&collaborator.user_id)
 754                    {
 755                        if let Some((local_selection_style, _)) = selections.first_mut() {
 756                            *local_selection_style = cx
 757                                .theme()
 758                                .players()
 759                                .color_for_participant(participant_index.0);
 760                        }
 761                    }
 762                }
 763            }
 764
 765            let mut remote_selections = HashMap::default();
 766            for selection in snapshot.remote_selections_in_range(
 767                &(start_anchor..end_anchor),
 768                collaboration_hub.as_ref(),
 769                cx,
 770            ) {
 771                let selection_style = if let Some(participant_index) = selection.participant_index {
 772                    cx.theme()
 773                        .players()
 774                        .color_for_participant(participant_index.0)
 775                } else {
 776                    cx.theme().players().absent()
 777                };
 778
 779                // Don't re-render the leader's selections, since the local selections
 780                // match theirs.
 781                if Some(selection.peer_id) == editor.leader_peer_id {
 782                    continue;
 783                }
 784                let key = HoveredCursor {
 785                    replica_id: selection.replica_id,
 786                    selection_id: selection.selection.id,
 787                };
 788
 789                let is_shown =
 790                    editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
 791
 792                remote_selections
 793                    .entry(selection.replica_id)
 794                    .or_insert((selection_style, Vec::new()))
 795                    .1
 796                    .push(SelectionLayout::new(
 797                        selection.selection,
 798                        selection.line_mode,
 799                        selection.cursor_shape,
 800                        &snapshot.display_snapshot,
 801                        false,
 802                        false,
 803                        if is_shown { selection.user_name } else { None },
 804                    ));
 805            }
 806
 807            selections.extend(remote_selections.into_values());
 808        }
 809        (selections, active_rows, newest_selection_head)
 810    }
 811
 812    #[allow(clippy::too_many_arguments)]
 813    fn layout_folds(
 814        &self,
 815        snapshot: &EditorSnapshot,
 816        content_origin: gpui::Point<Pixels>,
 817        visible_anchor_range: Range<Anchor>,
 818        visible_display_row_range: Range<u32>,
 819        scroll_pixel_position: gpui::Point<Pixels>,
 820        line_height: Pixels,
 821        line_layouts: &[LineWithInvisibles],
 822        cx: &mut ElementContext,
 823    ) -> Vec<FoldLayout> {
 824        snapshot
 825            .folds_in_range(visible_anchor_range.clone())
 826            .filter_map(|fold| {
 827                let fold_range = fold.range.clone();
 828                let display_range = fold.range.start.to_display_point(&snapshot)
 829                    ..fold.range.end.to_display_point(&snapshot);
 830                debug_assert_eq!(display_range.start.row(), display_range.end.row());
 831                let row = display_range.start.row();
 832                debug_assert!(row < visible_display_row_range.end);
 833                let line_layout = line_layouts
 834                    .get((row - visible_display_row_range.start) as usize)
 835                    .map(|l| &l.line)?;
 836
 837                let start_x = content_origin.x
 838                    + line_layout.x_for_index(display_range.start.column() as usize)
 839                    - scroll_pixel_position.x;
 840                let start_y = content_origin.y + row as f32 * line_height - scroll_pixel_position.y;
 841                let end_x = content_origin.x
 842                    + line_layout.x_for_index(display_range.end.column() as usize)
 843                    - scroll_pixel_position.x;
 844
 845                let fold_bounds = Bounds {
 846                    origin: point(start_x, start_y),
 847                    size: size(end_x - start_x, line_height),
 848                };
 849
 850                let mut hover_element = div()
 851                    .id(fold.id)
 852                    .size_full()
 853                    .cursor_pointer()
 854                    .on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
 855                    .on_click(
 856                        cx.listener_for(&self.editor, move |editor: &mut Editor, _, cx| {
 857                            editor.unfold_ranges(
 858                                [fold_range.start..fold_range.end],
 859                                true,
 860                                false,
 861                                cx,
 862                            );
 863                            cx.stop_propagation();
 864                        }),
 865                    )
 866                    .into_any();
 867                hover_element.layout(fold_bounds.origin, fold_bounds.size.into(), cx);
 868                Some(FoldLayout {
 869                    display_range,
 870                    hover_element,
 871                })
 872            })
 873            .collect()
 874    }
 875
 876    #[allow(clippy::too_many_arguments)]
 877    fn layout_cursors(
 878        &self,
 879        snapshot: &EditorSnapshot,
 880        selections: &[(PlayerColor, Vec<SelectionLayout>)],
 881        visible_display_row_range: Range<u32>,
 882        line_layouts: &[LineWithInvisibles],
 883        text_hitbox: &Hitbox,
 884        content_origin: gpui::Point<Pixels>,
 885        scroll_pixel_position: gpui::Point<Pixels>,
 886        line_height: Pixels,
 887        em_width: Pixels,
 888        cx: &mut ElementContext,
 889    ) -> Vec<CursorLayout> {
 890        self.editor.update(cx, |editor, cx| {
 891            let mut cursors = Vec::new();
 892            for (player_color, selections) in selections {
 893                for selection in selections {
 894                    let cursor_position = selection.head;
 895                    if (selection.is_local && !editor.show_local_cursors(cx))
 896                        || !visible_display_row_range.contains(&cursor_position.row())
 897                    {
 898                        continue;
 899                    }
 900
 901                    let cursor_row_layout = &line_layouts
 902                        [(cursor_position.row() - visible_display_row_range.start) as usize]
 903                        .line;
 904                    let cursor_column = cursor_position.column() as usize;
 905
 906                    let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
 907                    let mut block_width =
 908                        cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
 909                    if block_width == Pixels::ZERO {
 910                        block_width = em_width;
 911                    }
 912                    let block_text = if let CursorShape::Block = selection.cursor_shape {
 913                        snapshot.display_chars_at(cursor_position).next().and_then(
 914                            |(character, _)| {
 915                                let text = if character == '\n' {
 916                                    SharedString::from(" ")
 917                                } else {
 918                                    SharedString::from(character.to_string())
 919                                };
 920                                let len = text.len();
 921
 922                                let font = cursor_row_layout
 923                                    .font_id_for_index(cursor_column)
 924                                    .and_then(|cursor_font_id| {
 925                                        cx.text_system().get_font_for_id(cursor_font_id)
 926                                    })
 927                                    .unwrap_or(self.style.text.font());
 928
 929                                cx.text_system()
 930                                    .shape_line(
 931                                        text,
 932                                        cursor_row_layout.font_size,
 933                                        &[TextRun {
 934                                            len,
 935                                            font: font,
 936                                            color: self.style.background,
 937                                            background_color: None,
 938                                            strikethrough: None,
 939                                            underline: None,
 940                                        }],
 941                                    )
 942                                    .log_err()
 943                            },
 944                        )
 945                    } else {
 946                        None
 947                    };
 948
 949                    let x = cursor_character_x - scroll_pixel_position.x;
 950                    let y = (cursor_position.row() as f32 - scroll_pixel_position.y / line_height)
 951                        * line_height;
 952                    if selection.is_newest {
 953                        editor.pixel_position_of_newest_cursor = Some(point(
 954                            text_hitbox.origin.x + x + block_width / 2.,
 955                            text_hitbox.origin.y + y + line_height / 2.,
 956                        ))
 957                    }
 958
 959                    let mut cursor = CursorLayout {
 960                        color: player_color.cursor,
 961                        block_width,
 962                        origin: point(x, y),
 963                        line_height,
 964                        shape: selection.cursor_shape,
 965                        block_text,
 966                        cursor_name: None,
 967                    };
 968                    let cursor_name = selection.user_name.clone().map(|name| CursorName {
 969                        string: name,
 970                        color: self.style.background,
 971                        is_top_row: cursor_position.row() == 0,
 972                    });
 973                    cx.with_element_context(|cx| cursor.layout(content_origin, cursor_name, cx));
 974                    cursors.push(cursor);
 975                }
 976            }
 977            cursors
 978        })
 979    }
 980
 981    fn layout_scrollbar(
 982        &self,
 983        snapshot: &EditorSnapshot,
 984        bounds: Bounds<Pixels>,
 985        scroll_position: gpui::Point<f32>,
 986        rows_per_page: f32,
 987        cx: &mut ElementContext,
 988    ) -> Option<ScrollbarLayout> {
 989        let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
 990        let show_scrollbars = match scrollbar_settings.show {
 991            ShowScrollbar::Auto => {
 992                let editor = self.editor.read(cx);
 993                let is_singleton = editor.is_singleton(cx);
 994                // Git
 995                (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
 996                    ||
 997                    // Buffer Search Results
 998                    (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
 999                    ||
1000                    // Selected Symbol Occurrences
1001                    (is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
1002                    ||
1003                    // Diagnostics
1004                    (is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
1005                    ||
1006                    // Scrollmanager
1007                    editor.scroll_manager.scrollbars_visible()
1008            }
1009            ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
1010            ShowScrollbar::Always => true,
1011            ShowScrollbar::Never => false,
1012        };
1013        if snapshot.mode != EditorMode::Full {
1014            return None;
1015        }
1016
1017        let visible_row_range = scroll_position.y..scroll_position.y + rows_per_page;
1018
1019        // If a drag took place after we started dragging the scrollbar,
1020        // cancel the scrollbar drag.
1021        if cx.has_active_drag() {
1022            self.editor.update(cx, |editor, cx| {
1023                editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
1024            });
1025        }
1026
1027        let track_bounds = Bounds::from_corners(
1028            point(self.scrollbar_left(&bounds), bounds.origin.y),
1029            point(bounds.lower_right().x, bounds.lower_left().y),
1030        );
1031
1032        let height = bounds.size.height;
1033        let total_rows = snapshot.max_point().row() as f32 + rows_per_page;
1034        let px_per_row = height / total_rows;
1035        let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
1036        let row_height = (height - thumb_height) / snapshot.max_point().row() as f32;
1037
1038        Some(ScrollbarLayout {
1039            hitbox: cx.insert_hitbox(track_bounds, false),
1040            visible_row_range,
1041            row_height,
1042            visible: show_scrollbars,
1043            thumb_height,
1044        })
1045    }
1046
1047    #[allow(clippy::too_many_arguments)]
1048    fn layout_gutter_fold_indicators(
1049        &self,
1050        fold_statuses: Vec<Option<(FoldStatus, u32, bool)>>,
1051        line_height: Pixels,
1052        gutter_dimensions: &GutterDimensions,
1053        gutter_settings: crate::editor_settings::Gutter,
1054        scroll_pixel_position: gpui::Point<Pixels>,
1055        gutter_hitbox: &Hitbox,
1056        cx: &mut ElementContext,
1057    ) -> Vec<Option<AnyElement>> {
1058        let mut indicators = self.editor.update(cx, |editor, cx| {
1059            editor.render_fold_indicators(
1060                fold_statuses,
1061                &self.style,
1062                editor.gutter_hovered,
1063                line_height,
1064                gutter_dimensions.margin,
1065                cx,
1066            )
1067        });
1068
1069        for (ix, fold_indicator) in indicators.iter_mut().enumerate() {
1070            if let Some(fold_indicator) = fold_indicator {
1071                debug_assert!(gutter_settings.folds);
1072                let available_space = size(
1073                    AvailableSpace::MinContent,
1074                    AvailableSpace::Definite(line_height * 0.55),
1075                );
1076                let fold_indicator_size = fold_indicator.measure(available_space, cx);
1077
1078                let position = point(
1079                    gutter_dimensions.width - gutter_dimensions.right_padding,
1080                    ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1081                );
1082                let centering_offset = point(
1083                    (gutter_dimensions.right_padding + gutter_dimensions.margin
1084                        - fold_indicator_size.width)
1085                        / 2.,
1086                    (line_height - fold_indicator_size.height) / 2.,
1087                );
1088                let origin = gutter_hitbox.origin + position + centering_offset;
1089                fold_indicator.layout(origin, available_space, cx);
1090            }
1091        }
1092
1093        indicators
1094    }
1095
1096    //Folds contained in a hunk are ignored apart from shrinking visual size
1097    //If a fold contains any hunks then that fold line is marked as modified
1098    fn layout_git_gutters(
1099        &self,
1100        display_rows: Range<u32>,
1101        snapshot: &EditorSnapshot,
1102    ) -> Vec<DisplayDiffHunk> {
1103        let buffer_snapshot = &snapshot.buffer_snapshot;
1104
1105        let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
1106            .to_point(snapshot)
1107            .row;
1108        let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
1109            .to_point(snapshot)
1110            .row;
1111
1112        buffer_snapshot
1113            .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
1114            .map(|hunk| diff_hunk_to_display(hunk, snapshot))
1115            .dedup()
1116            .collect()
1117    }
1118
1119    #[allow(clippy::too_many_arguments)]
1120    fn layout_inline_blame(
1121        &self,
1122        display_row: u32,
1123        display_snapshot: &DisplaySnapshot,
1124        line_layout: &LineWithInvisibles,
1125        em_width: Pixels,
1126        content_origin: gpui::Point<Pixels>,
1127        scroll_pixel_position: gpui::Point<Pixels>,
1128        line_height: Pixels,
1129        cx: &mut ElementContext,
1130    ) -> Option<AnyElement> {
1131        if !self
1132            .editor
1133            .update(cx, |editor, cx| editor.render_git_blame_inline(cx))
1134        {
1135            return None;
1136        }
1137
1138        let workspace = self
1139            .editor
1140            .read(cx)
1141            .workspace
1142            .as_ref()
1143            .map(|(w, _)| w.clone());
1144
1145        let display_point = DisplayPoint::new(display_row, 0);
1146        let buffer_row = display_point.to_point(display_snapshot).row;
1147
1148        let blame = self.editor.read(cx).blame.clone()?;
1149        let blame_entry = blame
1150            .update(cx, |blame, cx| {
1151                blame.blame_for_rows([Some(buffer_row)], cx).next()
1152            })
1153            .flatten()?;
1154
1155        let mut element =
1156            render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
1157
1158        let start_y = content_origin.y
1159            + line_height * (display_row as f32 - scroll_pixel_position.y / line_height);
1160
1161        let start_x = {
1162            const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
1163
1164            let padded_line_width =
1165                line_layout.line.width + (em_width * INLINE_BLAME_PADDING_EM_WIDTHS);
1166
1167            let min_column = ProjectSettings::get_global(cx)
1168                .git
1169                .inline_blame
1170                .and_then(|settings| settings.min_column)
1171                .map(|col| self.column_pixels(col as usize, cx))
1172                .unwrap_or(px(0.));
1173
1174            content_origin.x + max(padded_line_width, min_column)
1175        };
1176
1177        let absolute_offset = point(start_x, start_y);
1178        let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1179
1180        element.layout(absolute_offset, available_space, cx);
1181
1182        Some(element)
1183    }
1184
1185    #[allow(clippy::too_many_arguments)]
1186    fn layout_blame_entries(
1187        &self,
1188        buffer_rows: impl Iterator<Item = Option<u32>>,
1189        em_width: Pixels,
1190        scroll_position: gpui::Point<f32>,
1191        line_height: Pixels,
1192        gutter_hitbox: &Hitbox,
1193        max_width: Option<Pixels>,
1194        cx: &mut ElementContext,
1195    ) -> Option<Vec<AnyElement>> {
1196        if !self
1197            .editor
1198            .update(cx, |editor, cx| editor.render_git_blame_gutter(cx))
1199        {
1200            return None;
1201        }
1202
1203        let blame = self.editor.read(cx).blame.clone()?;
1204        let blamed_rows: Vec<_> = blame.update(cx, |blame, cx| {
1205            blame.blame_for_rows(buffer_rows, cx).collect()
1206        });
1207
1208        let width = if let Some(max_width) = max_width {
1209            AvailableSpace::Definite(max_width)
1210        } else {
1211            AvailableSpace::MaxContent
1212        };
1213        let scroll_top = scroll_position.y * line_height;
1214        let start_x = em_width * 1;
1215
1216        let mut last_used_color: Option<(PlayerColor, Oid)> = None;
1217
1218        let shaped_lines = blamed_rows
1219            .into_iter()
1220            .enumerate()
1221            .flat_map(|(ix, blame_entry)| {
1222                if let Some(blame_entry) = blame_entry {
1223                    let mut element = render_blame_entry(
1224                        ix,
1225                        &blame,
1226                        blame_entry,
1227                        &self.style,
1228                        &mut last_used_color,
1229                        self.editor.clone(),
1230                        cx,
1231                    );
1232
1233                    let start_y = ix as f32 * line_height - (scroll_top % line_height);
1234                    let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
1235
1236                    element.layout(absolute_offset, size(width, AvailableSpace::MinContent), cx);
1237
1238                    Some(element)
1239                } else {
1240                    None
1241                }
1242            })
1243            .collect();
1244
1245        Some(shaped_lines)
1246    }
1247
1248    fn layout_code_actions_indicator(
1249        &self,
1250        line_height: Pixels,
1251        newest_selection_head: DisplayPoint,
1252        scroll_pixel_position: gpui::Point<Pixels>,
1253        gutter_dimensions: &GutterDimensions,
1254        gutter_hitbox: &Hitbox,
1255        cx: &mut ElementContext,
1256    ) -> Option<AnyElement> {
1257        let mut active = false;
1258        let mut button = None;
1259        self.editor.update(cx, |editor, cx| {
1260            active = matches!(
1261                editor.context_menu.read().as_ref(),
1262                Some(crate::ContextMenu::CodeActions(_))
1263            );
1264            button = editor.render_code_actions_indicator(&self.style, active, cx);
1265        });
1266
1267        let mut button = button?.into_any_element();
1268        let available_space = size(
1269            AvailableSpace::MinContent,
1270            AvailableSpace::Definite(line_height),
1271        );
1272        let indicator_size = button.measure(available_space, cx);
1273
1274        let blame_width = gutter_dimensions
1275            .git_blame_entries_width
1276            .unwrap_or(Pixels::ZERO);
1277
1278        let mut x = blame_width;
1279        let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
1280            - indicator_size.width
1281            - blame_width;
1282        x += available_width / 2.;
1283
1284        let mut y = newest_selection_head.row() as f32 * line_height - scroll_pixel_position.y;
1285        y += (line_height - indicator_size.height) / 2.;
1286
1287        button.layout(gutter_hitbox.origin + point(x, y), available_space, cx);
1288        Some(button)
1289    }
1290
1291    fn calculate_relative_line_numbers(
1292        &self,
1293        buffer_rows: Vec<Option<u32>>,
1294        rows: &Range<u32>,
1295        relative_to: Option<u32>,
1296    ) -> HashMap<u32, u32> {
1297        let mut relative_rows: HashMap<u32, u32> = Default::default();
1298        let Some(relative_to) = relative_to else {
1299            return relative_rows;
1300        };
1301
1302        let start = rows.start.min(relative_to);
1303
1304        let head_idx = relative_to - start;
1305        let mut delta = 1;
1306        let mut i = head_idx + 1;
1307        while i < buffer_rows.len() as u32 {
1308            if buffer_rows[i as usize].is_some() {
1309                if rows.contains(&(i + start)) {
1310                    relative_rows.insert(i + start, delta);
1311                }
1312                delta += 1;
1313            }
1314            i += 1;
1315        }
1316        delta = 1;
1317        i = head_idx.min(buffer_rows.len() as u32 - 1);
1318        while i > 0 && buffer_rows[i as usize].is_none() {
1319            i -= 1;
1320        }
1321
1322        while i > 0 {
1323            i -= 1;
1324            if buffer_rows[i as usize].is_some() {
1325                if rows.contains(&(i + start)) {
1326                    relative_rows.insert(i + start, delta);
1327                }
1328                delta += 1;
1329            }
1330        }
1331
1332        relative_rows
1333    }
1334
1335    fn layout_line_numbers(
1336        &self,
1337        rows: Range<u32>,
1338        buffer_rows: impl Iterator<Item = Option<u32>>,
1339        active_rows: &BTreeMap<u32, bool>,
1340        newest_selection_head: Option<DisplayPoint>,
1341        snapshot: &EditorSnapshot,
1342        cx: &ElementContext,
1343    ) -> (
1344        Vec<Option<ShapedLine>>,
1345        Vec<Option<(FoldStatus, BufferRow, bool)>>,
1346    ) {
1347        let editor = self.editor.read(cx);
1348        let is_singleton = editor.is_singleton(cx);
1349        let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
1350            let newest = editor.selections.newest::<Point>(cx);
1351            SelectionLayout::new(
1352                newest,
1353                editor.selections.line_mode,
1354                editor.cursor_shape,
1355                &snapshot.display_snapshot,
1356                true,
1357                true,
1358                None,
1359            )
1360            .head
1361        });
1362        let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1363        let include_line_numbers =
1364            EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
1365        let include_fold_statuses =
1366            EditorSettings::get_global(cx).gutter.folds && snapshot.mode == EditorMode::Full;
1367        let mut shaped_line_numbers = Vec::with_capacity(rows.len());
1368        let mut fold_statuses = Vec::with_capacity(rows.len());
1369        let mut line_number = String::new();
1370        let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
1371        let relative_to = if is_relative {
1372            Some(newest_selection_head.row())
1373        } else {
1374            None
1375        };
1376
1377        let buffer_rows = buffer_rows.collect::<Vec<_>>();
1378        let relative_rows =
1379            self.calculate_relative_line_numbers(buffer_rows.clone(), &rows, relative_to);
1380
1381        for (ix, row) in buffer_rows.into_iter().enumerate() {
1382            let display_row = rows.start + ix as u32;
1383            let (active, color) = if active_rows.contains_key(&display_row) {
1384                (true, cx.theme().colors().editor_active_line_number)
1385            } else {
1386                (false, cx.theme().colors().editor_line_number)
1387            };
1388            if let Some(buffer_row) = row {
1389                if include_line_numbers {
1390                    line_number.clear();
1391                    let default_number = buffer_row + 1;
1392                    let number = relative_rows
1393                        .get(&(ix as u32 + rows.start))
1394                        .unwrap_or(&default_number);
1395                    write!(&mut line_number, "{}", number).unwrap();
1396                    let run = TextRun {
1397                        len: line_number.len(),
1398                        font: self.style.text.font(),
1399                        color,
1400                        background_color: None,
1401                        underline: None,
1402                        strikethrough: None,
1403                    };
1404                    let shaped_line = cx
1405                        .text_system()
1406                        .shape_line(line_number.clone().into(), font_size, &[run])
1407                        .unwrap();
1408                    shaped_line_numbers.push(Some(shaped_line));
1409                }
1410                if include_fold_statuses {
1411                    fold_statuses.push(
1412                        is_singleton
1413                            .then(|| {
1414                                snapshot
1415                                    .fold_for_line(buffer_row)
1416                                    .map(|fold_status| (fold_status, buffer_row, active))
1417                            })
1418                            .flatten(),
1419                    )
1420                }
1421            } else {
1422                fold_statuses.push(None);
1423                shaped_line_numbers.push(None);
1424            }
1425        }
1426
1427        (shaped_line_numbers, fold_statuses)
1428    }
1429
1430    fn layout_lines(
1431        &self,
1432        rows: Range<u32>,
1433        line_number_layouts: &[Option<ShapedLine>],
1434        snapshot: &EditorSnapshot,
1435        cx: &ElementContext,
1436    ) -> Vec<LineWithInvisibles> {
1437        if rows.start >= rows.end {
1438            return Vec::new();
1439        }
1440
1441        // Show the placeholder when the editor is empty
1442        if snapshot.is_empty() {
1443            let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1444            let placeholder_color = cx.theme().colors().text_placeholder;
1445            let placeholder_text = snapshot.placeholder_text();
1446
1447            let placeholder_lines = placeholder_text
1448                .as_ref()
1449                .map_or("", AsRef::as_ref)
1450                .split('\n')
1451                .skip(rows.start as usize)
1452                .chain(iter::repeat(""))
1453                .take(rows.len());
1454            placeholder_lines
1455                .filter_map(move |line| {
1456                    let run = TextRun {
1457                        len: line.len(),
1458                        font: self.style.text.font(),
1459                        color: placeholder_color,
1460                        background_color: None,
1461                        underline: Default::default(),
1462                        strikethrough: None,
1463                    };
1464                    cx.text_system()
1465                        .shape_line(line.to_string().into(), font_size, &[run])
1466                        .log_err()
1467                })
1468                .map(|line| LineWithInvisibles {
1469                    line,
1470                    invisibles: Vec::new(),
1471                })
1472                .collect()
1473        } else {
1474            let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
1475            LineWithInvisibles::from_chunks(
1476                chunks,
1477                &self.style.text,
1478                MAX_LINE_LEN,
1479                rows.len(),
1480                line_number_layouts,
1481                snapshot.mode,
1482                cx,
1483            )
1484        }
1485    }
1486
1487    #[allow(clippy::too_many_arguments)]
1488    fn build_blocks(
1489        &self,
1490        rows: Range<u32>,
1491        snapshot: &EditorSnapshot,
1492        hitbox: &Hitbox,
1493        text_hitbox: &Hitbox,
1494        scroll_width: &mut Pixels,
1495        gutter_dimensions: &GutterDimensions,
1496        em_width: Pixels,
1497        text_x: Pixels,
1498        line_height: Pixels,
1499        line_layouts: &[LineWithInvisibles],
1500        cx: &mut ElementContext,
1501    ) -> Vec<BlockLayout> {
1502        let mut block_id = 0;
1503        let (fixed_blocks, non_fixed_blocks) = snapshot
1504            .blocks_in_range(rows.clone())
1505            .partition::<Vec<_>, _>(|(_, block)| match block {
1506                TransformBlock::ExcerptHeader { .. } => false,
1507                TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
1508            });
1509
1510        let render_block = |block: &TransformBlock,
1511                            available_space: Size<AvailableSpace>,
1512                            block_id: usize,
1513                            block_row_start: u32,
1514                            cx: &mut ElementContext| {
1515            let mut element = match block {
1516                TransformBlock::Custom(block) => {
1517                    let align_to = block
1518                        .position()
1519                        .to_point(&snapshot.buffer_snapshot)
1520                        .to_display_point(snapshot);
1521                    let anchor_x = text_x
1522                        + if rows.contains(&align_to.row()) {
1523                            line_layouts[(align_to.row() - rows.start) as usize]
1524                                .line
1525                                .x_for_index(align_to.column() as usize)
1526                        } else {
1527                            layout_line(align_to.row(), snapshot, &self.style, cx)
1528                                .unwrap()
1529                                .x_for_index(align_to.column() as usize)
1530                        };
1531
1532                    block.render(&mut BlockContext {
1533                        context: cx,
1534                        anchor_x,
1535                        gutter_dimensions,
1536                        line_height,
1537                        em_width,
1538                        block_id,
1539                        max_width: text_hitbox.size.width.max(*scroll_width),
1540                        editor_style: &self.style,
1541                    })
1542                }
1543
1544                TransformBlock::ExcerptHeader {
1545                    buffer,
1546                    range,
1547                    starts_new_buffer,
1548                    height,
1549                    id,
1550                    ..
1551                } => {
1552                    let include_root = self
1553                        .editor
1554                        .read(cx)
1555                        .project
1556                        .as_ref()
1557                        .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
1558                        .unwrap_or_default();
1559
1560                    #[derive(Clone)]
1561                    struct JumpData {
1562                        position: Point,
1563                        anchor: text::Anchor,
1564                        path: ProjectPath,
1565                        line_offset_from_top: u32,
1566                    }
1567
1568                    let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
1569                        let jump_path = ProjectPath {
1570                            worktree_id: file.worktree_id(cx),
1571                            path: file.path.clone(),
1572                        };
1573                        let jump_anchor = range
1574                            .primary
1575                            .as_ref()
1576                            .map_or(range.context.start, |primary| primary.start);
1577
1578                        let excerpt_start = range.context.start;
1579                        let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
1580                        let offset_from_excerpt_start = if jump_anchor == excerpt_start {
1581                            0
1582                        } else {
1583                            let excerpt_start_row =
1584                                language::ToPoint::to_point(&jump_anchor, buffer).row;
1585                            jump_position.row - excerpt_start_row
1586                        };
1587
1588                        let line_offset_from_top =
1589                            block_row_start + *height as u32 + offset_from_excerpt_start
1590                                - snapshot
1591                                    .scroll_anchor
1592                                    .scroll_position(&snapshot.display_snapshot)
1593                                    .y as u32;
1594
1595                        JumpData {
1596                            position: jump_position,
1597                            anchor: jump_anchor,
1598                            path: jump_path,
1599                            line_offset_from_top,
1600                        }
1601                    });
1602
1603                    let element = if *starts_new_buffer {
1604                        let path = buffer.resolve_file_path(cx, include_root);
1605                        let mut filename = None;
1606                        let mut parent_path = None;
1607                        // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
1608                        if let Some(path) = path {
1609                            filename = path.file_name().map(|f| f.to_string_lossy().to_string());
1610                            parent_path = path
1611                                .parent()
1612                                .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
1613                        }
1614
1615                        v_flex()
1616                            .id(("path header container", block_id))
1617                            .size_full()
1618                            .justify_center()
1619                            .p(gpui::px(6.))
1620                            .child(
1621                                h_flex()
1622                                    .id("path header block")
1623                                    .size_full()
1624                                    .pl(gpui::px(12.))
1625                                    .pr(gpui::px(8.))
1626                                    .rounded_md()
1627                                    .shadow_md()
1628                                    .border()
1629                                    .border_color(cx.theme().colors().border)
1630                                    .bg(cx.theme().colors().editor_subheader_background)
1631                                    .justify_between()
1632                                    .hover(|style| style.bg(cx.theme().colors().element_hover))
1633                                    .child(
1634                                        h_flex().gap_3().child(
1635                                            h_flex()
1636                                                .gap_2()
1637                                                .child(
1638                                                    filename
1639                                                        .map(SharedString::from)
1640                                                        .unwrap_or_else(|| "untitled".into()),
1641                                                )
1642                                                .when_some(parent_path, |then, path| {
1643                                                    then.child(
1644                                                        div().child(path).text_color(
1645                                                            cx.theme().colors().text_muted,
1646                                                        ),
1647                                                    )
1648                                                }),
1649                                        ),
1650                                    )
1651                                    .when_some(jump_data.clone(), |this, jump_data| {
1652                                        this.cursor_pointer()
1653                                            .tooltip(|cx| {
1654                                                Tooltip::for_action(
1655                                                    "Jump to File",
1656                                                    &OpenExcerpts,
1657                                                    cx,
1658                                                )
1659                                            })
1660                                            .on_mouse_down(MouseButton::Left, |_, cx| {
1661                                                cx.stop_propagation()
1662                                            })
1663                                            .on_click(cx.listener_for(&self.editor, {
1664                                                move |editor, _, cx| {
1665                                                    editor.jump(
1666                                                        jump_data.path.clone(),
1667                                                        jump_data.position,
1668                                                        jump_data.anchor,
1669                                                        jump_data.line_offset_from_top,
1670                                                        cx,
1671                                                    );
1672                                                }
1673                                            }))
1674                                    }),
1675                            )
1676                    } else {
1677                        v_flex()
1678                            .id(("collapsed context", block_id))
1679                            .size_full()
1680                            .child(
1681                                div()
1682                                    .flex()
1683                                    .v_flex()
1684                                    .justify_start()
1685                                    .id("jump to collapsed context")
1686                                    .w(relative(1.0))
1687                                    .h_full()
1688                                    .child(
1689                                        div()
1690                                            .h_px()
1691                                            .w_full()
1692                                            .bg(cx.theme().colors().border_variant)
1693                                            .group_hover("excerpt-jump-action", |style| {
1694                                                style.bg(cx.theme().colors().border)
1695                                            }),
1696                                    ),
1697                            )
1698                            .child(
1699                                h_flex()
1700                                    .justify_end()
1701                                    .flex_none()
1702                                    .w(
1703                                        gutter_dimensions.width - (gutter_dimensions.left_padding), // + gutter_dimensions.right_padding)
1704                                    )
1705                                    .h_full()
1706                                    .child(
1707                                        ButtonLike::new("expand-icon")
1708                                            .style(ButtonStyle::Transparent)
1709                                            .child(
1710                                                svg()
1711                                                    .path(IconName::ExpandVertical.path())
1712                                                    .size(IconSize::XSmall.rems())
1713                                                    .text_color(
1714                                                        cx.theme().colors().editor_line_number,
1715                                                    )
1716                                                    .group("")
1717                                                    .hover(|style| {
1718                                                        style.text_color(
1719                                                            cx.theme()
1720                                                                .colors()
1721                                                                .editor_active_line_number,
1722                                                        )
1723                                                    }),
1724                                            )
1725                                            .on_click(cx.listener_for(&self.editor, {
1726                                                let id = *id;
1727                                                move |editor, _, cx| {
1728                                                    editor.expand_excerpt(id, cx);
1729                                                }
1730                                            }))
1731                                            .tooltip({
1732                                                move |cx| {
1733                                                    Tooltip::for_action(
1734                                                        "Expand Excerpt",
1735                                                        &ExpandExcerpts { lines: 0 },
1736                                                        cx,
1737                                                    )
1738                                                }
1739                                            }),
1740                                    ),
1741                            )
1742                            .group("excerpt-jump-action")
1743                            .cursor_pointer()
1744                            .when_some(jump_data.clone(), |this, jump_data| {
1745                                this.on_click(cx.listener_for(&self.editor, {
1746                                    let path = jump_data.path.clone();
1747                                    move |editor, _, cx| {
1748                                        cx.stop_propagation();
1749
1750                                        editor.jump(
1751                                            path.clone(),
1752                                            jump_data.position,
1753                                            jump_data.anchor,
1754                                            jump_data.line_offset_from_top,
1755                                            cx,
1756                                        );
1757                                    }
1758                                }))
1759                                .tooltip(move |cx| {
1760                                    Tooltip::for_action(
1761                                        format!(
1762                                            "Jump to {}:L{}",
1763                                            jump_data.path.path.display(),
1764                                            jump_data.position.row + 1
1765                                        ),
1766                                        &OpenExcerpts,
1767                                        cx,
1768                                    )
1769                                })
1770                            })
1771                    };
1772                    element.into_any()
1773                }
1774            };
1775
1776            let size = element.measure(available_space, cx);
1777            (element, size)
1778        };
1779
1780        let mut fixed_block_max_width = Pixels::ZERO;
1781        let mut blocks = Vec::new();
1782        for (row, block) in fixed_blocks {
1783            let available_space = size(
1784                AvailableSpace::MinContent,
1785                AvailableSpace::Definite(block.height() as f32 * line_height),
1786            );
1787            let (element, element_size) = render_block(block, available_space, block_id, row, cx);
1788            block_id += 1;
1789            fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
1790            blocks.push(BlockLayout {
1791                row,
1792                element,
1793                available_space,
1794                style: BlockStyle::Fixed,
1795            });
1796        }
1797        for (row, block) in non_fixed_blocks {
1798            let style = match block {
1799                TransformBlock::Custom(block) => block.style(),
1800                TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
1801            };
1802            let width = match style {
1803                BlockStyle::Sticky => hitbox.size.width,
1804                BlockStyle::Flex => hitbox
1805                    .size
1806                    .width
1807                    .max(fixed_block_max_width)
1808                    .max(gutter_dimensions.width + *scroll_width),
1809                BlockStyle::Fixed => unreachable!(),
1810            };
1811            let available_space = size(
1812                AvailableSpace::Definite(width),
1813                AvailableSpace::Definite(block.height() as f32 * line_height),
1814            );
1815            let (element, _) = render_block(block, available_space, block_id, row, cx);
1816            block_id += 1;
1817            blocks.push(BlockLayout {
1818                row,
1819                element,
1820                available_space,
1821                style,
1822            });
1823        }
1824
1825        *scroll_width = (*scroll_width).max(fixed_block_max_width - gutter_dimensions.width);
1826        blocks
1827    }
1828
1829    fn layout_blocks(
1830        &self,
1831        blocks: &mut Vec<BlockLayout>,
1832        hitbox: &Hitbox,
1833        line_height: Pixels,
1834        scroll_pixel_position: gpui::Point<Pixels>,
1835        cx: &mut ElementContext,
1836    ) {
1837        for block in blocks {
1838            let mut origin = hitbox.origin
1839                + point(
1840                    Pixels::ZERO,
1841                    block.row as f32 * line_height - scroll_pixel_position.y,
1842                );
1843            if !matches!(block.style, BlockStyle::Sticky) {
1844                origin += point(-scroll_pixel_position.x, Pixels::ZERO);
1845            }
1846            block.element.layout(origin, block.available_space, cx);
1847        }
1848    }
1849
1850    #[allow(clippy::too_many_arguments)]
1851    fn layout_context_menu(
1852        &self,
1853        line_height: Pixels,
1854        hitbox: &Hitbox,
1855        text_hitbox: &Hitbox,
1856        content_origin: gpui::Point<Pixels>,
1857        start_row: u32,
1858        scroll_pixel_position: gpui::Point<Pixels>,
1859        line_layouts: &[LineWithInvisibles],
1860        newest_selection_head: DisplayPoint,
1861        cx: &mut ElementContext,
1862    ) -> bool {
1863        let max_height = cmp::min(
1864            12. * line_height,
1865            cmp::max(3. * line_height, (hitbox.size.height - line_height) / 2.),
1866        );
1867        let Some((position, mut context_menu)) = self.editor.update(cx, |editor, cx| {
1868            if editor.context_menu_visible() {
1869                editor.render_context_menu(newest_selection_head, &self.style, max_height, cx)
1870            } else {
1871                None
1872            }
1873        }) else {
1874            return false;
1875        };
1876
1877        let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1878        let context_menu_size = context_menu.measure(available_space, cx);
1879
1880        let cursor_row_layout = &line_layouts[(position.row() - start_row) as usize].line;
1881        let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
1882        let y = (position.row() + 1) as f32 * line_height - scroll_pixel_position.y;
1883        let mut list_origin = content_origin + point(x, y);
1884        let list_width = context_menu_size.width;
1885        let list_height = context_menu_size.height;
1886
1887        // Snap the right edge of the list to the right edge of the window if
1888        // its horizontal bounds overflow.
1889        if list_origin.x + list_width > cx.viewport_size().width {
1890            list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO);
1891        }
1892
1893        if list_origin.y + list_height > text_hitbox.lower_right().y {
1894            list_origin.y -= line_height + list_height;
1895        }
1896
1897        cx.defer_draw(context_menu, list_origin, 1);
1898        true
1899    }
1900
1901    fn layout_mouse_context_menu(&self, cx: &mut ElementContext) -> Option<AnyElement> {
1902        let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?;
1903        let mut element = deferred(
1904            anchored()
1905                .position(mouse_context_menu.position)
1906                .child(mouse_context_menu.context_menu.clone())
1907                .anchor(AnchorCorner::TopLeft)
1908                .snap_to_window(),
1909        )
1910        .with_priority(1)
1911        .into_any();
1912
1913        element.layout(gpui::Point::default(), AvailableSpace::min_size(), cx);
1914        Some(element)
1915    }
1916
1917    #[allow(clippy::too_many_arguments)]
1918    fn layout_hover_popovers(
1919        &self,
1920        snapshot: &EditorSnapshot,
1921        hitbox: &Hitbox,
1922        text_hitbox: &Hitbox,
1923        visible_display_row_range: Range<u32>,
1924        content_origin: gpui::Point<Pixels>,
1925        scroll_pixel_position: gpui::Point<Pixels>,
1926        line_layouts: &[LineWithInvisibles],
1927        line_height: Pixels,
1928        em_width: Pixels,
1929        cx: &mut ElementContext,
1930    ) {
1931        struct MeasuredHoverPopover {
1932            element: AnyElement,
1933            size: Size<Pixels>,
1934            horizontal_offset: Pixels,
1935        }
1936
1937        let max_size = size(
1938            (120. * em_width) // Default size
1939                .min(hitbox.size.width / 2.) // Shrink to half of the editor width
1940                .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
1941            (16. * line_height) // Default size
1942                .min(hitbox.size.height / 2.) // Shrink to half of the editor height
1943                .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
1944        );
1945
1946        let hover_popovers = self.editor.update(cx, |editor, cx| {
1947            editor.hover_state.render(
1948                &snapshot,
1949                &self.style,
1950                visible_display_row_range.clone(),
1951                max_size,
1952                editor.workspace.as_ref().map(|(w, _)| w.clone()),
1953                cx,
1954            )
1955        });
1956        let Some((position, hover_popovers)) = hover_popovers else {
1957            return;
1958        };
1959
1960        let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
1961
1962        // This is safe because we check on layout whether the required row is available
1963        let hovered_row_layout =
1964            &line_layouts[(position.row() - visible_display_row_range.start) as usize].line;
1965
1966        // Compute Hovered Point
1967        let x =
1968            hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
1969        let y = position.row() as f32 * line_height - scroll_pixel_position.y;
1970        let hovered_point = content_origin + point(x, y);
1971
1972        let mut overall_height = Pixels::ZERO;
1973        let mut measured_hover_popovers = Vec::new();
1974        for mut hover_popover in hover_popovers {
1975            let size = hover_popover.measure(available_space, cx);
1976            let horizontal_offset =
1977                (text_hitbox.upper_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
1978
1979            overall_height += HOVER_POPOVER_GAP + size.height;
1980
1981            measured_hover_popovers.push(MeasuredHoverPopover {
1982                element: hover_popover,
1983                size,
1984                horizontal_offset,
1985            });
1986        }
1987        overall_height += HOVER_POPOVER_GAP;
1988
1989        fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
1990            let mut occlusion = div()
1991                .size_full()
1992                .occlude()
1993                .on_mouse_move(|_, cx| cx.stop_propagation())
1994                .into_any_element();
1995            occlusion.measure(size(width, HOVER_POPOVER_GAP).into(), cx);
1996            cx.defer_draw(occlusion, origin, 2);
1997        }
1998
1999        if hovered_point.y > overall_height {
2000            // There is enough space above. Render popovers above the hovered point
2001            let mut current_y = hovered_point.y;
2002            for (position, popover) in measured_hover_popovers.into_iter().with_position() {
2003                let size = popover.size;
2004                let popover_origin = point(
2005                    hovered_point.x + popover.horizontal_offset,
2006                    current_y - size.height,
2007                );
2008
2009                cx.defer_draw(popover.element, popover_origin, 2);
2010                if position != itertools::Position::Last {
2011                    let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
2012                    draw_occluder(size.width, origin, cx);
2013                }
2014
2015                current_y = popover_origin.y - HOVER_POPOVER_GAP;
2016            }
2017        } else {
2018            // There is not enough space above. Render popovers below the hovered point
2019            let mut current_y = hovered_point.y + line_height;
2020            for (position, popover) in measured_hover_popovers.into_iter().with_position() {
2021                let size = popover.size;
2022                let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
2023
2024                cx.defer_draw(popover.element, popover_origin, 2);
2025                if position != itertools::Position::Last {
2026                    let origin = point(popover_origin.x, popover_origin.y + size.height);
2027                    draw_occluder(size.width, origin, cx);
2028                }
2029
2030                current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
2031            }
2032        }
2033    }
2034
2035    fn paint_background(&self, layout: &EditorLayout, cx: &mut ElementContext) {
2036        cx.paint_layer(layout.hitbox.bounds, |cx| {
2037            let scroll_top = layout.position_map.snapshot.scroll_position().y;
2038            let gutter_bg = cx.theme().colors().editor_gutter_background;
2039            cx.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
2040            cx.paint_quad(fill(layout.text_hitbox.bounds, self.style.background));
2041
2042            if let EditorMode::Full = layout.mode {
2043                let mut active_rows = layout.active_rows.iter().peekable();
2044                while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
2045                    let mut end_row = *start_row;
2046                    while active_rows.peek().map_or(false, |r| {
2047                        *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
2048                    }) {
2049                        active_rows.next().unwrap();
2050                        end_row += 1;
2051                    }
2052
2053                    if !contains_non_empty_selection {
2054                        let origin = point(
2055                            layout.hitbox.origin.x,
2056                            layout.hitbox.origin.y
2057                                + (*start_row as f32 - scroll_top)
2058                                    * layout.position_map.line_height,
2059                        );
2060                        let size = size(
2061                            layout.hitbox.size.width,
2062                            layout.position_map.line_height * (end_row - start_row + 1) as f32,
2063                        );
2064                        let active_line_bg = cx.theme().colors().editor_active_line_background;
2065                        cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
2066                    }
2067                }
2068
2069                let mut paint_highlight =
2070                    |highlight_row_start: u32, highlight_row_end: u32, color| {
2071                        let origin = point(
2072                            layout.hitbox.origin.x,
2073                            layout.hitbox.origin.y
2074                                + (highlight_row_start as f32 - scroll_top)
2075                                    * layout.position_map.line_height,
2076                        );
2077                        let size = size(
2078                            layout.hitbox.size.width,
2079                            layout.position_map.line_height
2080                                * (highlight_row_end + 1 - highlight_row_start) as f32,
2081                        );
2082                        cx.paint_quad(fill(Bounds { origin, size }, color));
2083                    };
2084
2085                let mut last_row = None;
2086                let mut highlight_row_start = 0u32;
2087                let mut highlight_row_end = 0u32;
2088                for (&row, &color) in &layout.highlighted_rows {
2089                    let paint = last_row.map_or(false, |(last_row, last_color)| {
2090                        last_color != color || last_row + 1 < row
2091                    });
2092
2093                    if paint {
2094                        let paint_range_is_unfinished = highlight_row_end == 0;
2095                        if paint_range_is_unfinished {
2096                            highlight_row_end = row;
2097                            last_row = None;
2098                        }
2099                        paint_highlight(highlight_row_start, highlight_row_end, color);
2100                        highlight_row_start = 0;
2101                        highlight_row_end = 0;
2102                        if !paint_range_is_unfinished {
2103                            highlight_row_start = row;
2104                            last_row = Some((row, color));
2105                        }
2106                    } else {
2107                        if last_row.is_none() {
2108                            highlight_row_start = row;
2109                        } else {
2110                            highlight_row_end = row;
2111                        }
2112                        last_row = Some((row, color));
2113                    }
2114                }
2115                if let Some((row, hsla)) = last_row {
2116                    highlight_row_end = row;
2117                    paint_highlight(highlight_row_start, highlight_row_end, hsla);
2118                }
2119
2120                let scroll_left =
2121                    layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
2122
2123                for (wrap_position, active) in layout.wrap_guides.iter() {
2124                    let x = (layout.text_hitbox.origin.x
2125                        + *wrap_position
2126                        + layout.position_map.em_width / 2.)
2127                        - scroll_left;
2128
2129                    let show_scrollbars = layout
2130                        .scrollbar_layout
2131                        .as_ref()
2132                        .map_or(false, |scrollbar| scrollbar.visible);
2133                    if x < layout.text_hitbox.origin.x
2134                        || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
2135                    {
2136                        continue;
2137                    }
2138
2139                    let color = if *active {
2140                        cx.theme().colors().editor_active_wrap_guide
2141                    } else {
2142                        cx.theme().colors().editor_wrap_guide
2143                    };
2144                    cx.paint_quad(fill(
2145                        Bounds {
2146                            origin: point(x, layout.text_hitbox.origin.y),
2147                            size: size(px(1.), layout.text_hitbox.size.height),
2148                        },
2149                        color,
2150                    ));
2151                }
2152            }
2153        })
2154    }
2155
2156    fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2157        let line_height = layout.position_map.line_height;
2158
2159        let scroll_position = layout.position_map.snapshot.scroll_position();
2160        let scroll_top = scroll_position.y * line_height;
2161
2162        cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
2163
2164        let show_git_gutter = matches!(
2165            ProjectSettings::get_global(cx).git.git_gutter,
2166            Some(GitGutterSetting::TrackedFiles)
2167        );
2168
2169        if show_git_gutter {
2170            Self::paint_diff_hunks(layout, cx);
2171        }
2172
2173        if layout.blamed_display_rows.is_some() {
2174            self.paint_blamed_display_rows(layout, cx);
2175        }
2176
2177        for (ix, line) in layout.line_numbers.iter().enumerate() {
2178            if let Some(line) = line {
2179                let line_origin = layout.gutter_hitbox.origin
2180                    + point(
2181                        layout.gutter_hitbox.size.width
2182                            - line.width
2183                            - layout.gutter_dimensions.right_padding,
2184                        ix as f32 * line_height - (scroll_top % line_height),
2185                    );
2186
2187                line.paint(line_origin, line_height, cx).log_err();
2188            }
2189        }
2190
2191        cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2192            cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
2193                for fold_indicator in layout.fold_indicators.iter_mut().flatten() {
2194                    fold_indicator.paint(cx);
2195                }
2196            });
2197
2198            if let Some(indicator) = layout.code_actions_indicator.as_mut() {
2199                indicator.paint(cx);
2200            }
2201        })
2202    }
2203
2204    fn paint_diff_hunks(layout: &EditorLayout, cx: &mut ElementContext) {
2205        if layout.display_hunks.is_empty() {
2206            return;
2207        }
2208
2209        let line_height = layout.position_map.line_height;
2210
2211        let scroll_position = layout.position_map.snapshot.scroll_position();
2212        let scroll_top = scroll_position.y * line_height;
2213
2214        cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2215            for hunk in &layout.display_hunks {
2216                let (display_row_range, status) = match hunk {
2217                    //TODO: This rendering is entirely a horrible hack
2218                    &DisplayDiffHunk::Folded { display_row: row } => {
2219                        let start_y = row as f32 * line_height - scroll_top;
2220                        let end_y = start_y + line_height;
2221
2222                        let width = 0.275 * line_height;
2223                        let highlight_origin = layout.gutter_hitbox.origin + point(-width, start_y);
2224                        let highlight_size = size(width * 2., end_y - start_y);
2225                        let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
2226                        cx.paint_quad(quad(
2227                            highlight_bounds,
2228                            Corners::all(1. * line_height),
2229                            cx.theme().status().modified,
2230                            Edges::default(),
2231                            transparent_black(),
2232                        ));
2233
2234                        continue;
2235                    }
2236
2237                    DisplayDiffHunk::Unfolded {
2238                        display_row_range,
2239                        status,
2240                    } => (display_row_range, status),
2241                };
2242
2243                let color = match status {
2244                    DiffHunkStatus::Added => cx.theme().status().created,
2245                    DiffHunkStatus::Modified => cx.theme().status().modified,
2246
2247                    //TODO: This rendering is entirely a horrible hack
2248                    DiffHunkStatus::Removed => {
2249                        let row = display_row_range.start;
2250
2251                        let offset = line_height / 2.;
2252                        let start_y = row as f32 * line_height - offset - scroll_top;
2253                        let end_y = start_y + line_height;
2254
2255                        let width = 0.275 * line_height;
2256                        let highlight_origin = layout.gutter_hitbox.origin + point(-width, start_y);
2257                        let highlight_size = size(width * 2., end_y - start_y);
2258                        let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
2259                        cx.paint_quad(quad(
2260                            highlight_bounds,
2261                            Corners::all(1. * line_height),
2262                            cx.theme().status().deleted,
2263                            Edges::default(),
2264                            transparent_black(),
2265                        ));
2266
2267                        continue;
2268                    }
2269                };
2270
2271                let start_row = display_row_range.start;
2272                let end_row = display_row_range.end;
2273                // If we're in a multibuffer, row range span might include an
2274                // excerpt header, so if we were to draw the marker straight away,
2275                // the hunk might include the rows of that header.
2276                // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
2277                // Instead, we simply check whether the range we're dealing with includes
2278                // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
2279                let end_row_in_current_excerpt = layout
2280                    .position_map
2281                    .snapshot
2282                    .blocks_in_range(start_row..end_row)
2283                    .find_map(|(start_row, block)| {
2284                        if matches!(block, TransformBlock::ExcerptHeader { .. }) {
2285                            Some(start_row)
2286                        } else {
2287                            None
2288                        }
2289                    })
2290                    .unwrap_or(end_row);
2291
2292                let start_y = start_row as f32 * line_height - scroll_top;
2293                let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top;
2294
2295                let width = 0.275 * line_height;
2296                let highlight_origin = layout.gutter_hitbox.origin + point(-width, start_y);
2297                let highlight_size = size(width * 2., end_y - start_y);
2298                let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
2299                cx.paint_quad(quad(
2300                    highlight_bounds,
2301                    Corners::all(0.05 * line_height),
2302                    color,
2303                    Edges::default(),
2304                    transparent_black(),
2305                ));
2306            }
2307        })
2308    }
2309
2310    fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2311        let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
2312            return;
2313        };
2314
2315        cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
2316            for mut blame_element in blamed_display_rows.into_iter() {
2317                blame_element.paint(cx);
2318            }
2319        })
2320    }
2321
2322    fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2323        cx.with_content_mask(
2324            Some(ContentMask {
2325                bounds: layout.text_hitbox.bounds,
2326            }),
2327            |cx| {
2328                let cursor_style = if self
2329                    .editor
2330                    .read(cx)
2331                    .hovered_link_state
2332                    .as_ref()
2333                    .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
2334                {
2335                    CursorStyle::PointingHand
2336                } else {
2337                    CursorStyle::IBeam
2338                };
2339                cx.set_cursor_style(cursor_style, &layout.text_hitbox);
2340
2341                cx.with_element_id(Some("folds"), |cx| self.paint_folds(layout, cx));
2342                let invisible_display_ranges = self.paint_highlights(layout, cx);
2343                self.paint_lines(&invisible_display_ranges, layout, cx);
2344                self.paint_redactions(layout, cx);
2345                self.paint_cursors(layout, cx);
2346                self.paint_inline_blame(layout, cx);
2347            },
2348        )
2349    }
2350
2351    fn paint_highlights(
2352        &mut self,
2353        layout: &mut EditorLayout,
2354        cx: &mut ElementContext,
2355    ) -> SmallVec<[Range<DisplayPoint>; 32]> {
2356        cx.paint_layer(layout.text_hitbox.bounds, |cx| {
2357            let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
2358            let line_end_overshoot = 0.15 * layout.position_map.line_height;
2359            for (range, color) in &layout.highlighted_ranges {
2360                self.paint_highlighted_range(
2361                    range.clone(),
2362                    *color,
2363                    Pixels::ZERO,
2364                    line_end_overshoot,
2365                    layout,
2366                    cx,
2367                );
2368            }
2369
2370            let corner_radius = 0.15 * layout.position_map.line_height;
2371
2372            for (player_color, selections) in &layout.selections {
2373                for selection in selections.into_iter() {
2374                    self.paint_highlighted_range(
2375                        selection.range.clone(),
2376                        player_color.selection,
2377                        corner_radius,
2378                        corner_radius * 2.,
2379                        layout,
2380                        cx,
2381                    );
2382
2383                    if selection.is_local && !selection.range.is_empty() {
2384                        invisible_display_ranges.push(selection.range.clone());
2385                    }
2386                }
2387            }
2388            invisible_display_ranges
2389        })
2390    }
2391
2392    fn paint_lines(
2393        &mut self,
2394        invisible_display_ranges: &[Range<DisplayPoint>],
2395        layout: &EditorLayout,
2396        cx: &mut ElementContext,
2397    ) {
2398        let whitespace_setting = self
2399            .editor
2400            .read(cx)
2401            .buffer
2402            .read(cx)
2403            .settings_at(0, cx)
2404            .show_whitespaces;
2405
2406        for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
2407            let row = layout.visible_display_row_range.start + ix as u32;
2408            line_with_invisibles.draw(
2409                layout,
2410                row,
2411                layout.content_origin,
2412                whitespace_setting,
2413                invisible_display_ranges,
2414                cx,
2415            )
2416        }
2417    }
2418
2419    fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
2420        if layout.redacted_ranges.is_empty() {
2421            return;
2422        }
2423
2424        let line_end_overshoot = layout.line_end_overshoot();
2425
2426        // A softer than perfect black
2427        let redaction_color = gpui::rgb(0x0e1111);
2428
2429        cx.paint_layer(layout.text_hitbox.bounds, |cx| {
2430            for range in layout.redacted_ranges.iter() {
2431                self.paint_highlighted_range(
2432                    range.clone(),
2433                    redaction_color.into(),
2434                    Pixels::ZERO,
2435                    line_end_overshoot,
2436                    layout,
2437                    cx,
2438                );
2439            }
2440        });
2441    }
2442
2443    fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2444        for cursor in &mut layout.cursors {
2445            cursor.paint(layout.content_origin, cx);
2446        }
2447    }
2448
2449    fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2450        let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
2451            return;
2452        };
2453
2454        let thumb_bounds = scrollbar_layout.thumb_bounds();
2455        if scrollbar_layout.visible {
2456            cx.paint_layer(scrollbar_layout.hitbox.bounds, |cx| {
2457                cx.paint_quad(quad(
2458                    scrollbar_layout.hitbox.bounds,
2459                    Corners::default(),
2460                    cx.theme().colors().scrollbar_track_background,
2461                    Edges {
2462                        top: Pixels::ZERO,
2463                        right: Pixels::ZERO,
2464                        bottom: Pixels::ZERO,
2465                        left: ScrollbarLayout::BORDER_WIDTH,
2466                    },
2467                    cx.theme().colors().scrollbar_track_border,
2468                ));
2469
2470                // Refresh scrollbar markers in the background. Below, we paint whatever markers have already been computed.
2471                self.refresh_scrollbar_markers(layout, scrollbar_layout, cx);
2472
2473                let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
2474                for marker in markers.iter() {
2475                    let mut marker = marker.clone();
2476                    marker.bounds.origin += scrollbar_layout.hitbox.origin;
2477                    cx.paint_quad(marker);
2478                }
2479
2480                cx.paint_quad(quad(
2481                    thumb_bounds,
2482                    Corners::default(),
2483                    cx.theme().colors().scrollbar_thumb_background,
2484                    Edges {
2485                        top: Pixels::ZERO,
2486                        right: Pixels::ZERO,
2487                        bottom: Pixels::ZERO,
2488                        left: ScrollbarLayout::BORDER_WIDTH,
2489                    },
2490                    cx.theme().colors().scrollbar_thumb_border,
2491                ));
2492            });
2493        }
2494
2495        cx.set_cursor_style(CursorStyle::Arrow, &scrollbar_layout.hitbox);
2496
2497        let row_height = scrollbar_layout.row_height;
2498        let row_range = scrollbar_layout.visible_row_range.clone();
2499
2500        cx.on_mouse_event({
2501            let editor = self.editor.clone();
2502            let hitbox = scrollbar_layout.hitbox.clone();
2503            let mut mouse_position = cx.mouse_position();
2504            move |event: &MouseMoveEvent, phase, cx| {
2505                if phase == DispatchPhase::Capture {
2506                    return;
2507                }
2508
2509                editor.update(cx, |editor, cx| {
2510                    if event.pressed_button == Some(MouseButton::Left)
2511                        && editor.scroll_manager.is_dragging_scrollbar()
2512                    {
2513                        let y = mouse_position.y;
2514                        let new_y = event.position.y;
2515                        if (hitbox.top()..hitbox.bottom()).contains(&y) {
2516                            let mut position = editor.scroll_position(cx);
2517                            position.y += (new_y - y) / row_height;
2518                            if position.y < 0.0 {
2519                                position.y = 0.0;
2520                            }
2521                            editor.set_scroll_position(position, cx);
2522                        }
2523
2524                        cx.stop_propagation();
2525                    } else {
2526                        editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
2527                        if hitbox.is_hovered(cx) {
2528                            editor.scroll_manager.show_scrollbar(cx);
2529                        }
2530                    }
2531                    mouse_position = event.position;
2532                })
2533            }
2534        });
2535
2536        if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
2537            cx.on_mouse_event({
2538                let editor = self.editor.clone();
2539                move |_: &MouseUpEvent, phase, cx| {
2540                    if phase == DispatchPhase::Capture {
2541                        return;
2542                    }
2543
2544                    editor.update(cx, |editor, cx| {
2545                        editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
2546                        cx.stop_propagation();
2547                    });
2548                }
2549            });
2550        } else {
2551            cx.on_mouse_event({
2552                let editor = self.editor.clone();
2553                let hitbox = scrollbar_layout.hitbox.clone();
2554                move |event: &MouseDownEvent, phase, cx| {
2555                    if phase == DispatchPhase::Capture || !hitbox.is_hovered(cx) {
2556                        return;
2557                    }
2558
2559                    editor.update(cx, |editor, cx| {
2560                        editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
2561
2562                        let y = event.position.y;
2563                        if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
2564                            let center_row = ((y - hitbox.top()) / row_height).round() as u32;
2565                            let top_row = center_row
2566                                .saturating_sub((row_range.end - row_range.start) as u32 / 2);
2567                            let mut position = editor.scroll_position(cx);
2568                            position.y = top_row as f32;
2569                            editor.set_scroll_position(position, cx);
2570                        } else {
2571                            editor.scroll_manager.show_scrollbar(cx);
2572                        }
2573
2574                        cx.stop_propagation();
2575                    });
2576                }
2577            });
2578        }
2579    }
2580
2581    fn refresh_scrollbar_markers(
2582        &self,
2583        layout: &EditorLayout,
2584        scrollbar_layout: &ScrollbarLayout,
2585        cx: &mut ElementContext,
2586    ) {
2587        self.editor.update(cx, |editor, cx| {
2588            if !editor.is_singleton(cx)
2589                || !editor
2590                    .scrollbar_marker_state
2591                    .should_refresh(scrollbar_layout.hitbox.size)
2592            {
2593                return;
2594            }
2595
2596            let scrollbar_layout = scrollbar_layout.clone();
2597            let background_highlights = editor.background_highlights.clone();
2598            let snapshot = layout.position_map.snapshot.clone();
2599            let theme = cx.theme().clone();
2600            let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
2601            let max_row = layout.max_row;
2602
2603            editor.scrollbar_marker_state.dirty = false;
2604            editor.scrollbar_marker_state.pending_refresh =
2605                Some(cx.spawn(|editor, mut cx| async move {
2606                    let scrollbar_size = scrollbar_layout.hitbox.size;
2607                    let scrollbar_markers = cx
2608                        .background_executor()
2609                        .spawn(async move {
2610                            let mut marker_quads = Vec::new();
2611
2612                            if scrollbar_settings.git_diff {
2613                                let marker_row_ranges = snapshot
2614                                    .buffer_snapshot
2615                                    .git_diff_hunks_in_range(0..max_row)
2616                                    .map(|hunk| {
2617                                        let start_display_row =
2618                                            Point::new(hunk.associated_range.start, 0)
2619                                                .to_display_point(&snapshot.display_snapshot)
2620                                                .row();
2621                                        let mut end_display_row =
2622                                            Point::new(hunk.associated_range.end, 0)
2623                                                .to_display_point(&snapshot.display_snapshot)
2624                                                .row();
2625                                        if end_display_row != start_display_row {
2626                                            end_display_row -= 1;
2627                                        }
2628                                        let color = match hunk.status() {
2629                                            DiffHunkStatus::Added => theme.status().created,
2630                                            DiffHunkStatus::Modified => theme.status().modified,
2631                                            DiffHunkStatus::Removed => theme.status().deleted,
2632                                        };
2633                                        ColoredRange {
2634                                            start: start_display_row,
2635                                            end: end_display_row,
2636                                            color,
2637                                        }
2638                                    });
2639
2640                                marker_quads.extend(
2641                                    scrollbar_layout.marker_quads_for_ranges(marker_row_ranges, 0),
2642                                );
2643                            }
2644
2645                            for (background_highlight_id, (_, background_ranges)) in
2646                                background_highlights.iter()
2647                            {
2648                                let is_search_highlights = *background_highlight_id
2649                                    == TypeId::of::<BufferSearchHighlights>();
2650                                let is_symbol_occurrences = *background_highlight_id
2651                                    == TypeId::of::<DocumentHighlightRead>()
2652                                    || *background_highlight_id
2653                                        == TypeId::of::<DocumentHighlightWrite>();
2654                                if (is_search_highlights && scrollbar_settings.search_results)
2655                                    || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
2656                                {
2657                                    let marker_row_ranges =
2658                                        background_ranges.into_iter().map(|range| {
2659                                            let display_start = range
2660                                                .start
2661                                                .to_display_point(&snapshot.display_snapshot);
2662                                            let display_end = range
2663                                                .end
2664                                                .to_display_point(&snapshot.display_snapshot);
2665                                            ColoredRange {
2666                                                start: display_start.row(),
2667                                                end: display_end.row(),
2668                                                color: theme.status().info,
2669                                            }
2670                                        });
2671                                    marker_quads.extend(
2672                                        scrollbar_layout
2673                                            .marker_quads_for_ranges(marker_row_ranges, 1),
2674                                    );
2675                                }
2676                            }
2677
2678                            if scrollbar_settings.diagnostics {
2679                                let max_point =
2680                                    snapshot.display_snapshot.buffer_snapshot.max_point();
2681
2682                                let diagnostics = snapshot
2683                                    .buffer_snapshot
2684                                    .diagnostics_in_range::<_, Point>(
2685                                        Point::zero()..max_point,
2686                                        false,
2687                                    )
2688                                    // We want to sort by severity, in order to paint the most severe diagnostics last.
2689                                    .sorted_by_key(|diagnostic| {
2690                                        std::cmp::Reverse(diagnostic.diagnostic.severity)
2691                                    });
2692
2693                                let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| {
2694                                    let start_display = diagnostic
2695                                        .range
2696                                        .start
2697                                        .to_display_point(&snapshot.display_snapshot);
2698                                    let end_display = diagnostic
2699                                        .range
2700                                        .end
2701                                        .to_display_point(&snapshot.display_snapshot);
2702                                    let color = match diagnostic.diagnostic.severity {
2703                                        DiagnosticSeverity::ERROR => theme.status().error,
2704                                        DiagnosticSeverity::WARNING => theme.status().warning,
2705                                        DiagnosticSeverity::INFORMATION => theme.status().info,
2706                                        _ => theme.status().hint,
2707                                    };
2708                                    ColoredRange {
2709                                        start: start_display.row(),
2710                                        end: end_display.row(),
2711                                        color,
2712                                    }
2713                                });
2714                                marker_quads.extend(
2715                                    scrollbar_layout.marker_quads_for_ranges(marker_row_ranges, 2),
2716                                );
2717                            }
2718
2719                            Arc::from(marker_quads)
2720                        })
2721                        .await;
2722
2723                    editor.update(&mut cx, |editor, cx| {
2724                        editor.scrollbar_marker_state.markers = scrollbar_markers;
2725                        editor.scrollbar_marker_state.scrollbar_size = scrollbar_size;
2726                        editor.scrollbar_marker_state.pending_refresh = None;
2727                        cx.notify();
2728                    })?;
2729
2730                    Ok(())
2731                }));
2732        });
2733    }
2734
2735    #[allow(clippy::too_many_arguments)]
2736    fn paint_highlighted_range(
2737        &self,
2738        range: Range<DisplayPoint>,
2739        color: Hsla,
2740        corner_radius: Pixels,
2741        line_end_overshoot: Pixels,
2742        layout: &EditorLayout,
2743        cx: &mut ElementContext,
2744    ) {
2745        let start_row = layout.visible_display_row_range.start;
2746        let end_row = layout.visible_display_row_range.end;
2747        if range.start != range.end {
2748            let row_range = if range.end.column() == 0 {
2749                cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
2750            } else {
2751                cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
2752            };
2753
2754            let highlighted_range = HighlightedRange {
2755                color,
2756                line_height: layout.position_map.line_height,
2757                corner_radius,
2758                start_y: layout.content_origin.y
2759                    + row_range.start as f32 * layout.position_map.line_height
2760                    - layout.position_map.scroll_pixel_position.y,
2761                lines: row_range
2762                    .into_iter()
2763                    .map(|row| {
2764                        let line_layout =
2765                            &layout.position_map.line_layouts[(row - start_row) as usize].line;
2766                        HighlightedRangeLine {
2767                            start_x: if row == range.start.row() {
2768                                layout.content_origin.x
2769                                    + line_layout.x_for_index(range.start.column() as usize)
2770                                    - layout.position_map.scroll_pixel_position.x
2771                            } else {
2772                                layout.content_origin.x
2773                                    - layout.position_map.scroll_pixel_position.x
2774                            },
2775                            end_x: if row == range.end.row() {
2776                                layout.content_origin.x
2777                                    + line_layout.x_for_index(range.end.column() as usize)
2778                                    - layout.position_map.scroll_pixel_position.x
2779                            } else {
2780                                layout.content_origin.x + line_layout.width + line_end_overshoot
2781                                    - layout.position_map.scroll_pixel_position.x
2782                            },
2783                        }
2784                    })
2785                    .collect(),
2786            };
2787
2788            highlighted_range.paint(layout.text_hitbox.bounds, cx);
2789        }
2790    }
2791
2792    fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2793        if layout.folds.is_empty() {
2794            return;
2795        }
2796
2797        cx.paint_layer(layout.text_hitbox.bounds, |cx| {
2798            let fold_corner_radius = 0.15 * layout.position_map.line_height;
2799            for mut fold in mem::take(&mut layout.folds) {
2800                fold.hover_element.paint(cx);
2801
2802                let hover_element = fold.hover_element.downcast_mut::<Stateful<Div>>().unwrap();
2803                let fold_background = if hover_element.interactivity().active.unwrap() {
2804                    cx.theme().colors().ghost_element_active
2805                } else if hover_element.interactivity().hovered.unwrap() {
2806                    cx.theme().colors().ghost_element_hover
2807                } else {
2808                    cx.theme().colors().ghost_element_background
2809                };
2810
2811                self.paint_highlighted_range(
2812                    fold.display_range.clone(),
2813                    fold_background,
2814                    fold_corner_radius,
2815                    fold_corner_radius * 2.,
2816                    layout,
2817                    cx,
2818                );
2819            }
2820        })
2821    }
2822
2823    fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2824        if let Some(mut inline_blame) = layout.inline_blame.take() {
2825            cx.paint_layer(layout.text_hitbox.bounds, |cx| {
2826                inline_blame.paint(cx);
2827            })
2828        }
2829    }
2830
2831    fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2832        for mut block in layout.blocks.drain(..) {
2833            block.element.paint(cx);
2834        }
2835    }
2836
2837    fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
2838        if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
2839            mouse_context_menu.paint(cx);
2840        }
2841    }
2842
2843    fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
2844        cx.on_mouse_event({
2845            let position_map = layout.position_map.clone();
2846            let editor = self.editor.clone();
2847            let hitbox = layout.hitbox.clone();
2848            let mut delta = ScrollDelta::default();
2849
2850            // Set a minimum scroll_sensitivity of 0.01 to make sure the user doesn't
2851            // accidentally turn off their scrolling.
2852            let scroll_sensitivity = EditorSettings::get_global(cx).scroll_sensitivity.max(0.01);
2853
2854            move |event: &ScrollWheelEvent, phase, cx| {
2855                if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
2856                    delta = delta.coalesce(event.delta);
2857                    editor.update(cx, |editor, cx| {
2858                        let position_map: &PositionMap = &position_map;
2859
2860                        let line_height = position_map.line_height;
2861                        let max_glyph_width = position_map.em_width;
2862                        let (delta, axis) = match delta {
2863                            gpui::ScrollDelta::Pixels(mut pixels) => {
2864                                //Trackpad
2865                                let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
2866                                (pixels, axis)
2867                            }
2868
2869                            gpui::ScrollDelta::Lines(lines) => {
2870                                //Not trackpad
2871                                let pixels =
2872                                    point(lines.x * max_glyph_width, lines.y * line_height);
2873                                (pixels, None)
2874                            }
2875                        };
2876
2877                        let scroll_position = position_map.snapshot.scroll_position();
2878                        let x = (scroll_position.x * max_glyph_width
2879                            - (delta.x * scroll_sensitivity))
2880                            / max_glyph_width;
2881                        let y = (scroll_position.y * line_height - (delta.y * scroll_sensitivity))
2882                            / line_height;
2883                        let scroll_position =
2884                            point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
2885                        editor.scroll(scroll_position, axis, cx);
2886                        cx.stop_propagation();
2887                    });
2888                }
2889            }
2890        });
2891    }
2892
2893    fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
2894        self.paint_scroll_wheel_listener(layout, cx);
2895
2896        cx.on_mouse_event({
2897            let position_map = layout.position_map.clone();
2898            let editor = self.editor.clone();
2899            let text_hitbox = layout.text_hitbox.clone();
2900            let gutter_hitbox = layout.gutter_hitbox.clone();
2901
2902            move |event: &MouseDownEvent, phase, cx| {
2903                if phase == DispatchPhase::Bubble {
2904                    match event.button {
2905                        MouseButton::Left => editor.update(cx, |editor, cx| {
2906                            Self::mouse_left_down(
2907                                editor,
2908                                event,
2909                                &position_map,
2910                                &text_hitbox,
2911                                &gutter_hitbox,
2912                                cx,
2913                            );
2914                        }),
2915                        MouseButton::Right => editor.update(cx, |editor, cx| {
2916                            Self::mouse_right_down(editor, event, &position_map, &text_hitbox, cx);
2917                        }),
2918                        MouseButton::Middle => editor.update(cx, |editor, cx| {
2919                            Self::mouse_middle_down(editor, event, &position_map, &text_hitbox, cx);
2920                        }),
2921                        _ => {}
2922                    };
2923                }
2924            }
2925        });
2926
2927        cx.on_mouse_event({
2928            let editor = self.editor.clone();
2929            let position_map = layout.position_map.clone();
2930            let text_hitbox = layout.text_hitbox.clone();
2931
2932            move |event: &MouseUpEvent, phase, cx| {
2933                if phase == DispatchPhase::Bubble {
2934                    editor.update(cx, |editor, cx| {
2935                        Self::mouse_up(editor, event, &position_map, &text_hitbox, cx)
2936                    });
2937                }
2938            }
2939        });
2940        cx.on_mouse_event({
2941            let position_map = layout.position_map.clone();
2942            let editor = self.editor.clone();
2943            let text_hitbox = layout.text_hitbox.clone();
2944            let gutter_hitbox = layout.gutter_hitbox.clone();
2945
2946            move |event: &MouseMoveEvent, phase, cx| {
2947                if phase == DispatchPhase::Bubble {
2948                    editor.update(cx, |editor, cx| {
2949                        if event.pressed_button == Some(MouseButton::Left) {
2950                            Self::mouse_dragged(
2951                                editor,
2952                                event,
2953                                &position_map,
2954                                text_hitbox.bounds,
2955                                cx,
2956                            )
2957                        }
2958
2959                        Self::mouse_moved(
2960                            editor,
2961                            event,
2962                            &position_map,
2963                            &text_hitbox,
2964                            &gutter_hitbox,
2965                            cx,
2966                        )
2967                    });
2968                }
2969            }
2970        });
2971    }
2972
2973    fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
2974        bounds.upper_right().x - self.style.scrollbar_width
2975    }
2976
2977    fn column_pixels(&self, column: usize, cx: &WindowContext) -> Pixels {
2978        let style = &self.style;
2979        let font_size = style.text.font_size.to_pixels(cx.rem_size());
2980        let layout = cx
2981            .text_system()
2982            .shape_line(
2983                SharedString::from(" ".repeat(column)),
2984                font_size,
2985                &[TextRun {
2986                    len: column,
2987                    font: style.text.font(),
2988                    color: Hsla::default(),
2989                    background_color: None,
2990                    underline: None,
2991                    strikethrough: None,
2992                }],
2993            )
2994            .unwrap();
2995
2996        layout.width
2997    }
2998
2999    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
3000        let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
3001        self.column_pixels(digit_count, cx)
3002    }
3003}
3004
3005fn render_inline_blame_entry(
3006    blame: &gpui::Model<GitBlame>,
3007    blame_entry: BlameEntry,
3008    style: &EditorStyle,
3009    workspace: Option<WeakView<Workspace>>,
3010    cx: &mut ElementContext<'_>,
3011) -> AnyElement {
3012    let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
3013
3014    let author = blame_entry.author.as_deref().unwrap_or_default();
3015    let text = format!("{}, {}", author, relative_timestamp);
3016
3017    let details = blame.read(cx).details_for_entry(&blame_entry);
3018
3019    let tooltip = cx.new_view(|_| BlameEntryTooltip::new(blame_entry, details, style, workspace));
3020
3021    h_flex()
3022        .id("inline-blame")
3023        .w_full()
3024        .font(style.text.font().family)
3025        .text_color(cx.theme().status().hint)
3026        .line_height(style.text.line_height)
3027        .child(Icon::new(IconName::FileGit).color(Color::Hint))
3028        .child(text)
3029        .gap_2()
3030        .hoverable_tooltip(move |_| tooltip.clone().into())
3031        .into_any()
3032}
3033
3034fn render_blame_entry(
3035    ix: usize,
3036    blame: &gpui::Model<GitBlame>,
3037    blame_entry: BlameEntry,
3038    style: &EditorStyle,
3039    last_used_color: &mut Option<(PlayerColor, Oid)>,
3040    editor: View<Editor>,
3041    cx: &mut ElementContext<'_>,
3042) -> AnyElement {
3043    let mut sha_color = cx
3044        .theme()
3045        .players()
3046        .color_for_participant(blame_entry.sha.into());
3047    // If the last color we used is the same as the one we get for this line, but
3048    // the commit SHAs are different, then we try again to get a different color.
3049    match *last_used_color {
3050        Some((color, sha)) if sha != blame_entry.sha && color.cursor == sha_color.cursor => {
3051            let index: u32 = blame_entry.sha.into();
3052            sha_color = cx.theme().players().color_for_participant(index + 1);
3053        }
3054        _ => {}
3055    };
3056    last_used_color.replace((sha_color, blame_entry.sha));
3057
3058    let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
3059
3060    let pretty_commit_id = format!("{}", blame_entry.sha);
3061    let short_commit_id = pretty_commit_id.chars().take(6).collect::<String>();
3062
3063    let author_name = blame_entry.author.as_deref().unwrap_or("<no name>");
3064    let name = util::truncate_and_trailoff(author_name, 20);
3065
3066    let details = blame.read(cx).details_for_entry(&blame_entry);
3067
3068    let workspace = editor.read(cx).workspace.as_ref().map(|(w, _)| w.clone());
3069
3070    let tooltip = cx.new_view(|_| {
3071        BlameEntryTooltip::new(blame_entry.clone(), details.clone(), style, workspace)
3072    });
3073
3074    h_flex()
3075        .w_full()
3076        .font(style.text.font().family)
3077        .line_height(style.text.line_height)
3078        .id(("blame", ix))
3079        .children([
3080            div()
3081                .text_color(sha_color.cursor)
3082                .child(short_commit_id)
3083                .mr_2(),
3084            div()
3085                .w_full()
3086                .h_flex()
3087                .justify_between()
3088                .text_color(cx.theme().status().hint)
3089                .child(name)
3090                .child(relative_timestamp),
3091        ])
3092        .on_mouse_down(MouseButton::Right, {
3093            let blame_entry = blame_entry.clone();
3094            move |event, cx| {
3095                deploy_blame_entry_context_menu(&blame_entry, editor.clone(), event.position, cx);
3096            }
3097        })
3098        .hover(|style| style.bg(cx.theme().colors().element_hover))
3099        .when_some(
3100            details.and_then(|details| details.permalink),
3101            |this, url| {
3102                let url = url.clone();
3103                this.cursor_pointer().on_click(move |_, cx| {
3104                    cx.stop_propagation();
3105                    cx.open_url(url.as_str())
3106                })
3107            },
3108        )
3109        .hoverable_tooltip(move |_| tooltip.clone().into())
3110        .into_any()
3111}
3112
3113fn deploy_blame_entry_context_menu(
3114    blame_entry: &BlameEntry,
3115    editor: View<Editor>,
3116    position: gpui::Point<Pixels>,
3117    cx: &mut WindowContext<'_>,
3118) {
3119    let context_menu = ContextMenu::build(cx, move |this, _| {
3120        let sha = format!("{}", blame_entry.sha);
3121        this.entry("Copy commit SHA", None, move |cx| {
3122            cx.write_to_clipboard(ClipboardItem::new(sha.clone()));
3123        })
3124    });
3125
3126    editor.update(cx, move |editor, cx| {
3127        editor.mouse_context_menu = Some(MouseContextMenu::new(position, context_menu, cx));
3128        cx.notify();
3129    });
3130}
3131
3132#[derive(Debug)]
3133pub(crate) struct LineWithInvisibles {
3134    pub line: ShapedLine,
3135    invisibles: Vec<Invisible>,
3136}
3137
3138impl LineWithInvisibles {
3139    fn from_chunks<'a>(
3140        chunks: impl Iterator<Item = HighlightedChunk<'a>>,
3141        text_style: &TextStyle,
3142        max_line_len: usize,
3143        max_line_count: usize,
3144        line_number_layouts: &[Option<ShapedLine>],
3145        editor_mode: EditorMode,
3146        cx: &WindowContext,
3147    ) -> Vec<Self> {
3148        let mut layouts = Vec::with_capacity(max_line_count);
3149        let mut line = String::new();
3150        let mut invisibles = Vec::new();
3151        let mut styles = Vec::new();
3152        let mut non_whitespace_added = false;
3153        let mut row = 0;
3154        let mut line_exceeded_max_len = false;
3155        let font_size = text_style.font_size.to_pixels(cx.rem_size());
3156
3157        for highlighted_chunk in chunks.chain([HighlightedChunk {
3158            chunk: "\n",
3159            style: None,
3160            is_tab: false,
3161        }]) {
3162            for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
3163                if ix > 0 {
3164                    let shaped_line = cx
3165                        .text_system()
3166                        .shape_line(line.clone().into(), font_size, &styles)
3167                        .unwrap();
3168                    layouts.push(Self {
3169                        line: shaped_line,
3170                        invisibles: std::mem::take(&mut invisibles),
3171                    });
3172
3173                    line.clear();
3174                    styles.clear();
3175                    row += 1;
3176                    line_exceeded_max_len = false;
3177                    non_whitespace_added = false;
3178                    if row == max_line_count {
3179                        return layouts;
3180                    }
3181                }
3182
3183                if !line_chunk.is_empty() && !line_exceeded_max_len {
3184                    let text_style = if let Some(style) = highlighted_chunk.style {
3185                        Cow::Owned(text_style.clone().highlight(style))
3186                    } else {
3187                        Cow::Borrowed(text_style)
3188                    };
3189
3190                    if line.len() + line_chunk.len() > max_line_len {
3191                        let mut chunk_len = max_line_len - line.len();
3192                        while !line_chunk.is_char_boundary(chunk_len) {
3193                            chunk_len -= 1;
3194                        }
3195                        line_chunk = &line_chunk[..chunk_len];
3196                        line_exceeded_max_len = true;
3197                    }
3198
3199                    styles.push(TextRun {
3200                        len: line_chunk.len(),
3201                        font: text_style.font(),
3202                        color: text_style.color,
3203                        background_color: text_style.background_color,
3204                        underline: text_style.underline,
3205                        strikethrough: text_style.strikethrough,
3206                    });
3207
3208                    if editor_mode == EditorMode::Full {
3209                        // Line wrap pads its contents with fake whitespaces,
3210                        // avoid printing them
3211                        let inside_wrapped_string = line_number_layouts
3212                            .get(row)
3213                            .and_then(|layout| layout.as_ref())
3214                            .is_none();
3215                        if highlighted_chunk.is_tab {
3216                            if non_whitespace_added || !inside_wrapped_string {
3217                                invisibles.push(Invisible::Tab {
3218                                    line_start_offset: line.len(),
3219                                });
3220                            }
3221                        } else {
3222                            invisibles.extend(
3223                                line_chunk
3224                                    .chars()
3225                                    .enumerate()
3226                                    .filter(|(_, line_char)| {
3227                                        let is_whitespace = line_char.is_whitespace();
3228                                        non_whitespace_added |= !is_whitespace;
3229                                        is_whitespace
3230                                            && (non_whitespace_added || !inside_wrapped_string)
3231                                    })
3232                                    .map(|(whitespace_index, _)| Invisible::Whitespace {
3233                                        line_offset: line.len() + whitespace_index,
3234                                    }),
3235                            )
3236                        }
3237                    }
3238
3239                    line.push_str(line_chunk);
3240                }
3241            }
3242        }
3243
3244        layouts
3245    }
3246
3247    fn draw(
3248        &self,
3249        layout: &EditorLayout,
3250        row: u32,
3251        content_origin: gpui::Point<Pixels>,
3252        whitespace_setting: ShowWhitespaceSetting,
3253        selection_ranges: &[Range<DisplayPoint>],
3254        cx: &mut ElementContext,
3255    ) {
3256        let line_height = layout.position_map.line_height;
3257        let line_y =
3258            line_height * (row as f32 - layout.position_map.scroll_pixel_position.y / line_height);
3259
3260        let line_origin =
3261            content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
3262        self.line.paint(line_origin, line_height, cx).log_err();
3263
3264        self.draw_invisibles(
3265            &selection_ranges,
3266            layout,
3267            content_origin,
3268            line_y,
3269            row,
3270            line_height,
3271            whitespace_setting,
3272            cx,
3273        );
3274    }
3275
3276    #[allow(clippy::too_many_arguments)]
3277    fn draw_invisibles(
3278        &self,
3279        selection_ranges: &[Range<DisplayPoint>],
3280        layout: &EditorLayout,
3281        content_origin: gpui::Point<Pixels>,
3282        line_y: Pixels,
3283        row: u32,
3284        line_height: Pixels,
3285        whitespace_setting: ShowWhitespaceSetting,
3286        cx: &mut ElementContext,
3287    ) {
3288        let allowed_invisibles_regions = match whitespace_setting {
3289            ShowWhitespaceSetting::None => return,
3290            ShowWhitespaceSetting::Selection => Some(selection_ranges),
3291            ShowWhitespaceSetting::All => None,
3292        };
3293
3294        for invisible in &self.invisibles {
3295            let (&token_offset, invisible_symbol) = match invisible {
3296                Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
3297                Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
3298            };
3299
3300            let x_offset = self.line.x_for_index(token_offset);
3301            let invisible_offset =
3302                (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
3303            let origin = content_origin
3304                + gpui::point(
3305                    x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
3306                    line_y,
3307                );
3308
3309            if let Some(allowed_regions) = allowed_invisibles_regions {
3310                let invisible_point = DisplayPoint::new(row, token_offset as u32);
3311                if !allowed_regions
3312                    .iter()
3313                    .any(|region| region.start <= invisible_point && invisible_point < region.end)
3314                {
3315                    continue;
3316                }
3317            }
3318            invisible_symbol.paint(origin, line_height, cx).log_err();
3319        }
3320    }
3321}
3322
3323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3324enum Invisible {
3325    Tab { line_start_offset: usize },
3326    Whitespace { line_offset: usize },
3327}
3328
3329impl Element for EditorElement {
3330    type BeforeLayout = ();
3331    type AfterLayout = EditorLayout;
3332
3333    fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, ()) {
3334        self.editor.update(cx, |editor, cx| {
3335            editor.set_style(self.style.clone(), cx);
3336
3337            let layout_id = match editor.mode {
3338                EditorMode::SingleLine => {
3339                    let rem_size = cx.rem_size();
3340                    let mut style = Style::default();
3341                    style.size.width = relative(1.).into();
3342                    style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
3343                    cx.with_element_context(|cx| cx.request_layout(&style, None))
3344                }
3345                EditorMode::AutoHeight { max_lines } => {
3346                    let editor_handle = cx.view().clone();
3347                    let max_line_number_width =
3348                        self.max_line_number_width(&editor.snapshot(cx), cx);
3349                    cx.with_element_context(|cx| {
3350                        cx.request_measured_layout(
3351                            Style::default(),
3352                            move |known_dimensions, _, cx| {
3353                                editor_handle
3354                                    .update(cx, |editor, cx| {
3355                                        compute_auto_height_layout(
3356                                            editor,
3357                                            max_lines,
3358                                            max_line_number_width,
3359                                            known_dimensions,
3360                                            cx,
3361                                        )
3362                                    })
3363                                    .unwrap_or_default()
3364                            },
3365                        )
3366                    })
3367                }
3368                EditorMode::Full => {
3369                    let mut style = Style::default();
3370                    style.size.width = relative(1.).into();
3371                    style.size.height = relative(1.).into();
3372                    cx.with_element_context(|cx| cx.request_layout(&style, None))
3373                }
3374            };
3375
3376            (layout_id, ())
3377        })
3378    }
3379
3380    fn after_layout(
3381        &mut self,
3382        bounds: Bounds<Pixels>,
3383        _: &mut Self::BeforeLayout,
3384        cx: &mut ElementContext,
3385    ) -> Self::AfterLayout {
3386        let text_style = TextStyleRefinement {
3387            font_size: Some(self.style.text.font_size),
3388            line_height: Some(self.style.text.line_height),
3389            ..Default::default()
3390        };
3391        cx.with_text_style(Some(text_style), |cx| {
3392            cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
3393                let mut snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
3394                let style = self.style.clone();
3395
3396                let font_id = cx.text_system().resolve_font(&style.text.font());
3397                let font_size = style.text.font_size.to_pixels(cx.rem_size());
3398                let line_height = style.text.line_height_in_pixels(cx.rem_size());
3399                let em_width = cx
3400                    .text_system()
3401                    .typographic_bounds(font_id, font_size, 'm')
3402                    .unwrap()
3403                    .size
3404                    .width;
3405                let em_advance = cx
3406                    .text_system()
3407                    .advance(font_id, font_size, 'm')
3408                    .unwrap()
3409                    .width;
3410
3411                let gutter_dimensions = snapshot.gutter_dimensions(
3412                    font_id,
3413                    font_size,
3414                    em_width,
3415                    self.max_line_number_width(&snapshot, cx),
3416                    cx,
3417                );
3418                let text_width = bounds.size.width - gutter_dimensions.width;
3419                let overscroll = size(em_width, px(0.));
3420
3421                snapshot = self.editor.update(cx, |editor, cx| {
3422                    editor.last_bounds = Some(bounds);
3423                    editor.gutter_width = gutter_dimensions.width;
3424                    editor.set_visible_line_count(bounds.size.height / line_height, cx);
3425
3426                    let editor_width =
3427                        text_width - gutter_dimensions.margin - overscroll.width - em_width;
3428                    let wrap_width = match editor.soft_wrap_mode(cx) {
3429                        SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
3430                        SoftWrap::EditorWidth => editor_width,
3431                        SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
3432                    };
3433
3434                    if editor.set_wrap_width(Some(wrap_width), cx) {
3435                        editor.snapshot(cx)
3436                    } else {
3437                        snapshot
3438                    }
3439                });
3440
3441                let wrap_guides = self
3442                    .editor
3443                    .read(cx)
3444                    .wrap_guides(cx)
3445                    .iter()
3446                    .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
3447                    .collect::<SmallVec<[_; 2]>>();
3448
3449                let hitbox = cx.insert_hitbox(bounds, false);
3450                let gutter_hitbox = cx.insert_hitbox(
3451                    Bounds {
3452                        origin: bounds.origin,
3453                        size: size(gutter_dimensions.width, bounds.size.height),
3454                    },
3455                    false,
3456                );
3457                let text_hitbox = cx.insert_hitbox(
3458                    Bounds {
3459                        origin: gutter_hitbox.upper_right(),
3460                        size: size(text_width, bounds.size.height),
3461                    },
3462                    false,
3463                );
3464                // Offset the content_bounds from the text_bounds by the gutter margin (which
3465                // is roughly half a character wide) to make hit testing work more like how we want.
3466                let content_origin =
3467                    text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
3468
3469                let autoscroll_horizontally = self.editor.update(cx, |editor, cx| {
3470                    let autoscroll_horizontally =
3471                        editor.autoscroll_vertically(bounds, line_height, cx);
3472                    snapshot = editor.snapshot(cx);
3473                    autoscroll_horizontally
3474                });
3475
3476                let mut scroll_position = snapshot.scroll_position();
3477                // The scroll position is a fractional point, the whole number of which represents
3478                // the top of the window in terms of display rows.
3479                let start_row = scroll_position.y as u32;
3480                let height_in_lines = bounds.size.height / line_height;
3481                let max_row = snapshot.max_point().row();
3482
3483                // Add 1 to ensure selections bleed off screen
3484                let end_row =
3485                    1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
3486
3487                let buffer_rows = snapshot
3488                    .buffer_rows(start_row)
3489                    .take((start_row..end_row).len());
3490
3491                let start_anchor = if start_row == 0 {
3492                    Anchor::min()
3493                } else {
3494                    snapshot.buffer_snapshot.anchor_before(
3495                        DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
3496                    )
3497                };
3498                let end_anchor = if end_row > max_row {
3499                    Anchor::max()
3500                } else {
3501                    snapshot.buffer_snapshot.anchor_before(
3502                        DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
3503                    )
3504                };
3505
3506                let highlighted_rows = self
3507                    .editor
3508                    .update(cx, |editor, cx| editor.highlighted_display_rows(cx));
3509                let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
3510                    start_anchor..end_anchor,
3511                    &snapshot.display_snapshot,
3512                    cx.theme().colors(),
3513                );
3514
3515                let redacted_ranges = self.editor.read(cx).redacted_ranges(
3516                    start_anchor..end_anchor,
3517                    &snapshot.display_snapshot,
3518                    cx,
3519                );
3520
3521                let (selections, active_rows, newest_selection_head) = self.layout_selections(
3522                    start_anchor,
3523                    end_anchor,
3524                    &snapshot,
3525                    start_row,
3526                    end_row,
3527                    cx,
3528                );
3529
3530                let (line_numbers, fold_statuses) = self.layout_line_numbers(
3531                    start_row..end_row,
3532                    buffer_rows.clone(),
3533                    &active_rows,
3534                    newest_selection_head,
3535                    &snapshot,
3536                    cx,
3537                );
3538
3539                let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
3540
3541                let mut max_visible_line_width = Pixels::ZERO;
3542                let line_layouts =
3543                    self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
3544                for line_with_invisibles in &line_layouts {
3545                    if line_with_invisibles.line.width > max_visible_line_width {
3546                        max_visible_line_width = line_with_invisibles.line.width;
3547                    }
3548                }
3549
3550                let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
3551                    .unwrap()
3552                    .width;
3553                let mut scroll_width =
3554                    longest_line_width.max(max_visible_line_width) + overscroll.width;
3555                let mut blocks = self.build_blocks(
3556                    start_row..end_row,
3557                    &snapshot,
3558                    &hitbox,
3559                    &text_hitbox,
3560                    &mut scroll_width,
3561                    &gutter_dimensions,
3562                    em_width,
3563                    gutter_dimensions.width + gutter_dimensions.margin,
3564                    line_height,
3565                    &line_layouts,
3566                    cx,
3567                );
3568
3569                let scroll_pixel_position = point(
3570                    scroll_position.x * em_width,
3571                    scroll_position.y * line_height,
3572                );
3573
3574                let mut inline_blame = None;
3575                if let Some(newest_selection_head) = newest_selection_head {
3576                    let display_row = newest_selection_head.row();
3577                    if (start_row..end_row).contains(&display_row) {
3578                        let line_layout = &line_layouts[(display_row - start_row) as usize];
3579                        inline_blame = self.layout_inline_blame(
3580                            display_row,
3581                            &snapshot.display_snapshot,
3582                            line_layout,
3583                            em_width,
3584                            content_origin,
3585                            scroll_pixel_position,
3586                            line_height,
3587                            cx,
3588                        );
3589                    }
3590                }
3591
3592                let blamed_display_rows = self.layout_blame_entries(
3593                    buffer_rows,
3594                    em_width,
3595                    scroll_position,
3596                    line_height,
3597                    &gutter_hitbox,
3598                    gutter_dimensions.git_blame_entries_width,
3599                    cx,
3600                );
3601
3602                let scroll_max = point(
3603                    ((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
3604                    max_row as f32,
3605                );
3606
3607                self.editor.update(cx, |editor, cx| {
3608                    let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
3609
3610                    let autoscrolled = if autoscroll_horizontally {
3611                        editor.autoscroll_horizontally(
3612                            start_row,
3613                            text_hitbox.size.width,
3614                            scroll_width,
3615                            em_width,
3616                            &line_layouts,
3617                            cx,
3618                        )
3619                    } else {
3620                        false
3621                    };
3622
3623                    if clamped || autoscrolled {
3624                        snapshot = editor.snapshot(cx);
3625                        scroll_position = snapshot.scroll_position();
3626                    }
3627                });
3628
3629                cx.with_element_id(Some("blocks"), |cx| {
3630                    self.layout_blocks(
3631                        &mut blocks,
3632                        &hitbox,
3633                        line_height,
3634                        scroll_pixel_position,
3635                        cx,
3636                    );
3637                });
3638
3639                let cursors = self.layout_cursors(
3640                    &snapshot,
3641                    &selections,
3642                    start_row..end_row,
3643                    &line_layouts,
3644                    &text_hitbox,
3645                    content_origin,
3646                    scroll_pixel_position,
3647                    line_height,
3648                    em_width,
3649                    cx,
3650                );
3651
3652                let scrollbar_layout =
3653                    self.layout_scrollbar(&snapshot, bounds, scroll_position, height_in_lines, cx);
3654
3655                let folds = cx.with_element_id(Some("folds"), |cx| {
3656                    self.layout_folds(
3657                        &snapshot,
3658                        content_origin,
3659                        start_anchor..end_anchor,
3660                        start_row..end_row,
3661                        scroll_pixel_position,
3662                        line_height,
3663                        &line_layouts,
3664                        cx,
3665                    )
3666                });
3667
3668                let gutter_settings = EditorSettings::get_global(cx).gutter;
3669
3670                let mut context_menu_visible = false;
3671                let mut code_actions_indicator = None;
3672                if let Some(newest_selection_head) = newest_selection_head {
3673                    if (start_row..end_row).contains(&newest_selection_head.row()) {
3674                        context_menu_visible = self.layout_context_menu(
3675                            line_height,
3676                            &hitbox,
3677                            &text_hitbox,
3678                            content_origin,
3679                            start_row,
3680                            scroll_pixel_position,
3681                            &line_layouts,
3682                            newest_selection_head,
3683                            cx,
3684                        );
3685                        if gutter_settings.code_actions {
3686                            code_actions_indicator = self.layout_code_actions_indicator(
3687                                line_height,
3688                                newest_selection_head,
3689                                scroll_pixel_position,
3690                                &gutter_dimensions,
3691                                &gutter_hitbox,
3692                                cx,
3693                            );
3694                        }
3695                    }
3696                }
3697
3698                if !context_menu_visible && !cx.has_active_drag() {
3699                    self.layout_hover_popovers(
3700                        &snapshot,
3701                        &hitbox,
3702                        &text_hitbox,
3703                        start_row..end_row,
3704                        content_origin,
3705                        scroll_pixel_position,
3706                        &line_layouts,
3707                        line_height,
3708                        em_width,
3709                        cx,
3710                    );
3711                }
3712
3713                let mouse_context_menu = self.layout_mouse_context_menu(cx);
3714
3715                let fold_indicators = if gutter_settings.folds {
3716                    cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
3717                        self.layout_gutter_fold_indicators(
3718                            fold_statuses,
3719                            line_height,
3720                            &gutter_dimensions,
3721                            gutter_settings,
3722                            scroll_pixel_position,
3723                            &gutter_hitbox,
3724                            cx,
3725                        )
3726                    })
3727                } else {
3728                    Vec::new()
3729                };
3730
3731                let invisible_symbol_font_size = font_size / 2.;
3732                let tab_invisible = cx
3733                    .text_system()
3734                    .shape_line(
3735                        "".into(),
3736                        invisible_symbol_font_size,
3737                        &[TextRun {
3738                            len: "".len(),
3739                            font: self.style.text.font(),
3740                            color: cx.theme().colors().editor_invisible,
3741                            background_color: None,
3742                            underline: None,
3743                            strikethrough: None,
3744                        }],
3745                    )
3746                    .unwrap();
3747                let space_invisible = cx
3748                    .text_system()
3749                    .shape_line(
3750                        "".into(),
3751                        invisible_symbol_font_size,
3752                        &[TextRun {
3753                            len: "".len(),
3754                            font: self.style.text.font(),
3755                            color: cx.theme().colors().editor_invisible,
3756                            background_color: None,
3757                            underline: None,
3758                            strikethrough: None,
3759                        }],
3760                    )
3761                    .unwrap();
3762
3763                EditorLayout {
3764                    mode: snapshot.mode,
3765                    position_map: Arc::new(PositionMap {
3766                        size: bounds.size,
3767                        scroll_pixel_position,
3768                        scroll_max,
3769                        line_layouts,
3770                        line_height,
3771                        em_width,
3772                        em_advance,
3773                        snapshot,
3774                    }),
3775                    visible_display_row_range: start_row..end_row,
3776                    wrap_guides,
3777                    hitbox,
3778                    text_hitbox,
3779                    gutter_hitbox,
3780                    gutter_dimensions,
3781                    content_origin,
3782                    scrollbar_layout,
3783                    max_row,
3784                    active_rows,
3785                    highlighted_rows,
3786                    highlighted_ranges,
3787                    redacted_ranges,
3788                    line_numbers,
3789                    display_hunks,
3790                    blamed_display_rows,
3791                    inline_blame,
3792                    folds,
3793                    blocks,
3794                    cursors,
3795                    selections,
3796                    mouse_context_menu,
3797                    code_actions_indicator,
3798                    fold_indicators,
3799                    tab_invisible,
3800                    space_invisible,
3801                }
3802            })
3803        })
3804    }
3805
3806    fn paint(
3807        &mut self,
3808        bounds: Bounds<gpui::Pixels>,
3809        _: &mut Self::BeforeLayout,
3810        layout: &mut Self::AfterLayout,
3811        cx: &mut ElementContext,
3812    ) {
3813        let focus_handle = self.editor.focus_handle(cx);
3814        let key_context = self.editor.read(cx).key_context(cx);
3815        cx.set_focus_handle(&focus_handle);
3816        cx.set_key_context(key_context);
3817        cx.set_view_id(self.editor.entity_id());
3818        cx.handle_input(
3819            &focus_handle,
3820            ElementInputHandler::new(bounds, self.editor.clone()),
3821        );
3822        self.register_actions(cx);
3823        self.register_key_listeners(cx, layout);
3824
3825        let text_style = TextStyleRefinement {
3826            font_size: Some(self.style.text.font_size),
3827            line_height: Some(self.style.text.line_height),
3828            ..Default::default()
3829        };
3830        cx.with_text_style(Some(text_style), |cx| {
3831            cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
3832                self.paint_mouse_listeners(layout, cx);
3833
3834                self.paint_background(layout, cx);
3835                if layout.gutter_hitbox.size.width > Pixels::ZERO {
3836                    self.paint_gutter(layout, cx);
3837                }
3838                self.paint_text(layout, cx);
3839
3840                if !layout.blocks.is_empty() {
3841                    cx.with_element_id(Some("blocks"), |cx| {
3842                        self.paint_blocks(layout, cx);
3843                    });
3844                }
3845
3846                self.paint_scrollbar(layout, cx);
3847                self.paint_mouse_context_menu(layout, cx);
3848            });
3849        })
3850    }
3851}
3852
3853impl IntoElement for EditorElement {
3854    type Element = Self;
3855
3856    fn into_element(self) -> Self::Element {
3857        self
3858    }
3859}
3860
3861type BufferRow = u32;
3862
3863pub struct EditorLayout {
3864    position_map: Arc<PositionMap>,
3865    hitbox: Hitbox,
3866    text_hitbox: Hitbox,
3867    gutter_hitbox: Hitbox,
3868    gutter_dimensions: GutterDimensions,
3869    content_origin: gpui::Point<Pixels>,
3870    scrollbar_layout: Option<ScrollbarLayout>,
3871    mode: EditorMode,
3872    wrap_guides: SmallVec<[(Pixels, bool); 2]>,
3873    visible_display_row_range: Range<u32>,
3874    active_rows: BTreeMap<u32, bool>,
3875    highlighted_rows: BTreeMap<u32, Hsla>,
3876    line_numbers: Vec<Option<ShapedLine>>,
3877    display_hunks: Vec<DisplayDiffHunk>,
3878    blamed_display_rows: Option<Vec<AnyElement>>,
3879    inline_blame: Option<AnyElement>,
3880    folds: Vec<FoldLayout>,
3881    blocks: Vec<BlockLayout>,
3882    highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
3883    redacted_ranges: Vec<Range<DisplayPoint>>,
3884    cursors: Vec<CursorLayout>,
3885    selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
3886    max_row: u32,
3887    code_actions_indicator: Option<AnyElement>,
3888    fold_indicators: Vec<Option<AnyElement>>,
3889    mouse_context_menu: Option<AnyElement>,
3890    tab_invisible: ShapedLine,
3891    space_invisible: ShapedLine,
3892}
3893
3894impl EditorLayout {
3895    fn line_end_overshoot(&self) -> Pixels {
3896        0.15 * self.position_map.line_height
3897    }
3898}
3899
3900struct ColoredRange<T> {
3901    start: T,
3902    end: T,
3903    color: Hsla,
3904}
3905
3906#[derive(Clone)]
3907struct ScrollbarLayout {
3908    hitbox: Hitbox,
3909    visible_row_range: Range<f32>,
3910    visible: bool,
3911    row_height: Pixels,
3912    thumb_height: Pixels,
3913}
3914
3915impl ScrollbarLayout {
3916    const BORDER_WIDTH: Pixels = px(1.0);
3917    const MIN_MARKER_HEIGHT: Pixels = px(2.0);
3918    const MIN_THUMB_HEIGHT: Pixels = px(20.0);
3919
3920    fn thumb_bounds(&self) -> Bounds<Pixels> {
3921        let thumb_top = self.y_for_row(self.visible_row_range.start);
3922        let thumb_bottom = thumb_top + self.thumb_height;
3923        Bounds::from_corners(
3924            point(self.hitbox.left(), thumb_top),
3925            point(self.hitbox.right(), thumb_bottom),
3926        )
3927    }
3928
3929    fn y_for_row(&self, row: f32) -> Pixels {
3930        self.hitbox.top() + row * self.row_height
3931    }
3932
3933    fn marker_quads_for_ranges(
3934        &self,
3935        row_ranges: impl IntoIterator<Item = ColoredRange<u32>>,
3936        column: usize,
3937    ) -> Vec<PaintQuad> {
3938        let column_width =
3939            px(((self.hitbox.size.width - ScrollbarLayout::BORDER_WIDTH).0 / 3.0).floor());
3940
3941        let left_x = ScrollbarLayout::BORDER_WIDTH + (column as f32 * column_width);
3942        let right_x = left_x + column_width;
3943
3944        let mut background_pixel_ranges = row_ranges
3945            .into_iter()
3946            .map(|range| {
3947                let start_y = range.start as f32 * self.row_height;
3948                let end_y = (range.end + 1) as f32 * self.row_height;
3949                ColoredRange {
3950                    start: start_y,
3951                    end: end_y,
3952                    color: range.color,
3953                }
3954            })
3955            .peekable();
3956
3957        let mut quads = Vec::new();
3958        while let Some(mut pixel_range) = background_pixel_ranges.next() {
3959            pixel_range.end = pixel_range
3960                .end
3961                .max(pixel_range.start + Self::MIN_MARKER_HEIGHT);
3962            while let Some(next_pixel_range) = background_pixel_ranges.peek() {
3963                if pixel_range.end >= next_pixel_range.start
3964                    && pixel_range.color == next_pixel_range.color
3965                {
3966                    pixel_range.end = next_pixel_range.end.max(pixel_range.end);
3967                    background_pixel_ranges.next();
3968                } else {
3969                    break;
3970                }
3971            }
3972
3973            let bounds = Bounds::from_corners(
3974                point(left_x, pixel_range.start),
3975                point(right_x, pixel_range.end),
3976            );
3977            quads.push(quad(
3978                bounds,
3979                Corners::default(),
3980                pixel_range.color,
3981                Edges::default(),
3982                Hsla::transparent_black(),
3983            ));
3984        }
3985
3986        quads
3987    }
3988}
3989
3990struct FoldLayout {
3991    display_range: Range<DisplayPoint>,
3992    hover_element: AnyElement,
3993}
3994
3995struct PositionMap {
3996    size: Size<Pixels>,
3997    line_height: Pixels,
3998    scroll_pixel_position: gpui::Point<Pixels>,
3999    scroll_max: gpui::Point<f32>,
4000    em_width: Pixels,
4001    em_advance: Pixels,
4002    line_layouts: Vec<LineWithInvisibles>,
4003    snapshot: EditorSnapshot,
4004}
4005
4006#[derive(Debug, Copy, Clone)]
4007pub struct PointForPosition {
4008    pub previous_valid: DisplayPoint,
4009    pub next_valid: DisplayPoint,
4010    pub exact_unclipped: DisplayPoint,
4011    pub column_overshoot_after_line_end: u32,
4012}
4013
4014impl PointForPosition {
4015    pub fn as_valid(&self) -> Option<DisplayPoint> {
4016        if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
4017            Some(self.previous_valid)
4018        } else {
4019            None
4020        }
4021    }
4022}
4023
4024impl PositionMap {
4025    fn point_for_position(
4026        &self,
4027        text_bounds: Bounds<Pixels>,
4028        position: gpui::Point<Pixels>,
4029    ) -> PointForPosition {
4030        let scroll_position = self.snapshot.scroll_position();
4031        let position = position - text_bounds.origin;
4032        let y = position.y.max(px(0.)).min(self.size.height);
4033        let x = position.x + (scroll_position.x * self.em_width);
4034        let row = ((y / self.line_height) + scroll_position.y) as u32;
4035
4036        let (column, x_overshoot_after_line_end) = if let Some(line) = self
4037            .line_layouts
4038            .get(row as usize - scroll_position.y as usize)
4039            .map(|LineWithInvisibles { line, .. }| line)
4040        {
4041            if let Some(ix) = line.index_for_x(x) {
4042                (ix as u32, px(0.))
4043            } else {
4044                (line.len as u32, px(0.).max(x - line.width))
4045            }
4046        } else {
4047            (0, x)
4048        };
4049
4050        let mut exact_unclipped = DisplayPoint::new(row, column);
4051        let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
4052        let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
4053
4054        let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
4055        *exact_unclipped.column_mut() += column_overshoot_after_line_end;
4056        PointForPosition {
4057            previous_valid,
4058            next_valid,
4059            exact_unclipped,
4060            column_overshoot_after_line_end,
4061        }
4062    }
4063}
4064
4065struct BlockLayout {
4066    row: u32,
4067    element: AnyElement,
4068    available_space: Size<AvailableSpace>,
4069    style: BlockStyle,
4070}
4071
4072fn layout_line(
4073    row: u32,
4074    snapshot: &EditorSnapshot,
4075    style: &EditorStyle,
4076    cx: &WindowContext,
4077) -> Result<ShapedLine> {
4078    let mut line = snapshot.line(row);
4079
4080    if line.len() > MAX_LINE_LEN {
4081        let mut len = MAX_LINE_LEN;
4082        while !line.is_char_boundary(len) {
4083            len -= 1;
4084        }
4085
4086        line.truncate(len);
4087    }
4088
4089    cx.text_system().shape_line(
4090        line.into(),
4091        style.text.font_size.to_pixels(cx.rem_size()),
4092        &[TextRun {
4093            len: snapshot.line_len(row) as usize,
4094            font: style.text.font(),
4095            color: Hsla::default(),
4096            background_color: None,
4097            underline: None,
4098            strikethrough: None,
4099        }],
4100    )
4101}
4102
4103pub struct CursorLayout {
4104    origin: gpui::Point<Pixels>,
4105    block_width: Pixels,
4106    line_height: Pixels,
4107    color: Hsla,
4108    shape: CursorShape,
4109    block_text: Option<ShapedLine>,
4110    cursor_name: Option<AnyElement>,
4111}
4112
4113#[derive(Debug)]
4114pub struct CursorName {
4115    string: SharedString,
4116    color: Hsla,
4117    is_top_row: bool,
4118}
4119
4120impl CursorLayout {
4121    pub fn new(
4122        origin: gpui::Point<Pixels>,
4123        block_width: Pixels,
4124        line_height: Pixels,
4125        color: Hsla,
4126        shape: CursorShape,
4127        block_text: Option<ShapedLine>,
4128    ) -> CursorLayout {
4129        CursorLayout {
4130            origin,
4131            block_width,
4132            line_height,
4133            color,
4134            shape,
4135            block_text,
4136            cursor_name: None,
4137        }
4138    }
4139
4140    pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
4141        Bounds {
4142            origin: self.origin + origin,
4143            size: size(self.block_width, self.line_height),
4144        }
4145    }
4146
4147    fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
4148        match self.shape {
4149            CursorShape::Bar => Bounds {
4150                origin: self.origin + origin,
4151                size: size(px(2.0), self.line_height),
4152            },
4153            CursorShape::Block | CursorShape::Hollow => Bounds {
4154                origin: self.origin + origin,
4155                size: size(self.block_width, self.line_height),
4156            },
4157            CursorShape::Underscore => Bounds {
4158                origin: self.origin
4159                    + origin
4160                    + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
4161                size: size(self.block_width, px(2.0)),
4162            },
4163        }
4164    }
4165
4166    pub fn layout(
4167        &mut self,
4168        origin: gpui::Point<Pixels>,
4169        cursor_name: Option<CursorName>,
4170        cx: &mut ElementContext,
4171    ) {
4172        if let Some(cursor_name) = cursor_name {
4173            let bounds = self.bounds(origin);
4174            let text_size = self.line_height / 1.5;
4175
4176            let name_origin = if cursor_name.is_top_row {
4177                point(bounds.right() - px(1.), bounds.top())
4178            } else {
4179                point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
4180            };
4181            let mut name_element = div()
4182                .bg(self.color)
4183                .text_size(text_size)
4184                .px_0p5()
4185                .line_height(text_size + px(2.))
4186                .text_color(cursor_name.color)
4187                .child(cursor_name.string.clone())
4188                .into_any_element();
4189
4190            name_element.layout(
4191                name_origin,
4192                size(AvailableSpace::MinContent, AvailableSpace::MinContent),
4193                cx,
4194            );
4195
4196            self.cursor_name = Some(name_element);
4197        }
4198    }
4199
4200    pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
4201        let bounds = self.bounds(origin);
4202
4203        //Draw background or border quad
4204        let cursor = if matches!(self.shape, CursorShape::Hollow) {
4205            outline(bounds, self.color)
4206        } else {
4207            fill(bounds, self.color)
4208        };
4209
4210        if let Some(name) = &mut self.cursor_name {
4211            name.paint(cx);
4212        }
4213
4214        cx.paint_quad(cursor);
4215
4216        if let Some(block_text) = &self.block_text {
4217            block_text
4218                .paint(self.origin + origin, self.line_height, cx)
4219                .log_err();
4220        }
4221    }
4222
4223    pub fn shape(&self) -> CursorShape {
4224        self.shape
4225    }
4226}
4227
4228#[derive(Debug)]
4229pub struct HighlightedRange {
4230    pub start_y: Pixels,
4231    pub line_height: Pixels,
4232    pub lines: Vec<HighlightedRangeLine>,
4233    pub color: Hsla,
4234    pub corner_radius: Pixels,
4235}
4236
4237#[derive(Debug)]
4238pub struct HighlightedRangeLine {
4239    pub start_x: Pixels,
4240    pub end_x: Pixels,
4241}
4242
4243impl HighlightedRange {
4244    pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ElementContext) {
4245        if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
4246            self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
4247            self.paint_lines(
4248                self.start_y + self.line_height,
4249                &self.lines[1..],
4250                bounds,
4251                cx,
4252            );
4253        } else {
4254            self.paint_lines(self.start_y, &self.lines, bounds, cx);
4255        }
4256    }
4257
4258    fn paint_lines(
4259        &self,
4260        start_y: Pixels,
4261        lines: &[HighlightedRangeLine],
4262        _bounds: Bounds<Pixels>,
4263        cx: &mut ElementContext,
4264    ) {
4265        if lines.is_empty() {
4266            return;
4267        }
4268
4269        let first_line = lines.first().unwrap();
4270        let last_line = lines.last().unwrap();
4271
4272        let first_top_left = point(first_line.start_x, start_y);
4273        let first_top_right = point(first_line.end_x, start_y);
4274
4275        let curve_height = point(Pixels::ZERO, self.corner_radius);
4276        let curve_width = |start_x: Pixels, end_x: Pixels| {
4277            let max = (end_x - start_x) / 2.;
4278            let width = if max < self.corner_radius {
4279                max
4280            } else {
4281                self.corner_radius
4282            };
4283
4284            point(width, Pixels::ZERO)
4285        };
4286
4287        let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
4288        let mut path = gpui::Path::new(first_top_right - top_curve_width);
4289        path.curve_to(first_top_right + curve_height, first_top_right);
4290
4291        let mut iter = lines.iter().enumerate().peekable();
4292        while let Some((ix, line)) = iter.next() {
4293            let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
4294
4295            if let Some((_, next_line)) = iter.peek() {
4296                let next_top_right = point(next_line.end_x, bottom_right.y);
4297
4298                match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
4299                    Ordering::Equal => {
4300                        path.line_to(bottom_right);
4301                    }
4302                    Ordering::Less => {
4303                        let curve_width = curve_width(next_top_right.x, bottom_right.x);
4304                        path.line_to(bottom_right - curve_height);
4305                        if self.corner_radius > Pixels::ZERO {
4306                            path.curve_to(bottom_right - curve_width, bottom_right);
4307                        }
4308                        path.line_to(next_top_right + curve_width);
4309                        if self.corner_radius > Pixels::ZERO {
4310                            path.curve_to(next_top_right + curve_height, next_top_right);
4311                        }
4312                    }
4313                    Ordering::Greater => {
4314                        let curve_width = curve_width(bottom_right.x, next_top_right.x);
4315                        path.line_to(bottom_right - curve_height);
4316                        if self.corner_radius > Pixels::ZERO {
4317                            path.curve_to(bottom_right + curve_width, bottom_right);
4318                        }
4319                        path.line_to(next_top_right - curve_width);
4320                        if self.corner_radius > Pixels::ZERO {
4321                            path.curve_to(next_top_right + curve_height, next_top_right);
4322                        }
4323                    }
4324                }
4325            } else {
4326                let curve_width = curve_width(line.start_x, line.end_x);
4327                path.line_to(bottom_right - curve_height);
4328                if self.corner_radius > Pixels::ZERO {
4329                    path.curve_to(bottom_right - curve_width, bottom_right);
4330                }
4331
4332                let bottom_left = point(line.start_x, bottom_right.y);
4333                path.line_to(bottom_left + curve_width);
4334                if self.corner_radius > Pixels::ZERO {
4335                    path.curve_to(bottom_left - curve_height, bottom_left);
4336                }
4337            }
4338        }
4339
4340        if first_line.start_x > last_line.start_x {
4341            let curve_width = curve_width(last_line.start_x, first_line.start_x);
4342            let second_top_left = point(last_line.start_x, start_y + self.line_height);
4343            path.line_to(second_top_left + curve_height);
4344            if self.corner_radius > Pixels::ZERO {
4345                path.curve_to(second_top_left + curve_width, second_top_left);
4346            }
4347            let first_bottom_left = point(first_line.start_x, second_top_left.y);
4348            path.line_to(first_bottom_left - curve_width);
4349            if self.corner_radius > Pixels::ZERO {
4350                path.curve_to(first_bottom_left - curve_height, first_bottom_left);
4351            }
4352        }
4353
4354        path.line_to(first_top_left + curve_height);
4355        if self.corner_radius > Pixels::ZERO {
4356            path.curve_to(first_top_left + top_curve_width, first_top_left);
4357        }
4358        path.line_to(first_top_right - top_curve_width);
4359
4360        cx.paint_path(path, self.color);
4361    }
4362}
4363
4364pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
4365    (delta.pow(1.5) / 100.0).into()
4366}
4367
4368fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
4369    (delta.pow(1.2) / 300.0).into()
4370}
4371
4372#[cfg(test)]
4373mod tests {
4374    use super::*;
4375    use crate::{
4376        display_map::{BlockDisposition, BlockProperties},
4377        editor_tests::{init_test, update_test_language_settings},
4378        Editor, MultiBuffer,
4379    };
4380    use gpui::TestAppContext;
4381    use language::language_settings;
4382    use log::info;
4383    use std::num::NonZeroU32;
4384    use util::test::sample_text;
4385
4386    #[gpui::test]
4387    fn test_shape_line_numbers(cx: &mut TestAppContext) {
4388        init_test(cx, |_| {});
4389        let window = cx.add_window(|cx| {
4390            let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
4391            Editor::new(EditorMode::Full, buffer, None, cx)
4392        });
4393
4394        let editor = window.root(cx).unwrap();
4395        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
4396        let element = EditorElement::new(&editor, style);
4397        let snapshot = window.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
4398
4399        let layouts = cx
4400            .update_window(*window, |_, cx| {
4401                cx.with_element_context(|cx| {
4402                    element
4403                        .layout_line_numbers(
4404                            0..6,
4405                            (0..6).map(Some),
4406                            &Default::default(),
4407                            Some(DisplayPoint::new(0, 0)),
4408                            &snapshot,
4409                            cx,
4410                        )
4411                        .0
4412                })
4413            })
4414            .unwrap();
4415        assert_eq!(layouts.len(), 6);
4416
4417        let relative_rows =
4418            element.calculate_relative_line_numbers((0..6).map(Some).collect(), &(0..6), Some(3));
4419        assert_eq!(relative_rows[&0], 3);
4420        assert_eq!(relative_rows[&1], 2);
4421        assert_eq!(relative_rows[&2], 1);
4422        // current line has no relative number
4423        assert_eq!(relative_rows[&4], 1);
4424        assert_eq!(relative_rows[&5], 2);
4425
4426        // works if cursor is before screen
4427        let relative_rows =
4428            element.calculate_relative_line_numbers((0..6).map(Some).collect(), &(3..6), Some(1));
4429        assert_eq!(relative_rows.len(), 3);
4430        assert_eq!(relative_rows[&3], 2);
4431        assert_eq!(relative_rows[&4], 3);
4432        assert_eq!(relative_rows[&5], 4);
4433
4434        // works if cursor is after screen
4435        let relative_rows =
4436            element.calculate_relative_line_numbers((0..6).map(Some).collect(), &(0..3), Some(6));
4437        assert_eq!(relative_rows.len(), 3);
4438        assert_eq!(relative_rows[&0], 5);
4439        assert_eq!(relative_rows[&1], 4);
4440        assert_eq!(relative_rows[&2], 3);
4441    }
4442
4443    #[gpui::test]
4444    async fn test_vim_visual_selections(cx: &mut TestAppContext) {
4445        init_test(cx, |_| {});
4446
4447        let window = cx.add_window(|cx| {
4448            let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
4449            Editor::new(EditorMode::Full, buffer, None, cx)
4450        });
4451        let editor = window.root(cx).unwrap();
4452        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
4453        let mut element = EditorElement::new(&editor, style);
4454
4455        window
4456            .update(cx, |editor, cx| {
4457                editor.cursor_shape = CursorShape::Block;
4458                editor.change_selections(None, cx, |s| {
4459                    s.select_ranges([
4460                        Point::new(0, 0)..Point::new(1, 0),
4461                        Point::new(3, 2)..Point::new(3, 3),
4462                        Point::new(5, 6)..Point::new(6, 0),
4463                    ]);
4464                });
4465            })
4466            .unwrap();
4467        let state = cx
4468            .update_window(window.into(), |_view, cx| {
4469                cx.with_element_context(|cx| {
4470                    element.after_layout(
4471                        Bounds {
4472                            origin: point(px(500.), px(500.)),
4473                            size: size(px(500.), px(500.)),
4474                        },
4475                        &mut (),
4476                        cx,
4477                    )
4478                })
4479            })
4480            .unwrap();
4481
4482        assert_eq!(state.selections.len(), 1);
4483        let local_selections = &state.selections[0].1;
4484        assert_eq!(local_selections.len(), 3);
4485        // moves cursor back one line
4486        assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
4487        assert_eq!(
4488            local_selections[0].range,
4489            DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
4490        );
4491
4492        // moves cursor back one column
4493        assert_eq!(
4494            local_selections[1].range,
4495            DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
4496        );
4497        assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
4498
4499        // leaves cursor on the max point
4500        assert_eq!(
4501            local_selections[2].range,
4502            DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
4503        );
4504        assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
4505
4506        // active lines does not include 1 (even though the range of the selection does)
4507        assert_eq!(
4508            state.active_rows.keys().cloned().collect::<Vec<u32>>(),
4509            vec![0, 3, 5, 6]
4510        );
4511
4512        // multi-buffer support
4513        // in DisplayPoint coordinates, this is what we're dealing with:
4514        //  0: [[file
4515        //  1:   header]]
4516        //  2: aaaaaa
4517        //  3: bbbbbb
4518        //  4: cccccc
4519        //  5:
4520        //  6: ...
4521        //  7: ffffff
4522        //  8: gggggg
4523        //  9: hhhhhh
4524        // 10:
4525        // 11: [[file
4526        // 12:   header]]
4527        // 13: bbbbbb
4528        // 14: cccccc
4529        // 15: dddddd
4530        let window = cx.add_window(|cx| {
4531            let buffer = MultiBuffer::build_multi(
4532                [
4533                    (
4534                        &(sample_text(8, 6, 'a') + "\n"),
4535                        vec![
4536                            Point::new(0, 0)..Point::new(3, 0),
4537                            Point::new(4, 0)..Point::new(7, 0),
4538                        ],
4539                    ),
4540                    (
4541                        &(sample_text(8, 6, 'a') + "\n"),
4542                        vec![Point::new(1, 0)..Point::new(3, 0)],
4543                    ),
4544                ],
4545                cx,
4546            );
4547            Editor::new(EditorMode::Full, buffer, None, cx)
4548        });
4549        let editor = window.root(cx).unwrap();
4550        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
4551        let mut element = EditorElement::new(&editor, style);
4552        let _state = window.update(cx, |editor, cx| {
4553            editor.cursor_shape = CursorShape::Block;
4554            editor.change_selections(None, cx, |s| {
4555                s.select_display_ranges([
4556                    DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
4557                    DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
4558                ]);
4559            });
4560        });
4561
4562        let state = cx
4563            .update_window(window.into(), |_view, cx| {
4564                cx.with_element_context(|cx| {
4565                    element.after_layout(
4566                        Bounds {
4567                            origin: point(px(500.), px(500.)),
4568                            size: size(px(500.), px(500.)),
4569                        },
4570                        &mut (),
4571                        cx,
4572                    )
4573                })
4574            })
4575            .unwrap();
4576        assert_eq!(state.selections.len(), 1);
4577        let local_selections = &state.selections[0].1;
4578        assert_eq!(local_selections.len(), 2);
4579
4580        // moves cursor on excerpt boundary back a line
4581        // and doesn't allow selection to bleed through
4582        assert_eq!(
4583            local_selections[0].range,
4584            DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
4585        );
4586        assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
4587        // moves cursor on buffer boundary back two lines
4588        // and doesn't allow selection to bleed through
4589        assert_eq!(
4590            local_selections[1].range,
4591            DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
4592        );
4593        assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
4594    }
4595
4596    #[gpui::test]
4597    fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
4598        init_test(cx, |_| {});
4599
4600        let window = cx.add_window(|cx| {
4601            let buffer = MultiBuffer::build_simple("", cx);
4602            Editor::new(EditorMode::Full, buffer, None, cx)
4603        });
4604        let editor = window.root(cx).unwrap();
4605        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
4606        window
4607            .update(cx, |editor, cx| {
4608                editor.set_placeholder_text("hello", cx);
4609                editor.insert_blocks(
4610                    [BlockProperties {
4611                        style: BlockStyle::Fixed,
4612                        disposition: BlockDisposition::Above,
4613                        height: 3,
4614                        position: Anchor::min(),
4615                        render: Box::new(|_| div().into_any()),
4616                    }],
4617                    None,
4618                    cx,
4619                );
4620
4621                // Blur the editor so that it displays placeholder text.
4622                cx.blur();
4623            })
4624            .unwrap();
4625
4626        let mut element = EditorElement::new(&editor, style);
4627        let state = cx
4628            .update_window(window.into(), |_view, cx| {
4629                cx.with_element_context(|cx| {
4630                    element.after_layout(
4631                        Bounds {
4632                            origin: point(px(500.), px(500.)),
4633                            size: size(px(500.), px(500.)),
4634                        },
4635                        &mut (),
4636                        cx,
4637                    )
4638                })
4639            })
4640            .unwrap();
4641
4642        assert_eq!(state.position_map.line_layouts.len(), 4);
4643        assert_eq!(
4644            state
4645                .line_numbers
4646                .iter()
4647                .map(Option::is_some)
4648                .collect::<Vec<_>>(),
4649            &[false, false, false, true]
4650        );
4651    }
4652
4653    #[gpui::test]
4654    fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
4655        const TAB_SIZE: u32 = 4;
4656
4657        let input_text = "\t \t|\t| a b";
4658        let expected_invisibles = vec![
4659            Invisible::Tab {
4660                line_start_offset: 0,
4661            },
4662            Invisible::Whitespace {
4663                line_offset: TAB_SIZE as usize,
4664            },
4665            Invisible::Tab {
4666                line_start_offset: TAB_SIZE as usize + 1,
4667            },
4668            Invisible::Tab {
4669                line_start_offset: TAB_SIZE as usize * 2 + 1,
4670            },
4671            Invisible::Whitespace {
4672                line_offset: TAB_SIZE as usize * 3 + 1,
4673            },
4674            Invisible::Whitespace {
4675                line_offset: TAB_SIZE as usize * 3 + 3,
4676            },
4677        ];
4678        assert_eq!(
4679            expected_invisibles.len(),
4680            input_text
4681                .chars()
4682                .filter(|initial_char| initial_char.is_whitespace())
4683                .count(),
4684            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
4685        );
4686
4687        init_test(cx, |s| {
4688            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
4689            s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
4690        });
4691
4692        let actual_invisibles =
4693            collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, px(500.0));
4694
4695        assert_eq!(expected_invisibles, actual_invisibles);
4696    }
4697
4698    #[gpui::test]
4699    fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
4700        init_test(cx, |s| {
4701            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
4702            s.defaults.tab_size = NonZeroU32::new(4);
4703        });
4704
4705        for editor_mode_without_invisibles in [
4706            EditorMode::SingleLine,
4707            EditorMode::AutoHeight { max_lines: 100 },
4708        ] {
4709            let invisibles = collect_invisibles_from_new_editor(
4710                cx,
4711                editor_mode_without_invisibles,
4712                "\t\t\t| | a b",
4713                px(500.0),
4714            );
4715            assert!(invisibles.is_empty(),
4716                    "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
4717        }
4718    }
4719
4720    #[gpui::test]
4721    fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
4722        let tab_size = 4;
4723        let input_text = "a\tbcd   ".repeat(9);
4724        let repeated_invisibles = [
4725            Invisible::Tab {
4726                line_start_offset: 1,
4727            },
4728            Invisible::Whitespace {
4729                line_offset: tab_size as usize + 3,
4730            },
4731            Invisible::Whitespace {
4732                line_offset: tab_size as usize + 4,
4733            },
4734            Invisible::Whitespace {
4735                line_offset: tab_size as usize + 5,
4736            },
4737        ];
4738        let expected_invisibles = std::iter::once(repeated_invisibles)
4739            .cycle()
4740            .take(9)
4741            .flatten()
4742            .collect::<Vec<_>>();
4743        assert_eq!(
4744            expected_invisibles.len(),
4745            input_text
4746                .chars()
4747                .filter(|initial_char| initial_char.is_whitespace())
4748                .count(),
4749            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
4750        );
4751        info!("Expected invisibles: {expected_invisibles:?}");
4752
4753        init_test(cx, |_| {});
4754
4755        // Put the same string with repeating whitespace pattern into editors of various size,
4756        // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
4757        let resize_step = 10.0;
4758        let mut editor_width = 200.0;
4759        while editor_width <= 1000.0 {
4760            update_test_language_settings(cx, |s| {
4761                s.defaults.tab_size = NonZeroU32::new(tab_size);
4762                s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
4763                s.defaults.preferred_line_length = Some(editor_width as u32);
4764                s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
4765            });
4766
4767            let actual_invisibles = collect_invisibles_from_new_editor(
4768                cx,
4769                EditorMode::Full,
4770                &input_text,
4771                px(editor_width),
4772            );
4773
4774            // Whatever the editor size is, ensure it has the same invisible kinds in the same order
4775            // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
4776            let mut i = 0;
4777            for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
4778                i = actual_index;
4779                match expected_invisibles.get(i) {
4780                    Some(expected_invisible) => match (expected_invisible, actual_invisible) {
4781                        (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
4782                        | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
4783                        _ => {
4784                            panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
4785                        }
4786                    },
4787                    None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
4788                }
4789            }
4790            let missing_expected_invisibles = &expected_invisibles[i + 1..];
4791            assert!(
4792                missing_expected_invisibles.is_empty(),
4793                "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
4794            );
4795
4796            editor_width += resize_step;
4797        }
4798    }
4799
4800    fn collect_invisibles_from_new_editor(
4801        cx: &mut TestAppContext,
4802        editor_mode: EditorMode,
4803        input_text: &str,
4804        editor_width: Pixels,
4805    ) -> Vec<Invisible> {
4806        info!(
4807            "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
4808            editor_width.0
4809        );
4810        let window = cx.add_window(|cx| {
4811            let buffer = MultiBuffer::build_simple(&input_text, cx);
4812            Editor::new(editor_mode, buffer, None, cx)
4813        });
4814        let editor = window.root(cx).unwrap();
4815        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
4816        let mut element = EditorElement::new(&editor, style);
4817        window
4818            .update(cx, |editor, cx| {
4819                editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
4820                editor.set_wrap_width(Some(editor_width), cx);
4821            })
4822            .unwrap();
4823        let layout_state = cx
4824            .update_window(window.into(), |_, cx| {
4825                cx.with_element_context(|cx| {
4826                    element.after_layout(
4827                        Bounds {
4828                            origin: point(px(500.), px(500.)),
4829                            size: size(px(500.), px(500.)),
4830                        },
4831                        &mut (),
4832                        cx,
4833                    )
4834                })
4835            })
4836            .unwrap();
4837
4838        layout_state
4839            .position_map
4840            .line_layouts
4841            .iter()
4842            .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles)
4843            .cloned()
4844            .collect()
4845    }
4846}
4847
4848pub fn register_action<T: Action>(
4849    view: &View<Editor>,
4850    cx: &mut WindowContext,
4851    listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
4852) {
4853    let view = view.clone();
4854    cx.on_action(TypeId::of::<T>(), move |action, phase, cx| {
4855        let action = action.downcast_ref().unwrap();
4856        if phase == DispatchPhase::Bubble {
4857            view.update(cx, |editor, cx| {
4858                listener(editor, action, cx);
4859            })
4860        }
4861    })
4862}
4863
4864fn compute_auto_height_layout(
4865    editor: &mut Editor,
4866    max_lines: usize,
4867    max_line_number_width: Pixels,
4868    known_dimensions: Size<Option<Pixels>>,
4869    cx: &mut ViewContext<Editor>,
4870) -> Option<Size<Pixels>> {
4871    let width = known_dimensions.width?;
4872    if let Some(height) = known_dimensions.height {
4873        return Some(size(width, height));
4874    }
4875
4876    let style = editor.style.as_ref().unwrap();
4877    let font_id = cx.text_system().resolve_font(&style.text.font());
4878    let font_size = style.text.font_size.to_pixels(cx.rem_size());
4879    let line_height = style.text.line_height_in_pixels(cx.rem_size());
4880    let em_width = cx
4881        .text_system()
4882        .typographic_bounds(font_id, font_size, 'm')
4883        .unwrap()
4884        .size
4885        .width;
4886
4887    let mut snapshot = editor.snapshot(cx);
4888    let gutter_dimensions =
4889        snapshot.gutter_dimensions(font_id, font_size, em_width, max_line_number_width, cx);
4890
4891    editor.gutter_width = gutter_dimensions.width;
4892    let text_width = width - gutter_dimensions.width;
4893    let overscroll = size(em_width, px(0.));
4894
4895    let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
4896    if editor.set_wrap_width(Some(editor_width), cx) {
4897        snapshot = editor.snapshot(cx);
4898    }
4899
4900    let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
4901    let height = scroll_height
4902        .max(line_height)
4903        .min(line_height * max_lines as f32);
4904
4905    Some(size(width, height))
4906}