element.rs

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