element.rs

   1use crate::{
   2    blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
   3    code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
   4    display_map::{
   5        Block, BlockContext, BlockStyle, DisplaySnapshot, HighlightedChunk, ToDisplayPoint,
   6    },
   7    editor_settings::{
   8        CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ScrollBeyondLastLine,
   9        ScrollbarDiagnostics, ShowScrollbar,
  10    },
  11    git::blame::{CommitDetails, GitBlame},
  12    hover_popover::{
  13        self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
  14    },
  15    items::BufferSearchHighlights,
  16    mouse_context_menu::{self, MenuPosition, MouseContextMenu},
  17    scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
  18    AcceptEditPrediction, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint,
  19    DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
  20    EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
  21    GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
  22    InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
  23    RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
  24    StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
  25    EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
  26    GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
  27};
  28use client::ParticipantIndex;
  29use collections::{BTreeMap, HashMap, HashSet};
  30use diff::DiffHunkStatus;
  31use file_icons::FileIcons;
  32use git::{blame::BlameEntry, Oid};
  33use gpui::{
  34    anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
  35    relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
  36    ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase,
  37    Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox,
  38    Hsla, InteractiveElement, IntoElement, KeyBindingContextPredicate, Keystroke, Length,
  39    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
  40    ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
  41    StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement,
  42    WeakEntity, Window,
  43};
  44use itertools::Itertools;
  45use language::{
  46    language_settings::{
  47        IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings,
  48        ShowWhitespaceSetting,
  49    },
  50    ChunkRendererContext,
  51};
  52use lsp::DiagnosticSeverity;
  53use multi_buffer::{
  54    Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
  55    RowInfo, ToOffset,
  56};
  57use project::project_settings::{GitGutterSetting, ProjectSettings};
  58use settings::{KeyBindingValidator, KeyBindingValidatorRegistration, Settings};
  59use smallvec::{smallvec, SmallVec};
  60use std::{
  61    any::TypeId,
  62    borrow::Cow,
  63    cmp::{self, Ordering},
  64    fmt::{self, Write},
  65    iter, mem,
  66    ops::{Deref, Range},
  67    rc::Rc,
  68    sync::Arc,
  69};
  70use sum_tree::Bias;
  71use text::BufferId;
  72use theme::{ActiveTheme, Appearance, PlayerColor};
  73use ui::{
  74    h_flex, prelude::*, ButtonLike, ButtonStyle, ContextMenu, IconButtonShape, KeyBinding, Tooltip,
  75    POPOVER_Y_PADDING,
  76};
  77use unicode_segmentation::UnicodeSegmentation;
  78use util::{markdown::MarkdownString, RangeExt, ResultExt};
  79use workspace::{item::Item, notifications::NotifyTaskExt, Workspace};
  80
  81const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
  82
  83#[derive(Debug, Clone, PartialEq, Eq)]
  84enum DisplayDiffHunk {
  85    Folded {
  86        display_row: DisplayRow,
  87    },
  88
  89    Unfolded {
  90        diff_base_byte_range: Range<usize>,
  91        display_row_range: Range<DisplayRow>,
  92        multi_buffer_range: Range<Anchor>,
  93        status: DiffHunkStatus,
  94    },
  95}
  96
  97struct SelectionLayout {
  98    head: DisplayPoint,
  99    cursor_shape: CursorShape,
 100    is_newest: bool,
 101    is_local: bool,
 102    range: Range<DisplayPoint>,
 103    active_rows: Range<DisplayRow>,
 104    user_name: Option<SharedString>,
 105}
 106
 107impl SelectionLayout {
 108    fn new<T: ToPoint + ToDisplayPoint + Clone>(
 109        selection: Selection<T>,
 110        line_mode: bool,
 111        cursor_shape: CursorShape,
 112        map: &DisplaySnapshot,
 113        is_newest: bool,
 114        is_local: bool,
 115        user_name: Option<SharedString>,
 116    ) -> Self {
 117        let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
 118        let display_selection = point_selection.map(|p| p.to_display_point(map));
 119        let mut range = display_selection.range();
 120        let mut head = display_selection.head();
 121        let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
 122            ..map.next_line_boundary(point_selection.end).1.row();
 123
 124        // vim visual line mode
 125        if line_mode {
 126            let point_range = map.expand_to_line(point_selection.range());
 127            range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
 128        }
 129
 130        // any vim visual mode (including line mode)
 131        if (cursor_shape == CursorShape::Block || cursor_shape == CursorShape::Hollow)
 132            && !range.is_empty()
 133            && !selection.reversed
 134        {
 135            if head.column() > 0 {
 136                head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
 137            } else if head.row().0 > 0 && head != map.max_point() {
 138                head = map.clip_point(
 139                    DisplayPoint::new(
 140                        head.row().previous_row(),
 141                        map.line_len(head.row().previous_row()),
 142                    ),
 143                    Bias::Left,
 144                );
 145                // updating range.end is a no-op unless you're cursor is
 146                // on the newline containing a multi-buffer divider
 147                // in which case the clip_point may have moved the head up
 148                // an additional row.
 149                range.end = DisplayPoint::new(head.row().next_row(), 0);
 150                active_rows.end = head.row();
 151            }
 152        }
 153
 154        Self {
 155            head,
 156            cursor_shape,
 157            is_newest,
 158            is_local,
 159            range,
 160            active_rows,
 161            user_name,
 162        }
 163    }
 164}
 165
 166pub struct EditorElement {
 167    editor: Entity<Editor>,
 168    style: EditorStyle,
 169}
 170
 171type DisplayRowDelta = u32;
 172
 173impl EditorElement {
 174    pub(crate) const SCROLLBAR_WIDTH: Pixels = px(15.);
 175
 176    pub fn new(editor: &Entity<Editor>, style: EditorStyle) -> Self {
 177        Self {
 178            editor: editor.clone(),
 179            style,
 180        }
 181    }
 182
 183    fn register_actions(&self, window: &mut Window, cx: &mut App) {
 184        let editor = &self.editor;
 185        editor.update(cx, |editor, cx| {
 186            for action in editor.editor_actions.borrow().values() {
 187                (action)(window, cx)
 188            }
 189        });
 190
 191        crate::rust_analyzer_ext::apply_related_actions(editor, window, cx);
 192        crate::clangd_ext::apply_related_actions(editor, window, cx);
 193        register_action(editor, window, Editor::open_context_menu);
 194        register_action(editor, window, Editor::move_left);
 195        register_action(editor, window, Editor::move_right);
 196        register_action(editor, window, Editor::move_down);
 197        register_action(editor, window, Editor::move_down_by_lines);
 198        register_action(editor, window, Editor::select_down_by_lines);
 199        register_action(editor, window, Editor::move_up);
 200        register_action(editor, window, Editor::move_up_by_lines);
 201        register_action(editor, window, Editor::select_up_by_lines);
 202        register_action(editor, window, Editor::select_page_down);
 203        register_action(editor, window, Editor::select_page_up);
 204        register_action(editor, window, Editor::cancel);
 205        register_action(editor, window, Editor::newline);
 206        register_action(editor, window, Editor::newline_above);
 207        register_action(editor, window, Editor::newline_below);
 208        register_action(editor, window, Editor::backspace);
 209        register_action(editor, window, Editor::delete);
 210        register_action(editor, window, Editor::tab);
 211        register_action(editor, window, Editor::tab_prev);
 212        register_action(editor, window, Editor::indent);
 213        register_action(editor, window, Editor::outdent);
 214        register_action(editor, window, Editor::autoindent);
 215        register_action(editor, window, Editor::delete_line);
 216        register_action(editor, window, Editor::join_lines);
 217        register_action(editor, window, Editor::sort_lines_case_sensitive);
 218        register_action(editor, window, Editor::sort_lines_case_insensitive);
 219        register_action(editor, window, Editor::reverse_lines);
 220        register_action(editor, window, Editor::shuffle_lines);
 221        register_action(editor, window, Editor::convert_to_upper_case);
 222        register_action(editor, window, Editor::convert_to_lower_case);
 223        register_action(editor, window, Editor::convert_to_title_case);
 224        register_action(editor, window, Editor::convert_to_snake_case);
 225        register_action(editor, window, Editor::convert_to_kebab_case);
 226        register_action(editor, window, Editor::convert_to_upper_camel_case);
 227        register_action(editor, window, Editor::convert_to_lower_camel_case);
 228        register_action(editor, window, Editor::convert_to_opposite_case);
 229        register_action(editor, window, Editor::delete_to_previous_word_start);
 230        register_action(editor, window, Editor::delete_to_previous_subword_start);
 231        register_action(editor, window, Editor::delete_to_next_word_end);
 232        register_action(editor, window, Editor::delete_to_next_subword_end);
 233        register_action(editor, window, Editor::delete_to_beginning_of_line);
 234        register_action(editor, window, Editor::delete_to_end_of_line);
 235        register_action(editor, window, Editor::cut_to_end_of_line);
 236        register_action(editor, window, Editor::duplicate_line_up);
 237        register_action(editor, window, Editor::duplicate_line_down);
 238        register_action(editor, window, Editor::duplicate_selection);
 239        register_action(editor, window, Editor::move_line_up);
 240        register_action(editor, window, Editor::move_line_down);
 241        register_action(editor, window, Editor::transpose);
 242        register_action(editor, window, Editor::rewrap);
 243        register_action(editor, window, Editor::cut);
 244        register_action(editor, window, Editor::kill_ring_cut);
 245        register_action(editor, window, Editor::kill_ring_yank);
 246        register_action(editor, window, Editor::copy);
 247        register_action(editor, window, Editor::paste);
 248        register_action(editor, window, Editor::undo);
 249        register_action(editor, window, Editor::redo);
 250        register_action(editor, window, Editor::move_page_up);
 251        register_action(editor, window, Editor::move_page_down);
 252        register_action(editor, window, Editor::next_screen);
 253        register_action(editor, window, Editor::scroll_cursor_top);
 254        register_action(editor, window, Editor::scroll_cursor_center);
 255        register_action(editor, window, Editor::scroll_cursor_bottom);
 256        register_action(editor, window, Editor::scroll_cursor_center_top_bottom);
 257        register_action(editor, window, |editor, _: &LineDown, window, cx| {
 258            editor.scroll_screen(&ScrollAmount::Line(1.), window, cx)
 259        });
 260        register_action(editor, window, |editor, _: &LineUp, window, cx| {
 261            editor.scroll_screen(&ScrollAmount::Line(-1.), window, cx)
 262        });
 263        register_action(editor, window, |editor, _: &HalfPageDown, window, cx| {
 264            editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx)
 265        });
 266        register_action(
 267            editor,
 268            window,
 269            |editor, HandleInput(text): &HandleInput, window, cx| {
 270                if text.is_empty() {
 271                    return;
 272                }
 273                editor.handle_input(text, window, cx);
 274            },
 275        );
 276        register_action(editor, window, |editor, _: &HalfPageUp, window, cx| {
 277            editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx)
 278        });
 279        register_action(editor, window, |editor, _: &PageDown, window, cx| {
 280            editor.scroll_screen(&ScrollAmount::Page(1.), window, cx)
 281        });
 282        register_action(editor, window, |editor, _: &PageUp, window, cx| {
 283            editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx)
 284        });
 285        register_action(editor, window, Editor::move_to_previous_word_start);
 286        register_action(editor, window, Editor::move_to_previous_subword_start);
 287        register_action(editor, window, Editor::move_to_next_word_end);
 288        register_action(editor, window, Editor::move_to_next_subword_end);
 289        register_action(editor, window, Editor::move_to_beginning_of_line);
 290        register_action(editor, window, Editor::move_to_end_of_line);
 291        register_action(editor, window, Editor::move_to_start_of_paragraph);
 292        register_action(editor, window, Editor::move_to_end_of_paragraph);
 293        register_action(editor, window, Editor::move_to_beginning);
 294        register_action(editor, window, Editor::move_to_end);
 295        register_action(editor, window, Editor::select_up);
 296        register_action(editor, window, Editor::select_down);
 297        register_action(editor, window, Editor::select_left);
 298        register_action(editor, window, Editor::select_right);
 299        register_action(editor, window, Editor::select_to_previous_word_start);
 300        register_action(editor, window, Editor::select_to_previous_subword_start);
 301        register_action(editor, window, Editor::select_to_next_word_end);
 302        register_action(editor, window, Editor::select_to_next_subword_end);
 303        register_action(editor, window, Editor::select_to_beginning_of_line);
 304        register_action(editor, window, Editor::select_to_end_of_line);
 305        register_action(editor, window, Editor::select_to_start_of_paragraph);
 306        register_action(editor, window, Editor::select_to_end_of_paragraph);
 307        register_action(editor, window, Editor::select_to_beginning);
 308        register_action(editor, window, Editor::select_to_end);
 309        register_action(editor, window, Editor::select_all);
 310        register_action(editor, window, |editor, action, window, cx| {
 311            editor.select_all_matches(action, window, cx).log_err();
 312        });
 313        register_action(editor, window, Editor::select_line);
 314        register_action(editor, window, Editor::split_selection_into_lines);
 315        register_action(editor, window, Editor::add_selection_above);
 316        register_action(editor, window, Editor::add_selection_below);
 317        register_action(editor, window, |editor, action, window, cx| {
 318            editor.select_next(action, window, cx).log_err();
 319        });
 320        register_action(editor, window, |editor, action, window, cx| {
 321            editor.select_previous(action, window, cx).log_err();
 322        });
 323        register_action(editor, window, Editor::toggle_comments);
 324        register_action(editor, window, Editor::select_larger_syntax_node);
 325        register_action(editor, window, Editor::select_smaller_syntax_node);
 326        register_action(editor, window, Editor::select_enclosing_symbol);
 327        register_action(editor, window, Editor::move_to_enclosing_bracket);
 328        register_action(editor, window, Editor::undo_selection);
 329        register_action(editor, window, Editor::redo_selection);
 330        if !editor.read(cx).is_singleton(cx) {
 331            register_action(editor, window, Editor::expand_excerpts);
 332            register_action(editor, window, Editor::expand_excerpts_up);
 333            register_action(editor, window, Editor::expand_excerpts_down);
 334        }
 335        register_action(editor, window, Editor::go_to_diagnostic);
 336        register_action(editor, window, Editor::go_to_prev_diagnostic);
 337        register_action(editor, window, Editor::go_to_next_hunk);
 338        register_action(editor, window, Editor::go_to_prev_hunk);
 339        register_action(editor, window, |editor, action, window, cx| {
 340            editor
 341                .go_to_definition(action, window, cx)
 342                .detach_and_log_err(cx);
 343        });
 344        register_action(editor, window, |editor, action, window, cx| {
 345            editor
 346                .go_to_definition_split(action, window, cx)
 347                .detach_and_log_err(cx);
 348        });
 349        register_action(editor, window, |editor, action, window, cx| {
 350            editor
 351                .go_to_declaration(action, window, cx)
 352                .detach_and_log_err(cx);
 353        });
 354        register_action(editor, window, |editor, action, window, cx| {
 355            editor
 356                .go_to_declaration_split(action, window, cx)
 357                .detach_and_log_err(cx);
 358        });
 359        register_action(editor, window, |editor, action, window, cx| {
 360            editor
 361                .go_to_implementation(action, window, cx)
 362                .detach_and_log_err(cx);
 363        });
 364        register_action(editor, window, |editor, action, window, cx| {
 365            editor
 366                .go_to_implementation_split(action, window, cx)
 367                .detach_and_log_err(cx);
 368        });
 369        register_action(editor, window, |editor, action, window, cx| {
 370            editor
 371                .go_to_type_definition(action, window, cx)
 372                .detach_and_log_err(cx);
 373        });
 374        register_action(editor, window, |editor, action, window, cx| {
 375            editor
 376                .go_to_type_definition_split(action, window, cx)
 377                .detach_and_log_err(cx);
 378        });
 379        register_action(editor, window, Editor::open_url);
 380        register_action(editor, window, Editor::open_selected_filename);
 381        register_action(editor, window, Editor::fold);
 382        register_action(editor, window, Editor::fold_at_level);
 383        register_action(editor, window, Editor::fold_all);
 384        register_action(editor, window, Editor::fold_function_bodies);
 385        register_action(editor, window, Editor::fold_at);
 386        register_action(editor, window, Editor::fold_recursive);
 387        register_action(editor, window, Editor::toggle_fold);
 388        register_action(editor, window, Editor::toggle_fold_recursive);
 389        register_action(editor, window, Editor::unfold_lines);
 390        register_action(editor, window, Editor::unfold_recursive);
 391        register_action(editor, window, Editor::unfold_all);
 392        register_action(editor, window, Editor::unfold_at);
 393        register_action(editor, window, Editor::fold_selected_ranges);
 394        register_action(editor, window, Editor::set_mark);
 395        register_action(editor, window, Editor::swap_selection_ends);
 396        register_action(editor, window, Editor::show_completions);
 397        register_action(editor, window, Editor::toggle_code_actions);
 398        register_action(editor, window, Editor::open_excerpts);
 399        register_action(editor, window, Editor::open_excerpts_in_split);
 400        register_action(editor, window, Editor::open_proposed_changes_editor);
 401        register_action(editor, window, Editor::toggle_soft_wrap);
 402        register_action(editor, window, Editor::toggle_tab_bar);
 403        register_action(editor, window, Editor::toggle_line_numbers);
 404        register_action(editor, window, Editor::toggle_relative_line_numbers);
 405        register_action(editor, window, Editor::toggle_indent_guides);
 406        register_action(editor, window, Editor::toggle_inlay_hints);
 407        register_action(editor, window, Editor::toggle_inline_completions);
 408        register_action(editor, window, hover_popover::hover);
 409        register_action(editor, window, Editor::reveal_in_finder);
 410        register_action(editor, window, Editor::copy_path);
 411        register_action(editor, window, Editor::copy_relative_path);
 412        register_action(editor, window, Editor::copy_highlight_json);
 413        register_action(editor, window, Editor::copy_permalink_to_line);
 414        register_action(editor, window, Editor::open_permalink_to_line);
 415        register_action(editor, window, Editor::copy_file_location);
 416        register_action(editor, window, Editor::toggle_git_blame);
 417        register_action(editor, window, Editor::toggle_git_blame_inline);
 418        register_action(editor, window, Editor::toggle_selected_diff_hunks);
 419        register_action(editor, window, Editor::expand_all_diff_hunks);
 420        register_action(editor, window, |editor, action, window, cx| {
 421            if let Some(task) = editor.format(action, window, cx) {
 422                task.detach_and_notify_err(window, cx);
 423            } else {
 424                cx.propagate();
 425            }
 426        });
 427        register_action(editor, window, |editor, action, window, cx| {
 428            if let Some(task) = editor.format_selections(action, window, cx) {
 429                task.detach_and_notify_err(window, cx);
 430            } else {
 431                cx.propagate();
 432            }
 433        });
 434        register_action(editor, window, Editor::restart_language_server);
 435        register_action(editor, window, Editor::show_character_palette);
 436        register_action(editor, window, |editor, action, window, cx| {
 437            if let Some(task) = editor.confirm_completion(action, window, cx) {
 438                task.detach_and_notify_err(window, cx);
 439            } else {
 440                cx.propagate();
 441            }
 442        });
 443        register_action(editor, window, |editor, action, window, cx| {
 444            if let Some(task) = editor.compose_completion(action, window, cx) {
 445                task.detach_and_notify_err(window, cx);
 446            } else {
 447                cx.propagate();
 448            }
 449        });
 450        register_action(editor, window, |editor, action, window, cx| {
 451            if let Some(task) = editor.confirm_code_action(action, window, cx) {
 452                task.detach_and_notify_err(window, cx);
 453            } else {
 454                cx.propagate();
 455            }
 456        });
 457        register_action(editor, window, |editor, action, window, cx| {
 458            if let Some(task) = editor.rename(action, window, cx) {
 459                task.detach_and_notify_err(window, cx);
 460            } else {
 461                cx.propagate();
 462            }
 463        });
 464        register_action(editor, window, |editor, action, window, cx| {
 465            if let Some(task) = editor.confirm_rename(action, window, cx) {
 466                task.detach_and_notify_err(window, cx);
 467            } else {
 468                cx.propagate();
 469            }
 470        });
 471        register_action(editor, window, |editor, action, window, cx| {
 472            if let Some(task) = editor.find_all_references(action, window, cx) {
 473                task.detach_and_log_err(cx);
 474            } else {
 475                cx.propagate();
 476            }
 477        });
 478        register_action(editor, window, Editor::show_signature_help);
 479        register_action(editor, window, Editor::next_edit_prediction);
 480        register_action(editor, window, Editor::previous_edit_prediction);
 481        register_action(editor, window, Editor::show_inline_completion);
 482        register_action(editor, window, Editor::context_menu_first);
 483        register_action(editor, window, Editor::context_menu_prev);
 484        register_action(editor, window, Editor::context_menu_next);
 485        register_action(editor, window, Editor::context_menu_last);
 486        register_action(editor, window, Editor::display_cursor_names);
 487        register_action(editor, window, Editor::unique_lines_case_insensitive);
 488        register_action(editor, window, Editor::unique_lines_case_sensitive);
 489        register_action(editor, window, Editor::accept_partial_inline_completion);
 490        register_action(editor, window, Editor::accept_edit_prediction);
 491        register_action(editor, window, Editor::revert_file);
 492        register_action(editor, window, Editor::revert_selected_hunks);
 493        register_action(editor, window, Editor::apply_all_diff_hunks);
 494        register_action(editor, window, Editor::apply_selected_diff_hunks);
 495        register_action(editor, window, Editor::open_active_item_in_terminal);
 496        register_action(editor, window, Editor::reload_file);
 497        register_action(editor, window, Editor::spawn_nearest_task);
 498        register_action(editor, window, Editor::insert_uuid_v4);
 499        register_action(editor, window, Editor::insert_uuid_v7);
 500        register_action(editor, window, Editor::open_selections_in_multibuffer);
 501    }
 502
 503    fn register_key_listeners(&self, window: &mut Window, _: &mut App, layout: &EditorLayout) {
 504        let position_map = layout.position_map.clone();
 505        window.on_key_event({
 506            let editor = self.editor.clone();
 507            move |event: &ModifiersChangedEvent, phase, window, cx| {
 508                if phase != DispatchPhase::Bubble {
 509                    return;
 510                }
 511                editor.update(cx, |editor, cx| {
 512                    if editor.hover_state.focused(window, cx) {
 513                        return;
 514                    }
 515                    editor.handle_modifiers_changed(event.modifiers, &position_map, window, cx);
 516                })
 517            }
 518        });
 519    }
 520
 521    fn mouse_left_down(
 522        editor: &mut Editor,
 523        event: &MouseDownEvent,
 524        hovered_hunk: Option<Range<Anchor>>,
 525        position_map: &PositionMap,
 526        line_numbers: &HashMap<MultiBufferRow, LineNumberLayout>,
 527        window: &mut Window,
 528        cx: &mut Context<Editor>,
 529    ) {
 530        if window.default_prevented() {
 531            return;
 532        }
 533
 534        let text_hitbox = &position_map.text_hitbox;
 535        let gutter_hitbox = &position_map.gutter_hitbox;
 536        let mut click_count = event.click_count;
 537        let mut modifiers = event.modifiers;
 538
 539        if let Some(hovered_hunk) = hovered_hunk {
 540            editor.toggle_diff_hunks_in_ranges_narrow(vec![hovered_hunk], cx);
 541            cx.notify();
 542            return;
 543        } else if gutter_hitbox.is_hovered(window) {
 544            click_count = 3; // Simulate triple-click when clicking the gutter to select lines
 545        } else if !text_hitbox.is_hovered(window) {
 546            return;
 547        }
 548
 549        let is_singleton = editor.buffer().read(cx).is_singleton();
 550
 551        if click_count == 2 && !is_singleton {
 552            match EditorSettings::get_global(cx).double_click_in_multibuffer {
 553                DoubleClickInMultibuffer::Select => {
 554                    // do nothing special on double click, all selection logic is below
 555                }
 556                DoubleClickInMultibuffer::Open => {
 557                    if modifiers.alt {
 558                        // if double click is made with alt, pretend it's a regular double click without opening and alt,
 559                        // and run the selection logic.
 560                        modifiers.alt = false;
 561                    } else {
 562                        let scroll_position_row =
 563                            position_map.scroll_pixel_position.y / position_map.line_height;
 564                        let display_row = (((event.position - gutter_hitbox.bounds.origin).y
 565                            + position_map.scroll_pixel_position.y)
 566                            / position_map.line_height)
 567                            as u32;
 568                        let multi_buffer_row = position_map
 569                            .snapshot
 570                            .display_point_to_point(
 571                                DisplayPoint::new(DisplayRow(display_row), 0),
 572                                Bias::Right,
 573                            )
 574                            .row;
 575                        let line_offset_from_top = display_row - scroll_position_row as u32;
 576                        // if double click is made without alt, open the corresponding excerp
 577                        editor.open_excerpts_common(
 578                            Some(JumpData::MultiBufferRow {
 579                                row: MultiBufferRow(multi_buffer_row),
 580                                line_offset_from_top,
 581                            }),
 582                            false,
 583                            window,
 584                            cx,
 585                        );
 586                        return;
 587                    }
 588                }
 589            }
 590        }
 591
 592        let point_for_position = position_map.point_for_position(event.position);
 593        let position = point_for_position.previous_valid;
 594        if modifiers.shift && modifiers.alt {
 595            editor.select(
 596                SelectPhase::BeginColumnar {
 597                    position,
 598                    reset: false,
 599                    goal_column: point_for_position.exact_unclipped.column(),
 600                },
 601                window,
 602                cx,
 603            );
 604        } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.secondary()
 605        {
 606            editor.select(
 607                SelectPhase::Extend {
 608                    position,
 609                    click_count,
 610                },
 611                window,
 612                cx,
 613            );
 614        } else {
 615            let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
 616            let multi_cursor_modifier = match multi_cursor_setting {
 617                MultiCursorModifier::Alt => modifiers.alt,
 618                MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
 619            };
 620            editor.select(
 621                SelectPhase::Begin {
 622                    position,
 623                    add: multi_cursor_modifier,
 624                    click_count,
 625                },
 626                window,
 627                cx,
 628            );
 629        }
 630        cx.stop_propagation();
 631
 632        if !is_singleton {
 633            let display_row = (((event.position - gutter_hitbox.bounds.origin).y
 634                + position_map.scroll_pixel_position.y)
 635                / position_map.line_height) as u32;
 636            let multi_buffer_row = position_map
 637                .snapshot
 638                .display_point_to_point(DisplayPoint::new(DisplayRow(display_row), 0), Bias::Right)
 639                .row;
 640            if line_numbers
 641                .get(&MultiBufferRow(multi_buffer_row))
 642                .and_then(|line_number| line_number.hitbox.as_ref())
 643                .is_some_and(|hitbox| hitbox.contains(&event.position))
 644            {
 645                let scroll_position_row =
 646                    position_map.scroll_pixel_position.y / position_map.line_height;
 647                let line_offset_from_top = display_row - scroll_position_row as u32;
 648
 649                editor.open_excerpts_common(
 650                    Some(JumpData::MultiBufferRow {
 651                        row: MultiBufferRow(multi_buffer_row),
 652                        line_offset_from_top,
 653                    }),
 654                    modifiers.alt,
 655                    window,
 656                    cx,
 657                );
 658                cx.stop_propagation();
 659            }
 660        }
 661    }
 662
 663    fn mouse_right_down(
 664        editor: &mut Editor,
 665        event: &MouseDownEvent,
 666        position_map: &PositionMap,
 667        window: &mut Window,
 668        cx: &mut Context<Editor>,
 669    ) {
 670        if !position_map.text_hitbox.is_hovered(window) {
 671            return;
 672        }
 673        let point_for_position = position_map.point_for_position(event.position);
 674        mouse_context_menu::deploy_context_menu(
 675            editor,
 676            Some(event.position),
 677            point_for_position.previous_valid,
 678            window,
 679            cx,
 680        );
 681        cx.stop_propagation();
 682    }
 683
 684    fn mouse_middle_down(
 685        editor: &mut Editor,
 686        event: &MouseDownEvent,
 687        position_map: &PositionMap,
 688        window: &mut Window,
 689        cx: &mut Context<Editor>,
 690    ) {
 691        if !position_map.text_hitbox.is_hovered(window) || window.default_prevented() {
 692            return;
 693        }
 694
 695        let point_for_position = position_map.point_for_position(event.position);
 696        let position = point_for_position.previous_valid;
 697
 698        editor.select(
 699            SelectPhase::BeginColumnar {
 700                position,
 701                reset: true,
 702                goal_column: point_for_position.exact_unclipped.column(),
 703            },
 704            window,
 705            cx,
 706        );
 707    }
 708
 709    fn mouse_up(
 710        editor: &mut Editor,
 711        event: &MouseUpEvent,
 712        position_map: &PositionMap,
 713        window: &mut Window,
 714        cx: &mut Context<Editor>,
 715    ) {
 716        let text_hitbox = &position_map.text_hitbox;
 717        let end_selection = editor.has_pending_selection();
 718        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
 719
 720        if end_selection {
 721            editor.select(SelectPhase::End, window, cx);
 722        }
 723
 724        if end_selection && pending_nonempty_selections {
 725            cx.stop_propagation();
 726        } else if cfg!(any(target_os = "linux", target_os = "freebsd"))
 727            && event.button == MouseButton::Middle
 728        {
 729            if !text_hitbox.is_hovered(window) || editor.read_only(cx) {
 730                return;
 731            }
 732
 733            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 734            if EditorSettings::get_global(cx).middle_click_paste {
 735                if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
 736                    let point_for_position = position_map.point_for_position(event.position);
 737                    let position = point_for_position.previous_valid;
 738
 739                    editor.select(
 740                        SelectPhase::Begin {
 741                            position,
 742                            add: false,
 743                            click_count: 1,
 744                        },
 745                        window,
 746                        cx,
 747                    );
 748                    editor.insert(&text, window, cx);
 749                }
 750                cx.stop_propagation()
 751            }
 752        }
 753    }
 754
 755    fn click(
 756        editor: &mut Editor,
 757        event: &ClickEvent,
 758        position_map: &PositionMap,
 759        window: &mut Window,
 760        cx: &mut Context<Editor>,
 761    ) {
 762        let text_hitbox = &position_map.text_hitbox;
 763        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
 764
 765        let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
 766        let multi_cursor_modifier = match multi_cursor_setting {
 767            MultiCursorModifier::Alt => event.modifiers().secondary(),
 768            MultiCursorModifier::CmdOrCtrl => event.modifiers().alt,
 769        };
 770
 771        if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
 772            let point = position_map.point_for_position(event.up.position);
 773            editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
 774
 775            cx.stop_propagation();
 776        }
 777    }
 778
 779    fn mouse_dragged(
 780        editor: &mut Editor,
 781        event: &MouseMoveEvent,
 782        position_map: &PositionMap,
 783        window: &mut Window,
 784        cx: &mut Context<Editor>,
 785    ) {
 786        if !editor.has_pending_selection() {
 787            return;
 788        }
 789
 790        let text_bounds = position_map.text_hitbox.bounds;
 791        let point_for_position = position_map.point_for_position(event.position);
 792        let mut scroll_delta = gpui::Point::<f32>::default();
 793        let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
 794        let top = text_bounds.origin.y + vertical_margin;
 795        let bottom = text_bounds.bottom_left().y - vertical_margin;
 796        if event.position.y < top {
 797            scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
 798        }
 799        if event.position.y > bottom {
 800            scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
 801        }
 802
 803        // We need horizontal width of text
 804        let style = editor.style.clone().unwrap_or_default();
 805        let font_id = window.text_system().resolve_font(&style.text.font());
 806        let font_size = style.text.font_size.to_pixels(window.rem_size());
 807        let em_width = window.text_system().em_width(font_id, font_size).unwrap();
 808
 809        let scroll_margin_x = EditorSettings::get_global(cx).horizontal_scroll_margin;
 810
 811        let scroll_space: Pixels = scroll_margin_x * em_width;
 812
 813        let left = text_bounds.origin.x + scroll_space;
 814        let right = text_bounds.top_right().x - scroll_space;
 815
 816        if event.position.x < left {
 817            scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
 818        }
 819        if event.position.x > right {
 820            scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
 821        }
 822
 823        editor.select(
 824            SelectPhase::Update {
 825                position: point_for_position.previous_valid,
 826                goal_column: point_for_position.exact_unclipped.column(),
 827                scroll_delta,
 828            },
 829            window,
 830            cx,
 831        );
 832    }
 833
 834    fn mouse_moved(
 835        editor: &mut Editor,
 836        event: &MouseMoveEvent,
 837        position_map: &PositionMap,
 838        window: &mut Window,
 839        cx: &mut Context<Editor>,
 840    ) {
 841        let text_hitbox = &position_map.text_hitbox;
 842        let gutter_hitbox = &position_map.gutter_hitbox;
 843        let modifiers = event.modifiers;
 844        let gutter_hovered = gutter_hitbox.is_hovered(window);
 845        editor.set_gutter_hovered(gutter_hovered, cx);
 846
 847        // Don't trigger hover popover if mouse is hovering over context menu
 848        if text_hitbox.is_hovered(window) {
 849            let point_for_position = position_map.point_for_position(event.position);
 850
 851            editor.update_hovered_link(
 852                point_for_position,
 853                &position_map.snapshot,
 854                modifiers,
 855                window,
 856                cx,
 857            );
 858
 859            if let Some(point) = point_for_position.as_valid() {
 860                let anchor = position_map
 861                    .snapshot
 862                    .buffer_snapshot
 863                    .anchor_before(point.to_offset(&position_map.snapshot, Bias::Left));
 864                hover_at(editor, Some(anchor), window, cx);
 865                Self::update_visible_cursor(editor, point, position_map, window, cx);
 866            } else {
 867                hover_at(editor, None, window, cx);
 868            }
 869        } else {
 870            editor.hide_hovered_link(cx);
 871            hover_at(editor, None, window, cx);
 872            if gutter_hovered {
 873                cx.stop_propagation();
 874            }
 875        }
 876    }
 877
 878    fn update_visible_cursor(
 879        editor: &mut Editor,
 880        point: DisplayPoint,
 881        position_map: &PositionMap,
 882        window: &mut Window,
 883        cx: &mut Context<Editor>,
 884    ) {
 885        let snapshot = &position_map.snapshot;
 886        let Some(hub) = editor.collaboration_hub() else {
 887            return;
 888        };
 889        let start = snapshot.display_snapshot.clip_point(
 890            DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
 891            Bias::Left,
 892        );
 893        let end = snapshot.display_snapshot.clip_point(
 894            DisplayPoint::new(
 895                point.row(),
 896                (point.column() + 1).min(snapshot.line_len(point.row())),
 897            ),
 898            Bias::Right,
 899        );
 900
 901        let range = snapshot
 902            .buffer_snapshot
 903            .anchor_at(start.to_point(&snapshot.display_snapshot), Bias::Left)
 904            ..snapshot
 905                .buffer_snapshot
 906                .anchor_at(end.to_point(&snapshot.display_snapshot), Bias::Right);
 907
 908        let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
 909            return;
 910        };
 911        let key = crate::HoveredCursor {
 912            replica_id: selection.replica_id,
 913            selection_id: selection.selection.id,
 914        };
 915        editor.hovered_cursors.insert(
 916            key.clone(),
 917            cx.spawn_in(window, |editor, mut cx| async move {
 918                cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
 919                editor
 920                    .update(&mut cx, |editor, cx| {
 921                        editor.hovered_cursors.remove(&key);
 922                        cx.notify();
 923                    })
 924                    .ok();
 925            }),
 926        );
 927        cx.notify()
 928    }
 929
 930    #[allow(clippy::too_many_arguments)]
 931    fn layout_selections(
 932        &self,
 933        start_anchor: Anchor,
 934        end_anchor: Anchor,
 935        local_selections: &[Selection<Point>],
 936        snapshot: &EditorSnapshot,
 937        start_row: DisplayRow,
 938        end_row: DisplayRow,
 939        window: &mut Window,
 940        cx: &mut App,
 941    ) -> (
 942        Vec<(PlayerColor, Vec<SelectionLayout>)>,
 943        BTreeMap<DisplayRow, bool>,
 944        Option<DisplayPoint>,
 945    ) {
 946        let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
 947        let mut active_rows = BTreeMap::new();
 948        let mut newest_selection_head = None;
 949        self.editor.update(cx, |editor, cx| {
 950            if editor.show_local_selections {
 951                let mut layouts = Vec::new();
 952                let newest = editor.selections.newest(cx);
 953                for selection in local_selections.iter().cloned() {
 954                    let is_empty = selection.start == selection.end;
 955                    let is_newest = selection == newest;
 956
 957                    let layout = SelectionLayout::new(
 958                        selection,
 959                        editor.selections.line_mode,
 960                        editor.cursor_shape,
 961                        &snapshot.display_snapshot,
 962                        is_newest,
 963                        editor.leader_peer_id.is_none(),
 964                        None,
 965                    );
 966                    if is_newest {
 967                        newest_selection_head = Some(layout.head);
 968                    }
 969
 970                    for row in cmp::max(layout.active_rows.start.0, start_row.0)
 971                        ..=cmp::min(layout.active_rows.end.0, end_row.0)
 972                    {
 973                        let contains_non_empty_selection =
 974                            active_rows.entry(DisplayRow(row)).or_insert(!is_empty);
 975                        *contains_non_empty_selection |= !is_empty;
 976                    }
 977                    layouts.push(layout);
 978                }
 979
 980                let player = editor.current_user_player_color(cx);
 981                selections.push((player, layouts));
 982            }
 983
 984            if let Some(collaboration_hub) = &editor.collaboration_hub {
 985                // When following someone, render the local selections in their color.
 986                if let Some(leader_id) = editor.leader_peer_id {
 987                    if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id)
 988                    {
 989                        if let Some(participant_index) = collaboration_hub
 990                            .user_participant_indices(cx)
 991                            .get(&collaborator.user_id)
 992                        {
 993                            if let Some((local_selection_style, _)) = selections.first_mut() {
 994                                *local_selection_style = cx
 995                                    .theme()
 996                                    .players()
 997                                    .color_for_participant(participant_index.0);
 998                            }
 999                        }
1000                    }
1001                }
1002
1003                let mut remote_selections = HashMap::default();
1004                for selection in snapshot.remote_selections_in_range(
1005                    &(start_anchor..end_anchor),
1006                    collaboration_hub.as_ref(),
1007                    cx,
1008                ) {
1009                    let selection_style =
1010                        Self::get_participant_color(selection.participant_index, cx);
1011
1012                    // Don't re-render the leader's selections, since the local selections
1013                    // match theirs.
1014                    if Some(selection.peer_id) == editor.leader_peer_id {
1015                        continue;
1016                    }
1017                    let key = HoveredCursor {
1018                        replica_id: selection.replica_id,
1019                        selection_id: selection.selection.id,
1020                    };
1021
1022                    let is_shown =
1023                        editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
1024
1025                    remote_selections
1026                        .entry(selection.replica_id)
1027                        .or_insert((selection_style, Vec::new()))
1028                        .1
1029                        .push(SelectionLayout::new(
1030                            selection.selection,
1031                            selection.line_mode,
1032                            selection.cursor_shape,
1033                            &snapshot.display_snapshot,
1034                            false,
1035                            false,
1036                            if is_shown { selection.user_name } else { None },
1037                        ));
1038                }
1039
1040                selections.extend(remote_selections.into_values());
1041            } else if !editor.is_focused(window) && editor.show_cursor_when_unfocused {
1042                let layouts = snapshot
1043                    .buffer_snapshot
1044                    .selections_in_range(&(start_anchor..end_anchor), true)
1045                    .map(move |(_, line_mode, cursor_shape, selection)| {
1046                        SelectionLayout::new(
1047                            selection,
1048                            line_mode,
1049                            cursor_shape,
1050                            &snapshot.display_snapshot,
1051                            false,
1052                            false,
1053                            None,
1054                        )
1055                    })
1056                    .collect::<Vec<_>>();
1057                let player = editor.current_user_player_color(cx);
1058                selections.push((player, layouts));
1059            }
1060        });
1061        (selections, active_rows, newest_selection_head)
1062    }
1063
1064    fn collect_cursors(
1065        &self,
1066        snapshot: &EditorSnapshot,
1067        cx: &mut App,
1068    ) -> Vec<(DisplayPoint, Hsla)> {
1069        let editor = self.editor.read(cx);
1070        let mut cursors = Vec::new();
1071        let mut skip_local = false;
1072        let mut add_cursor = |anchor: Anchor, color| {
1073            cursors.push((anchor.to_display_point(&snapshot.display_snapshot), color));
1074        };
1075        // Remote cursors
1076        if let Some(collaboration_hub) = &editor.collaboration_hub {
1077            for remote_selection in snapshot.remote_selections_in_range(
1078                &(Anchor::min()..Anchor::max()),
1079                collaboration_hub.deref(),
1080                cx,
1081            ) {
1082                let color = Self::get_participant_color(remote_selection.participant_index, cx);
1083                add_cursor(remote_selection.selection.head(), color.cursor);
1084                if Some(remote_selection.peer_id) == editor.leader_peer_id {
1085                    skip_local = true;
1086                }
1087            }
1088        }
1089        // Local cursors
1090        if !skip_local {
1091            let color = cx.theme().players().local().cursor;
1092            editor.selections.disjoint.iter().for_each(|selection| {
1093                add_cursor(selection.head(), color);
1094            });
1095            if let Some(ref selection) = editor.selections.pending_anchor() {
1096                add_cursor(selection.head(), color);
1097            }
1098        }
1099        cursors
1100    }
1101
1102    #[allow(clippy::too_many_arguments)]
1103    fn layout_visible_cursors(
1104        &self,
1105        snapshot: &EditorSnapshot,
1106        selections: &[(PlayerColor, Vec<SelectionLayout>)],
1107        block_start_rows: &HashSet<DisplayRow>,
1108        visible_display_row_range: Range<DisplayRow>,
1109        line_layouts: &[LineWithInvisibles],
1110        text_hitbox: &Hitbox,
1111        content_origin: gpui::Point<Pixels>,
1112        scroll_position: gpui::Point<f32>,
1113        scroll_pixel_position: gpui::Point<Pixels>,
1114        line_height: Pixels,
1115        em_width: Pixels,
1116        em_advance: Pixels,
1117        autoscroll_containing_element: bool,
1118        window: &mut Window,
1119        cx: &mut App,
1120    ) -> Vec<CursorLayout> {
1121        let mut autoscroll_bounds = None;
1122        let cursor_layouts = self.editor.update(cx, |editor, cx| {
1123            let mut cursors = Vec::new();
1124            for (player_color, selections) in selections {
1125                for selection in selections {
1126                    let cursor_position = selection.head;
1127
1128                    let in_range = visible_display_row_range.contains(&cursor_position.row());
1129                    if (selection.is_local && !editor.show_local_cursors(window, cx))
1130                        || !in_range
1131                        || block_start_rows.contains(&cursor_position.row())
1132                    {
1133                        continue;
1134                    }
1135
1136                    let cursor_row_layout = &line_layouts
1137                        [cursor_position.row().minus(visible_display_row_range.start) as usize];
1138                    let cursor_column = cursor_position.column() as usize;
1139
1140                    let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
1141                    let mut block_width =
1142                        cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
1143                    if block_width == Pixels::ZERO {
1144                        block_width = em_advance;
1145                    }
1146                    let block_text = if let CursorShape::Block = selection.cursor_shape {
1147                        snapshot
1148                            .grapheme_at(cursor_position)
1149                            .or_else(|| {
1150                                if cursor_column == 0 {
1151                                    snapshot.placeholder_text().and_then(|s| {
1152                                        s.graphemes(true).next().map(|s| s.to_string().into())
1153                                    })
1154                                } else {
1155                                    None
1156                                }
1157                            })
1158                            .and_then(|text| {
1159                                let len = text.len();
1160
1161                                let font = cursor_row_layout
1162                                    .font_id_for_index(cursor_column)
1163                                    .and_then(|cursor_font_id| {
1164                                        window.text_system().get_font_for_id(cursor_font_id)
1165                                    })
1166                                    .unwrap_or(self.style.text.font());
1167
1168                                // Invert the text color for the block cursor. Ensure that the text
1169                                // color is opaque enough to be visible against the background color.
1170                                //
1171                                // 0.75 is an arbitrary threshold to determine if the background color is
1172                                // opaque enough to use as a text color.
1173                                //
1174                                // TODO: In the future we should ensure themes have a `text_inverse` color.
1175                                let color = if cx.theme().colors().editor_background.a < 0.75 {
1176                                    match cx.theme().appearance {
1177                                        Appearance::Dark => Hsla::black(),
1178                                        Appearance::Light => Hsla::white(),
1179                                    }
1180                                } else {
1181                                    cx.theme().colors().editor_background
1182                                };
1183
1184                                window
1185                                    .text_system()
1186                                    .shape_line(
1187                                        text,
1188                                        cursor_row_layout.font_size,
1189                                        &[TextRun {
1190                                            len,
1191                                            font,
1192                                            color,
1193                                            background_color: None,
1194                                            strikethrough: None,
1195                                            underline: None,
1196                                        }],
1197                                    )
1198                                    .log_err()
1199                            })
1200                    } else {
1201                        None
1202                    };
1203
1204                    let x = cursor_character_x - scroll_pixel_position.x;
1205                    let y = (cursor_position.row().as_f32()
1206                        - scroll_pixel_position.y / line_height)
1207                        * line_height;
1208                    if selection.is_newest {
1209                        editor.pixel_position_of_newest_cursor = Some(point(
1210                            text_hitbox.origin.x + x + block_width / 2.,
1211                            text_hitbox.origin.y + y + line_height / 2.,
1212                        ));
1213
1214                        if autoscroll_containing_element {
1215                            let top = text_hitbox.origin.y
1216                                + (cursor_position.row().as_f32() - scroll_position.y - 3.).max(0.)
1217                                    * line_height;
1218                            let left = text_hitbox.origin.x
1219                                + (cursor_position.column() as f32 - scroll_position.x - 3.)
1220                                    .max(0.)
1221                                    * em_width;
1222
1223                            let bottom = text_hitbox.origin.y
1224                                + (cursor_position.row().as_f32() - scroll_position.y + 4.)
1225                                    * line_height;
1226                            let right = text_hitbox.origin.x
1227                                + (cursor_position.column() as f32 - scroll_position.x + 4.)
1228                                    * em_width;
1229
1230                            autoscroll_bounds =
1231                                Some(Bounds::from_corners(point(left, top), point(right, bottom)))
1232                        }
1233                    }
1234
1235                    let mut cursor = CursorLayout {
1236                        color: player_color.cursor,
1237                        block_width,
1238                        origin: point(x, y),
1239                        line_height,
1240                        shape: selection.cursor_shape,
1241                        block_text,
1242                        cursor_name: None,
1243                    };
1244                    let cursor_name = selection.user_name.clone().map(|name| CursorName {
1245                        string: name,
1246                        color: self.style.background,
1247                        is_top_row: cursor_position.row().0 == 0,
1248                    });
1249                    cursor.layout(content_origin, cursor_name, window, cx);
1250                    cursors.push(cursor);
1251                }
1252            }
1253            cursors
1254        });
1255
1256        if let Some(bounds) = autoscroll_bounds {
1257            window.request_autoscroll(bounds);
1258        }
1259
1260        cursor_layouts
1261    }
1262
1263    fn layout_scrollbars(
1264        &self,
1265        snapshot: &EditorSnapshot,
1266        scrollbar_range_data: ScrollbarRangeData,
1267        scroll_position: gpui::Point<f32>,
1268        non_visible_cursors: bool,
1269        window: &mut Window,
1270        cx: &mut App,
1271    ) -> AxisPair<Option<ScrollbarLayout>> {
1272        let letter_size = scrollbar_range_data.letter_size;
1273        let text_units_per_page = axis_pair(
1274            scrollbar_range_data.scrollbar_bounds.size.width / letter_size.width,
1275            scrollbar_range_data.scrollbar_bounds.size.height / letter_size.height,
1276        );
1277
1278        let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1279        let show_scrollbars = self.editor.read(cx).show_scrollbars
1280            && match scrollbar_settings.show {
1281                ShowScrollbar::Auto => {
1282                    let editor = self.editor.read(cx);
1283                    let is_singleton = editor.is_singleton(cx);
1284                    // Git
1285                    (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_diff_hunks())
1286                    ||
1287                    // Buffer Search Results
1288                    (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
1289                    ||
1290                    // Selected Symbol Occurrences
1291                    (is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
1292                    ||
1293                    // Diagnostics
1294                    (is_singleton && scrollbar_settings.diagnostics != ScrollbarDiagnostics::None && snapshot.buffer_snapshot.has_diagnostics())
1295                    ||
1296                    // Cursors out of sight
1297                    non_visible_cursors
1298                    ||
1299                    // Scrollmanager
1300                    editor.scroll_manager.scrollbars_visible()
1301                }
1302                ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
1303                ShowScrollbar::Always => true,
1304                ShowScrollbar::Never => false,
1305            };
1306
1307        let axes: AxisPair<bool> = scrollbar_settings.axes.into();
1308
1309        if snapshot.mode != EditorMode::Full {
1310            return axis_pair(None, None);
1311        }
1312
1313        let visible_range = axis_pair(
1314            axes.horizontal
1315                .then(|| scroll_position.x..scroll_position.x + text_units_per_page.horizontal),
1316            axes.vertical
1317                .then(|| scroll_position.y..scroll_position.y + text_units_per_page.vertical),
1318        );
1319
1320        // If a drag took place after we started dragging the scrollbar,
1321        // cancel the scrollbar drag.
1322        if cx.has_active_drag() {
1323            self.editor.update(cx, |editor, cx| {
1324                editor
1325                    .scroll_manager
1326                    .set_is_dragging_scrollbar(Axis::Horizontal, false, cx);
1327                editor
1328                    .scroll_manager
1329                    .set_is_dragging_scrollbar(Axis::Vertical, false, cx);
1330            });
1331        }
1332
1333        let text_bounds = scrollbar_range_data.scrollbar_bounds;
1334
1335        let track_bounds = axis_pair(
1336            axes.horizontal.then(|| {
1337                Bounds::from_corners(
1338                    point(
1339                        text_bounds.bottom_left().x,
1340                        text_bounds.bottom_left().y - self.style.scrollbar_width,
1341                    ),
1342                    point(
1343                        text_bounds.bottom_right().x
1344                            - if axes.vertical {
1345                                self.style.scrollbar_width
1346                            } else {
1347                                px(0.)
1348                            },
1349                        text_bounds.bottom_right().y,
1350                    ),
1351                )
1352            }),
1353            axes.vertical.then(|| {
1354                Bounds::from_corners(
1355                    point(self.scrollbar_left(&text_bounds), text_bounds.origin.y),
1356                    text_bounds.bottom_right(),
1357                )
1358            }),
1359        );
1360
1361        let scroll_range_size = scrollbar_range_data.scroll_range.size;
1362        let total_text_units = axis_pair(
1363            Some(scroll_range_size.width / letter_size.width),
1364            Some(scroll_range_size.height / letter_size.height),
1365        );
1366
1367        let thumb_size = axis_pair(
1368            total_text_units
1369                .horizontal
1370                .zip(track_bounds.horizontal)
1371                .and_then(|(total_text_units_x, track_bounds_x)| {
1372                    if text_units_per_page.horizontal >= total_text_units_x {
1373                        return None;
1374                    }
1375
1376                    let thumb_percent =
1377                        (text_units_per_page.horizontal / total_text_units_x).min(1.);
1378
1379                    Some(track_bounds_x.size.width * thumb_percent)
1380                }),
1381            total_text_units.vertical.zip(track_bounds.vertical).map(
1382                |(total_text_units_y, track_bounds_y)| {
1383                    let thumb_percent = (text_units_per_page.vertical / total_text_units_y).min(1.);
1384
1385                    track_bounds_y.size.height * thumb_percent
1386                },
1387            ),
1388        );
1389
1390        // NOTE: Space not taken by track bounds divided by text units not on screen
1391        let text_unit_size = axis_pair(
1392            thumb_size
1393                .horizontal
1394                .zip(track_bounds.horizontal)
1395                .zip(total_text_units.horizontal)
1396                .map(|((thumb_size, track_bounds), total_text_units)| {
1397                    (track_bounds.size.width - thumb_size)
1398                        / (total_text_units - text_units_per_page.horizontal).max(0.)
1399                }),
1400            thumb_size
1401                .vertical
1402                .zip(track_bounds.vertical)
1403                .zip(total_text_units.vertical)
1404                .map(|((thumb_size, track_bounds), total_text_units)| {
1405                    (track_bounds.size.height - thumb_size)
1406                        / (total_text_units - text_units_per_page.vertical).max(0.)
1407                }),
1408        );
1409
1410        let horizontal_scrollbar = track_bounds
1411            .horizontal
1412            .zip(visible_range.horizontal)
1413            .zip(text_unit_size.horizontal)
1414            .zip(thumb_size.horizontal)
1415            .map(
1416                |(((track_bounds, visible_range), text_unit_size), thumb_size)| ScrollbarLayout {
1417                    hitbox: window.insert_hitbox(track_bounds, false),
1418                    visible_range,
1419                    text_unit_size,
1420                    visible: show_scrollbars,
1421                    thumb_size,
1422                    axis: Axis::Horizontal,
1423                },
1424            );
1425
1426        let vertical_scrollbar = track_bounds
1427            .vertical
1428            .zip(visible_range.vertical)
1429            .zip(text_unit_size.vertical)
1430            .zip(thumb_size.vertical)
1431            .map(
1432                |(((track_bounds, visible_range), text_unit_size), thumb_size)| ScrollbarLayout {
1433                    hitbox: window.insert_hitbox(track_bounds, false),
1434                    visible_range,
1435                    text_unit_size,
1436                    visible: show_scrollbars,
1437                    thumb_size,
1438                    axis: Axis::Vertical,
1439                },
1440            );
1441
1442        axis_pair(horizontal_scrollbar, vertical_scrollbar)
1443    }
1444
1445    #[allow(clippy::too_many_arguments)]
1446    fn prepaint_crease_toggles(
1447        &self,
1448        crease_toggles: &mut [Option<AnyElement>],
1449        line_height: Pixels,
1450        gutter_dimensions: &GutterDimensions,
1451        gutter_settings: crate::editor_settings::Gutter,
1452        scroll_pixel_position: gpui::Point<Pixels>,
1453        gutter_hitbox: &Hitbox,
1454        window: &mut Window,
1455        cx: &mut App,
1456    ) {
1457        for (ix, crease_toggle) in crease_toggles.iter_mut().enumerate() {
1458            if let Some(crease_toggle) = crease_toggle {
1459                debug_assert!(gutter_settings.folds);
1460                let available_space = size(
1461                    AvailableSpace::MinContent,
1462                    AvailableSpace::Definite(line_height * 0.55),
1463                );
1464                let crease_toggle_size = crease_toggle.layout_as_root(available_space, window, cx);
1465
1466                let position = point(
1467                    gutter_dimensions.width - gutter_dimensions.right_padding,
1468                    ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1469                );
1470                let centering_offset = point(
1471                    (gutter_dimensions.fold_area_width() - crease_toggle_size.width) / 2.,
1472                    (line_height - crease_toggle_size.height) / 2.,
1473                );
1474                let origin = gutter_hitbox.origin + position + centering_offset;
1475                crease_toggle.prepaint_as_root(origin, available_space, window, cx);
1476            }
1477        }
1478    }
1479
1480    #[allow(clippy::too_many_arguments)]
1481    fn prepaint_crease_trailers(
1482        &self,
1483        trailers: Vec<Option<AnyElement>>,
1484        lines: &[LineWithInvisibles],
1485        line_height: Pixels,
1486        content_origin: gpui::Point<Pixels>,
1487        scroll_pixel_position: gpui::Point<Pixels>,
1488        em_width: Pixels,
1489        window: &mut Window,
1490        cx: &mut App,
1491    ) -> Vec<Option<CreaseTrailerLayout>> {
1492        trailers
1493            .into_iter()
1494            .enumerate()
1495            .map(|(ix, element)| {
1496                let mut element = element?;
1497                let available_space = size(
1498                    AvailableSpace::MinContent,
1499                    AvailableSpace::Definite(line_height),
1500                );
1501                let size = element.layout_as_root(available_space, window, cx);
1502
1503                let line = &lines[ix];
1504                let padding = if line.width == Pixels::ZERO {
1505                    Pixels::ZERO
1506                } else {
1507                    4. * em_width
1508                };
1509                let position = point(
1510                    scroll_pixel_position.x + line.width + padding,
1511                    ix as f32 * line_height - (scroll_pixel_position.y % line_height),
1512                );
1513                let centering_offset = point(px(0.), (line_height - size.height) / 2.);
1514                let origin = content_origin + position + centering_offset;
1515                element.prepaint_as_root(origin, available_space, window, cx);
1516                Some(CreaseTrailerLayout {
1517                    element,
1518                    bounds: Bounds::new(origin, size),
1519                })
1520            })
1521            .collect()
1522    }
1523
1524    // Folds contained in a hunk are ignored apart from shrinking visual size
1525    // If a fold contains any hunks then that fold line is marked as modified
1526    fn layout_gutter_diff_hunks(
1527        &self,
1528        line_height: Pixels,
1529        gutter_hitbox: &Hitbox,
1530        display_rows: Range<DisplayRow>,
1531        snapshot: &EditorSnapshot,
1532        window: &mut Window,
1533        cx: &mut App,
1534    ) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
1535        let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(snapshot);
1536        let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(snapshot);
1537
1538        let mut display_hunks = Vec::<(DisplayDiffHunk, Option<Hitbox>)>::new();
1539        let folded_buffers = self.editor.read(cx).folded_buffers(cx);
1540
1541        for hunk in snapshot
1542            .buffer_snapshot
1543            .diff_hunks_in_range(buffer_start..buffer_end)
1544        {
1545            if folded_buffers.contains(&hunk.buffer_id) {
1546                continue;
1547            }
1548
1549            let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
1550            let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
1551
1552            let hunk_display_start = snapshot.point_to_display_point(hunk_start_point, Bias::Left);
1553            let hunk_display_end = snapshot.point_to_display_point(hunk_end_point, Bias::Right);
1554
1555            let display_hunk = if hunk_display_start.column() != 0 {
1556                DisplayDiffHunk::Folded {
1557                    display_row: hunk_display_start.row(),
1558                }
1559            } else {
1560                let mut end_row = hunk_display_end.row();
1561                if hunk_display_end.column() > 0 {
1562                    end_row.0 += 1;
1563                }
1564                DisplayDiffHunk::Unfolded {
1565                    status: hunk.status(),
1566                    diff_base_byte_range: hunk.diff_base_byte_range,
1567                    display_row_range: hunk_display_start.row()..end_row,
1568                    multi_buffer_range: Anchor::range_in_buffer(
1569                        hunk.excerpt_id,
1570                        hunk.buffer_id,
1571                        hunk.buffer_range,
1572                    ),
1573                }
1574            };
1575
1576            display_hunks.push((display_hunk, None));
1577        }
1578
1579        let git_gutter_setting = ProjectSettings::get_global(cx)
1580            .git
1581            .git_gutter
1582            .unwrap_or_default();
1583        if let GitGutterSetting::TrackedFiles = git_gutter_setting {
1584            for (hunk, hitbox) in &mut display_hunks {
1585                if matches!(hunk, DisplayDiffHunk::Unfolded { .. }) {
1586                    let hunk_bounds =
1587                        Self::diff_hunk_bounds(snapshot, line_height, gutter_hitbox.bounds, hunk);
1588                    *hitbox = Some(window.insert_hitbox(hunk_bounds, true));
1589                }
1590            }
1591        }
1592
1593        display_hunks
1594    }
1595
1596    #[allow(clippy::too_many_arguments)]
1597    fn layout_inline_blame(
1598        &self,
1599        display_row: DisplayRow,
1600        row_info: &RowInfo,
1601        line_layout: &LineWithInvisibles,
1602        crease_trailer: Option<&CreaseTrailerLayout>,
1603        em_width: Pixels,
1604        content_origin: gpui::Point<Pixels>,
1605        scroll_pixel_position: gpui::Point<Pixels>,
1606        line_height: Pixels,
1607        window: &mut Window,
1608        cx: &mut App,
1609    ) -> Option<AnyElement> {
1610        if !self
1611            .editor
1612            .update(cx, |editor, cx| editor.render_git_blame_inline(window, cx))
1613        {
1614            return None;
1615        }
1616
1617        let workspace = self
1618            .editor
1619            .read(cx)
1620            .workspace
1621            .as_ref()
1622            .map(|(w, _)| w.clone());
1623
1624        let editor = self.editor.read(cx);
1625        let blame = editor.blame.clone()?;
1626        let padding = {
1627            const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
1628            const INLINE_ACCEPT_SUGGESTION_EM_WIDTHS: f32 = 14.;
1629
1630            let mut padding = INLINE_BLAME_PADDING_EM_WIDTHS;
1631
1632            if let Some(inline_completion) = editor.active_inline_completion.as_ref() {
1633                match &inline_completion.completion {
1634                    InlineCompletion::Edit {
1635                        display_mode: EditDisplayMode::TabAccept,
1636                        ..
1637                    } => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS,
1638                    _ => {}
1639                }
1640            }
1641
1642            padding * em_width
1643        };
1644
1645        let blame_entry = blame
1646            .update(cx, |blame, cx| {
1647                blame.blame_for_rows(&[*row_info], cx).next()
1648            })
1649            .flatten()?;
1650
1651        let mut element =
1652            render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
1653
1654        let start_y = content_origin.y
1655            + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
1656
1657        let start_x = {
1658            let line_end = if let Some(crease_trailer) = crease_trailer {
1659                crease_trailer.bounds.right()
1660            } else {
1661                content_origin.x - scroll_pixel_position.x + line_layout.width
1662            };
1663
1664            let padded_line_end = line_end + padding;
1665
1666            let min_column_in_pixels = ProjectSettings::get_global(cx)
1667                .git
1668                .inline_blame
1669                .and_then(|settings| settings.min_column)
1670                .map(|col| self.column_pixels(col as usize, window, cx))
1671                .unwrap_or(px(0.));
1672            let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
1673
1674            cmp::max(padded_line_end, min_start)
1675        };
1676
1677        let absolute_offset = point(start_x, start_y);
1678        element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), window, cx);
1679
1680        Some(element)
1681    }
1682
1683    #[allow(clippy::too_many_arguments)]
1684    fn layout_blame_entries(
1685        &self,
1686        buffer_rows: &[RowInfo],
1687        em_width: Pixels,
1688        scroll_position: gpui::Point<f32>,
1689        line_height: Pixels,
1690        gutter_hitbox: &Hitbox,
1691        max_width: Option<Pixels>,
1692        window: &mut Window,
1693        cx: &mut App,
1694    ) -> Option<Vec<AnyElement>> {
1695        if !self
1696            .editor
1697            .update(cx, |editor, cx| editor.render_git_blame_gutter(cx))
1698        {
1699            return None;
1700        }
1701
1702        let blame = self.editor.read(cx).blame.clone()?;
1703        let blamed_rows: Vec<_> = blame.update(cx, |blame, cx| {
1704            blame.blame_for_rows(buffer_rows, cx).collect()
1705        });
1706
1707        let width = if let Some(max_width) = max_width {
1708            AvailableSpace::Definite(max_width)
1709        } else {
1710            AvailableSpace::MaxContent
1711        };
1712        let scroll_top = scroll_position.y * line_height;
1713        let start_x = em_width;
1714
1715        let mut last_used_color: Option<(PlayerColor, Oid)> = None;
1716
1717        let shaped_lines = blamed_rows
1718            .into_iter()
1719            .enumerate()
1720            .flat_map(|(ix, blame_entry)| {
1721                if let Some(blame_entry) = blame_entry {
1722                    let mut element = render_blame_entry(
1723                        ix,
1724                        &blame,
1725                        blame_entry,
1726                        &self.style,
1727                        &mut last_used_color,
1728                        self.editor.clone(),
1729                        cx,
1730                    );
1731
1732                    let start_y = ix as f32 * line_height - (scroll_top % line_height);
1733                    let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
1734
1735                    element.prepaint_as_root(
1736                        absolute_offset,
1737                        size(width, AvailableSpace::MinContent),
1738                        window,
1739                        cx,
1740                    );
1741
1742                    Some(element)
1743                } else {
1744                    None
1745                }
1746            })
1747            .collect();
1748
1749        Some(shaped_lines)
1750    }
1751
1752    #[allow(clippy::too_many_arguments)]
1753    fn layout_indent_guides(
1754        &self,
1755        content_origin: gpui::Point<Pixels>,
1756        text_origin: gpui::Point<Pixels>,
1757        visible_buffer_range: Range<MultiBufferRow>,
1758        scroll_pixel_position: gpui::Point<Pixels>,
1759        line_height: Pixels,
1760        snapshot: &DisplaySnapshot,
1761        window: &mut Window,
1762        cx: &mut App,
1763    ) -> Option<Vec<IndentGuideLayout>> {
1764        let indent_guides = self.editor.update(cx, |editor, cx| {
1765            editor.indent_guides(visible_buffer_range, snapshot, cx)
1766        })?;
1767
1768        let active_indent_guide_indices = self.editor.update(cx, |editor, cx| {
1769            editor
1770                .find_active_indent_guide_indices(&indent_guides, snapshot, window, cx)
1771                .unwrap_or_default()
1772        });
1773
1774        Some(
1775            indent_guides
1776                .into_iter()
1777                .enumerate()
1778                .filter_map(|(i, indent_guide)| {
1779                    let single_indent_width =
1780                        self.column_pixels(indent_guide.tab_size as usize, window, cx);
1781                    let total_width = single_indent_width * indent_guide.depth as f32;
1782                    let start_x = content_origin.x + total_width - scroll_pixel_position.x;
1783                    if start_x >= text_origin.x {
1784                        let (offset_y, length) = Self::calculate_indent_guide_bounds(
1785                            indent_guide.start_row..indent_guide.end_row,
1786                            line_height,
1787                            snapshot,
1788                        );
1789
1790                        let start_y = content_origin.y + offset_y - scroll_pixel_position.y;
1791
1792                        Some(IndentGuideLayout {
1793                            origin: point(start_x, start_y),
1794                            length,
1795                            single_indent_width,
1796                            depth: indent_guide.depth,
1797                            active: active_indent_guide_indices.contains(&i),
1798                            settings: indent_guide.settings,
1799                        })
1800                    } else {
1801                        None
1802                    }
1803                })
1804                .collect(),
1805        )
1806    }
1807
1808    fn calculate_indent_guide_bounds(
1809        row_range: Range<MultiBufferRow>,
1810        line_height: Pixels,
1811        snapshot: &DisplaySnapshot,
1812    ) -> (gpui::Pixels, gpui::Pixels) {
1813        let start_point = Point::new(row_range.start.0, 0);
1814        let end_point = Point::new(row_range.end.0, 0);
1815
1816        let row_range = start_point.to_display_point(snapshot).row()
1817            ..end_point.to_display_point(snapshot).row();
1818
1819        let mut prev_line = start_point;
1820        prev_line.row = prev_line.row.saturating_sub(1);
1821        let prev_line = prev_line.to_display_point(snapshot).row();
1822
1823        let mut cons_line = end_point;
1824        cons_line.row += 1;
1825        let cons_line = cons_line.to_display_point(snapshot).row();
1826
1827        let mut offset_y = row_range.start.0 as f32 * line_height;
1828        let mut length = (cons_line.0.saturating_sub(row_range.start.0)) as f32 * line_height;
1829
1830        // If we are at the end of the buffer, ensure that the indent guide extends to the end of the line.
1831        if row_range.end == cons_line {
1832            length += line_height;
1833        }
1834
1835        // If there is a block (e.g. diagnostic) in between the start of the indent guide and the line above,
1836        // we want to extend the indent guide to the start of the block.
1837        let mut block_height = 0;
1838        let mut block_offset = 0;
1839        let mut found_excerpt_header = false;
1840        for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
1841            if matches!(block, Block::ExcerptBoundary { .. }) {
1842                found_excerpt_header = true;
1843                break;
1844            }
1845            block_offset += block.height();
1846            block_height += block.height();
1847        }
1848        if !found_excerpt_header {
1849            offset_y -= block_offset as f32 * line_height;
1850            length += block_height as f32 * line_height;
1851        }
1852
1853        // If there is a block (e.g. diagnostic) at the end of an multibuffer excerpt,
1854        // we want to ensure that the indent guide stops before the excerpt header.
1855        let mut block_height = 0;
1856        let mut found_excerpt_header = false;
1857        for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
1858            if matches!(block, Block::ExcerptBoundary { .. }) {
1859                found_excerpt_header = true;
1860            }
1861            block_height += block.height();
1862        }
1863        if found_excerpt_header {
1864            length -= block_height as f32 * line_height;
1865        }
1866
1867        (offset_y, length)
1868    }
1869
1870    #[allow(clippy::too_many_arguments)]
1871    fn layout_run_indicators(
1872        &self,
1873        line_height: Pixels,
1874        range: Range<DisplayRow>,
1875        scroll_pixel_position: gpui::Point<Pixels>,
1876        gutter_dimensions: &GutterDimensions,
1877        gutter_hitbox: &Hitbox,
1878        rows_with_hunk_bounds: &HashMap<DisplayRow, Bounds<Pixels>>,
1879        snapshot: &EditorSnapshot,
1880        window: &mut Window,
1881        cx: &mut App,
1882    ) -> Vec<AnyElement> {
1883        self.editor.update(cx, |editor, cx| {
1884            let active_task_indicator_row =
1885                if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
1886                    deployed_from_indicator,
1887                    actions,
1888                    ..
1889                })) = editor.context_menu.borrow().as_ref()
1890                {
1891                    actions
1892                        .tasks
1893                        .as_ref()
1894                        .map(|tasks| tasks.position.to_display_point(snapshot).row())
1895                        .or(*deployed_from_indicator)
1896                } else {
1897                    None
1898                };
1899
1900            let offset_range_start = snapshot
1901                .display_point_to_anchor(DisplayPoint::new(range.start, 0), Bias::Left)
1902                .to_offset(&snapshot.buffer_snapshot);
1903            let offset_range_end = snapshot
1904                .display_point_to_anchor(DisplayPoint::new(range.end, 0), Bias::Right)
1905                .to_offset(&snapshot.buffer_snapshot);
1906
1907            editor
1908                .tasks
1909                .iter()
1910                .filter_map(|(_, tasks)| {
1911                    if tasks.offset.0 < offset_range_start || tasks.offset.0 >= offset_range_end {
1912                        return None;
1913                    }
1914                    let multibuffer_point = tasks.offset.0.to_point(&snapshot.buffer_snapshot);
1915                    let multibuffer_row = MultiBufferRow(multibuffer_point.row);
1916                    let buffer_folded = snapshot
1917                        .buffer_snapshot
1918                        .buffer_line_for_row(multibuffer_row)
1919                        .map(|(buffer_snapshot, _)| buffer_snapshot.remote_id())
1920                        .map(|buffer_id| editor.is_buffer_folded(buffer_id, cx))
1921                        .unwrap_or(false);
1922                    if buffer_folded {
1923                        return None;
1924                    }
1925
1926                    if snapshot.is_line_folded(multibuffer_row) {
1927                        // Skip folded indicators, unless it's the starting line of a fold.
1928                        if multibuffer_row
1929                            .0
1930                            .checked_sub(1)
1931                            .map_or(false, |previous_row| {
1932                                snapshot.is_line_folded(MultiBufferRow(previous_row))
1933                            })
1934                        {
1935                            return None;
1936                        }
1937                    }
1938                    let display_row = multibuffer_point.to_display_point(snapshot).row();
1939                    let button = editor.render_run_indicator(
1940                        &self.style,
1941                        Some(display_row) == active_task_indicator_row,
1942                        display_row,
1943                        cx,
1944                    );
1945
1946                    let button = prepaint_gutter_button(
1947                        button,
1948                        display_row,
1949                        line_height,
1950                        gutter_dimensions,
1951                        scroll_pixel_position,
1952                        gutter_hitbox,
1953                        rows_with_hunk_bounds,
1954                        window,
1955                        cx,
1956                    );
1957                    Some(button)
1958                })
1959                .collect_vec()
1960        })
1961    }
1962
1963    #[allow(clippy::too_many_arguments)]
1964    fn layout_code_actions_indicator(
1965        &self,
1966        line_height: Pixels,
1967        newest_selection_head: DisplayPoint,
1968        scroll_pixel_position: gpui::Point<Pixels>,
1969        gutter_dimensions: &GutterDimensions,
1970        gutter_hitbox: &Hitbox,
1971        rows_with_hunk_bounds: &HashMap<DisplayRow, Bounds<Pixels>>,
1972        window: &mut Window,
1973        cx: &mut App,
1974    ) -> Option<AnyElement> {
1975        let mut active = false;
1976        let mut button = None;
1977        let row = newest_selection_head.row();
1978        self.editor.update(cx, |editor, cx| {
1979            if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
1980                deployed_from_indicator,
1981                ..
1982            })) = editor.context_menu.borrow().as_ref()
1983            {
1984                active = deployed_from_indicator.map_or(true, |indicator_row| indicator_row == row);
1985            };
1986            button = editor.render_code_actions_indicator(&self.style, row, active, cx);
1987        });
1988
1989        let button = prepaint_gutter_button(
1990            button?,
1991            row,
1992            line_height,
1993            gutter_dimensions,
1994            scroll_pixel_position,
1995            gutter_hitbox,
1996            rows_with_hunk_bounds,
1997            window,
1998            cx,
1999        );
2000
2001        Some(button)
2002    }
2003
2004    fn get_participant_color(participant_index: Option<ParticipantIndex>, cx: &App) -> PlayerColor {
2005        if let Some(index) = participant_index {
2006            cx.theme().players().color_for_participant(index.0)
2007        } else {
2008            cx.theme().players().absent()
2009        }
2010    }
2011
2012    fn calculate_relative_line_numbers(
2013        &self,
2014        snapshot: &EditorSnapshot,
2015        rows: &Range<DisplayRow>,
2016        relative_to: Option<DisplayRow>,
2017    ) -> HashMap<DisplayRow, DisplayRowDelta> {
2018        let mut relative_rows: HashMap<DisplayRow, DisplayRowDelta> = Default::default();
2019        let Some(relative_to) = relative_to else {
2020            return relative_rows;
2021        };
2022
2023        let start = rows.start.min(relative_to);
2024        let end = rows.end.max(relative_to);
2025
2026        let buffer_rows = snapshot
2027            .row_infos(start)
2028            .take(1 + end.minus(start) as usize)
2029            .collect::<Vec<_>>();
2030
2031        let head_idx = relative_to.minus(start);
2032        let mut delta = 1;
2033        let mut i = head_idx + 1;
2034        while i < buffer_rows.len() as u32 {
2035            if buffer_rows[i as usize].buffer_row.is_some() {
2036                if rows.contains(&DisplayRow(i + start.0)) {
2037                    relative_rows.insert(DisplayRow(i + start.0), delta);
2038                }
2039                delta += 1;
2040            }
2041            i += 1;
2042        }
2043        delta = 1;
2044        i = head_idx.min(buffer_rows.len() as u32 - 1);
2045        while i > 0 && buffer_rows[i as usize].buffer_row.is_none() {
2046            i -= 1;
2047        }
2048
2049        while i > 0 {
2050            i -= 1;
2051            if buffer_rows[i as usize].buffer_row.is_some() {
2052                if rows.contains(&DisplayRow(i + start.0)) {
2053                    relative_rows.insert(DisplayRow(i + start.0), delta);
2054                }
2055                delta += 1;
2056            }
2057        }
2058
2059        relative_rows
2060    }
2061
2062    #[allow(clippy::too_many_arguments)]
2063    fn layout_line_numbers(
2064        &self,
2065        gutter_hitbox: Option<&Hitbox>,
2066        gutter_dimensions: GutterDimensions,
2067        line_height: Pixels,
2068        scroll_position: gpui::Point<f32>,
2069        rows: Range<DisplayRow>,
2070        buffer_rows: &[RowInfo],
2071        newest_selection_head: Option<DisplayPoint>,
2072        snapshot: &EditorSnapshot,
2073        window: &mut Window,
2074        cx: &mut App,
2075    ) -> Arc<HashMap<MultiBufferRow, LineNumberLayout>> {
2076        let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| {
2077            EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full
2078        });
2079        if !include_line_numbers {
2080            return Arc::default();
2081        }
2082
2083        let (newest_selection_head, is_relative) = self.editor.update(cx, |editor, cx| {
2084            let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
2085                let newest = editor.selections.newest::<Point>(cx);
2086                SelectionLayout::new(
2087                    newest,
2088                    editor.selections.line_mode,
2089                    editor.cursor_shape,
2090                    &snapshot.display_snapshot,
2091                    true,
2092                    true,
2093                    None,
2094                )
2095                .head
2096            });
2097            let is_relative = editor.should_use_relative_line_numbers(cx);
2098            (newest_selection_head, is_relative)
2099        });
2100
2101        let relative_to = if is_relative {
2102            Some(newest_selection_head.row())
2103        } else {
2104            None
2105        };
2106        let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
2107        let mut line_number = String::new();
2108        let line_numbers = buffer_rows
2109            .into_iter()
2110            .enumerate()
2111            .flat_map(|(ix, row_info)| {
2112                let display_row = DisplayRow(rows.start.0 + ix as u32);
2113                line_number.clear();
2114                let non_relative_number = row_info.buffer_row? + 1;
2115                let number = relative_rows
2116                    .get(&display_row)
2117                    .unwrap_or(&non_relative_number);
2118                write!(&mut line_number, "{number}").unwrap();
2119                if row_info.diff_status == Some(DiffHunkStatus::Removed) {
2120                    return None;
2121                }
2122
2123                let color = cx.theme().colors().editor_line_number;
2124                let shaped_line = self
2125                    .shape_line_number(SharedString::from(&line_number), color, window)
2126                    .log_err()?;
2127                let scroll_top = scroll_position.y * line_height;
2128                let line_origin = gutter_hitbox.map(|hitbox| {
2129                    hitbox.origin
2130                        + point(
2131                            hitbox.size.width - shaped_line.width - gutter_dimensions.right_padding,
2132                            ix as f32 * line_height - (scroll_top % line_height),
2133                        )
2134                });
2135
2136                #[cfg(not(test))]
2137                let hitbox = line_origin.map(|line_origin| {
2138                    window.insert_hitbox(
2139                        Bounds::new(line_origin, size(shaped_line.width, line_height)),
2140                        false,
2141                    )
2142                });
2143                #[cfg(test)]
2144                let hitbox = {
2145                    let _ = line_origin;
2146                    None
2147                };
2148
2149                let multi_buffer_row = DisplayPoint::new(display_row, 0).to_point(snapshot).row;
2150                let multi_buffer_row = MultiBufferRow(multi_buffer_row);
2151                let line_number = LineNumberLayout {
2152                    shaped_line,
2153                    hitbox,
2154                    display_row,
2155                };
2156                Some((multi_buffer_row, line_number))
2157            })
2158            .collect();
2159        Arc::new(line_numbers)
2160    }
2161
2162    fn layout_crease_toggles(
2163        &self,
2164        rows: Range<DisplayRow>,
2165        row_infos: &[RowInfo],
2166        active_rows: &BTreeMap<DisplayRow, bool>,
2167        snapshot: &EditorSnapshot,
2168        window: &mut Window,
2169        cx: &mut App,
2170    ) -> Vec<Option<AnyElement>> {
2171        let include_fold_statuses = EditorSettings::get_global(cx).gutter.folds
2172            && snapshot.mode == EditorMode::Full
2173            && self.editor.read(cx).is_singleton(cx);
2174        if include_fold_statuses {
2175            row_infos
2176                .into_iter()
2177                .enumerate()
2178                .map(|(ix, info)| {
2179                    let row = info.multibuffer_row?;
2180                    let display_row = DisplayRow(rows.start.0 + ix as u32);
2181                    let active = active_rows.contains_key(&display_row);
2182
2183                    snapshot.render_crease_toggle(row, active, self.editor.clone(), window, cx)
2184                })
2185                .collect()
2186        } else {
2187            Vec::new()
2188        }
2189    }
2190
2191    fn layout_crease_trailers(
2192        &self,
2193        buffer_rows: impl IntoIterator<Item = RowInfo>,
2194        snapshot: &EditorSnapshot,
2195        window: &mut Window,
2196        cx: &mut App,
2197    ) -> Vec<Option<AnyElement>> {
2198        buffer_rows
2199            .into_iter()
2200            .map(|row_info| {
2201                if let Some(row) = row_info.multibuffer_row {
2202                    snapshot.render_crease_trailer(row, window, cx)
2203                } else {
2204                    None
2205                }
2206            })
2207            .collect()
2208    }
2209
2210    fn layout_lines(
2211        rows: Range<DisplayRow>,
2212        snapshot: &EditorSnapshot,
2213        style: &EditorStyle,
2214        editor_width: Pixels,
2215        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
2216        window: &mut Window,
2217        cx: &mut App,
2218    ) -> Vec<LineWithInvisibles> {
2219        if rows.start >= rows.end {
2220            return Vec::new();
2221        }
2222
2223        // Show the placeholder when the editor is empty
2224        if snapshot.is_empty() {
2225            let font_size = style.text.font_size.to_pixels(window.rem_size());
2226            let placeholder_color = cx.theme().colors().text_placeholder;
2227            let placeholder_text = snapshot.placeholder_text();
2228
2229            let placeholder_lines = placeholder_text
2230                .as_ref()
2231                .map_or("", AsRef::as_ref)
2232                .split('\n')
2233                .skip(rows.start.0 as usize)
2234                .chain(iter::repeat(""))
2235                .take(rows.len());
2236            placeholder_lines
2237                .filter_map(move |line| {
2238                    let run = TextRun {
2239                        len: line.len(),
2240                        font: style.text.font(),
2241                        color: placeholder_color,
2242                        background_color: None,
2243                        underline: Default::default(),
2244                        strikethrough: None,
2245                    };
2246                    window
2247                        .text_system()
2248                        .shape_line(line.to_string().into(), font_size, &[run])
2249                        .log_err()
2250                })
2251                .map(|line| LineWithInvisibles {
2252                    width: line.width,
2253                    len: line.len,
2254                    fragments: smallvec![LineFragment::Text(line)],
2255                    invisibles: Vec::new(),
2256                    font_size,
2257                })
2258                .collect()
2259        } else {
2260            let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
2261            LineWithInvisibles::from_chunks(
2262                chunks,
2263                &style,
2264                MAX_LINE_LEN,
2265                rows.len(),
2266                snapshot.mode,
2267                editor_width,
2268                is_row_soft_wrapped,
2269                window,
2270                cx,
2271            )
2272        }
2273    }
2274
2275    #[allow(clippy::too_many_arguments)]
2276    fn prepaint_lines(
2277        &self,
2278        start_row: DisplayRow,
2279        line_layouts: &mut [LineWithInvisibles],
2280        line_height: Pixels,
2281        scroll_pixel_position: gpui::Point<Pixels>,
2282        content_origin: gpui::Point<Pixels>,
2283        window: &mut Window,
2284        cx: &mut App,
2285    ) -> SmallVec<[AnyElement; 1]> {
2286        let mut line_elements = SmallVec::new();
2287        for (ix, line) in line_layouts.iter_mut().enumerate() {
2288            let row = start_row + DisplayRow(ix as u32);
2289            line.prepaint(
2290                line_height,
2291                scroll_pixel_position,
2292                row,
2293                content_origin,
2294                &mut line_elements,
2295                window,
2296                cx,
2297            );
2298        }
2299        line_elements
2300    }
2301
2302    #[allow(clippy::too_many_arguments)]
2303    fn render_block(
2304        &self,
2305        block: &Block,
2306        available_width: AvailableSpace,
2307        block_id: BlockId,
2308        block_row_start: DisplayRow,
2309        snapshot: &EditorSnapshot,
2310        text_x: Pixels,
2311        rows: &Range<DisplayRow>,
2312        line_layouts: &[LineWithInvisibles],
2313        gutter_dimensions: &GutterDimensions,
2314        line_height: Pixels,
2315        em_width: Pixels,
2316        text_hitbox: &Hitbox,
2317        editor_width: Pixels,
2318        scroll_width: &mut Pixels,
2319        resized_blocks: &mut HashMap<CustomBlockId, u32>,
2320        selections: &[Selection<Point>],
2321        selected_buffer_ids: &Vec<BufferId>,
2322        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
2323        sticky_header_excerpt_id: Option<ExcerptId>,
2324        window: &mut Window,
2325        cx: &mut App,
2326    ) -> (AnyElement, Size<Pixels>) {
2327        let mut element = match block {
2328            Block::Custom(block) => {
2329                let block_start = block.start().to_point(&snapshot.buffer_snapshot);
2330                let block_end = block.end().to_point(&snapshot.buffer_snapshot);
2331                let align_to = block_start.to_display_point(snapshot);
2332                let anchor_x = text_x
2333                    + if rows.contains(&align_to.row()) {
2334                        line_layouts[align_to.row().minus(rows.start) as usize]
2335                            .x_for_index(align_to.column() as usize)
2336                    } else {
2337                        layout_line(
2338                            align_to.row(),
2339                            snapshot,
2340                            &self.style,
2341                            editor_width,
2342                            is_row_soft_wrapped,
2343                            window,
2344                            cx,
2345                        )
2346                        .x_for_index(align_to.column() as usize)
2347                    };
2348
2349                let selected = selections
2350                    .binary_search_by(|selection| {
2351                        if selection.end <= block_start {
2352                            Ordering::Less
2353                        } else if selection.start >= block_end {
2354                            Ordering::Greater
2355                        } else {
2356                            Ordering::Equal
2357                        }
2358                    })
2359                    .is_ok();
2360
2361                div()
2362                    .size_full()
2363                    .child(block.render(&mut BlockContext {
2364                        window,
2365                        app: cx,
2366                        anchor_x,
2367                        gutter_dimensions,
2368                        line_height,
2369                        em_width,
2370                        block_id,
2371                        selected,
2372                        max_width: text_hitbox.size.width.max(*scroll_width),
2373                        editor_style: &self.style,
2374                    }))
2375                    .into_any()
2376            }
2377
2378            Block::FoldedBuffer {
2379                first_excerpt,
2380                prev_excerpt,
2381                show_excerpt_controls,
2382                height,
2383            } => {
2384                let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
2385                let mut result = v_flex().id(block_id).w_full();
2386
2387                if let Some(prev_excerpt) = prev_excerpt {
2388                    if *show_excerpt_controls {
2389                        result = result.child(self.render_expand_excerpt_control(
2390                            block_id,
2391                            ExpandExcerptDirection::Down,
2392                            prev_excerpt.id,
2393                            gutter_dimensions,
2394                            window,
2395                            cx,
2396                        ));
2397                    }
2398                }
2399
2400                let jump_data = header_jump_data(snapshot, block_row_start, *height, first_excerpt);
2401                result
2402                    .child(self.render_buffer_header(
2403                        first_excerpt,
2404                        true,
2405                        selected,
2406                        jump_data,
2407                        window,
2408                        cx,
2409                    ))
2410                    .into_any_element()
2411            }
2412
2413            Block::ExcerptBoundary {
2414                prev_excerpt,
2415                next_excerpt,
2416                show_excerpt_controls,
2417                height,
2418                starts_new_buffer,
2419            } => {
2420                let color = cx.theme().colors().clone();
2421                let mut result = v_flex().id(block_id).w_full();
2422
2423                if let Some(prev_excerpt) = prev_excerpt {
2424                    if *show_excerpt_controls {
2425                        result = result.child(self.render_expand_excerpt_control(
2426                            block_id,
2427                            ExpandExcerptDirection::Down,
2428                            prev_excerpt.id,
2429                            gutter_dimensions,
2430                            window,
2431                            cx,
2432                        ));
2433                    }
2434                }
2435
2436                if let Some(next_excerpt) = next_excerpt {
2437                    let jump_data =
2438                        header_jump_data(snapshot, block_row_start, *height, next_excerpt);
2439
2440                    if *starts_new_buffer {
2441                        if sticky_header_excerpt_id != Some(next_excerpt.id) {
2442                            let selected = selected_buffer_ids.contains(&next_excerpt.buffer_id);
2443
2444                            result = result.child(self.render_buffer_header(
2445                                next_excerpt,
2446                                false,
2447                                selected,
2448                                jump_data,
2449                                window,
2450                                cx,
2451                            ));
2452                        } else {
2453                            result = result
2454                                .child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
2455                        }
2456
2457                        if *show_excerpt_controls {
2458                            result = result.child(self.render_expand_excerpt_control(
2459                                block_id,
2460                                ExpandExcerptDirection::Up,
2461                                next_excerpt.id,
2462                                gutter_dimensions,
2463                                window,
2464                                cx,
2465                            ));
2466                        }
2467                    } else {
2468                        if *show_excerpt_controls {
2469                            result = result.child(
2470                                h_flex()
2471                                    .relative()
2472                                    .child(
2473                                        div()
2474                                            .top(px(0.))
2475                                            .absolute()
2476                                            .w_full()
2477                                            .h_px()
2478                                            .bg(color.border_variant),
2479                                    )
2480                                    .child(self.render_expand_excerpt_control(
2481                                        block_id,
2482                                        ExpandExcerptDirection::Up,
2483                                        next_excerpt.id,
2484                                        gutter_dimensions,
2485                                        window,
2486                                        cx,
2487                                    )),
2488                            );
2489                        }
2490                    };
2491                }
2492
2493                result.into_any()
2494            }
2495        };
2496
2497        // Discover the element's content height, then round up to the nearest multiple of line height.
2498        let preliminary_size = element.layout_as_root(
2499            size(available_width, AvailableSpace::MinContent),
2500            window,
2501            cx,
2502        );
2503        let quantized_height = (preliminary_size.height / line_height).ceil() * line_height;
2504        let final_size = if preliminary_size.height == quantized_height {
2505            preliminary_size
2506        } else {
2507            element.layout_as_root(size(available_width, quantized_height.into()), window, cx)
2508        };
2509
2510        if let BlockId::Custom(custom_block_id) = block_id {
2511            if block.height() > 0 {
2512                let element_height_in_lines =
2513                    ((final_size.height / line_height).ceil() as u32).max(1);
2514                if element_height_in_lines != block.height() {
2515                    resized_blocks.insert(custom_block_id, element_height_in_lines);
2516                }
2517            }
2518        }
2519
2520        (element, final_size)
2521    }
2522
2523    fn render_buffer_header(
2524        &self,
2525        for_excerpt: &ExcerptInfo,
2526        is_folded: bool,
2527        is_selected: bool,
2528        jump_data: JumpData,
2529        window: &mut Window,
2530        cx: &mut App,
2531    ) -> Div {
2532        let include_root = self
2533            .editor
2534            .read(cx)
2535            .project
2536            .as_ref()
2537            .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
2538            .unwrap_or_default();
2539        let path = for_excerpt.buffer.resolve_file_path(cx, include_root);
2540        let filename = path
2541            .as_ref()
2542            .and_then(|path| Some(path.file_name()?.to_string_lossy().to_string()));
2543        let parent_path = path.as_ref().and_then(|path| {
2544            Some(path.parent()?.to_string_lossy().to_string() + std::path::MAIN_SEPARATOR_STR)
2545        });
2546        let focus_handle = self.editor.focus_handle(cx);
2547        let colors = cx.theme().colors();
2548
2549        div()
2550            .px_2()
2551            .pt_2()
2552            .w_full()
2553            .h(FILE_HEADER_HEIGHT as f32 * window.line_height())
2554            .child(
2555                h_flex()
2556                    .size_full()
2557                    .gap_2()
2558                    .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
2559                    .pl_0p5()
2560                    .pr_5()
2561                    .rounded_md()
2562                    .shadow_md()
2563                    .border_1()
2564                    .map(|div| {
2565                        let border_color = if is_selected && is_folded {
2566                            colors.border_focused
2567                        } else {
2568                            colors.border
2569                        };
2570                        div.border_color(border_color)
2571                    })
2572                    .bg(colors.editor_subheader_background)
2573                    .hover(|style| style.bg(colors.element_hover))
2574                    .map(|header| {
2575                        let editor = self.editor.clone();
2576                        let buffer_id = for_excerpt.buffer_id;
2577                        let toggle_chevron_icon =
2578                            FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
2579                        header.child(
2580                            div()
2581                                .hover(|style| style.bg(colors.element_selected))
2582                                .rounded_sm()
2583                                .child(
2584                                    ButtonLike::new("toggle-buffer-fold")
2585                                        .style(ui::ButtonStyle::Transparent)
2586                                        .size(ButtonSize::Large)
2587                                        .width(px(30.).into())
2588                                        .children(toggle_chevron_icon)
2589                                        .tooltip({
2590                                            let focus_handle = focus_handle.clone();
2591                                            move |window, cx| {
2592                                                Tooltip::for_action_in(
2593                                                    "Toggle Excerpt Fold",
2594                                                    &ToggleFold,
2595                                                    &focus_handle,
2596                                                    window,
2597                                                    cx,
2598                                                )
2599                                            }
2600                                        })
2601                                        .on_click(move |_, _, cx| {
2602                                            if is_folded {
2603                                                editor.update(cx, |editor, cx| {
2604                                                    editor.unfold_buffer(buffer_id, cx);
2605                                                });
2606                                            } else {
2607                                                editor.update(cx, |editor, cx| {
2608                                                    editor.fold_buffer(buffer_id, cx);
2609                                                });
2610                                            }
2611                                        }),
2612                                ),
2613                        )
2614                    })
2615                    .children(
2616                        self.editor
2617                            .read(cx)
2618                            .addons
2619                            .values()
2620                            .filter_map(|addon| {
2621                                addon.render_buffer_header_controls(for_excerpt, window, cx)
2622                            })
2623                            .take(1),
2624                    )
2625                    .child(
2626                        h_flex()
2627                            .cursor_pointer()
2628                            .id("path header block")
2629                            .size_full()
2630                            .justify_between()
2631                            .child(
2632                                h_flex()
2633                                    .gap_2()
2634                                    .child(
2635                                        filename
2636                                            .map(SharedString::from)
2637                                            .unwrap_or_else(|| "untitled".into()),
2638                                    )
2639                                    .when_some(parent_path, |then, path| {
2640                                        then.child(div().child(path).text_color(colors.text_muted))
2641                                    }),
2642                            )
2643                            .when(is_selected, |el| {
2644                                el.child(
2645                                    h_flex()
2646                                        .id("jump-to-file-button")
2647                                        .gap_2p5()
2648                                        .child(Label::new("Jump To File"))
2649                                        .children(
2650                                            KeyBinding::for_action_in(
2651                                                &OpenExcerpts,
2652                                                &focus_handle,
2653                                                window,
2654                                            )
2655                                            .map(|binding| binding.into_any_element()),
2656                                        ),
2657                                )
2658                            })
2659                            .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2660                            .on_click(window.listener_for(&self.editor, {
2661                                move |editor, e: &ClickEvent, window, cx| {
2662                                    editor.open_excerpts_common(
2663                                        Some(jump_data.clone()),
2664                                        e.down.modifiers.secondary(),
2665                                        window,
2666                                        cx,
2667                                    );
2668                                }
2669                            })),
2670                    ),
2671            )
2672    }
2673
2674    fn render_expand_excerpt_control(
2675        &self,
2676        block_id: BlockId,
2677        direction: ExpandExcerptDirection,
2678        excerpt_id: ExcerptId,
2679        gutter_dimensions: &GutterDimensions,
2680        window: &Window,
2681        cx: &mut App,
2682    ) -> impl IntoElement {
2683        let color = cx.theme().colors().clone();
2684        let hover_color = color.border_variant.opacity(0.5);
2685        let focus_handle = self.editor.focus_handle(cx).clone();
2686
2687        let icon_offset =
2688            gutter_dimensions.width - (gutter_dimensions.left_padding + gutter_dimensions.margin);
2689        let header_height = MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * window.line_height();
2690        let group_name = if direction == ExpandExcerptDirection::Down {
2691            "expand-down"
2692        } else {
2693            "expand-up"
2694        };
2695
2696        let expand_area = |id: SharedString| {
2697            h_flex()
2698                .id(id)
2699                .w_full()
2700                .cursor_pointer()
2701                .block_mouse_down()
2702                .on_mouse_move(|_, _, cx| cx.stop_propagation())
2703                .hover(|style| style.bg(hover_color))
2704                .tooltip({
2705                    let focus_handle = focus_handle.clone();
2706                    move |window, cx| {
2707                        Tooltip::for_action_in(
2708                            "Expand Excerpt",
2709                            &ExpandExcerpts { lines: 0 },
2710                            &focus_handle,
2711                            window,
2712                            cx,
2713                        )
2714                    }
2715                })
2716        };
2717
2718        expand_area(
2719            format!(
2720                "block-{}-{}",
2721                block_id,
2722                if direction == ExpandExcerptDirection::Down {
2723                    "down"
2724                } else {
2725                    "up"
2726                }
2727            )
2728            .into(),
2729        )
2730        .group(group_name)
2731        .child(
2732            h_flex()
2733                .w(icon_offset)
2734                .h(header_height)
2735                .flex_none()
2736                .justify_end()
2737                .child(
2738                    ButtonLike::new("expand-icon")
2739                        .style(ButtonStyle::Transparent)
2740                        .child(
2741                            svg()
2742                                .path(if direction == ExpandExcerptDirection::Down {
2743                                    IconName::ArrowDownFromLine.path()
2744                                } else {
2745                                    IconName::ArrowUpFromLine.path()
2746                                })
2747                                .size(IconSize::XSmall.rems())
2748                                .text_color(cx.theme().colors().editor_line_number)
2749                                .group_hover(group_name, |style| {
2750                                    style.text_color(cx.theme().colors().editor_active_line_number)
2751                                }),
2752                        ),
2753                ),
2754        )
2755        .on_click(window.listener_for(&self.editor, {
2756            move |editor, _, _, cx| {
2757                editor.expand_excerpt(excerpt_id, direction, cx);
2758                cx.stop_propagation();
2759            }
2760        }))
2761    }
2762
2763    #[allow(clippy::too_many_arguments)]
2764    fn render_blocks(
2765        &self,
2766        rows: Range<DisplayRow>,
2767        snapshot: &EditorSnapshot,
2768        hitbox: &Hitbox,
2769        text_hitbox: &Hitbox,
2770        editor_width: Pixels,
2771        scroll_width: &mut Pixels,
2772        gutter_dimensions: &GutterDimensions,
2773        em_width: Pixels,
2774        text_x: Pixels,
2775        line_height: Pixels,
2776        line_layouts: &[LineWithInvisibles],
2777        selections: &[Selection<Point>],
2778        selected_buffer_ids: &Vec<BufferId>,
2779        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
2780        sticky_header_excerpt_id: Option<ExcerptId>,
2781        window: &mut Window,
2782        cx: &mut App,
2783    ) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
2784        let (fixed_blocks, non_fixed_blocks) = snapshot
2785            .blocks_in_range(rows.clone())
2786            .partition::<Vec<_>, _>(|(_, block)| block.style() == BlockStyle::Fixed);
2787
2788        let mut focused_block = self
2789            .editor
2790            .update(cx, |editor, _| editor.take_focused_block());
2791        let mut fixed_block_max_width = Pixels::ZERO;
2792        let mut blocks = Vec::new();
2793        let mut resized_blocks = HashMap::default();
2794
2795        for (row, block) in fixed_blocks {
2796            let block_id = block.id();
2797
2798            if focused_block.as_ref().map_or(false, |b| b.id == block_id) {
2799                focused_block = None;
2800            }
2801
2802            let (element, element_size) = self.render_block(
2803                block,
2804                AvailableSpace::MinContent,
2805                block_id,
2806                row,
2807                snapshot,
2808                text_x,
2809                &rows,
2810                line_layouts,
2811                gutter_dimensions,
2812                line_height,
2813                em_width,
2814                text_hitbox,
2815                editor_width,
2816                scroll_width,
2817                &mut resized_blocks,
2818                selections,
2819                selected_buffer_ids,
2820                is_row_soft_wrapped,
2821                sticky_header_excerpt_id,
2822                window,
2823                cx,
2824            );
2825            fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
2826            blocks.push(BlockLayout {
2827                id: block_id,
2828                row: Some(row),
2829                element,
2830                available_space: size(AvailableSpace::MinContent, element_size.height.into()),
2831                style: BlockStyle::Fixed,
2832            });
2833        }
2834
2835        for (row, block) in non_fixed_blocks {
2836            let style = block.style();
2837            let width = match style {
2838                BlockStyle::Sticky => hitbox.size.width,
2839                BlockStyle::Flex => hitbox
2840                    .size
2841                    .width
2842                    .max(fixed_block_max_width)
2843                    .max(gutter_dimensions.width + *scroll_width),
2844                BlockStyle::Fixed => unreachable!(),
2845            };
2846            let block_id = block.id();
2847
2848            if focused_block.as_ref().map_or(false, |b| b.id == block_id) {
2849                focused_block = None;
2850            }
2851
2852            let (element, element_size) = self.render_block(
2853                block,
2854                width.into(),
2855                block_id,
2856                row,
2857                snapshot,
2858                text_x,
2859                &rows,
2860                line_layouts,
2861                gutter_dimensions,
2862                line_height,
2863                em_width,
2864                text_hitbox,
2865                editor_width,
2866                scroll_width,
2867                &mut resized_blocks,
2868                selections,
2869                selected_buffer_ids,
2870                is_row_soft_wrapped,
2871                sticky_header_excerpt_id,
2872                window,
2873                cx,
2874            );
2875
2876            blocks.push(BlockLayout {
2877                id: block_id,
2878                row: Some(row),
2879                element,
2880                available_space: size(width.into(), element_size.height.into()),
2881                style,
2882            });
2883        }
2884
2885        if let Some(focused_block) = focused_block {
2886            if let Some(focus_handle) = focused_block.focus_handle.upgrade() {
2887                if focus_handle.is_focused(window) {
2888                    if let Some(block) = snapshot.block_for_id(focused_block.id) {
2889                        let style = block.style();
2890                        let width = match style {
2891                            BlockStyle::Fixed => AvailableSpace::MinContent,
2892                            BlockStyle::Flex => AvailableSpace::Definite(
2893                                hitbox
2894                                    .size
2895                                    .width
2896                                    .max(fixed_block_max_width)
2897                                    .max(gutter_dimensions.width + *scroll_width),
2898                            ),
2899                            BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width),
2900                        };
2901
2902                        let (element, element_size) = self.render_block(
2903                            &block,
2904                            width,
2905                            focused_block.id,
2906                            rows.end,
2907                            snapshot,
2908                            text_x,
2909                            &rows,
2910                            line_layouts,
2911                            gutter_dimensions,
2912                            line_height,
2913                            em_width,
2914                            text_hitbox,
2915                            editor_width,
2916                            scroll_width,
2917                            &mut resized_blocks,
2918                            selections,
2919                            selected_buffer_ids,
2920                            is_row_soft_wrapped,
2921                            sticky_header_excerpt_id,
2922                            window,
2923                            cx,
2924                        );
2925
2926                        blocks.push(BlockLayout {
2927                            id: block.id(),
2928                            row: None,
2929                            element,
2930                            available_space: size(width, element_size.height.into()),
2931                            style,
2932                        });
2933                    }
2934                }
2935            }
2936        }
2937
2938        if resized_blocks.is_empty() {
2939            *scroll_width = (*scroll_width).max(fixed_block_max_width - gutter_dimensions.width);
2940            Ok(blocks)
2941        } else {
2942            Err(resized_blocks)
2943        }
2944    }
2945
2946    /// Returns true if any of the blocks changed size since the previous frame. This will trigger
2947    /// a restart of rendering for the editor based on the new sizes.
2948    #[allow(clippy::too_many_arguments)]
2949    fn layout_blocks(
2950        &self,
2951        blocks: &mut Vec<BlockLayout>,
2952        block_starts: &mut HashSet<DisplayRow>,
2953        hitbox: &Hitbox,
2954        line_height: Pixels,
2955        scroll_pixel_position: gpui::Point<Pixels>,
2956        window: &mut Window,
2957        cx: &mut App,
2958    ) {
2959        for block in blocks {
2960            let mut origin = if let Some(row) = block.row {
2961                block_starts.insert(row);
2962                hitbox.origin
2963                    + point(
2964                        Pixels::ZERO,
2965                        row.as_f32() * line_height - scroll_pixel_position.y,
2966                    )
2967            } else {
2968                // Position the block outside the visible area
2969                hitbox.origin + point(Pixels::ZERO, hitbox.size.height)
2970            };
2971
2972            if !matches!(block.style, BlockStyle::Sticky) {
2973                origin += point(-scroll_pixel_position.x, Pixels::ZERO);
2974            }
2975
2976            let focus_handle =
2977                block
2978                    .element
2979                    .prepaint_as_root(origin, block.available_space, window, cx);
2980
2981            if let Some(focus_handle) = focus_handle {
2982                self.editor.update(cx, |editor, _cx| {
2983                    editor.set_focused_block(FocusedBlock {
2984                        id: block.id,
2985                        focus_handle: focus_handle.downgrade(),
2986                    });
2987                });
2988            }
2989        }
2990    }
2991
2992    #[allow(clippy::too_many_arguments)]
2993    fn layout_sticky_buffer_header(
2994        &self,
2995        StickyHeaderExcerpt {
2996            excerpt,
2997            next_excerpt_controls_present,
2998            next_buffer_row,
2999        }: StickyHeaderExcerpt<'_>,
3000        scroll_position: f32,
3001        line_height: Pixels,
3002        snapshot: &EditorSnapshot,
3003        hitbox: &Hitbox,
3004        selected_buffer_ids: &Vec<BufferId>,
3005        window: &mut Window,
3006        cx: &mut App,
3007    ) -> AnyElement {
3008        let jump_data = header_jump_data(
3009            snapshot,
3010            DisplayRow(scroll_position as u32),
3011            FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3012            excerpt,
3013        );
3014
3015        let editor_bg_color = cx.theme().colors().editor_background;
3016
3017        let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
3018
3019        let mut header = v_flex()
3020            .relative()
3021            .child(
3022                div()
3023                    .w(hitbox.bounds.size.width)
3024                    .h(FILE_HEADER_HEIGHT as f32 * line_height)
3025                    .bg(linear_gradient(
3026                        0.,
3027                        linear_color_stop(editor_bg_color.opacity(0.), 0.),
3028                        linear_color_stop(editor_bg_color, 0.6),
3029                    ))
3030                    .absolute()
3031                    .top_0(),
3032            )
3033            .child(
3034                self.render_buffer_header(excerpt, false, selected, jump_data, window, cx)
3035                    .into_any_element(),
3036            )
3037            .into_any_element();
3038
3039        let mut origin = hitbox.origin;
3040
3041        if let Some(next_buffer_row) = next_buffer_row {
3042            // Push up the sticky header when the excerpt is getting close to the top of the viewport
3043
3044            let mut max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
3045
3046            if next_excerpt_controls_present {
3047                max_row -= MULTI_BUFFER_EXCERPT_HEADER_HEIGHT;
3048            }
3049
3050            let offset = scroll_position - max_row as f32;
3051
3052            if offset > 0.0 {
3053                origin.y -= Pixels(offset) * line_height;
3054            }
3055        }
3056
3057        let size = size(
3058            AvailableSpace::Definite(hitbox.size.width),
3059            AvailableSpace::MinContent,
3060        );
3061
3062        header.prepaint_as_root(origin, size, window, cx);
3063
3064        header
3065    }
3066
3067    #[allow(clippy::too_many_arguments)]
3068    fn layout_cursor_popovers(
3069        &self,
3070        line_height: Pixels,
3071        text_hitbox: &Hitbox,
3072        content_origin: gpui::Point<Pixels>,
3073        start_row: DisplayRow,
3074        scroll_pixel_position: gpui::Point<Pixels>,
3075        line_layouts: &[LineWithInvisibles],
3076        cursor: DisplayPoint,
3077        cursor_point: Point,
3078        style: &EditorStyle,
3079        window: &mut Window,
3080        cx: &mut App,
3081    ) {
3082        let mut min_menu_height = Pixels::ZERO;
3083        let mut max_menu_height = Pixels::ZERO;
3084        let mut height_above_menu = Pixels::ZERO;
3085        let height_below_menu = Pixels::ZERO;
3086        let mut edit_prediction_popover_visible = false;
3087        let mut context_menu_visible = false;
3088
3089        {
3090            let editor = self.editor.read(cx);
3091            if editor
3092                .edit_prediction_visible_in_cursor_popover(editor.has_active_inline_completion())
3093            {
3094                height_above_menu +=
3095                    editor.edit_prediction_cursor_popover_height() + POPOVER_Y_PADDING;
3096                edit_prediction_popover_visible = true;
3097            }
3098
3099            if editor.context_menu_visible() {
3100                if let Some(crate::ContextMenuOrigin::Cursor) = editor.context_menu_origin() {
3101                    min_menu_height += line_height * 3. + POPOVER_Y_PADDING;
3102                    max_menu_height += line_height * 12. + POPOVER_Y_PADDING;
3103                    context_menu_visible = true;
3104                }
3105            }
3106        }
3107
3108        let visible = edit_prediction_popover_visible || context_menu_visible;
3109        if !visible {
3110            return;
3111        }
3112
3113        let cursor_row_layout = &line_layouts[cursor.row().minus(start_row) as usize];
3114        let target_position = content_origin
3115            + gpui::Point {
3116                x: cmp::max(
3117                    px(0.),
3118                    cursor_row_layout.x_for_index(cursor.column() as usize)
3119                        - scroll_pixel_position.x,
3120                ),
3121                y: cmp::max(
3122                    px(0.),
3123                    cursor.row().next_row().as_f32() * line_height - scroll_pixel_position.y,
3124                ),
3125            };
3126
3127        let viewport_bounds =
3128            Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
3129                right: -Self::SCROLLBAR_WIDTH - MENU_GAP,
3130                ..Default::default()
3131            });
3132
3133        let min_height = height_above_menu + min_menu_height + height_below_menu;
3134        let max_height = height_above_menu + max_menu_height + height_below_menu;
3135        let Some((laid_out_popovers, y_flipped)) = self.layout_popovers_above_or_below_line(
3136            target_position,
3137            line_height,
3138            min_height,
3139            max_height,
3140            text_hitbox,
3141            viewport_bounds,
3142            window,
3143            cx,
3144            |height, max_width_for_stable_x, y_flipped, window, cx| {
3145                // First layout the menu to get its size - others can be at least this wide.
3146                let context_menu = if context_menu_visible {
3147                    let menu_height = if y_flipped {
3148                        height - height_below_menu
3149                    } else {
3150                        height - height_above_menu
3151                    };
3152                    let mut element = self
3153                        .render_context_menu(line_height, menu_height, y_flipped, window, cx)
3154                        .expect("Visible context menu should always render.");
3155                    let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3156                    Some((CursorPopoverType::CodeContextMenu, element, size))
3157                } else {
3158                    None
3159                };
3160                let min_width = context_menu
3161                    .as_ref()
3162                    .map_or(px(0.), |(_, _, size)| size.width);
3163                let max_width = max_width_for_stable_x.max(
3164                    context_menu
3165                        .as_ref()
3166                        .map_or(px(0.), |(_, _, size)| size.width),
3167                );
3168
3169                let edit_prediction = if edit_prediction_popover_visible {
3170                    self.editor.update(cx, move |editor, cx| {
3171                        let accept_binding = editor.accept_edit_prediction_keybind(window, cx);
3172                        let mut element = editor.render_edit_prediction_cursor_popover(
3173                            min_width,
3174                            max_width,
3175                            cursor_point,
3176                            style,
3177                            accept_binding.keystroke()?,
3178                            window,
3179                            cx,
3180                        )?;
3181                        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3182                        Some((CursorPopoverType::EditPrediction, element, size))
3183                    })
3184                } else {
3185                    None
3186                };
3187                vec![edit_prediction, context_menu]
3188                    .into_iter()
3189                    .flatten()
3190                    .collect::<Vec<_>>()
3191            },
3192        ) else {
3193            return;
3194        };
3195
3196        let Some((menu_ix, (_, menu_bounds))) = laid_out_popovers
3197            .iter()
3198            .find_position(|(x, _)| matches!(x, CursorPopoverType::CodeContextMenu))
3199        else {
3200            return;
3201        };
3202        let last_ix = laid_out_popovers.len() - 1;
3203        let menu_is_last = menu_ix == last_ix;
3204        let first_popover_bounds = laid_out_popovers[0].1;
3205        let last_popover_bounds = laid_out_popovers[last_ix].1;
3206
3207        // Bounds to layout the aside around. When y_flipped, the aside goes either above or to the
3208        // right, and otherwise it goes below or to the right.
3209        let mut target_bounds = Bounds::from_corners(
3210            first_popover_bounds.origin,
3211            last_popover_bounds.bottom_right(),
3212        );
3213        target_bounds.size.width = menu_bounds.size.width;
3214
3215        // Like `target_bounds`, but with the max height it could occupy. Choosing an aside position
3216        // based on this is preferred for layout stability.
3217        let mut max_target_bounds = target_bounds;
3218        max_target_bounds.size.height = max_height;
3219        if y_flipped {
3220            max_target_bounds.origin.y -= max_height - target_bounds.size.height;
3221        }
3222
3223        // Add spacing around `target_bounds` and `max_target_bounds`.
3224        let mut extend_amount = Edges::all(MENU_GAP);
3225        if y_flipped {
3226            extend_amount.bottom = line_height;
3227        } else {
3228            extend_amount.top = line_height;
3229        }
3230        let target_bounds = target_bounds.extend(extend_amount);
3231        let max_target_bounds = max_target_bounds.extend(extend_amount);
3232
3233        let must_place_above_or_below =
3234            if y_flipped && !menu_is_last && menu_bounds.size.height < max_menu_height {
3235                laid_out_popovers[menu_ix + 1..]
3236                    .iter()
3237                    .any(|(_, popover_bounds)| popover_bounds.size.width > menu_bounds.size.width)
3238            } else {
3239                false
3240            };
3241
3242        self.layout_context_menu_aside(
3243            y_flipped,
3244            *menu_bounds,
3245            target_bounds,
3246            max_target_bounds,
3247            max_menu_height,
3248            must_place_above_or_below,
3249            text_hitbox,
3250            viewport_bounds,
3251            window,
3252            cx,
3253        );
3254    }
3255
3256    #[allow(clippy::too_many_arguments)]
3257    fn layout_gutter_menu(
3258        &self,
3259        line_height: Pixels,
3260        text_hitbox: &Hitbox,
3261        content_origin: gpui::Point<Pixels>,
3262        scroll_pixel_position: gpui::Point<Pixels>,
3263        gutter_overshoot: Pixels,
3264        window: &mut Window,
3265        cx: &mut App,
3266    ) {
3267        let editor = self.editor.read(cx);
3268        if !editor.context_menu_visible() {
3269            return;
3270        }
3271        let Some(crate::ContextMenuOrigin::GutterIndicator(gutter_row)) =
3272            editor.context_menu_origin()
3273        else {
3274            return;
3275        };
3276        // Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the
3277        // indicator than just a plain first column of the text field.
3278        let target_position = content_origin
3279            + gpui::Point {
3280                x: -gutter_overshoot,
3281                y: gutter_row.next_row().as_f32() * line_height - scroll_pixel_position.y,
3282            };
3283        let min_height = line_height * 3. + POPOVER_Y_PADDING;
3284        let max_height = line_height * 12. + POPOVER_Y_PADDING;
3285        let viewport_bounds =
3286            Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
3287                right: -Self::SCROLLBAR_WIDTH - MENU_GAP,
3288                ..Default::default()
3289            });
3290        self.layout_popovers_above_or_below_line(
3291            target_position,
3292            line_height,
3293            min_height,
3294            max_height,
3295            text_hitbox,
3296            viewport_bounds,
3297            window,
3298            cx,
3299            move |height, _max_width_for_stable_x, y_flipped, window, cx| {
3300                let mut element = self
3301                    .render_context_menu(line_height, height, y_flipped, window, cx)
3302                    .expect("Visible context menu should always render.");
3303                let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3304                vec![(CursorPopoverType::CodeContextMenu, element, size)]
3305            },
3306        );
3307    }
3308
3309    #[allow(clippy::too_many_arguments)]
3310    fn layout_popovers_above_or_below_line(
3311        &self,
3312        target_position: gpui::Point<Pixels>,
3313        line_height: Pixels,
3314        min_height: Pixels,
3315        max_height: Pixels,
3316        text_hitbox: &Hitbox,
3317        viewport_bounds: Bounds<Pixels>,
3318        window: &mut Window,
3319        cx: &mut App,
3320        make_sized_popovers: impl FnOnce(
3321            Pixels,
3322            Pixels,
3323            bool,
3324            &mut Window,
3325            &mut App,
3326        ) -> Vec<(CursorPopoverType, AnyElement, Size<Pixels>)>,
3327    ) -> Option<(Vec<(CursorPopoverType, Bounds<Pixels>)>, bool)> {
3328        // If the max height won't fit below and there is more space above, put it above the line.
3329        let bottom_y_when_flipped = target_position.y - line_height;
3330        let available_above = bottom_y_when_flipped - text_hitbox.top();
3331        let available_below = text_hitbox.bottom() - target_position.y;
3332        let y_overflows_below = max_height > available_below;
3333        let mut y_flipped = y_overflows_below && available_above > available_below;
3334        let mut height = cmp::min(
3335            max_height,
3336            if y_flipped {
3337                available_above
3338            } else {
3339                available_below
3340            },
3341        );
3342
3343        // If the min height doesn't fit within text bounds, instead fit within the window.
3344        if height < min_height {
3345            let available_above = bottom_y_when_flipped;
3346            let available_below = viewport_bounds.bottom() - target_position.y;
3347            if available_below > min_height {
3348                y_flipped = false;
3349                height = min_height;
3350            } else if available_above > min_height {
3351                y_flipped = true;
3352                height = min_height;
3353            } else if available_above > available_below {
3354                y_flipped = true;
3355                height = available_above;
3356            } else {
3357                y_flipped = false;
3358                height = available_below;
3359            }
3360        }
3361
3362        let max_width_for_stable_x = viewport_bounds.right() - target_position.x;
3363
3364        // TODO: Use viewport_bounds.width as a max width so that it doesn't get clipped on the left
3365        // for very narrow windows.
3366        let popovers = make_sized_popovers(height, max_width_for_stable_x, y_flipped, window, cx);
3367        if popovers.is_empty() {
3368            return None;
3369        }
3370
3371        let max_width = popovers
3372            .iter()
3373            .map(|(_, _, size)| size.width)
3374            .max()
3375            .unwrap_or_default();
3376
3377        let mut current_position = gpui::Point {
3378            // Snap the right edge of the list to the right edge of the window if its horizontal bounds
3379            // overflow. Include space for the scrollbar.
3380            x: target_position
3381                .x
3382                .min((viewport_bounds.right() - max_width).max(Pixels::ZERO)),
3383            y: if y_flipped {
3384                bottom_y_when_flipped
3385            } else {
3386                target_position.y
3387            },
3388        };
3389
3390        let mut laid_out_popovers = popovers
3391            .into_iter()
3392            .map(|(popover_type, element, size)| {
3393                if y_flipped {
3394                    current_position.y -= size.height;
3395                }
3396                let position = current_position;
3397                window.defer_draw(element, current_position, 1);
3398                if !y_flipped {
3399                    current_position.y += size.height + MENU_GAP;
3400                } else {
3401                    current_position.y -= MENU_GAP;
3402                }
3403                (popover_type, Bounds::new(position, size))
3404            })
3405            .collect::<Vec<_>>();
3406
3407        if y_flipped {
3408            laid_out_popovers.reverse();
3409        }
3410
3411        Some((laid_out_popovers, y_flipped))
3412    }
3413
3414    #[allow(clippy::too_many_arguments)]
3415    fn layout_context_menu_aside(
3416        &self,
3417        y_flipped: bool,
3418        menu_bounds: Bounds<Pixels>,
3419        target_bounds: Bounds<Pixels>,
3420        max_target_bounds: Bounds<Pixels>,
3421        max_height: Pixels,
3422        must_place_above_or_below: bool,
3423        text_hitbox: &Hitbox,
3424        viewport_bounds: Bounds<Pixels>,
3425        window: &mut Window,
3426        cx: &mut App,
3427    ) {
3428        let available_within_viewport = target_bounds.space_within(&viewport_bounds);
3429        let positioned_aside = if available_within_viewport.right >= MENU_ASIDE_MIN_WIDTH
3430            && !must_place_above_or_below
3431        {
3432            let max_width = cmp::min(
3433                available_within_viewport.right - px(1.),
3434                MENU_ASIDE_MAX_WIDTH,
3435            );
3436            let Some(mut aside) =
3437                self.render_context_menu_aside(size(max_width, max_height - POPOVER_Y_PADDING), cx)
3438            else {
3439                return;
3440            };
3441            aside.layout_as_root(AvailableSpace::min_size(), window, cx);
3442            let right_position = point(target_bounds.right(), menu_bounds.origin.y);
3443            Some((aside, right_position))
3444        } else {
3445            let max_size = size(
3446                // TODO(mgsloan): Once the menu is bounded by viewport width the bound on viewport
3447                // won't be needed here.
3448                cmp::min(
3449                    cmp::max(menu_bounds.size.width - px(2.), MENU_ASIDE_MIN_WIDTH),
3450                    viewport_bounds.right(),
3451                ),
3452                cmp::min(
3453                    max_height,
3454                    cmp::max(
3455                        available_within_viewport.top,
3456                        available_within_viewport.bottom,
3457                    ),
3458                ) - POPOVER_Y_PADDING,
3459            );
3460            let Some(mut aside) = self.render_context_menu_aside(max_size, cx) else {
3461                return;
3462            };
3463            let actual_size = aside.layout_as_root(AvailableSpace::min_size(), window, cx);
3464
3465            let top_position = point(
3466                menu_bounds.origin.x,
3467                target_bounds.top() - actual_size.height,
3468            );
3469            let bottom_position = point(menu_bounds.origin.x, target_bounds.bottom());
3470
3471            let fit_within = |available: Edges<Pixels>, wanted: Size<Pixels>| {
3472                // Prefer to fit on the same side of the line as the menu, then on the other side of
3473                // the line.
3474                if !y_flipped && wanted.height < available.bottom {
3475                    Some(bottom_position)
3476                } else if !y_flipped && wanted.height < available.top {
3477                    Some(top_position)
3478                } else if y_flipped && wanted.height < available.top {
3479                    Some(top_position)
3480                } else if y_flipped && wanted.height < available.bottom {
3481                    Some(bottom_position)
3482                } else {
3483                    None
3484                }
3485            };
3486
3487            // Prefer choosing a direction using max sizes rather than actual size for stability.
3488            let available_within_text = max_target_bounds.space_within(&text_hitbox.bounds);
3489            let wanted = size(MENU_ASIDE_MAX_WIDTH, max_height);
3490            let aside_position = fit_within(available_within_text, wanted)
3491                // Fallback: fit max size in window.
3492                .or_else(|| fit_within(max_target_bounds.space_within(&viewport_bounds), wanted))
3493                // Fallback: fit actual size in window.
3494                .or_else(|| fit_within(available_within_viewport, actual_size));
3495
3496            aside_position.map(|position| (aside, position))
3497        };
3498
3499        // Skip drawing if it doesn't fit anywhere.
3500        if let Some((aside, position)) = positioned_aside {
3501            window.defer_draw(aside, position, 1);
3502        }
3503    }
3504
3505    fn render_context_menu(
3506        &self,
3507        line_height: Pixels,
3508        height: Pixels,
3509        y_flipped: bool,
3510        window: &mut Window,
3511        cx: &mut App,
3512    ) -> Option<AnyElement> {
3513        let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
3514        self.editor.update(cx, |editor, cx| {
3515            editor.render_context_menu(&self.style, max_height_in_lines, y_flipped, window, cx)
3516        })
3517    }
3518
3519    fn render_context_menu_aside(
3520        &self,
3521        max_size: Size<Pixels>,
3522
3523        cx: &mut App,
3524    ) -> Option<AnyElement> {
3525        if max_size.width < px(100.) || max_size.height < px(12.) {
3526            None
3527        } else {
3528            self.editor.update(cx, |editor, cx| {
3529                editor.render_context_menu_aside(&self.style, max_size, cx)
3530            })
3531        }
3532    }
3533
3534    #[allow(clippy::too_many_arguments)]
3535    fn layout_inline_completion_popover(
3536        &self,
3537        text_bounds: &Bounds<Pixels>,
3538        editor_snapshot: &EditorSnapshot,
3539        visible_row_range: Range<DisplayRow>,
3540        scroll_top: f32,
3541        scroll_bottom: f32,
3542        line_layouts: &[LineWithInvisibles],
3543        line_height: Pixels,
3544        scroll_pixel_position: gpui::Point<Pixels>,
3545        newest_selection_head: Option<DisplayPoint>,
3546        editor_width: Pixels,
3547        style: &EditorStyle,
3548        window: &mut Window,
3549        cx: &mut App,
3550    ) -> Option<AnyElement> {
3551        const PADDING_X: Pixels = Pixels(24.);
3552        const PADDING_Y: Pixels = Pixels(2.);
3553
3554        let editor = self.editor.read(cx);
3555        let active_inline_completion = editor.active_inline_completion.as_ref()?;
3556
3557        if editor.edit_prediction_visible_in_cursor_popover(true) {
3558            return None;
3559        }
3560
3561        match &active_inline_completion.completion {
3562            InlineCompletion::Move { target, .. } => {
3563                let target_display_point = target.to_display_point(editor_snapshot);
3564                if target_display_point.row().as_f32() < scroll_top {
3565                    let mut element = inline_completion_accept_indicator(
3566                        "Jump to Edit",
3567                        Some(IconName::ArrowUp),
3568                        editor,
3569                        window,
3570                        cx,
3571                    )?;
3572                    let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3573                    let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y);
3574                    element.prepaint_at(text_bounds.origin + offset, window, cx);
3575                    Some(element)
3576                } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
3577                    let mut element = inline_completion_accept_indicator(
3578                        "Jump to Edit",
3579                        Some(IconName::ArrowDown),
3580                        editor,
3581                        window,
3582                        cx,
3583                    )?;
3584                    let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3585                    let offset = point(
3586                        (text_bounds.size.width - size.width) / 2.,
3587                        text_bounds.size.height - size.height - PADDING_Y,
3588                    );
3589                    element.prepaint_at(text_bounds.origin + offset, window, cx);
3590                    Some(element)
3591                } else {
3592                    let mut element = inline_completion_accept_indicator(
3593                        "Jump to Edit",
3594                        None,
3595                        editor,
3596                        window,
3597                        cx,
3598                    )?;
3599
3600                    let target_line_end = DisplayPoint::new(
3601                        target_display_point.row(),
3602                        editor_snapshot.line_len(target_display_point.row()),
3603                    );
3604                    let origin = self.editor.update(cx, |editor, _cx| {
3605                        editor.display_to_pixel_point(target_line_end, editor_snapshot, window)
3606                    })?;
3607                    element.prepaint_as_root(
3608                        text_bounds.origin + origin + point(PADDING_X, px(0.)),
3609                        AvailableSpace::min_size(),
3610                        window,
3611                        cx,
3612                    );
3613                    Some(element)
3614                }
3615            }
3616            InlineCompletion::Edit {
3617                edits,
3618                edit_preview,
3619                display_mode,
3620                snapshot,
3621            } => {
3622                if self.editor.read(cx).has_visible_completions_menu() {
3623                    return None;
3624                }
3625
3626                let edit_start = edits
3627                    .first()
3628                    .unwrap()
3629                    .0
3630                    .start
3631                    .to_display_point(editor_snapshot);
3632                let edit_end = edits
3633                    .last()
3634                    .unwrap()
3635                    .0
3636                    .end
3637                    .to_display_point(editor_snapshot);
3638
3639                let is_visible = visible_row_range.contains(&edit_start.row())
3640                    || visible_row_range.contains(&edit_end.row());
3641                if !is_visible {
3642                    return None;
3643                }
3644
3645                match display_mode {
3646                    EditDisplayMode::TabAccept => {
3647                        let range = &edits.first()?.0;
3648                        let target_display_point = range.end.to_display_point(editor_snapshot);
3649
3650                        let target_line_end = DisplayPoint::new(
3651                            target_display_point.row(),
3652                            editor_snapshot.line_len(target_display_point.row()),
3653                        );
3654                        let (mut element, origin) = self.editor.update(cx, |editor, cx| {
3655                            Some((
3656                                inline_completion_accept_indicator(
3657                                    "Accept", None, editor, window, cx,
3658                                )?,
3659                                editor.display_to_pixel_point(
3660                                    target_line_end,
3661                                    editor_snapshot,
3662                                    window,
3663                                )?,
3664                            ))
3665                        })?;
3666
3667                        element.prepaint_as_root(
3668                            text_bounds.origin + origin + point(PADDING_X, px(0.)),
3669                            AvailableSpace::min_size(),
3670                            window,
3671                            cx,
3672                        );
3673
3674                        return Some(element);
3675                    }
3676                    EditDisplayMode::Inline => return None,
3677                    EditDisplayMode::DiffPopover => {}
3678                }
3679
3680                let highlighted_edits = crate::inline_completion_edit_text(
3681                    &snapshot,
3682                    edits,
3683                    edit_preview.as_ref()?,
3684                    false,
3685                    cx,
3686                );
3687
3688                let line_count = highlighted_edits.text.lines().count();
3689
3690                let longest_row =
3691                    editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
3692                let longest_line_width = if visible_row_range.contains(&longest_row) {
3693                    line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
3694                } else {
3695                    layout_line(
3696                        longest_row,
3697                        editor_snapshot,
3698                        style,
3699                        editor_width,
3700                        |_| false,
3701                        window,
3702                        cx,
3703                    )
3704                    .width
3705                };
3706
3707                let styled_text = highlighted_edits.to_styled_text(&style.text);
3708
3709                let mut element = div()
3710                    .bg(cx.theme().colors().editor_background)
3711                    .border_1()
3712                    .border_color(cx.theme().colors().border)
3713                    .rounded_md()
3714                    .child(styled_text)
3715                    .into_any();
3716
3717                let viewport_bounds = Bounds::new(Default::default(), window.viewport_size())
3718                    .extend(Edges {
3719                        right: -Self::SCROLLBAR_WIDTH,
3720                        ..Default::default()
3721                    });
3722
3723                let x_after_longest =
3724                    text_bounds.origin.x + longest_line_width + PADDING_X - scroll_pixel_position.x;
3725
3726                let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
3727
3728                // Fully visible if it can be displayed within the window (allow overlapping other
3729                // panes). However, this is only allowed if the popover starts within text_bounds.
3730                let is_fully_visible = x_after_longest < text_bounds.right()
3731                    && x_after_longest + element_bounds.width < viewport_bounds.right();
3732
3733                let origin = if is_fully_visible {
3734                    point(
3735                        x_after_longest,
3736                        text_bounds.origin.y + edit_start.row().as_f32() * line_height
3737                            - scroll_pixel_position.y,
3738                    )
3739                } else {
3740                    // Avoid overlapping both the edited rows and the user's cursor.
3741                    let target_above = DisplayRow(
3742                        edit_start
3743                            .row()
3744                            .0
3745                            .min(
3746                                newest_selection_head
3747                                    .map_or(u32::MAX, |cursor_row| cursor_row.row().0),
3748                            )
3749                            .saturating_sub(line_count as u32),
3750                    );
3751                    let mut row_target;
3752                    if visible_row_range.contains(&DisplayRow(target_above.0.saturating_sub(1))) {
3753                        row_target = target_above;
3754                    } else {
3755                        row_target = DisplayRow(
3756                            edit_end.row().0.max(
3757                                newest_selection_head.map_or(0, |cursor_row| cursor_row.row().0),
3758                            ) + 1,
3759                        );
3760                        if !visible_row_range.contains(&row_target) {
3761                            // Not visible, so fallback on displaying immediately below the cursor.
3762                            if let Some(cursor) = newest_selection_head {
3763                                row_target = DisplayRow(cursor.row().0 + 1);
3764                            } else {
3765                                // Not visible and no cursor visible, so fallback on displaying at the top of the editor.
3766                                row_target = DisplayRow(0);
3767                            }
3768                        }
3769                    };
3770
3771                    text_bounds.origin
3772                        + point(
3773                            -scroll_pixel_position.x,
3774                            row_target.as_f32() * line_height - scroll_pixel_position.y,
3775                        )
3776                };
3777
3778                window.defer_draw(element, origin, 1);
3779
3780                // Do not return an element, since it will already be drawn due to defer_draw.
3781                None
3782            }
3783        }
3784    }
3785
3786    fn layout_mouse_context_menu(
3787        &self,
3788        editor_snapshot: &EditorSnapshot,
3789        visible_range: Range<DisplayRow>,
3790        content_origin: gpui::Point<Pixels>,
3791        window: &mut Window,
3792        cx: &mut App,
3793    ) -> Option<AnyElement> {
3794        let position = self.editor.update(cx, |editor, _cx| {
3795            let visible_start_point = editor.display_to_pixel_point(
3796                DisplayPoint::new(visible_range.start, 0),
3797                editor_snapshot,
3798                window,
3799            )?;
3800            let visible_end_point = editor.display_to_pixel_point(
3801                DisplayPoint::new(visible_range.end, 0),
3802                editor_snapshot,
3803                window,
3804            )?;
3805
3806            let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
3807            let (source_display_point, position) = match mouse_context_menu.position {
3808                MenuPosition::PinnedToScreen(point) => (None, point),
3809                MenuPosition::PinnedToEditor { source, offset } => {
3810                    let source_display_point = source.to_display_point(editor_snapshot);
3811                    let source_point = editor.to_pixel_point(source, editor_snapshot, window)?;
3812                    let position = content_origin + source_point + offset;
3813                    (Some(source_display_point), position)
3814                }
3815            };
3816
3817            let source_included = source_display_point.map_or(true, |source_display_point| {
3818                visible_range
3819                    .to_inclusive()
3820                    .contains(&source_display_point.row())
3821            });
3822            let position_included =
3823                visible_start_point.y <= position.y && position.y <= visible_end_point.y;
3824            if !source_included && !position_included {
3825                None
3826            } else {
3827                Some(position)
3828            }
3829        })?;
3830
3831        let mut element = self.editor.update(cx, |editor, _| {
3832            let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
3833            let context_menu = mouse_context_menu.context_menu.clone();
3834
3835            Some(
3836                deferred(
3837                    anchored()
3838                        .position(position)
3839                        .child(context_menu)
3840                        .anchor(Corner::TopLeft)
3841                        .snap_to_window_with_margin(px(8.)),
3842                )
3843                .with_priority(1)
3844                .into_any(),
3845            )
3846        })?;
3847
3848        element.prepaint_as_root(position, AvailableSpace::min_size(), window, cx);
3849        Some(element)
3850    }
3851
3852    #[allow(clippy::too_many_arguments)]
3853    fn layout_hover_popovers(
3854        &self,
3855        snapshot: &EditorSnapshot,
3856        hitbox: &Hitbox,
3857        text_hitbox: &Hitbox,
3858        visible_display_row_range: Range<DisplayRow>,
3859        content_origin: gpui::Point<Pixels>,
3860        scroll_pixel_position: gpui::Point<Pixels>,
3861        line_layouts: &[LineWithInvisibles],
3862        line_height: Pixels,
3863        em_width: Pixels,
3864        window: &mut Window,
3865        cx: &mut App,
3866    ) {
3867        struct MeasuredHoverPopover {
3868            element: AnyElement,
3869            size: Size<Pixels>,
3870            horizontal_offset: Pixels,
3871        }
3872
3873        let max_size = size(
3874            (120. * em_width) // Default size
3875                .min(hitbox.size.width / 2.) // Shrink to half of the editor width
3876                .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
3877            (16. * line_height) // Default size
3878                .min(hitbox.size.height / 2.) // Shrink to half of the editor height
3879                .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
3880        );
3881
3882        let hover_popovers = self.editor.update(cx, |editor, cx| {
3883            editor
3884                .hover_state
3885                .render(snapshot, visible_display_row_range.clone(), max_size, cx)
3886        });
3887        let Some((position, hover_popovers)) = hover_popovers else {
3888            return;
3889        };
3890
3891        // This is safe because we check on layout whether the required row is available
3892        let hovered_row_layout =
3893            &line_layouts[position.row().minus(visible_display_row_range.start) as usize];
3894
3895        // Compute Hovered Point
3896        let x =
3897            hovered_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
3898        let y = position.row().as_f32() * line_height - scroll_pixel_position.y;
3899        let hovered_point = content_origin + point(x, y);
3900
3901        let mut overall_height = Pixels::ZERO;
3902        let mut measured_hover_popovers = Vec::new();
3903        for mut hover_popover in hover_popovers {
3904            let size = hover_popover.layout_as_root(AvailableSpace::min_size(), window, cx);
3905            let horizontal_offset =
3906                (text_hitbox.top_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
3907
3908            overall_height += HOVER_POPOVER_GAP + size.height;
3909
3910            measured_hover_popovers.push(MeasuredHoverPopover {
3911                element: hover_popover,
3912                size,
3913                horizontal_offset,
3914            });
3915        }
3916        overall_height += HOVER_POPOVER_GAP;
3917
3918        fn draw_occluder(
3919            width: Pixels,
3920            origin: gpui::Point<Pixels>,
3921            window: &mut Window,
3922            cx: &mut App,
3923        ) {
3924            let mut occlusion = div()
3925                .size_full()
3926                .occlude()
3927                .on_mouse_move(|_, _, cx| cx.stop_propagation())
3928                .into_any_element();
3929            occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), window, cx);
3930            window.defer_draw(occlusion, origin, 2);
3931        }
3932
3933        if hovered_point.y > overall_height {
3934            // There is enough space above. Render popovers above the hovered point
3935            let mut current_y = hovered_point.y;
3936            for (position, popover) in measured_hover_popovers.into_iter().with_position() {
3937                let size = popover.size;
3938                let popover_origin = point(
3939                    hovered_point.x + popover.horizontal_offset,
3940                    current_y - size.height,
3941                );
3942
3943                window.defer_draw(popover.element, popover_origin, 2);
3944                if position != itertools::Position::Last {
3945                    let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
3946                    draw_occluder(size.width, origin, window, cx);
3947                }
3948
3949                current_y = popover_origin.y - HOVER_POPOVER_GAP;
3950            }
3951        } else {
3952            // There is not enough space above. Render popovers below the hovered point
3953            let mut current_y = hovered_point.y + line_height;
3954            for (position, popover) in measured_hover_popovers.into_iter().with_position() {
3955                let size = popover.size;
3956                let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
3957
3958                window.defer_draw(popover.element, popover_origin, 2);
3959                if position != itertools::Position::Last {
3960                    let origin = point(popover_origin.x, popover_origin.y + size.height);
3961                    draw_occluder(size.width, origin, window, cx);
3962                }
3963
3964                current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP;
3965            }
3966        }
3967    }
3968
3969    #[allow(clippy::too_many_arguments)]
3970    fn layout_diff_hunk_controls(
3971        &self,
3972        row_range: Range<DisplayRow>,
3973        row_infos: &[RowInfo],
3974        text_hitbox: &Hitbox,
3975        position_map: &PositionMap,
3976        newest_cursor_position: Option<DisplayPoint>,
3977        line_height: Pixels,
3978        scroll_pixel_position: gpui::Point<Pixels>,
3979        display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
3980        editor: Entity<Editor>,
3981        window: &mut Window,
3982        cx: &mut App,
3983    ) -> Vec<AnyElement> {
3984        let point_for_position = position_map.point_for_position(window.mouse_position());
3985
3986        let mut controls = vec![];
3987
3988        let active_positions = [
3989            Some(point_for_position.previous_valid),
3990            newest_cursor_position,
3991        ];
3992
3993        for (hunk, _) in display_hunks {
3994            if let DisplayDiffHunk::Unfolded {
3995                display_row_range,
3996                multi_buffer_range,
3997                status,
3998                ..
3999            } = &hunk
4000            {
4001                if display_row_range.start < row_range.start
4002                    || display_row_range.start >= row_range.end
4003                {
4004                    continue;
4005                }
4006                let row_ix = (display_row_range.start - row_range.start).0 as usize;
4007                if row_infos[row_ix].diff_status.is_none() {
4008                    continue;
4009                }
4010                if row_infos[row_ix].diff_status == Some(DiffHunkStatus::Added)
4011                    && *status != DiffHunkStatus::Added
4012                {
4013                    continue;
4014                }
4015                if active_positions
4016                    .iter()
4017                    .any(|p| p.map_or(false, |p| display_row_range.contains(&p.row())))
4018                {
4019                    let y = display_row_range.start.as_f32() * line_height
4020                        + text_hitbox.bounds.top()
4021                        - scroll_pixel_position.y;
4022                    let x = text_hitbox.bounds.right() - px(100.);
4023
4024                    let mut element = diff_hunk_controls(
4025                        display_row_range.start.0,
4026                        multi_buffer_range.clone(),
4027                        line_height,
4028                        &editor,
4029                        cx,
4030                    );
4031                    element.prepaint_as_root(
4032                        gpui::Point::new(x, y),
4033                        size(px(100.0), line_height).into(),
4034                        window,
4035                        cx,
4036                    );
4037                    controls.push(element);
4038                }
4039            }
4040        }
4041
4042        controls
4043    }
4044
4045    #[allow(clippy::too_many_arguments)]
4046    fn layout_signature_help(
4047        &self,
4048        hitbox: &Hitbox,
4049        content_origin: gpui::Point<Pixels>,
4050        scroll_pixel_position: gpui::Point<Pixels>,
4051        newest_selection_head: Option<DisplayPoint>,
4052        start_row: DisplayRow,
4053        line_layouts: &[LineWithInvisibles],
4054        line_height: Pixels,
4055        em_width: Pixels,
4056        window: &mut Window,
4057        cx: &mut App,
4058    ) {
4059        if !self.editor.focus_handle(cx).is_focused(window) {
4060            return;
4061        }
4062        let Some(newest_selection_head) = newest_selection_head else {
4063            return;
4064        };
4065        let selection_row = newest_selection_head.row();
4066        if selection_row < start_row {
4067            return;
4068        }
4069        let Some(cursor_row_layout) = line_layouts.get(selection_row.minus(start_row) as usize)
4070        else {
4071            return;
4072        };
4073
4074        let start_x = cursor_row_layout.x_for_index(newest_selection_head.column() as usize)
4075            - scroll_pixel_position.x
4076            + content_origin.x;
4077        let start_y =
4078            selection_row.as_f32() * line_height + content_origin.y - scroll_pixel_position.y;
4079
4080        let max_size = size(
4081            (120. * em_width) // Default size
4082                .min(hitbox.size.width / 2.) // Shrink to half of the editor width
4083                .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
4084            (16. * line_height) // Default size
4085                .min(hitbox.size.height / 2.) // Shrink to half of the editor height
4086                .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
4087        );
4088
4089        let maybe_element = self.editor.update(cx, |editor, cx| {
4090            if let Some(popover) = editor.signature_help_state.popover_mut() {
4091                let element = popover.render(
4092                    &self.style,
4093                    max_size,
4094                    editor.workspace.as_ref().map(|(w, _)| w.clone()),
4095                    cx,
4096                );
4097                Some(element)
4098            } else {
4099                None
4100            }
4101        });
4102        if let Some(mut element) = maybe_element {
4103            let window_size = window.viewport_size();
4104            let size = element.layout_as_root(Size::<AvailableSpace>::default(), window, cx);
4105            let mut point = point(start_x, start_y - size.height);
4106
4107            // Adjusting to ensure the popover does not overflow in the X-axis direction.
4108            if point.x + size.width >= window_size.width {
4109                point.x = window_size.width - size.width;
4110            }
4111
4112            window.defer_draw(element, point, 1)
4113        }
4114    }
4115
4116    fn paint_background(&self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
4117        window.paint_layer(layout.hitbox.bounds, |window| {
4118            let scroll_top = layout.position_map.snapshot.scroll_position().y;
4119            let gutter_bg = cx.theme().colors().editor_gutter_background;
4120            window.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg));
4121            window.paint_quad(fill(
4122                layout.position_map.text_hitbox.bounds,
4123                self.style.background,
4124            ));
4125
4126            if let EditorMode::Full = layout.mode {
4127                let mut active_rows = layout.active_rows.iter().peekable();
4128                while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
4129                    let mut end_row = start_row.0;
4130                    while active_rows
4131                        .peek()
4132                        .map_or(false, |(active_row, has_selection)| {
4133                            active_row.0 == end_row + 1
4134                                && *has_selection == contains_non_empty_selection
4135                        })
4136                    {
4137                        active_rows.next().unwrap();
4138                        end_row += 1;
4139                    }
4140
4141                    if !contains_non_empty_selection {
4142                        let highlight_h_range =
4143                            match layout.position_map.snapshot.current_line_highlight {
4144                                CurrentLineHighlight::Gutter => Some(Range {
4145                                    start: layout.hitbox.left(),
4146                                    end: layout.gutter_hitbox.right(),
4147                                }),
4148                                CurrentLineHighlight::Line => Some(Range {
4149                                    start: layout.position_map.text_hitbox.bounds.left(),
4150                                    end: layout.position_map.text_hitbox.bounds.right(),
4151                                }),
4152                                CurrentLineHighlight::All => Some(Range {
4153                                    start: layout.hitbox.left(),
4154                                    end: layout.hitbox.right(),
4155                                }),
4156                                CurrentLineHighlight::None => None,
4157                            };
4158                        if let Some(range) = highlight_h_range {
4159                            let active_line_bg = cx.theme().colors().editor_active_line_background;
4160                            let bounds = Bounds {
4161                                origin: point(
4162                                    range.start,
4163                                    layout.hitbox.origin.y
4164                                        + (start_row.as_f32() - scroll_top)
4165                                            * layout.position_map.line_height,
4166                                ),
4167                                size: size(
4168                                    range.end - range.start,
4169                                    layout.position_map.line_height
4170                                        * (end_row - start_row.0 + 1) as f32,
4171                                ),
4172                            };
4173                            window.paint_quad(fill(bounds, active_line_bg));
4174                        }
4175                    }
4176                }
4177
4178                let mut paint_highlight =
4179                    |highlight_row_start: DisplayRow, highlight_row_end: DisplayRow, color| {
4180                        let origin = point(
4181                            layout.hitbox.origin.x,
4182                            layout.hitbox.origin.y
4183                                + (highlight_row_start.as_f32() - scroll_top)
4184                                    * layout.position_map.line_height,
4185                        );
4186                        let size = size(
4187                            layout.hitbox.size.width,
4188                            layout.position_map.line_height
4189                                * highlight_row_end.next_row().minus(highlight_row_start) as f32,
4190                        );
4191                        window.paint_quad(fill(Bounds { origin, size }, color));
4192                    };
4193
4194                let mut current_paint: Option<(Hsla, Range<DisplayRow>)> = None;
4195                for (&new_row, &new_color) in &layout.highlighted_rows {
4196                    match &mut current_paint {
4197                        Some((current_color, current_range)) => {
4198                            let current_color = *current_color;
4199                            let new_range_started = current_color != new_color
4200                                || current_range.end.next_row() != new_row;
4201                            if new_range_started {
4202                                paint_highlight(
4203                                    current_range.start,
4204                                    current_range.end,
4205                                    current_color,
4206                                );
4207                                current_paint = Some((new_color, new_row..new_row));
4208                                continue;
4209                            } else {
4210                                current_range.end = current_range.end.next_row();
4211                            }
4212                        }
4213                        None => current_paint = Some((new_color, new_row..new_row)),
4214                    };
4215                }
4216                if let Some((color, range)) = current_paint {
4217                    paint_highlight(range.start, range.end, color);
4218                }
4219
4220                let scroll_left =
4221                    layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
4222
4223                for (wrap_position, active) in layout.wrap_guides.iter() {
4224                    let x = (layout.position_map.text_hitbox.origin.x
4225                        + *wrap_position
4226                        + layout.position_map.em_width / 2.)
4227                        - scroll_left;
4228
4229                    let show_scrollbars = {
4230                        let (scrollbar_x, scrollbar_y) = &layout.scrollbars_layout.as_xy();
4231
4232                        scrollbar_x.as_ref().map_or(false, |sx| sx.visible)
4233                            || scrollbar_y.as_ref().map_or(false, |sy| sy.visible)
4234                    };
4235
4236                    if x < layout.position_map.text_hitbox.origin.x
4237                        || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds))
4238                    {
4239                        continue;
4240                    }
4241
4242                    let color = if *active {
4243                        cx.theme().colors().editor_active_wrap_guide
4244                    } else {
4245                        cx.theme().colors().editor_wrap_guide
4246                    };
4247                    window.paint_quad(fill(
4248                        Bounds {
4249                            origin: point(x, layout.position_map.text_hitbox.origin.y),
4250                            size: size(px(1.), layout.position_map.text_hitbox.size.height),
4251                        },
4252                        color,
4253                    ));
4254                }
4255            }
4256        })
4257    }
4258
4259    fn paint_indent_guides(
4260        &mut self,
4261        layout: &mut EditorLayout,
4262        window: &mut Window,
4263        cx: &mut App,
4264    ) {
4265        let Some(indent_guides) = &layout.indent_guides else {
4266            return;
4267        };
4268
4269        let faded_color = |color: Hsla, alpha: f32| {
4270            let mut faded = color;
4271            faded.a = alpha;
4272            faded
4273        };
4274
4275        for indent_guide in indent_guides {
4276            let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth);
4277            let settings = indent_guide.settings;
4278
4279            // TODO fixed for now, expose them through themes later
4280            const INDENT_AWARE_ALPHA: f32 = 0.2;
4281            const INDENT_AWARE_ACTIVE_ALPHA: f32 = 0.4;
4282            const INDENT_AWARE_BACKGROUND_ALPHA: f32 = 0.1;
4283            const INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA: f32 = 0.2;
4284
4285            let line_color = match (settings.coloring, indent_guide.active) {
4286                (IndentGuideColoring::Disabled, _) => None,
4287                (IndentGuideColoring::Fixed, false) => {
4288                    Some(cx.theme().colors().editor_indent_guide)
4289                }
4290                (IndentGuideColoring::Fixed, true) => {
4291                    Some(cx.theme().colors().editor_indent_guide_active)
4292                }
4293                (IndentGuideColoring::IndentAware, false) => {
4294                    Some(faded_color(indent_accent_colors, INDENT_AWARE_ALPHA))
4295                }
4296                (IndentGuideColoring::IndentAware, true) => {
4297                    Some(faded_color(indent_accent_colors, INDENT_AWARE_ACTIVE_ALPHA))
4298                }
4299            };
4300
4301            let background_color = match (settings.background_coloring, indent_guide.active) {
4302                (IndentGuideBackgroundColoring::Disabled, _) => None,
4303                (IndentGuideBackgroundColoring::IndentAware, false) => Some(faded_color(
4304                    indent_accent_colors,
4305                    INDENT_AWARE_BACKGROUND_ALPHA,
4306                )),
4307                (IndentGuideBackgroundColoring::IndentAware, true) => Some(faded_color(
4308                    indent_accent_colors,
4309                    INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA,
4310                )),
4311            };
4312
4313            let requested_line_width = if indent_guide.active {
4314                settings.active_line_width
4315            } else {
4316                settings.line_width
4317            }
4318            .clamp(1, 10);
4319            let mut line_indicator_width = 0.;
4320            if let Some(color) = line_color {
4321                window.paint_quad(fill(
4322                    Bounds {
4323                        origin: indent_guide.origin,
4324                        size: size(px(requested_line_width as f32), indent_guide.length),
4325                    },
4326                    color,
4327                ));
4328                line_indicator_width = requested_line_width as f32;
4329            }
4330
4331            if let Some(color) = background_color {
4332                let width = indent_guide.single_indent_width - px(line_indicator_width);
4333                window.paint_quad(fill(
4334                    Bounds {
4335                        origin: point(
4336                            indent_guide.origin.x + px(line_indicator_width),
4337                            indent_guide.origin.y,
4338                        ),
4339                        size: size(width, indent_guide.length),
4340                    },
4341                    color,
4342                ));
4343            }
4344        }
4345    }
4346
4347    fn paint_line_numbers(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4348        let is_singleton = self.editor.read(cx).is_singleton(cx);
4349
4350        let line_height = layout.position_map.line_height;
4351        window.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
4352
4353        for LineNumberLayout {
4354            shaped_line,
4355            hitbox,
4356            display_row,
4357        } in layout.line_numbers.values()
4358        {
4359            let Some(hitbox) = hitbox else {
4360                continue;
4361            };
4362
4363            let is_active = layout.active_rows.contains_key(&display_row);
4364
4365            let color = if is_active {
4366                cx.theme().colors().editor_active_line_number
4367            } else if !is_singleton && hitbox.is_hovered(window) {
4368                cx.theme().colors().editor_hover_line_number
4369            } else {
4370                cx.theme().colors().editor_line_number
4371            };
4372
4373            let Some(line) = self
4374                .shape_line_number(shaped_line.text.clone(), color, window)
4375                .log_err()
4376            else {
4377                continue;
4378            };
4379            let Some(()) = line.paint(hitbox.origin, line_height, window, cx).log_err() else {
4380                continue;
4381            };
4382            // In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor.
4383            // In multi buffers, we open file at the line number clicked, so use a pointing hand cursor.
4384            if is_singleton {
4385                window.set_cursor_style(CursorStyle::IBeam, &hitbox);
4386            } else {
4387                window.set_cursor_style(CursorStyle::PointingHand, &hitbox);
4388            }
4389        }
4390    }
4391
4392    fn paint_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4393        if layout.display_hunks.is_empty() {
4394            return;
4395        }
4396
4397        let line_height = layout.position_map.line_height;
4398        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4399            for (hunk, hitbox) in &layout.display_hunks {
4400                let hunk_to_paint = match hunk {
4401                    DisplayDiffHunk::Folded { .. } => {
4402                        let hunk_bounds = Self::diff_hunk_bounds(
4403                            &layout.position_map.snapshot,
4404                            line_height,
4405                            layout.gutter_hitbox.bounds,
4406                            hunk,
4407                        );
4408                        Some((
4409                            hunk_bounds,
4410                            cx.theme().status().modified,
4411                            Corners::all(px(0.)),
4412                        ))
4413                    }
4414                    DisplayDiffHunk::Unfolded {
4415                        status,
4416                        display_row_range,
4417                        ..
4418                    } => hitbox.as_ref().map(|hunk_hitbox| match status {
4419                        DiffHunkStatus::Added => (
4420                            hunk_hitbox.bounds,
4421                            cx.theme().status().created,
4422                            Corners::all(px(0.)),
4423                        ),
4424                        DiffHunkStatus::Modified => (
4425                            hunk_hitbox.bounds,
4426                            cx.theme().status().modified,
4427                            Corners::all(px(0.)),
4428                        ),
4429                        DiffHunkStatus::Removed if !display_row_range.is_empty() => (
4430                            hunk_hitbox.bounds,
4431                            cx.theme().status().deleted,
4432                            Corners::all(px(0.)),
4433                        ),
4434                        DiffHunkStatus::Removed => (
4435                            Bounds::new(
4436                                point(
4437                                    hunk_hitbox.origin.x - hunk_hitbox.size.width,
4438                                    hunk_hitbox.origin.y,
4439                                ),
4440                                size(hunk_hitbox.size.width * px(2.), hunk_hitbox.size.height),
4441                            ),
4442                            cx.theme().status().deleted,
4443                            Corners::all(1. * line_height),
4444                        ),
4445                    }),
4446                };
4447
4448                if let Some((hunk_bounds, background_color, corner_radii)) = hunk_to_paint {
4449                    window.paint_quad(quad(
4450                        hunk_bounds,
4451                        corner_radii,
4452                        background_color,
4453                        Edges::default(),
4454                        transparent_black(),
4455                    ));
4456                }
4457            }
4458        });
4459    }
4460
4461    fn diff_hunk_bounds(
4462        snapshot: &EditorSnapshot,
4463        line_height: Pixels,
4464        gutter_bounds: Bounds<Pixels>,
4465        hunk: &DisplayDiffHunk,
4466    ) -> Bounds<Pixels> {
4467        let scroll_position = snapshot.scroll_position();
4468        let scroll_top = scroll_position.y * line_height;
4469        let gutter_strip_width = (0.275 * line_height).floor();
4470
4471        match hunk {
4472            DisplayDiffHunk::Folded { display_row, .. } => {
4473                let start_y = display_row.as_f32() * line_height - scroll_top;
4474                let end_y = start_y + line_height;
4475                let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
4476                let highlight_size = size(gutter_strip_width, end_y - start_y);
4477                Bounds::new(highlight_origin, highlight_size)
4478            }
4479            DisplayDiffHunk::Unfolded {
4480                display_row_range,
4481                status,
4482                ..
4483            } => {
4484                if *status == DiffHunkStatus::Removed && display_row_range.is_empty() {
4485                    let row = display_row_range.start;
4486
4487                    let offset = line_height / 2.;
4488                    let start_y = row.as_f32() * line_height - offset - scroll_top;
4489                    let end_y = start_y + line_height;
4490
4491                    let width = (0.35 * line_height).floor();
4492                    let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
4493                    let highlight_size = size(width, end_y - start_y);
4494                    Bounds::new(highlight_origin, highlight_size)
4495                } else {
4496                    let start_row = display_row_range.start;
4497                    let end_row = display_row_range.end;
4498                    // If we're in a multibuffer, row range span might include an
4499                    // excerpt header, so if we were to draw the marker straight away,
4500                    // the hunk might include the rows of that header.
4501                    // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap.
4502                    // Instead, we simply check whether the range we're dealing with includes
4503                    // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header.
4504                    let end_row_in_current_excerpt = snapshot
4505                        .blocks_in_range(start_row..end_row)
4506                        .find_map(|(start_row, block)| {
4507                            if matches!(block, Block::ExcerptBoundary { .. }) {
4508                                Some(start_row)
4509                            } else {
4510                                None
4511                            }
4512                        })
4513                        .unwrap_or(end_row);
4514
4515                    let start_y = start_row.as_f32() * line_height - scroll_top;
4516                    let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
4517
4518                    let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
4519                    let highlight_size = size(gutter_strip_width, end_y - start_y);
4520                    Bounds::new(highlight_origin, highlight_size)
4521                }
4522            }
4523        }
4524    }
4525
4526    fn paint_gutter_indicators(
4527        &self,
4528        layout: &mut EditorLayout,
4529        window: &mut Window,
4530        cx: &mut App,
4531    ) {
4532        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4533            window.with_element_namespace("crease_toggles", |window| {
4534                for crease_toggle in layout.crease_toggles.iter_mut().flatten() {
4535                    crease_toggle.paint(window, cx);
4536                }
4537            });
4538
4539            for test_indicator in layout.test_indicators.iter_mut() {
4540                test_indicator.paint(window, cx);
4541            }
4542
4543            if let Some(indicator) = layout.code_actions_indicator.as_mut() {
4544                indicator.paint(window, cx);
4545            }
4546        });
4547    }
4548
4549    fn paint_gutter_highlights(
4550        &self,
4551        layout: &mut EditorLayout,
4552        window: &mut Window,
4553        cx: &mut App,
4554    ) {
4555        for (_, hunk_hitbox) in &layout.display_hunks {
4556            if let Some(hunk_hitbox) = hunk_hitbox {
4557                window.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
4558            }
4559        }
4560
4561        let show_git_gutter = layout
4562            .position_map
4563            .snapshot
4564            .show_git_diff_gutter
4565            .unwrap_or_else(|| {
4566                matches!(
4567                    ProjectSettings::get_global(cx).git.git_gutter,
4568                    Some(GitGutterSetting::TrackedFiles)
4569                )
4570            });
4571        if show_git_gutter {
4572            Self::paint_diff_hunks(layout, window, cx)
4573        }
4574
4575        let highlight_width = 0.275 * layout.position_map.line_height;
4576        let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
4577        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4578            for (range, color) in &layout.highlighted_gutter_ranges {
4579                let start_row = if range.start.row() < layout.visible_display_row_range.start {
4580                    layout.visible_display_row_range.start - DisplayRow(1)
4581                } else {
4582                    range.start.row()
4583                };
4584                let end_row = if range.end.row() > layout.visible_display_row_range.end {
4585                    layout.visible_display_row_range.end + DisplayRow(1)
4586                } else {
4587                    range.end.row()
4588                };
4589
4590                let start_y = layout.gutter_hitbox.top()
4591                    + start_row.0 as f32 * layout.position_map.line_height
4592                    - layout.position_map.scroll_pixel_position.y;
4593                let end_y = layout.gutter_hitbox.top()
4594                    + (end_row.0 + 1) as f32 * layout.position_map.line_height
4595                    - layout.position_map.scroll_pixel_position.y;
4596                let bounds = Bounds::from_corners(
4597                    point(layout.gutter_hitbox.left(), start_y),
4598                    point(layout.gutter_hitbox.left() + highlight_width, end_y),
4599                );
4600                window.paint_quad(fill(bounds, *color).corner_radii(highlight_corner_radii));
4601            }
4602        });
4603    }
4604
4605    fn paint_blamed_display_rows(
4606        &self,
4607        layout: &mut EditorLayout,
4608        window: &mut Window,
4609        cx: &mut App,
4610    ) {
4611        let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
4612            return;
4613        };
4614
4615        window.paint_layer(layout.gutter_hitbox.bounds, |window| {
4616            for mut blame_element in blamed_display_rows.into_iter() {
4617                blame_element.paint(window, cx);
4618            }
4619        })
4620    }
4621
4622    fn paint_text(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4623        window.with_content_mask(
4624            Some(ContentMask {
4625                bounds: layout.position_map.text_hitbox.bounds,
4626            }),
4627            |window| {
4628                let cursor_style = if self
4629                    .editor
4630                    .read(cx)
4631                    .hovered_link_state
4632                    .as_ref()
4633                    .is_some_and(|hovered_link_state| !hovered_link_state.links.is_empty())
4634                {
4635                    CursorStyle::PointingHand
4636                } else {
4637                    CursorStyle::IBeam
4638                };
4639                window.set_cursor_style(cursor_style, &layout.position_map.text_hitbox);
4640
4641                let invisible_display_ranges = self.paint_highlights(layout, window);
4642                self.paint_lines(&invisible_display_ranges, layout, window, cx);
4643                self.paint_redactions(layout, window);
4644                self.paint_cursors(layout, window, cx);
4645                self.paint_inline_blame(layout, window, cx);
4646                self.paint_diff_hunk_controls(layout, window, cx);
4647                window.with_element_namespace("crease_trailers", |window| {
4648                    for trailer in layout.crease_trailers.iter_mut().flatten() {
4649                        trailer.element.paint(window, cx);
4650                    }
4651                });
4652            },
4653        )
4654    }
4655
4656    fn paint_highlights(
4657        &mut self,
4658        layout: &mut EditorLayout,
4659        window: &mut Window,
4660    ) -> SmallVec<[Range<DisplayPoint>; 32]> {
4661        window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
4662            let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
4663            let line_end_overshoot = 0.15 * layout.position_map.line_height;
4664            for (range, color) in &layout.highlighted_ranges {
4665                self.paint_highlighted_range(
4666                    range.clone(),
4667                    *color,
4668                    Pixels::ZERO,
4669                    line_end_overshoot,
4670                    layout,
4671                    window,
4672                );
4673            }
4674
4675            let corner_radius = 0.15 * layout.position_map.line_height;
4676
4677            for (player_color, selections) in &layout.selections {
4678                for selection in selections.iter() {
4679                    self.paint_highlighted_range(
4680                        selection.range.clone(),
4681                        player_color.selection,
4682                        corner_radius,
4683                        corner_radius * 2.,
4684                        layout,
4685                        window,
4686                    );
4687
4688                    if selection.is_local && !selection.range.is_empty() {
4689                        invisible_display_ranges.push(selection.range.clone());
4690                    }
4691                }
4692            }
4693            invisible_display_ranges
4694        })
4695    }
4696
4697    fn paint_lines(
4698        &mut self,
4699        invisible_display_ranges: &[Range<DisplayPoint>],
4700        layout: &mut EditorLayout,
4701        window: &mut Window,
4702        cx: &mut App,
4703    ) {
4704        let whitespace_setting = self
4705            .editor
4706            .read(cx)
4707            .buffer
4708            .read(cx)
4709            .settings_at(0, cx)
4710            .show_whitespaces;
4711
4712        for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
4713            let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
4714            line_with_invisibles.draw(
4715                layout,
4716                row,
4717                layout.content_origin,
4718                whitespace_setting,
4719                invisible_display_ranges,
4720                window,
4721                cx,
4722            )
4723        }
4724
4725        for line_element in &mut layout.line_elements {
4726            line_element.paint(window, cx);
4727        }
4728    }
4729
4730    fn paint_redactions(&mut self, layout: &EditorLayout, window: &mut Window) {
4731        if layout.redacted_ranges.is_empty() {
4732            return;
4733        }
4734
4735        let line_end_overshoot = layout.line_end_overshoot();
4736
4737        // A softer than perfect black
4738        let redaction_color = gpui::rgb(0x0e1111);
4739
4740        window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
4741            for range in layout.redacted_ranges.iter() {
4742                self.paint_highlighted_range(
4743                    range.clone(),
4744                    redaction_color.into(),
4745                    Pixels::ZERO,
4746                    line_end_overshoot,
4747                    layout,
4748                    window,
4749                );
4750            }
4751        });
4752    }
4753
4754    fn paint_cursors(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4755        for cursor in &mut layout.visible_cursors {
4756            cursor.paint(layout.content_origin, window, cx);
4757        }
4758    }
4759
4760    fn paint_scrollbars(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
4761        let (scrollbar_x, scrollbar_y) = layout.scrollbars_layout.as_xy();
4762
4763        if let Some(scrollbar_layout) = scrollbar_x {
4764            let hitbox = scrollbar_layout.hitbox.clone();
4765            let text_unit_size = scrollbar_layout.text_unit_size;
4766            let visible_range = scrollbar_layout.visible_range.clone();
4767            let thumb_bounds = scrollbar_layout.thumb_bounds();
4768
4769            if scrollbar_layout.visible {
4770                window.paint_layer(hitbox.bounds, |window| {
4771                    window.paint_quad(quad(
4772                        hitbox.bounds,
4773                        Corners::default(),
4774                        cx.theme().colors().scrollbar_track_background,
4775                        Edges {
4776                            top: Pixels::ZERO,
4777                            right: Pixels::ZERO,
4778                            bottom: Pixels::ZERO,
4779                            left: Pixels::ZERO,
4780                        },
4781                        cx.theme().colors().scrollbar_track_border,
4782                    ));
4783
4784                    window.paint_quad(quad(
4785                        thumb_bounds,
4786                        Corners::default(),
4787                        cx.theme().colors().scrollbar_thumb_background,
4788                        Edges {
4789                            top: Pixels::ZERO,
4790                            right: Pixels::ZERO,
4791                            bottom: Pixels::ZERO,
4792                            left: ScrollbarLayout::BORDER_WIDTH,
4793                        },
4794                        cx.theme().colors().scrollbar_thumb_border,
4795                    ));
4796                })
4797            }
4798
4799            window.set_cursor_style(CursorStyle::Arrow, &hitbox);
4800
4801            window.on_mouse_event({
4802                let editor = self.editor.clone();
4803
4804                // there may be a way to avoid this clone
4805                let hitbox = hitbox.clone();
4806
4807                let mut mouse_position = window.mouse_position();
4808                move |event: &MouseMoveEvent, phase, window, cx| {
4809                    if phase == DispatchPhase::Capture {
4810                        return;
4811                    }
4812
4813                    editor.update(cx, |editor, cx| {
4814                        if event.pressed_button == Some(MouseButton::Left)
4815                            && editor
4816                                .scroll_manager
4817                                .is_dragging_scrollbar(Axis::Horizontal)
4818                        {
4819                            let x = mouse_position.x;
4820                            let new_x = event.position.x;
4821                            if (hitbox.left()..hitbox.right()).contains(&x) {
4822                                let mut position = editor.scroll_position(cx);
4823
4824                                position.x += (new_x - x) / text_unit_size;
4825                                if position.x < 0.0 {
4826                                    position.x = 0.0;
4827                                }
4828                                editor.set_scroll_position(position, window, cx);
4829                            }
4830
4831                            cx.stop_propagation();
4832                        } else {
4833                            editor.scroll_manager.set_is_dragging_scrollbar(
4834                                Axis::Horizontal,
4835                                false,
4836                                cx,
4837                            );
4838
4839                            if hitbox.is_hovered(window) {
4840                                editor.scroll_manager.show_scrollbar(window, cx);
4841                            }
4842                        }
4843                        mouse_position = event.position;
4844                    })
4845                }
4846            });
4847
4848            if self
4849                .editor
4850                .read(cx)
4851                .scroll_manager
4852                .is_dragging_scrollbar(Axis::Horizontal)
4853            {
4854                window.on_mouse_event({
4855                    let editor = self.editor.clone();
4856                    move |_: &MouseUpEvent, phase, _, cx| {
4857                        if phase == DispatchPhase::Capture {
4858                            return;
4859                        }
4860
4861                        editor.update(cx, |editor, cx| {
4862                            editor.scroll_manager.set_is_dragging_scrollbar(
4863                                Axis::Horizontal,
4864                                false,
4865                                cx,
4866                            );
4867                            cx.stop_propagation();
4868                        });
4869                    }
4870                });
4871            } else {
4872                window.on_mouse_event({
4873                    let editor = self.editor.clone();
4874
4875                    move |event: &MouseDownEvent, phase, window, cx| {
4876                        if phase == DispatchPhase::Capture || !hitbox.is_hovered(window) {
4877                            return;
4878                        }
4879
4880                        editor.update(cx, |editor, cx| {
4881                            editor.scroll_manager.set_is_dragging_scrollbar(
4882                                Axis::Horizontal,
4883                                true,
4884                                cx,
4885                            );
4886
4887                            let x = event.position.x;
4888
4889                            if x < thumb_bounds.left() || thumb_bounds.right() < x {
4890                                let center_row =
4891                                    ((x - hitbox.left()) / text_unit_size).round() as u32;
4892                                let top_row = center_row.saturating_sub(
4893                                    (visible_range.end - visible_range.start) as u32 / 2,
4894                                );
4895
4896                                let mut position = editor.scroll_position(cx);
4897                                position.x = top_row as f32;
4898
4899                                editor.set_scroll_position(position, window, cx);
4900                            } else {
4901                                editor.scroll_manager.show_scrollbar(window, cx);
4902                            }
4903
4904                            cx.stop_propagation();
4905                        });
4906                    }
4907                });
4908            }
4909        }
4910
4911        if let Some(scrollbar_layout) = scrollbar_y {
4912            let hitbox = scrollbar_layout.hitbox.clone();
4913            let text_unit_size = scrollbar_layout.text_unit_size;
4914            let visible_range = scrollbar_layout.visible_range.clone();
4915            let thumb_bounds = scrollbar_layout.thumb_bounds();
4916
4917            if scrollbar_layout.visible {
4918                window.paint_layer(hitbox.bounds, |window| {
4919                    window.paint_quad(quad(
4920                        hitbox.bounds,
4921                        Corners::default(),
4922                        cx.theme().colors().scrollbar_track_background,
4923                        Edges {
4924                            top: Pixels::ZERO,
4925                            right: Pixels::ZERO,
4926                            bottom: Pixels::ZERO,
4927                            left: ScrollbarLayout::BORDER_WIDTH,
4928                        },
4929                        cx.theme().colors().scrollbar_track_border,
4930                    ));
4931
4932                    let fast_markers =
4933                        self.collect_fast_scrollbar_markers(layout, &scrollbar_layout, cx);
4934                    // Refresh slow scrollbar markers in the background. Below, we paint whatever markers have already been computed.
4935                    self.refresh_slow_scrollbar_markers(layout, &scrollbar_layout, window, cx);
4936
4937                    let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
4938                    for marker in markers.iter().chain(&fast_markers) {
4939                        let mut marker = marker.clone();
4940                        marker.bounds.origin += hitbox.origin;
4941                        window.paint_quad(marker);
4942                    }
4943
4944                    window.paint_quad(quad(
4945                        thumb_bounds,
4946                        Corners::default(),
4947                        cx.theme().colors().scrollbar_thumb_background,
4948                        Edges {
4949                            top: Pixels::ZERO,
4950                            right: Pixels::ZERO,
4951                            bottom: Pixels::ZERO,
4952                            left: ScrollbarLayout::BORDER_WIDTH,
4953                        },
4954                        cx.theme().colors().scrollbar_thumb_border,
4955                    ));
4956                });
4957            }
4958
4959            window.set_cursor_style(CursorStyle::Arrow, &hitbox);
4960
4961            window.on_mouse_event({
4962                let editor = self.editor.clone();
4963
4964                let hitbox = hitbox.clone();
4965
4966                let mut mouse_position = window.mouse_position();
4967                move |event: &MouseMoveEvent, phase, window, cx| {
4968                    if phase == DispatchPhase::Capture {
4969                        return;
4970                    }
4971
4972                    editor.update(cx, |editor, cx| {
4973                        if event.pressed_button == Some(MouseButton::Left)
4974                            && editor.scroll_manager.is_dragging_scrollbar(Axis::Vertical)
4975                        {
4976                            let y = mouse_position.y;
4977                            let new_y = event.position.y;
4978                            if (hitbox.top()..hitbox.bottom()).contains(&y) {
4979                                let mut position = editor.scroll_position(cx);
4980                                position.y += (new_y - y) / text_unit_size;
4981                                if position.y < 0.0 {
4982                                    position.y = 0.0;
4983                                }
4984                                editor.set_scroll_position(position, window, cx);
4985                            }
4986                        } else {
4987                            editor.scroll_manager.set_is_dragging_scrollbar(
4988                                Axis::Vertical,
4989                                false,
4990                                cx,
4991                            );
4992
4993                            if hitbox.is_hovered(window) {
4994                                editor.scroll_manager.show_scrollbar(window, cx);
4995                            }
4996                        }
4997                        mouse_position = event.position;
4998                    })
4999                }
5000            });
5001
5002            if self
5003                .editor
5004                .read(cx)
5005                .scroll_manager
5006                .is_dragging_scrollbar(Axis::Vertical)
5007            {
5008                window.on_mouse_event({
5009                    let editor = self.editor.clone();
5010                    move |_: &MouseUpEvent, phase, _, cx| {
5011                        if phase == DispatchPhase::Capture {
5012                            return;
5013                        }
5014
5015                        editor.update(cx, |editor, cx| {
5016                            editor.scroll_manager.set_is_dragging_scrollbar(
5017                                Axis::Vertical,
5018                                false,
5019                                cx,
5020                            );
5021                            cx.stop_propagation();
5022                        });
5023                    }
5024                });
5025            } else {
5026                window.on_mouse_event({
5027                    let editor = self.editor.clone();
5028
5029                    move |event: &MouseDownEvent, phase, window, cx| {
5030                        if phase == DispatchPhase::Capture || !hitbox.is_hovered(window) {
5031                            return;
5032                        }
5033
5034                        editor.update(cx, |editor, cx| {
5035                            editor.scroll_manager.set_is_dragging_scrollbar(
5036                                Axis::Vertical,
5037                                true,
5038                                cx,
5039                            );
5040
5041                            let y = event.position.y;
5042                            if y < thumb_bounds.top() || thumb_bounds.bottom() < y {
5043                                let center_row =
5044                                    ((y - hitbox.top()) / text_unit_size).round() as u32;
5045                                let top_row = center_row.saturating_sub(
5046                                    (visible_range.end - visible_range.start) as u32 / 2,
5047                                );
5048                                let mut position = editor.scroll_position(cx);
5049                                position.y = top_row as f32;
5050                                editor.set_scroll_position(position, window, cx);
5051                            } else {
5052                                editor.scroll_manager.show_scrollbar(window, cx);
5053                            }
5054
5055                            cx.stop_propagation();
5056                        });
5057                    }
5058                });
5059            }
5060        }
5061    }
5062
5063    fn collect_fast_scrollbar_markers(
5064        &self,
5065        layout: &EditorLayout,
5066        scrollbar_layout: &ScrollbarLayout,
5067        cx: &mut App,
5068    ) -> Vec<PaintQuad> {
5069        const LIMIT: usize = 100;
5070        if !EditorSettings::get_global(cx).scrollbar.cursors || layout.cursors.len() > LIMIT {
5071            return vec![];
5072        }
5073        let cursor_ranges = layout
5074            .cursors
5075            .iter()
5076            .map(|(point, color)| ColoredRange {
5077                start: point.row(),
5078                end: point.row(),
5079                color: *color,
5080            })
5081            .collect_vec();
5082        scrollbar_layout.marker_quads_for_ranges(cursor_ranges, None)
5083    }
5084
5085    fn refresh_slow_scrollbar_markers(
5086        &self,
5087        layout: &EditorLayout,
5088        scrollbar_layout: &ScrollbarLayout,
5089        window: &mut Window,
5090        cx: &mut App,
5091    ) {
5092        self.editor.update(cx, |editor, cx| {
5093            if !editor.is_singleton(cx)
5094                || !editor
5095                    .scrollbar_marker_state
5096                    .should_refresh(scrollbar_layout.hitbox.size)
5097            {
5098                return;
5099            }
5100
5101            let scrollbar_layout = scrollbar_layout.clone();
5102            let background_highlights = editor.background_highlights.clone();
5103            let snapshot = layout.position_map.snapshot.clone();
5104            let theme = cx.theme().clone();
5105            let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
5106
5107            editor.scrollbar_marker_state.dirty = false;
5108            editor.scrollbar_marker_state.pending_refresh =
5109                Some(cx.spawn_in(window, |editor, mut cx| async move {
5110                    let scrollbar_size = scrollbar_layout.hitbox.size;
5111                    let scrollbar_markers = cx
5112                        .background_executor()
5113                        .spawn(async move {
5114                            let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
5115                            let mut marker_quads = Vec::new();
5116                            if scrollbar_settings.git_diff {
5117                                let marker_row_ranges =
5118                                    snapshot.buffer_snapshot.diff_hunks().map(|hunk| {
5119                                        let start_display_row =
5120                                            MultiBufferPoint::new(hunk.row_range.start.0, 0)
5121                                                .to_display_point(&snapshot.display_snapshot)
5122                                                .row();
5123                                        let mut end_display_row =
5124                                            MultiBufferPoint::new(hunk.row_range.end.0, 0)
5125                                                .to_display_point(&snapshot.display_snapshot)
5126                                                .row();
5127                                        if end_display_row != start_display_row {
5128                                            end_display_row.0 -= 1;
5129                                        }
5130                                        let color = match &hunk.status() {
5131                                            DiffHunkStatus::Added => theme.status().created,
5132                                            DiffHunkStatus::Modified => theme.status().modified,
5133                                            DiffHunkStatus::Removed => theme.status().deleted,
5134                                        };
5135                                        ColoredRange {
5136                                            start: start_display_row,
5137                                            end: end_display_row,
5138                                            color,
5139                                        }
5140                                    });
5141
5142                                marker_quads.extend(
5143                                    scrollbar_layout
5144                                        .marker_quads_for_ranges(marker_row_ranges, Some(0)),
5145                                );
5146                            }
5147
5148                            for (background_highlight_id, (_, background_ranges)) in
5149                                background_highlights.iter()
5150                            {
5151                                let is_search_highlights = *background_highlight_id
5152                                    == TypeId::of::<BufferSearchHighlights>();
5153                                let is_symbol_occurrences = *background_highlight_id
5154                                    == TypeId::of::<DocumentHighlightRead>()
5155                                    || *background_highlight_id
5156                                        == TypeId::of::<DocumentHighlightWrite>();
5157                                if (is_search_highlights && scrollbar_settings.search_results)
5158                                    || (is_symbol_occurrences && scrollbar_settings.selected_symbol)
5159                                {
5160                                    let mut color = theme.status().info;
5161                                    if is_symbol_occurrences {
5162                                        color.fade_out(0.5);
5163                                    }
5164                                    let marker_row_ranges = background_ranges.iter().map(|range| {
5165                                        let display_start = range
5166                                            .start
5167                                            .to_display_point(&snapshot.display_snapshot);
5168                                        let display_end =
5169                                            range.end.to_display_point(&snapshot.display_snapshot);
5170                                        ColoredRange {
5171                                            start: display_start.row(),
5172                                            end: display_end.row(),
5173                                            color,
5174                                        }
5175                                    });
5176                                    marker_quads.extend(
5177                                        scrollbar_layout
5178                                            .marker_quads_for_ranges(marker_row_ranges, Some(1)),
5179                                    );
5180                                }
5181                            }
5182
5183                            if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
5184                                let diagnostics = snapshot
5185                                    .buffer_snapshot
5186                                    .diagnostics_in_range::<Point>(Point::zero()..max_point)
5187                                    // Don't show diagnostics the user doesn't care about
5188                                    .filter(|diagnostic| {
5189                                        match (
5190                                            scrollbar_settings.diagnostics,
5191                                            diagnostic.diagnostic.severity,
5192                                        ) {
5193                                            (ScrollbarDiagnostics::All, _) => true,
5194                                            (
5195                                                ScrollbarDiagnostics::Error,
5196                                                DiagnosticSeverity::ERROR,
5197                                            ) => true,
5198                                            (
5199                                                ScrollbarDiagnostics::Warning,
5200                                                DiagnosticSeverity::ERROR
5201                                                | DiagnosticSeverity::WARNING,
5202                                            ) => true,
5203                                            (
5204                                                ScrollbarDiagnostics::Information,
5205                                                DiagnosticSeverity::ERROR
5206                                                | DiagnosticSeverity::WARNING
5207                                                | DiagnosticSeverity::INFORMATION,
5208                                            ) => true,
5209                                            (_, _) => false,
5210                                        }
5211                                    })
5212                                    // We want to sort by severity, in order to paint the most severe diagnostics last.
5213                                    .sorted_by_key(|diagnostic| {
5214                                        std::cmp::Reverse(diagnostic.diagnostic.severity)
5215                                    });
5216
5217                                let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| {
5218                                    let start_display = diagnostic
5219                                        .range
5220                                        .start
5221                                        .to_display_point(&snapshot.display_snapshot);
5222                                    let end_display = diagnostic
5223                                        .range
5224                                        .end
5225                                        .to_display_point(&snapshot.display_snapshot);
5226                                    let color = match diagnostic.diagnostic.severity {
5227                                        DiagnosticSeverity::ERROR => theme.status().error,
5228                                        DiagnosticSeverity::WARNING => theme.status().warning,
5229                                        DiagnosticSeverity::INFORMATION => theme.status().info,
5230                                        _ => theme.status().hint,
5231                                    };
5232                                    ColoredRange {
5233                                        start: start_display.row(),
5234                                        end: end_display.row(),
5235                                        color,
5236                                    }
5237                                });
5238                                marker_quads.extend(
5239                                    scrollbar_layout
5240                                        .marker_quads_for_ranges(marker_row_ranges, Some(2)),
5241                                );
5242                            }
5243
5244                            Arc::from(marker_quads)
5245                        })
5246                        .await;
5247
5248                    editor.update(&mut cx, |editor, cx| {
5249                        editor.scrollbar_marker_state.markers = scrollbar_markers;
5250                        editor.scrollbar_marker_state.scrollbar_size = scrollbar_size;
5251                        editor.scrollbar_marker_state.pending_refresh = None;
5252                        cx.notify();
5253                    })?;
5254
5255                    Ok(())
5256                }));
5257        });
5258    }
5259
5260    #[allow(clippy::too_many_arguments)]
5261    fn paint_highlighted_range(
5262        &self,
5263        range: Range<DisplayPoint>,
5264        color: Hsla,
5265        corner_radius: Pixels,
5266        line_end_overshoot: Pixels,
5267        layout: &EditorLayout,
5268        window: &mut Window,
5269    ) {
5270        let start_row = layout.visible_display_row_range.start;
5271        let end_row = layout.visible_display_row_range.end;
5272        if range.start != range.end {
5273            let row_range = if range.end.column() == 0 {
5274                cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
5275            } else {
5276                cmp::max(range.start.row(), start_row)
5277                    ..cmp::min(range.end.row().next_row(), end_row)
5278            };
5279
5280            let highlighted_range = HighlightedRange {
5281                color,
5282                line_height: layout.position_map.line_height,
5283                corner_radius,
5284                start_y: layout.content_origin.y
5285                    + row_range.start.as_f32() * layout.position_map.line_height
5286                    - layout.position_map.scroll_pixel_position.y,
5287                lines: row_range
5288                    .iter_rows()
5289                    .map(|row| {
5290                        let line_layout =
5291                            &layout.position_map.line_layouts[row.minus(start_row) as usize];
5292                        HighlightedRangeLine {
5293                            start_x: if row == range.start.row() {
5294                                layout.content_origin.x
5295                                    + line_layout.x_for_index(range.start.column() as usize)
5296                                    - layout.position_map.scroll_pixel_position.x
5297                            } else {
5298                                layout.content_origin.x
5299                                    - layout.position_map.scroll_pixel_position.x
5300                            },
5301                            end_x: if row == range.end.row() {
5302                                layout.content_origin.x
5303                                    + line_layout.x_for_index(range.end.column() as usize)
5304                                    - layout.position_map.scroll_pixel_position.x
5305                            } else {
5306                                layout.content_origin.x + line_layout.width + line_end_overshoot
5307                                    - layout.position_map.scroll_pixel_position.x
5308                            },
5309                        }
5310                    })
5311                    .collect(),
5312            };
5313
5314            highlighted_range.paint(layout.position_map.text_hitbox.bounds, window);
5315        }
5316    }
5317
5318    fn paint_inline_blame(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5319        if let Some(mut inline_blame) = layout.inline_blame.take() {
5320            window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
5321                inline_blame.paint(window, cx);
5322            })
5323        }
5324    }
5325
5326    fn paint_diff_hunk_controls(
5327        &mut self,
5328        layout: &mut EditorLayout,
5329        window: &mut Window,
5330        cx: &mut App,
5331    ) {
5332        for mut diff_hunk_control in layout.diff_hunk_controls.drain(..) {
5333            diff_hunk_control.paint(window, cx);
5334        }
5335    }
5336
5337    fn paint_blocks(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
5338        for mut block in layout.blocks.drain(..) {
5339            block.element.paint(window, cx);
5340        }
5341    }
5342
5343    fn paint_inline_completion_popover(
5344        &mut self,
5345        layout: &mut EditorLayout,
5346        window: &mut Window,
5347        cx: &mut App,
5348    ) {
5349        if let Some(inline_completion_popover) = layout.inline_completion_popover.as_mut() {
5350            inline_completion_popover.paint(window, cx);
5351        }
5352    }
5353
5354    fn paint_mouse_context_menu(
5355        &mut self,
5356        layout: &mut EditorLayout,
5357        window: &mut Window,
5358        cx: &mut App,
5359    ) {
5360        if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
5361            mouse_context_menu.paint(window, cx);
5362        }
5363    }
5364
5365    fn paint_scroll_wheel_listener(
5366        &mut self,
5367        layout: &EditorLayout,
5368        window: &mut Window,
5369        cx: &mut App,
5370    ) {
5371        window.on_mouse_event({
5372            let position_map = layout.position_map.clone();
5373            let editor = self.editor.clone();
5374            let hitbox = layout.hitbox.clone();
5375            let mut delta = ScrollDelta::default();
5376
5377            // Set a minimum scroll_sensitivity of 0.01 to make sure the user doesn't
5378            // accidentally turn off their scrolling.
5379            let scroll_sensitivity = EditorSettings::get_global(cx).scroll_sensitivity.max(0.01);
5380
5381            move |event: &ScrollWheelEvent, phase, window, cx| {
5382                if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) {
5383                    delta = delta.coalesce(event.delta);
5384                    editor.update(cx, |editor, cx| {
5385                        let position_map: &PositionMap = &position_map;
5386
5387                        let line_height = position_map.line_height;
5388                        let max_glyph_width = position_map.em_width;
5389                        let (delta, axis) = match delta {
5390                            gpui::ScrollDelta::Pixels(mut pixels) => {
5391                                //Trackpad
5392                                let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
5393                                (pixels, axis)
5394                            }
5395
5396                            gpui::ScrollDelta::Lines(lines) => {
5397                                //Not trackpad
5398                                let pixels =
5399                                    point(lines.x * max_glyph_width, lines.y * line_height);
5400                                (pixels, None)
5401                            }
5402                        };
5403
5404                        let current_scroll_position = position_map.snapshot.scroll_position();
5405                        let x = (current_scroll_position.x * max_glyph_width
5406                            - (delta.x * scroll_sensitivity))
5407                            / max_glyph_width;
5408                        let y = (current_scroll_position.y * line_height
5409                            - (delta.y * scroll_sensitivity))
5410                            / line_height;
5411                        let mut scroll_position =
5412                            point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
5413                        let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
5414                        if forbid_vertical_scroll {
5415                            scroll_position.y = current_scroll_position.y;
5416                        }
5417
5418                        if scroll_position != current_scroll_position {
5419                            editor.scroll(scroll_position, axis, window, cx);
5420                            cx.stop_propagation();
5421                        } else if y < 0. {
5422                            // Due to clamping, we may fail to detect cases of overscroll to the top;
5423                            // We want the scroll manager to get an update in such cases and detect the change of direction
5424                            // on the next frame.
5425                            cx.notify();
5426                        }
5427                    });
5428                }
5429            }
5430        });
5431    }
5432
5433    fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
5434        self.paint_scroll_wheel_listener(layout, window, cx);
5435
5436        window.on_mouse_event({
5437            let position_map = layout.position_map.clone();
5438            let editor = self.editor.clone();
5439            let multi_buffer_range =
5440                layout
5441                    .display_hunks
5442                    .iter()
5443                    .find_map(|(hunk, hunk_hitbox)| match hunk {
5444                        DisplayDiffHunk::Folded { .. } => None,
5445                        DisplayDiffHunk::Unfolded {
5446                            multi_buffer_range, ..
5447                        } => {
5448                            if hunk_hitbox
5449                                .as_ref()
5450                                .map(|hitbox| hitbox.is_hovered(window))
5451                                .unwrap_or(false)
5452                            {
5453                                Some(multi_buffer_range.clone())
5454                            } else {
5455                                None
5456                            }
5457                        }
5458                    });
5459            let line_numbers = layout.line_numbers.clone();
5460
5461            move |event: &MouseDownEvent, phase, window, cx| {
5462                if phase == DispatchPhase::Bubble {
5463                    match event.button {
5464                        MouseButton::Left => editor.update(cx, |editor, cx| {
5465                            let pending_mouse_down = editor
5466                                .pending_mouse_down
5467                                .get_or_insert_with(Default::default)
5468                                .clone();
5469
5470                            *pending_mouse_down.borrow_mut() = Some(event.clone());
5471
5472                            Self::mouse_left_down(
5473                                editor,
5474                                event,
5475                                multi_buffer_range.clone(),
5476                                &position_map,
5477                                line_numbers.as_ref(),
5478                                window,
5479                                cx,
5480                            );
5481                        }),
5482                        MouseButton::Right => editor.update(cx, |editor, cx| {
5483                            Self::mouse_right_down(editor, event, &position_map, window, cx);
5484                        }),
5485                        MouseButton::Middle => editor.update(cx, |editor, cx| {
5486                            Self::mouse_middle_down(editor, event, &position_map, window, cx);
5487                        }),
5488                        _ => {}
5489                    };
5490                }
5491            }
5492        });
5493
5494        window.on_mouse_event({
5495            let editor = self.editor.clone();
5496            let position_map = layout.position_map.clone();
5497
5498            move |event: &MouseUpEvent, phase, window, cx| {
5499                if phase == DispatchPhase::Bubble {
5500                    editor.update(cx, |editor, cx| {
5501                        Self::mouse_up(editor, event, &position_map, window, cx)
5502                    });
5503                }
5504            }
5505        });
5506
5507        window.on_mouse_event({
5508            let editor = self.editor.clone();
5509            let position_map = layout.position_map.clone();
5510            let mut captured_mouse_down = None;
5511
5512            move |event: &MouseUpEvent, phase, window, cx| match phase {
5513                // Clear the pending mouse down during the capture phase,
5514                // so that it happens even if another event handler stops
5515                // propagation.
5516                DispatchPhase::Capture => editor.update(cx, |editor, _cx| {
5517                    let pending_mouse_down = editor
5518                        .pending_mouse_down
5519                        .get_or_insert_with(Default::default)
5520                        .clone();
5521
5522                    let mut pending_mouse_down = pending_mouse_down.borrow_mut();
5523                    if pending_mouse_down.is_some() && position_map.text_hitbox.is_hovered(window) {
5524                        captured_mouse_down = pending_mouse_down.take();
5525                        window.refresh();
5526                    }
5527                }),
5528                // Fire click handlers during the bubble phase.
5529                DispatchPhase::Bubble => editor.update(cx, |editor, cx| {
5530                    if let Some(mouse_down) = captured_mouse_down.take() {
5531                        let event = ClickEvent {
5532                            down: mouse_down,
5533                            up: event.clone(),
5534                        };
5535                        Self::click(editor, &event, &position_map, window, cx);
5536                    }
5537                }),
5538            }
5539        });
5540
5541        window.on_mouse_event({
5542            let position_map = layout.position_map.clone();
5543            let editor = self.editor.clone();
5544
5545            move |event: &MouseMoveEvent, phase, window, cx| {
5546                if phase == DispatchPhase::Bubble {
5547                    editor.update(cx, |editor, cx| {
5548                        if editor.hover_state.focused(window, cx) {
5549                            return;
5550                        }
5551                        if event.pressed_button == Some(MouseButton::Left)
5552                            || event.pressed_button == Some(MouseButton::Middle)
5553                        {
5554                            Self::mouse_dragged(editor, event, &position_map, window, cx)
5555                        }
5556
5557                        Self::mouse_moved(editor, event, &position_map, window, cx)
5558                    });
5559                }
5560            }
5561        });
5562    }
5563
5564    fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
5565        bounds.top_right().x - self.style.scrollbar_width
5566    }
5567
5568    fn column_pixels(&self, column: usize, window: &mut Window, _: &mut App) -> Pixels {
5569        let style = &self.style;
5570        let font_size = style.text.font_size.to_pixels(window.rem_size());
5571        let layout = window
5572            .text_system()
5573            .shape_line(
5574                SharedString::from(" ".repeat(column)),
5575                font_size,
5576                &[TextRun {
5577                    len: column,
5578                    font: style.text.font(),
5579                    color: Hsla::default(),
5580                    background_color: None,
5581                    underline: None,
5582                    strikethrough: None,
5583                }],
5584            )
5585            .unwrap();
5586
5587        layout.width
5588    }
5589
5590    fn max_line_number_width(
5591        &self,
5592        snapshot: &EditorSnapshot,
5593        window: &mut Window,
5594        cx: &mut App,
5595    ) -> Pixels {
5596        let digit_count = (snapshot.widest_line_number() as f32).log10().floor() as usize + 1;
5597        self.column_pixels(digit_count, window, cx)
5598    }
5599
5600    fn shape_line_number(
5601        &self,
5602        text: SharedString,
5603        color: Hsla,
5604        window: &mut Window,
5605    ) -> anyhow::Result<ShapedLine> {
5606        let run = TextRun {
5607            len: text.len(),
5608            font: self.style.text.font(),
5609            color,
5610            background_color: None,
5611            underline: None,
5612            strikethrough: None,
5613        };
5614        window.text_system().shape_line(
5615            text,
5616            self.style.text.font_size.to_pixels(window.rem_size()),
5617            &[run],
5618        )
5619    }
5620}
5621
5622fn header_jump_data(
5623    snapshot: &EditorSnapshot,
5624    block_row_start: DisplayRow,
5625    height: u32,
5626    for_excerpt: &ExcerptInfo,
5627) -> JumpData {
5628    let range = &for_excerpt.range;
5629    let buffer = &for_excerpt.buffer;
5630    let jump_anchor = range
5631        .primary
5632        .as_ref()
5633        .map_or(range.context.start, |primary| primary.start);
5634
5635    let excerpt_start = range.context.start;
5636    let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
5637    let rows_from_excerpt_start = if jump_anchor == excerpt_start {
5638        0
5639    } else {
5640        let excerpt_start_point = language::ToPoint::to_point(&excerpt_start, buffer);
5641        jump_position.row.saturating_sub(excerpt_start_point.row)
5642    };
5643
5644    let line_offset_from_top = (block_row_start.0 + height + rows_from_excerpt_start)
5645        .saturating_sub(
5646            snapshot
5647                .scroll_anchor
5648                .scroll_position(&snapshot.display_snapshot)
5649                .y as u32,
5650        );
5651
5652    JumpData::MultiBufferPoint {
5653        excerpt_id: for_excerpt.id,
5654        anchor: jump_anchor,
5655        position: jump_position,
5656        line_offset_from_top,
5657    }
5658}
5659
5660fn inline_completion_accept_indicator(
5661    label: impl Into<SharedString>,
5662    icon: Option<IconName>,
5663    editor: &Editor,
5664    window: &mut Window,
5665    cx: &App,
5666) -> Option<AnyElement> {
5667    let accept_binding = editor.accept_edit_prediction_keybind(window, cx);
5668    let accept_keystroke = accept_binding.keystroke()?;
5669
5670    let accept_key = h_flex()
5671        .px_0p5()
5672        .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
5673        .text_size(TextSize::XSmall.rems(cx))
5674        .text_color(cx.theme().colors().text)
5675        .gap_1()
5676        .when(!editor.previewing_inline_completion, |parent| {
5677            parent.children(ui::render_modifiers(
5678                &accept_keystroke.modifiers,
5679                PlatformStyle::platform(),
5680                Some(Color::Default),
5681                None,
5682                false,
5683            ))
5684        })
5685        .child(accept_keystroke.key.clone());
5686
5687    let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
5688    let accent_color = cx.theme().colors().text_accent;
5689    let editor_bg_color = cx.theme().colors().editor_background;
5690    let bg_color = editor_bg_color.blend(accent_color.opacity(0.2));
5691
5692    Some(
5693        h_flex()
5694            .py_0p5()
5695            .pl_1()
5696            .pr(padding_right)
5697            .gap_1()
5698            .bg(bg_color)
5699            .border_1()
5700            .border_color(cx.theme().colors().text_accent.opacity(0.8))
5701            .rounded_md()
5702            .shadow_sm()
5703            .child(accept_key)
5704            .child(Label::new(label).size(LabelSize::Small))
5705            .when_some(icon, |element, icon| {
5706                element.child(
5707                    div()
5708                        .mt(px(1.5))
5709                        .child(Icon::new(icon).size(IconSize::Small)),
5710                )
5711            })
5712            .into_any(),
5713    )
5714}
5715
5716pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
5717
5718impl AcceptEditPredictionBinding {
5719    pub fn keystroke(&self) -> Option<&Keystroke> {
5720        if let Some(binding) = self.0.as_ref() {
5721            match &binding.keystrokes() {
5722                [keystroke] => Some(keystroke),
5723                _ => None,
5724            }
5725        } else {
5726            None
5727        }
5728    }
5729}
5730
5731struct AcceptEditPredictionsBindingValidator;
5732
5733inventory::submit! { KeyBindingValidatorRegistration(|| Box::new(AcceptEditPredictionsBindingValidator)) }
5734
5735impl KeyBindingValidator for AcceptEditPredictionsBindingValidator {
5736    fn action_type_id(&self) -> TypeId {
5737        TypeId::of::<AcceptEditPrediction>()
5738    }
5739
5740    fn validate(&self, binding: &gpui::KeyBinding) -> Result<(), MarkdownString> {
5741        use KeyBindingContextPredicate::*;
5742
5743        if binding.keystrokes().len() == 1 && binding.keystrokes()[0].modifiers.modified() {
5744            return Ok(());
5745        }
5746        let required_predicate =
5747            Not(Identifier(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT.into()).into());
5748        match binding.predicate() {
5749            Some(predicate) if required_predicate.is_superset(&predicate) => {
5750                return Ok(());
5751            }
5752            _ => {}
5753        }
5754        Err(MarkdownString(format!(
5755            "{} can only be bound to a single keystroke with modifiers, so \
5756            that holding down these modifiers can be used to preview \
5757            completions inline when the completions menu is open.\n\n\
5758            This restriction does not apply when the context requires {}, \
5759            since these bindings will not be used when the completions menu \
5760            is open.",
5761            MarkdownString::inline_code(AcceptEditPrediction.name()),
5762            MarkdownString::inline_code(&format!(
5763                "!{}",
5764                EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT
5765            )),
5766        )))
5767    }
5768}
5769
5770#[allow(clippy::too_many_arguments)]
5771fn prepaint_gutter_button(
5772    button: IconButton,
5773    row: DisplayRow,
5774    line_height: Pixels,
5775    gutter_dimensions: &GutterDimensions,
5776    scroll_pixel_position: gpui::Point<Pixels>,
5777    gutter_hitbox: &Hitbox,
5778    rows_with_hunk_bounds: &HashMap<DisplayRow, Bounds<Pixels>>,
5779    window: &mut Window,
5780    cx: &mut App,
5781) -> AnyElement {
5782    let mut button = button.into_any_element();
5783    let available_space = size(
5784        AvailableSpace::MinContent,
5785        AvailableSpace::Definite(line_height),
5786    );
5787    let indicator_size = button.layout_as_root(available_space, window, cx);
5788
5789    let blame_width = gutter_dimensions.git_blame_entries_width;
5790    let gutter_width = rows_with_hunk_bounds
5791        .get(&row)
5792        .map(|bounds| bounds.size.width);
5793    let left_offset = blame_width.max(gutter_width).unwrap_or_default();
5794
5795    let mut x = left_offset;
5796    let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
5797        - indicator_size.width
5798        - left_offset;
5799    x += available_width / 2.;
5800
5801    let mut y = row.as_f32() * line_height - scroll_pixel_position.y;
5802    y += (line_height - indicator_size.height) / 2.;
5803
5804    button.prepaint_as_root(
5805        gutter_hitbox.origin + point(x, y),
5806        available_space,
5807        window,
5808        cx,
5809    );
5810    button
5811}
5812
5813fn render_inline_blame_entry(
5814    blame: &gpui::Entity<GitBlame>,
5815    blame_entry: BlameEntry,
5816    style: &EditorStyle,
5817    workspace: Option<WeakEntity<Workspace>>,
5818    cx: &mut App,
5819) -> AnyElement {
5820    let relative_timestamp = blame_entry_relative_timestamp(&blame_entry);
5821
5822    let author = blame_entry.author.as_deref().unwrap_or_default();
5823    let summary_enabled = ProjectSettings::get_global(cx)
5824        .git
5825        .show_inline_commit_summary();
5826
5827    let text = match blame_entry.summary.as_ref() {
5828        Some(summary) if summary_enabled => {
5829            format!("{}, {} - {}", author, relative_timestamp, summary)
5830        }
5831        _ => format!("{}, {}", author, relative_timestamp),
5832    };
5833
5834    let details = blame.read(cx).details_for_entry(&blame_entry);
5835
5836    let tooltip = cx.new(|_| BlameEntryTooltip::new(blame_entry, details, style, workspace));
5837
5838    h_flex()
5839        .id("inline-blame")
5840        .w_full()
5841        .font_family(style.text.font().family)
5842        .text_color(cx.theme().status().hint)
5843        .line_height(style.text.line_height)
5844        .child(Icon::new(IconName::FileGit).color(Color::Hint))
5845        .child(text)
5846        .gap_2()
5847        .hoverable_tooltip(move |_, _| tooltip.clone().into())
5848        .into_any()
5849}
5850
5851fn render_blame_entry(
5852    ix: usize,
5853    blame: &gpui::Entity<GitBlame>,
5854    blame_entry: BlameEntry,
5855    style: &EditorStyle,
5856    last_used_color: &mut Option<(PlayerColor, Oid)>,
5857    editor: Entity<Editor>,
5858    cx: &mut App,
5859) -> AnyElement {
5860    let mut sha_color = cx
5861        .theme()
5862        .players()
5863        .color_for_participant(blame_entry.sha.into());
5864    // If the last color we used is the same as the one we get for this line, but
5865    // the commit SHAs are different, then we try again to get a different color.
5866    match *last_used_color {
5867        Some((color, sha)) if sha != blame_entry.sha && color.cursor == sha_color.cursor => {
5868            let index: u32 = blame_entry.sha.into();
5869            sha_color = cx.theme().players().color_for_participant(index + 1);
5870        }
5871        _ => {}
5872    };
5873    last_used_color.replace((sha_color, blame_entry.sha));
5874
5875    let relative_timestamp = blame_entry_relative_timestamp(&blame_entry);
5876
5877    let short_commit_id = blame_entry.sha.display_short();
5878
5879    let author_name = blame_entry.author.as_deref().unwrap_or("<no name>");
5880    let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED);
5881
5882    let details = blame.read(cx).details_for_entry(&blame_entry);
5883
5884    let workspace = editor.read(cx).workspace.as_ref().map(|(w, _)| w.clone());
5885
5886    let tooltip =
5887        cx.new(|_| BlameEntryTooltip::new(blame_entry.clone(), details.clone(), style, workspace));
5888
5889    h_flex()
5890        .w_full()
5891        .justify_between()
5892        .font_family(style.text.font().family)
5893        .line_height(style.text.line_height)
5894        .id(("blame", ix))
5895        .text_color(cx.theme().status().hint)
5896        .pr_2()
5897        .gap_2()
5898        .child(
5899            h_flex()
5900                .items_center()
5901                .gap_2()
5902                .child(div().text_color(sha_color.cursor).child(short_commit_id))
5903                .child(name),
5904        )
5905        .child(relative_timestamp)
5906        .on_mouse_down(MouseButton::Right, {
5907            let blame_entry = blame_entry.clone();
5908            let details = details.clone();
5909            move |event, window, cx| {
5910                deploy_blame_entry_context_menu(
5911                    &blame_entry,
5912                    details.as_ref(),
5913                    editor.clone(),
5914                    event.position,
5915                    window,
5916                    cx,
5917                );
5918            }
5919        })
5920        .hover(|style| style.bg(cx.theme().colors().element_hover))
5921        .when_some(
5922            details.and_then(|details| details.permalink),
5923            |this, url| {
5924                let url = url.clone();
5925                this.cursor_pointer().on_click(move |_, _, cx| {
5926                    cx.stop_propagation();
5927                    cx.open_url(url.as_str())
5928                })
5929            },
5930        )
5931        .hoverable_tooltip(move |_, _| tooltip.clone().into())
5932        .into_any()
5933}
5934
5935fn deploy_blame_entry_context_menu(
5936    blame_entry: &BlameEntry,
5937    details: Option<&CommitDetails>,
5938    editor: Entity<Editor>,
5939    position: gpui::Point<Pixels>,
5940    window: &mut Window,
5941    cx: &mut App,
5942) {
5943    let context_menu = ContextMenu::build(window, cx, move |menu, _, _| {
5944        let sha = format!("{}", blame_entry.sha);
5945        menu.on_blur_subscription(Subscription::new(|| {}))
5946            .entry("Copy commit SHA", None, move |_, cx| {
5947                cx.write_to_clipboard(ClipboardItem::new_string(sha.clone()));
5948            })
5949            .when_some(
5950                details.and_then(|details| details.permalink.clone()),
5951                |this, url| {
5952                    this.entry("Open permalink", None, move |_, cx| {
5953                        cx.open_url(url.as_str())
5954                    })
5955                },
5956            )
5957    });
5958
5959    editor.update(cx, move |editor, cx| {
5960        editor.mouse_context_menu = Some(MouseContextMenu::new(
5961            MenuPosition::PinnedToScreen(position),
5962            context_menu,
5963            window,
5964            cx,
5965        ));
5966        cx.notify();
5967    });
5968}
5969
5970#[derive(Debug)]
5971pub(crate) struct LineWithInvisibles {
5972    fragments: SmallVec<[LineFragment; 1]>,
5973    invisibles: Vec<Invisible>,
5974    len: usize,
5975    width: Pixels,
5976    font_size: Pixels,
5977}
5978
5979#[allow(clippy::large_enum_variant)]
5980enum LineFragment {
5981    Text(ShapedLine),
5982    Element {
5983        element: Option<AnyElement>,
5984        size: Size<Pixels>,
5985        len: usize,
5986    },
5987}
5988
5989impl fmt::Debug for LineFragment {
5990    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5991        match self {
5992            LineFragment::Text(shaped_line) => f.debug_tuple("Text").field(shaped_line).finish(),
5993            LineFragment::Element { size, len, .. } => f
5994                .debug_struct("Element")
5995                .field("size", size)
5996                .field("len", len)
5997                .finish(),
5998        }
5999    }
6000}
6001
6002impl LineWithInvisibles {
6003    #[allow(clippy::too_many_arguments)]
6004    fn from_chunks<'a>(
6005        chunks: impl Iterator<Item = HighlightedChunk<'a>>,
6006        editor_style: &EditorStyle,
6007        max_line_len: usize,
6008        max_line_count: usize,
6009        editor_mode: EditorMode,
6010        text_width: Pixels,
6011        is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
6012        window: &mut Window,
6013        cx: &mut App,
6014    ) -> Vec<Self> {
6015        let text_style = &editor_style.text;
6016        let mut layouts = Vec::with_capacity(max_line_count);
6017        let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
6018        let mut line = String::new();
6019        let mut invisibles = Vec::new();
6020        let mut width = Pixels::ZERO;
6021        let mut len = 0;
6022        let mut styles = Vec::new();
6023        let mut non_whitespace_added = false;
6024        let mut row = 0;
6025        let mut line_exceeded_max_len = false;
6026        let font_size = text_style.font_size.to_pixels(window.rem_size());
6027
6028        let ellipsis = SharedString::from("");
6029
6030        for highlighted_chunk in chunks.chain([HighlightedChunk {
6031            text: "\n",
6032            style: None,
6033            is_tab: false,
6034            replacement: None,
6035        }]) {
6036            if let Some(replacement) = highlighted_chunk.replacement {
6037                if !line.is_empty() {
6038                    let shaped_line = window
6039                        .text_system()
6040                        .shape_line(line.clone().into(), font_size, &styles)
6041                        .unwrap();
6042                    width += shaped_line.width;
6043                    len += shaped_line.len;
6044                    fragments.push(LineFragment::Text(shaped_line));
6045                    line.clear();
6046                    styles.clear();
6047                }
6048
6049                match replacement {
6050                    ChunkReplacement::Renderer(renderer) => {
6051                        let available_width = if renderer.constrain_width {
6052                            let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
6053                                ellipsis.clone()
6054                            } else {
6055                                SharedString::from(Arc::from(highlighted_chunk.text))
6056                            };
6057                            let shaped_line = window
6058                                .text_system()
6059                                .shape_line(
6060                                    chunk,
6061                                    font_size,
6062                                    &[text_style.to_run(highlighted_chunk.text.len())],
6063                                )
6064                                .unwrap();
6065                            AvailableSpace::Definite(shaped_line.width)
6066                        } else {
6067                            AvailableSpace::MinContent
6068                        };
6069
6070                        let mut element = (renderer.render)(&mut ChunkRendererContext {
6071                            context: cx,
6072                            window,
6073                            max_width: text_width,
6074                        });
6075                        let line_height = text_style.line_height_in_pixels(window.rem_size());
6076                        let size = element.layout_as_root(
6077                            size(available_width, AvailableSpace::Definite(line_height)),
6078                            window,
6079                            cx,
6080                        );
6081
6082                        width += size.width;
6083                        len += highlighted_chunk.text.len();
6084                        fragments.push(LineFragment::Element {
6085                            element: Some(element),
6086                            size,
6087                            len: highlighted_chunk.text.len(),
6088                        });
6089                    }
6090                    ChunkReplacement::Str(x) => {
6091                        let text_style = if let Some(style) = highlighted_chunk.style {
6092                            Cow::Owned(text_style.clone().highlight(style))
6093                        } else {
6094                            Cow::Borrowed(text_style)
6095                        };
6096
6097                        let run = TextRun {
6098                            len: x.len(),
6099                            font: text_style.font(),
6100                            color: text_style.color,
6101                            background_color: text_style.background_color,
6102                            underline: text_style.underline,
6103                            strikethrough: text_style.strikethrough,
6104                        };
6105                        let line_layout = window
6106                            .text_system()
6107                            .shape_line(x, font_size, &[run])
6108                            .unwrap()
6109                            .with_len(highlighted_chunk.text.len());
6110
6111                        width += line_layout.width;
6112                        len += highlighted_chunk.text.len();
6113                        fragments.push(LineFragment::Text(line_layout))
6114                    }
6115                }
6116            } else {
6117                for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
6118                    if ix > 0 {
6119                        let shaped_line = window
6120                            .text_system()
6121                            .shape_line(line.clone().into(), font_size, &styles)
6122                            .unwrap();
6123                        width += shaped_line.width;
6124                        len += shaped_line.len;
6125                        fragments.push(LineFragment::Text(shaped_line));
6126                        layouts.push(Self {
6127                            width: mem::take(&mut width),
6128                            len: mem::take(&mut len),
6129                            fragments: mem::take(&mut fragments),
6130                            invisibles: std::mem::take(&mut invisibles),
6131                            font_size,
6132                        });
6133
6134                        line.clear();
6135                        styles.clear();
6136                        row += 1;
6137                        line_exceeded_max_len = false;
6138                        non_whitespace_added = false;
6139                        if row == max_line_count {
6140                            return layouts;
6141                        }
6142                    }
6143
6144                    if !line_chunk.is_empty() && !line_exceeded_max_len {
6145                        let text_style = if let Some(style) = highlighted_chunk.style {
6146                            Cow::Owned(text_style.clone().highlight(style))
6147                        } else {
6148                            Cow::Borrowed(text_style)
6149                        };
6150
6151                        if line.len() + line_chunk.len() > max_line_len {
6152                            let mut chunk_len = max_line_len - line.len();
6153                            while !line_chunk.is_char_boundary(chunk_len) {
6154                                chunk_len -= 1;
6155                            }
6156                            line_chunk = &line_chunk[..chunk_len];
6157                            line_exceeded_max_len = true;
6158                        }
6159
6160                        styles.push(TextRun {
6161                            len: line_chunk.len(),
6162                            font: text_style.font(),
6163                            color: text_style.color,
6164                            background_color: text_style.background_color,
6165                            underline: text_style.underline,
6166                            strikethrough: text_style.strikethrough,
6167                        });
6168
6169                        if editor_mode == EditorMode::Full {
6170                            // Line wrap pads its contents with fake whitespaces,
6171                            // avoid printing them
6172                            let is_soft_wrapped = is_row_soft_wrapped(row);
6173                            if highlighted_chunk.is_tab {
6174                                if non_whitespace_added || !is_soft_wrapped {
6175                                    invisibles.push(Invisible::Tab {
6176                                        line_start_offset: line.len(),
6177                                        line_end_offset: line.len() + line_chunk.len(),
6178                                    });
6179                                }
6180                            } else {
6181                                invisibles.extend(line_chunk.char_indices().filter_map(
6182                                    |(index, c)| {
6183                                        let is_whitespace = c.is_whitespace();
6184                                        non_whitespace_added |= !is_whitespace;
6185                                        if is_whitespace
6186                                            && (non_whitespace_added || !is_soft_wrapped)
6187                                        {
6188                                            Some(Invisible::Whitespace {
6189                                                line_offset: line.len() + index,
6190                                            })
6191                                        } else {
6192                                            None
6193                                        }
6194                                    },
6195                                ))
6196                            }
6197                        }
6198
6199                        line.push_str(line_chunk);
6200                    }
6201                }
6202            }
6203        }
6204
6205        layouts
6206    }
6207
6208    #[allow(clippy::too_many_arguments)]
6209    fn prepaint(
6210        &mut self,
6211        line_height: Pixels,
6212        scroll_pixel_position: gpui::Point<Pixels>,
6213        row: DisplayRow,
6214        content_origin: gpui::Point<Pixels>,
6215        line_elements: &mut SmallVec<[AnyElement; 1]>,
6216        window: &mut Window,
6217        cx: &mut App,
6218    ) {
6219        let line_y = line_height * (row.as_f32() - scroll_pixel_position.y / line_height);
6220        let mut fragment_origin = content_origin + gpui::point(-scroll_pixel_position.x, line_y);
6221        for fragment in &mut self.fragments {
6222            match fragment {
6223                LineFragment::Text(line) => {
6224                    fragment_origin.x += line.width;
6225                }
6226                LineFragment::Element { element, size, .. } => {
6227                    let mut element = element
6228                        .take()
6229                        .expect("you can't prepaint LineWithInvisibles twice");
6230
6231                    // Center the element vertically within the line.
6232                    let mut element_origin = fragment_origin;
6233                    element_origin.y += (line_height - size.height) / 2.;
6234                    element.prepaint_at(element_origin, window, cx);
6235                    line_elements.push(element);
6236
6237                    fragment_origin.x += size.width;
6238                }
6239            }
6240        }
6241    }
6242
6243    #[allow(clippy::too_many_arguments)]
6244    fn draw(
6245        &self,
6246        layout: &EditorLayout,
6247        row: DisplayRow,
6248        content_origin: gpui::Point<Pixels>,
6249        whitespace_setting: ShowWhitespaceSetting,
6250        selection_ranges: &[Range<DisplayPoint>],
6251        window: &mut Window,
6252        cx: &mut App,
6253    ) {
6254        let line_height = layout.position_map.line_height;
6255        let line_y = line_height
6256            * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
6257
6258        let mut fragment_origin =
6259            content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
6260
6261        for fragment in &self.fragments {
6262            match fragment {
6263                LineFragment::Text(line) => {
6264                    line.paint(fragment_origin, line_height, window, cx)
6265                        .log_err();
6266                    fragment_origin.x += line.width;
6267                }
6268                LineFragment::Element { size, .. } => {
6269                    fragment_origin.x += size.width;
6270                }
6271            }
6272        }
6273
6274        self.draw_invisibles(
6275            selection_ranges,
6276            layout,
6277            content_origin,
6278            line_y,
6279            row,
6280            line_height,
6281            whitespace_setting,
6282            window,
6283            cx,
6284        );
6285    }
6286
6287    #[allow(clippy::too_many_arguments)]
6288    fn draw_invisibles(
6289        &self,
6290        selection_ranges: &[Range<DisplayPoint>],
6291        layout: &EditorLayout,
6292        content_origin: gpui::Point<Pixels>,
6293        line_y: Pixels,
6294        row: DisplayRow,
6295        line_height: Pixels,
6296        whitespace_setting: ShowWhitespaceSetting,
6297        window: &mut Window,
6298        cx: &mut App,
6299    ) {
6300        let extract_whitespace_info = |invisible: &Invisible| {
6301            let (token_offset, token_end_offset, invisible_symbol) = match invisible {
6302                Invisible::Tab {
6303                    line_start_offset,
6304                    line_end_offset,
6305                } => (*line_start_offset, *line_end_offset, &layout.tab_invisible),
6306                Invisible::Whitespace { line_offset } => {
6307                    (*line_offset, line_offset + 1, &layout.space_invisible)
6308                }
6309            };
6310
6311            let x_offset = self.x_for_index(token_offset);
6312            let invisible_offset =
6313                (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
6314            let origin = content_origin
6315                + gpui::point(
6316                    x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
6317                    line_y,
6318                );
6319
6320            (
6321                [token_offset, token_end_offset],
6322                Box::new(move |window: &mut Window, cx: &mut App| {
6323                    invisible_symbol
6324                        .paint(origin, line_height, window, cx)
6325                        .log_err();
6326                }),
6327            )
6328        };
6329
6330        let invisible_iter = self.invisibles.iter().map(extract_whitespace_info);
6331        match whitespace_setting {
6332            ShowWhitespaceSetting::None => (),
6333            ShowWhitespaceSetting::All => invisible_iter.for_each(|(_, paint)| paint(window, cx)),
6334            ShowWhitespaceSetting::Selection => invisible_iter.for_each(|([start, _], paint)| {
6335                let invisible_point = DisplayPoint::new(row, start as u32);
6336                if !selection_ranges
6337                    .iter()
6338                    .any(|region| region.start <= invisible_point && invisible_point < region.end)
6339                {
6340                    return;
6341                }
6342
6343                paint(window, cx);
6344            }),
6345
6346            // For a whitespace to be on a boundary, any of the following conditions need to be met:
6347            // - It is a tab
6348            // - It is adjacent to an edge (start or end)
6349            // - It is adjacent to a whitespace (left or right)
6350            ShowWhitespaceSetting::Boundary => {
6351                // We'll need to keep track of the last invisible we've seen and then check if we are adjacent to it for some of
6352                // the above cases.
6353                // Note: We zip in the original `invisibles` to check for tab equality
6354                let mut last_seen: Option<(bool, usize, Box<dyn Fn(&mut Window, &mut App)>)> = None;
6355                for (([start, end], paint), invisible) in
6356                    invisible_iter.zip_eq(self.invisibles.iter())
6357                {
6358                    let should_render = match (&last_seen, invisible) {
6359                        (_, Invisible::Tab { .. }) => true,
6360                        (Some((_, last_end, _)), _) => *last_end == start,
6361                        _ => false,
6362                    };
6363
6364                    if should_render || start == 0 || end == self.len {
6365                        paint(window, cx);
6366
6367                        // Since we are scanning from the left, we will skip over the first available whitespace that is part
6368                        // of a boundary between non-whitespace segments, so we correct by manually redrawing it if needed.
6369                        if let Some((should_render_last, last_end, paint_last)) = last_seen {
6370                            // Note that we need to make sure that the last one is actually adjacent
6371                            if !should_render_last && last_end == start {
6372                                paint_last(window, cx);
6373                            }
6374                        }
6375                    }
6376
6377                    // Manually render anything within a selection
6378                    let invisible_point = DisplayPoint::new(row, start as u32);
6379                    if selection_ranges.iter().any(|region| {
6380                        region.start <= invisible_point && invisible_point < region.end
6381                    }) {
6382                        paint(window, cx);
6383                    }
6384
6385                    last_seen = Some((should_render, end, paint));
6386                }
6387            }
6388        }
6389    }
6390
6391    pub fn x_for_index(&self, index: usize) -> Pixels {
6392        let mut fragment_start_x = Pixels::ZERO;
6393        let mut fragment_start_index = 0;
6394
6395        for fragment in &self.fragments {
6396            match fragment {
6397                LineFragment::Text(shaped_line) => {
6398                    let fragment_end_index = fragment_start_index + shaped_line.len;
6399                    if index < fragment_end_index {
6400                        return fragment_start_x
6401                            + shaped_line.x_for_index(index - fragment_start_index);
6402                    }
6403                    fragment_start_x += shaped_line.width;
6404                    fragment_start_index = fragment_end_index;
6405                }
6406                LineFragment::Element { len, size, .. } => {
6407                    let fragment_end_index = fragment_start_index + len;
6408                    if index < fragment_end_index {
6409                        return fragment_start_x;
6410                    }
6411                    fragment_start_x += size.width;
6412                    fragment_start_index = fragment_end_index;
6413                }
6414            }
6415        }
6416
6417        fragment_start_x
6418    }
6419
6420    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
6421        let mut fragment_start_x = Pixels::ZERO;
6422        let mut fragment_start_index = 0;
6423
6424        for fragment in &self.fragments {
6425            match fragment {
6426                LineFragment::Text(shaped_line) => {
6427                    let fragment_end_x = fragment_start_x + shaped_line.width;
6428                    if x < fragment_end_x {
6429                        return Some(
6430                            fragment_start_index + shaped_line.index_for_x(x - fragment_start_x)?,
6431                        );
6432                    }
6433                    fragment_start_x = fragment_end_x;
6434                    fragment_start_index += shaped_line.len;
6435                }
6436                LineFragment::Element { len, size, .. } => {
6437                    let fragment_end_x = fragment_start_x + size.width;
6438                    if x < fragment_end_x {
6439                        return Some(fragment_start_index);
6440                    }
6441                    fragment_start_index += len;
6442                    fragment_start_x = fragment_end_x;
6443                }
6444            }
6445        }
6446
6447        None
6448    }
6449
6450    pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
6451        let mut fragment_start_index = 0;
6452
6453        for fragment in &self.fragments {
6454            match fragment {
6455                LineFragment::Text(shaped_line) => {
6456                    let fragment_end_index = fragment_start_index + shaped_line.len;
6457                    if index < fragment_end_index {
6458                        return shaped_line.font_id_for_index(index - fragment_start_index);
6459                    }
6460                    fragment_start_index = fragment_end_index;
6461                }
6462                LineFragment::Element { len, .. } => {
6463                    let fragment_end_index = fragment_start_index + len;
6464                    if index < fragment_end_index {
6465                        return None;
6466                    }
6467                    fragment_start_index = fragment_end_index;
6468                }
6469            }
6470        }
6471
6472        None
6473    }
6474}
6475
6476#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6477enum Invisible {
6478    /// A tab character
6479    ///
6480    /// A tab character is internally represented by spaces (configured by the user's tab width)
6481    /// aligned to the nearest column, so it's necessary to store the start and end offset for
6482    /// adjacency checks.
6483    Tab {
6484        line_start_offset: usize,
6485        line_end_offset: usize,
6486    },
6487    Whitespace {
6488        line_offset: usize,
6489    },
6490}
6491
6492impl EditorElement {
6493    /// Returns the rem size to use when rendering the [`EditorElement`].
6494    ///
6495    /// This allows UI elements to scale based on the `buffer_font_size`.
6496    fn rem_size(&self, cx: &mut App) -> Option<Pixels> {
6497        match self.editor.read(cx).mode {
6498            EditorMode::Full => {
6499                let buffer_font_size = self.style.text.font_size;
6500                match buffer_font_size {
6501                    AbsoluteLength::Pixels(pixels) => {
6502                        let rem_size_scale = {
6503                            // Our default UI font size is 14px on a 16px base scale.
6504                            // This means the default UI font size is 0.875rems.
6505                            let default_font_size_scale = 14. / ui::BASE_REM_SIZE_IN_PX;
6506
6507                            // We then determine the delta between a single rem and the default font
6508                            // size scale.
6509                            let default_font_size_delta = 1. - default_font_size_scale;
6510
6511                            // Finally, we add this delta to 1rem to get the scale factor that
6512                            // should be used to scale up the UI.
6513                            1. + default_font_size_delta
6514                        };
6515
6516                        Some(pixels * rem_size_scale)
6517                    }
6518                    AbsoluteLength::Rems(rems) => {
6519                        Some(rems.to_pixels(ui::BASE_REM_SIZE_IN_PX.into()))
6520                    }
6521                }
6522            }
6523            // We currently use single-line and auto-height editors in UI contexts,
6524            // so we don't want to scale everything with the buffer font size, as it
6525            // ends up looking off.
6526            EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => None,
6527        }
6528    }
6529}
6530
6531impl Element for EditorElement {
6532    type RequestLayoutState = ();
6533    type PrepaintState = EditorLayout;
6534
6535    fn id(&self) -> Option<ElementId> {
6536        None
6537    }
6538
6539    fn request_layout(
6540        &mut self,
6541        _: Option<&GlobalElementId>,
6542        window: &mut Window,
6543        cx: &mut App,
6544    ) -> (gpui::LayoutId, ()) {
6545        let rem_size = self.rem_size(cx);
6546        window.with_rem_size(rem_size, |window| {
6547            self.editor.update(cx, |editor, cx| {
6548                editor.set_style(self.style.clone(), window, cx);
6549
6550                let layout_id = match editor.mode {
6551                    EditorMode::SingleLine { auto_width } => {
6552                        let rem_size = window.rem_size();
6553
6554                        let height = self.style.text.line_height_in_pixels(rem_size);
6555                        if auto_width {
6556                            let editor_handle = cx.entity().clone();
6557                            let style = self.style.clone();
6558                            window.request_measured_layout(
6559                                Style::default(),
6560                                move |_, _, window, cx| {
6561                                    let editor_snapshot = editor_handle
6562                                        .update(cx, |editor, cx| editor.snapshot(window, cx));
6563                                    let line = Self::layout_lines(
6564                                        DisplayRow(0)..DisplayRow(1),
6565                                        &editor_snapshot,
6566                                        &style,
6567                                        px(f32::MAX),
6568                                        |_| false, // Single lines never soft wrap
6569                                        window,
6570                                        cx,
6571                                    )
6572                                    .pop()
6573                                    .unwrap();
6574
6575                                    let font_id =
6576                                        window.text_system().resolve_font(&style.text.font());
6577                                    let font_size =
6578                                        style.text.font_size.to_pixels(window.rem_size());
6579                                    let em_width =
6580                                        window.text_system().em_width(font_id, font_size).unwrap();
6581
6582                                    size(line.width + em_width, height)
6583                                },
6584                            )
6585                        } else {
6586                            let mut style = Style::default();
6587                            style.size.height = height.into();
6588                            style.size.width = relative(1.).into();
6589                            window.request_layout(style, None, cx)
6590                        }
6591                    }
6592                    EditorMode::AutoHeight { max_lines } => {
6593                        let editor_handle = cx.entity().clone();
6594                        let max_line_number_width =
6595                            self.max_line_number_width(&editor.snapshot(window, cx), window, cx);
6596                        window.request_measured_layout(
6597                            Style::default(),
6598                            move |known_dimensions, available_space, window, cx| {
6599                                editor_handle
6600                                    .update(cx, |editor, cx| {
6601                                        compute_auto_height_layout(
6602                                            editor,
6603                                            max_lines,
6604                                            max_line_number_width,
6605                                            known_dimensions,
6606                                            available_space.width,
6607                                            window,
6608                                            cx,
6609                                        )
6610                                    })
6611                                    .unwrap_or_default()
6612                            },
6613                        )
6614                    }
6615                    EditorMode::Full => {
6616                        let mut style = Style::default();
6617                        style.size.width = relative(1.).into();
6618                        style.size.height = relative(1.).into();
6619                        window.request_layout(style, None, cx)
6620                    }
6621                };
6622
6623                (layout_id, ())
6624            })
6625        })
6626    }
6627
6628    fn prepaint(
6629        &mut self,
6630        _: Option<&GlobalElementId>,
6631        bounds: Bounds<Pixels>,
6632        _: &mut Self::RequestLayoutState,
6633        window: &mut Window,
6634        cx: &mut App,
6635    ) -> Self::PrepaintState {
6636        let text_style = TextStyleRefinement {
6637            font_size: Some(self.style.text.font_size),
6638            line_height: Some(self.style.text.line_height),
6639            ..Default::default()
6640        };
6641        let focus_handle = self.editor.focus_handle(cx);
6642        window.set_view_id(self.editor.entity_id());
6643        window.set_focus_handle(&focus_handle, cx);
6644
6645        let rem_size = self.rem_size(cx);
6646        window.with_rem_size(rem_size, |window| {
6647            window.with_text_style(Some(text_style), |window| {
6648                window.with_content_mask(Some(ContentMask { bounds }), |window| {
6649                    let mut snapshot = self
6650                        .editor
6651                        .update(cx, |editor, cx| editor.snapshot(window, cx));
6652                    let style = self.style.clone();
6653
6654                    let font_id = window.text_system().resolve_font(&style.text.font());
6655                    let font_size = style.text.font_size.to_pixels(window.rem_size());
6656                    let line_height = style.text.line_height_in_pixels(window.rem_size());
6657                    let em_width = window.text_system().em_width(font_id, font_size).unwrap();
6658                    let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
6659
6660                    let letter_size = size(em_width, line_height);
6661
6662                    let gutter_dimensions = snapshot
6663                        .gutter_dimensions(
6664                            font_id,
6665                            font_size,
6666                            self.max_line_number_width(&snapshot, window, cx),
6667                            cx,
6668                        )
6669                        .unwrap_or_default();
6670                    let text_width = bounds.size.width - gutter_dimensions.width;
6671
6672                    let editor_width = text_width - gutter_dimensions.margin - em_width;
6673
6674                    snapshot = self.editor.update(cx, |editor, cx| {
6675                        editor.last_bounds = Some(bounds);
6676                        editor.gutter_dimensions = gutter_dimensions;
6677                        editor.set_visible_line_count(bounds.size.height / line_height, window, cx);
6678
6679                        if matches!(editor.mode, EditorMode::AutoHeight { .. }) {
6680                            snapshot
6681                        } else {
6682                            let wrap_width = match editor.soft_wrap_mode(cx) {
6683                                SoftWrap::GitDiff => None,
6684                                SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
6685                                SoftWrap::EditorWidth => Some(editor_width),
6686                                SoftWrap::Column(column) => Some(column as f32 * em_advance),
6687                                SoftWrap::Bounded(column) => {
6688                                    Some(editor_width.min(column as f32 * em_advance))
6689                                }
6690                            };
6691
6692                            if editor.set_wrap_width(wrap_width, cx) {
6693                                editor.snapshot(window, cx)
6694                            } else {
6695                                snapshot
6696                            }
6697                        }
6698                    });
6699
6700                    let wrap_guides = self
6701                        .editor
6702                        .read(cx)
6703                        .wrap_guides(cx)
6704                        .iter()
6705                        .map(|(guide, active)| (self.column_pixels(*guide, window, cx), *active))
6706                        .collect::<SmallVec<[_; 2]>>();
6707
6708                    let hitbox = window.insert_hitbox(bounds, false);
6709                    let gutter_hitbox =
6710                        window.insert_hitbox(gutter_bounds(bounds, gutter_dimensions), false);
6711                    let text_hitbox = window.insert_hitbox(
6712                        Bounds {
6713                            origin: gutter_hitbox.top_right(),
6714                            size: size(text_width, bounds.size.height),
6715                        },
6716                        false,
6717                    );
6718                    // Offset the content_bounds from the text_bounds by the gutter margin (which
6719                    // is roughly half a character wide) to make hit testing work more like how we want.
6720                    let content_origin =
6721                        text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
6722
6723                    let scrollbar_bounds =
6724                        Bounds::from_corners(content_origin, bounds.bottom_right());
6725
6726                    let height_in_lines = scrollbar_bounds.size.height / line_height;
6727
6728                    // NOTE: The max row number in the current file, minus one
6729                    let max_row = snapshot.max_point().row().as_f32();
6730
6731                    // NOTE: The max scroll position for the top of the window
6732                    let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
6733                        (max_row - height_in_lines + 1.).max(0.)
6734                    } else {
6735                        let settings = EditorSettings::get_global(cx);
6736                        match settings.scroll_beyond_last_line {
6737                            ScrollBeyondLastLine::OnePage => max_row,
6738                            ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
6739                            ScrollBeyondLastLine::VerticalScrollMargin => {
6740                                (max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
6741                                    .max(0.)
6742                            }
6743                        }
6744                    };
6745
6746                    // TODO: Autoscrolling for both axes
6747                    let mut autoscroll_request = None;
6748                    let mut autoscroll_containing_element = false;
6749                    let mut autoscroll_horizontally = false;
6750                    self.editor.update(cx, |editor, cx| {
6751                        autoscroll_request = editor.autoscroll_request();
6752                        autoscroll_containing_element =
6753                            autoscroll_request.is_some() || editor.has_pending_selection();
6754                        // TODO: Is this horizontal or vertical?!
6755                        autoscroll_horizontally = editor.autoscroll_vertically(
6756                            bounds,
6757                            line_height,
6758                            max_scroll_top,
6759                            window,
6760                            cx,
6761                        );
6762                        snapshot = editor.snapshot(window, cx);
6763                    });
6764
6765                    let mut scroll_position = snapshot.scroll_position();
6766                    // The scroll position is a fractional point, the whole number of which represents
6767                    // the top of the window in terms of display rows.
6768                    let start_row = DisplayRow(scroll_position.y as u32);
6769                    let max_row = snapshot.max_point().row();
6770                    let end_row = cmp::min(
6771                        (scroll_position.y + height_in_lines).ceil() as u32,
6772                        max_row.next_row().0,
6773                    );
6774                    let end_row = DisplayRow(end_row);
6775
6776                    let row_infos = snapshot
6777                        .row_infos(start_row)
6778                        .take((start_row..end_row).len())
6779                        .collect::<Vec<RowInfo>>();
6780                    let is_row_soft_wrapped = |row: usize| {
6781                        row_infos
6782                            .get(row)
6783                            .map_or(true, |info| info.buffer_row.is_none())
6784                    };
6785
6786                    let start_anchor = if start_row == Default::default() {
6787                        Anchor::min()
6788                    } else {
6789                        snapshot.buffer_snapshot.anchor_before(
6790                            DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
6791                        )
6792                    };
6793                    let end_anchor = if end_row > max_row {
6794                        Anchor::max()
6795                    } else {
6796                        snapshot.buffer_snapshot.anchor_before(
6797                            DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
6798                        )
6799                    };
6800
6801                    let mut highlighted_rows = self
6802                        .editor
6803                        .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx));
6804
6805                    for (ix, row_info) in row_infos.iter().enumerate() {
6806                        let color = match row_info.diff_status {
6807                            Some(DiffHunkStatus::Added) => style.status.created_background,
6808                            Some(DiffHunkStatus::Removed) => style.status.deleted_background,
6809                            _ => continue,
6810                        };
6811                        highlighted_rows
6812                            .entry(start_row + DisplayRow(ix as u32))
6813                            .or_insert(color);
6814                    }
6815
6816                    let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
6817                        start_anchor..end_anchor,
6818                        &snapshot.display_snapshot,
6819                        cx.theme().colors(),
6820                    );
6821                    let highlighted_gutter_ranges =
6822                        self.editor.read(cx).gutter_highlights_in_range(
6823                            start_anchor..end_anchor,
6824                            &snapshot.display_snapshot,
6825                            cx,
6826                        );
6827
6828                    let redacted_ranges = self.editor.read(cx).redacted_ranges(
6829                        start_anchor..end_anchor,
6830                        &snapshot.display_snapshot,
6831                        cx,
6832                    );
6833
6834                    let (local_selections, selected_buffer_ids): (
6835                        Vec<Selection<Point>>,
6836                        Vec<BufferId>,
6837                    ) = self.editor.update(cx, |editor, cx| {
6838                        let all_selections = editor.selections.all::<Point>(cx);
6839                        let selected_buffer_ids = if editor.is_singleton(cx) {
6840                            Vec::new()
6841                        } else {
6842                            let mut selected_buffer_ids = Vec::with_capacity(all_selections.len());
6843
6844                            for selection in all_selections {
6845                                for buffer_id in snapshot
6846                                    .buffer_snapshot
6847                                    .buffer_ids_for_range(selection.range())
6848                                {
6849                                    if selected_buffer_ids.last() != Some(&buffer_id) {
6850                                        selected_buffer_ids.push(buffer_id);
6851                                    }
6852                                }
6853                            }
6854
6855                            selected_buffer_ids
6856                        };
6857
6858                        let mut selections = editor
6859                            .selections
6860                            .disjoint_in_range(start_anchor..end_anchor, cx);
6861                        selections.extend(editor.selections.pending(cx));
6862
6863                        (selections, selected_buffer_ids)
6864                    });
6865
6866                    let (selections, active_rows, newest_selection_head) = self.layout_selections(
6867                        start_anchor,
6868                        end_anchor,
6869                        &local_selections,
6870                        &snapshot,
6871                        start_row,
6872                        end_row,
6873                        window,
6874                        cx,
6875                    );
6876
6877                    let line_numbers = self.layout_line_numbers(
6878                        Some(&gutter_hitbox),
6879                        gutter_dimensions,
6880                        line_height,
6881                        scroll_position,
6882                        start_row..end_row,
6883                        &row_infos,
6884                        newest_selection_head,
6885                        &snapshot,
6886                        window,
6887                        cx,
6888                    );
6889
6890                    let mut crease_toggles =
6891                        window.with_element_namespace("crease_toggles", |window| {
6892                            self.layout_crease_toggles(
6893                                start_row..end_row,
6894                                &row_infos,
6895                                &active_rows,
6896                                &snapshot,
6897                                window,
6898                                cx,
6899                            )
6900                        });
6901                    let crease_trailers =
6902                        window.with_element_namespace("crease_trailers", |window| {
6903                            self.layout_crease_trailers(
6904                                row_infos.iter().copied(),
6905                                &snapshot,
6906                                window,
6907                                cx,
6908                            )
6909                        });
6910
6911                    let display_hunks = self.layout_gutter_diff_hunks(
6912                        line_height,
6913                        &gutter_hitbox,
6914                        start_row..end_row,
6915                        &snapshot,
6916                        window,
6917                        cx,
6918                    );
6919
6920                    let mut line_layouts = Self::layout_lines(
6921                        start_row..end_row,
6922                        &snapshot,
6923                        &self.style,
6924                        editor_width,
6925                        is_row_soft_wrapped,
6926                        window,
6927                        cx,
6928                    );
6929
6930                    let longest_line_blame_width = self
6931                        .editor
6932                        .update(cx, |editor, cx| {
6933                            if !editor.show_git_blame_inline {
6934                                return None;
6935                            }
6936                            let blame = editor.blame.as_ref()?;
6937                            let blame_entry = blame
6938                                .update(cx, |blame, cx| {
6939                                    let row_infos =
6940                                        snapshot.row_infos(snapshot.longest_row()).next()?;
6941                                    blame.blame_for_rows(&[row_infos], cx).next()
6942                                })
6943                                .flatten()?;
6944                            let workspace = editor.workspace.as_ref().map(|(w, _)| w.to_owned());
6945                            let mut element = render_inline_blame_entry(
6946                                blame,
6947                                blame_entry,
6948                                &style,
6949                                workspace,
6950                                cx,
6951                            );
6952                            let inline_blame_padding = INLINE_BLAME_PADDING_EM_WIDTHS * em_advance;
6953                            Some(
6954                                element
6955                                    .layout_as_root(AvailableSpace::min_size(), window, cx)
6956                                    .width
6957                                    + inline_blame_padding,
6958                            )
6959                        })
6960                        .unwrap_or(Pixels::ZERO);
6961
6962                    let longest_line_width = layout_line(
6963                        snapshot.longest_row(),
6964                        &snapshot,
6965                        &style,
6966                        editor_width,
6967                        is_row_soft_wrapped,
6968                        window,
6969                        cx,
6970                    )
6971                    .width;
6972
6973                    let scrollbar_range_data = ScrollbarRangeData::new(
6974                        scrollbar_bounds,
6975                        letter_size,
6976                        &snapshot,
6977                        longest_line_width,
6978                        longest_line_blame_width,
6979                        &style,
6980                        cx,
6981                    );
6982
6983                    let scroll_range_bounds = scrollbar_range_data.scroll_range;
6984                    let mut scroll_width = scroll_range_bounds.size.width;
6985
6986                    let sticky_header_excerpt = if snapshot.buffer_snapshot.show_headers() {
6987                        snapshot.sticky_header_excerpt(start_row)
6988                    } else {
6989                        None
6990                    };
6991                    let sticky_header_excerpt_id =
6992                        sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
6993
6994                    let blocks = window.with_element_namespace("blocks", |window| {
6995                        self.render_blocks(
6996                            start_row..end_row,
6997                            &snapshot,
6998                            &hitbox,
6999                            &text_hitbox,
7000                            editor_width,
7001                            &mut scroll_width,
7002                            &gutter_dimensions,
7003                            em_width,
7004                            gutter_dimensions.full_width(),
7005                            line_height,
7006                            &line_layouts,
7007                            &local_selections,
7008                            &selected_buffer_ids,
7009                            is_row_soft_wrapped,
7010                            sticky_header_excerpt_id,
7011                            window,
7012                            cx,
7013                        )
7014                    });
7015                    let mut blocks = match blocks {
7016                        Ok(blocks) => blocks,
7017                        Err(resized_blocks) => {
7018                            self.editor.update(cx, |editor, cx| {
7019                                editor.resize_blocks(resized_blocks, autoscroll_request, cx)
7020                            });
7021                            return self.prepaint(None, bounds, &mut (), window, cx);
7022                        }
7023                    };
7024
7025                    let sticky_buffer_header = sticky_header_excerpt.map(|sticky_header_excerpt| {
7026                        window.with_element_namespace("blocks", |window| {
7027                            self.layout_sticky_buffer_header(
7028                                sticky_header_excerpt,
7029                                scroll_position.y,
7030                                line_height,
7031                                &snapshot,
7032                                &hitbox,
7033                                &selected_buffer_ids,
7034                                window,
7035                                cx,
7036                            )
7037                        })
7038                    });
7039
7040                    let start_buffer_row =
7041                        MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
7042                    let end_buffer_row =
7043                        MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
7044
7045                    let scroll_max = point(
7046                        ((scroll_width - scrollbar_bounds.size.width) / em_width).max(0.0),
7047                        max_row.as_f32(),
7048                    );
7049
7050                    self.editor.update(cx, |editor, cx| {
7051                        let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
7052
7053                        let autoscrolled = if autoscroll_horizontally {
7054                            editor.autoscroll_horizontally(
7055                                start_row,
7056                                editor_width - (letter_size.width / 2.0),
7057                                scroll_width,
7058                                em_width,
7059                                &line_layouts,
7060                                cx,
7061                            )
7062                        } else {
7063                            false
7064                        };
7065
7066                        if clamped || autoscrolled {
7067                            snapshot = editor.snapshot(window, cx);
7068                            scroll_position = snapshot.scroll_position();
7069                        }
7070                    });
7071
7072                    let scroll_pixel_position = point(
7073                        scroll_position.x * em_width,
7074                        scroll_position.y * line_height,
7075                    );
7076
7077                    let indent_guides = self.layout_indent_guides(
7078                        content_origin,
7079                        text_hitbox.origin,
7080                        start_buffer_row..end_buffer_row,
7081                        scroll_pixel_position,
7082                        line_height,
7083                        &snapshot,
7084                        window,
7085                        cx,
7086                    );
7087
7088                    let crease_trailers =
7089                        window.with_element_namespace("crease_trailers", |window| {
7090                            self.prepaint_crease_trailers(
7091                                crease_trailers,
7092                                &line_layouts,
7093                                line_height,
7094                                content_origin,
7095                                scroll_pixel_position,
7096                                em_width,
7097                                window,
7098                                cx,
7099                            )
7100                        });
7101
7102                    let mut inline_blame = None;
7103                    if let Some(newest_selection_head) = newest_selection_head {
7104                        let display_row = newest_selection_head.row();
7105                        if (start_row..end_row).contains(&display_row) {
7106                            let line_ix = display_row.minus(start_row) as usize;
7107                            let row_info = &row_infos[line_ix];
7108                            let line_layout = &line_layouts[line_ix];
7109                            let crease_trailer_layout = crease_trailers[line_ix].as_ref();
7110                            inline_blame = self.layout_inline_blame(
7111                                display_row,
7112                                row_info,
7113                                line_layout,
7114                                crease_trailer_layout,
7115                                em_width,
7116                                content_origin,
7117                                scroll_pixel_position,
7118                                line_height,
7119                                window,
7120                                cx,
7121                            );
7122                        }
7123                    }
7124
7125                    let blamed_display_rows = self.layout_blame_entries(
7126                        &row_infos,
7127                        em_width,
7128                        scroll_position,
7129                        line_height,
7130                        &gutter_hitbox,
7131                        gutter_dimensions.git_blame_entries_width,
7132                        window,
7133                        cx,
7134                    );
7135
7136                    let scroll_max = point(
7137                        ((scroll_width - scrollbar_bounds.size.width) / em_width).max(0.0),
7138                        max_scroll_top,
7139                    );
7140
7141                    self.editor.update(cx, |editor, cx| {
7142                        let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
7143
7144                        let autoscrolled = if autoscroll_horizontally {
7145                            editor.autoscroll_horizontally(
7146                                start_row,
7147                                editor_width - (letter_size.width / 2.0),
7148                                scroll_width,
7149                                em_width,
7150                                &line_layouts,
7151                                cx,
7152                            )
7153                        } else {
7154                            false
7155                        };
7156
7157                        if clamped || autoscrolled {
7158                            snapshot = editor.snapshot(window, cx);
7159                            scroll_position = snapshot.scroll_position();
7160                        }
7161                    });
7162
7163                    let line_elements = self.prepaint_lines(
7164                        start_row,
7165                        &mut line_layouts,
7166                        line_height,
7167                        scroll_pixel_position,
7168                        content_origin,
7169                        window,
7170                        cx,
7171                    );
7172
7173                    let mut block_start_rows = HashSet::default();
7174
7175                    window.with_element_namespace("blocks", |window| {
7176                        self.layout_blocks(
7177                            &mut blocks,
7178                            &mut block_start_rows,
7179                            &hitbox,
7180                            line_height,
7181                            scroll_pixel_position,
7182                            window,
7183                            cx,
7184                        );
7185                    });
7186
7187                    let cursors = self.collect_cursors(&snapshot, cx);
7188                    let visible_row_range = start_row..end_row;
7189                    let non_visible_cursors = cursors
7190                        .iter()
7191                        .any(move |c| !visible_row_range.contains(&c.0.row()));
7192
7193                    let visible_cursors = self.layout_visible_cursors(
7194                        &snapshot,
7195                        &selections,
7196                        &block_start_rows,
7197                        start_row..end_row,
7198                        &line_layouts,
7199                        &text_hitbox,
7200                        content_origin,
7201                        scroll_position,
7202                        scroll_pixel_position,
7203                        line_height,
7204                        em_width,
7205                        em_advance,
7206                        autoscroll_containing_element,
7207                        window,
7208                        cx,
7209                    );
7210
7211                    let scrollbars_layout = self.layout_scrollbars(
7212                        &snapshot,
7213                        scrollbar_range_data,
7214                        scroll_position,
7215                        non_visible_cursors,
7216                        window,
7217                        cx,
7218                    );
7219
7220                    let gutter_settings = EditorSettings::get_global(cx).gutter;
7221
7222                    let rows_with_hunk_bounds = display_hunks
7223                        .iter()
7224                        .filter_map(|(hunk, hitbox)| Some((hunk, hitbox.as_ref()?.bounds)))
7225                        .fold(
7226                            HashMap::default(),
7227                            |mut rows_with_hunk_bounds, (hunk, bounds)| {
7228                                match hunk {
7229                                    DisplayDiffHunk::Folded { display_row } => {
7230                                        rows_with_hunk_bounds.insert(*display_row, bounds);
7231                                    }
7232                                    DisplayDiffHunk::Unfolded {
7233                                        display_row_range, ..
7234                                    } => {
7235                                        for display_row in display_row_range.iter_rows() {
7236                                            rows_with_hunk_bounds.insert(display_row, bounds);
7237                                        }
7238                                    }
7239                                }
7240                                rows_with_hunk_bounds
7241                            },
7242                        );
7243                    let mut code_actions_indicator = None;
7244                    if let Some(newest_selection_head) = newest_selection_head {
7245                        let newest_selection_point =
7246                            newest_selection_head.to_point(&snapshot.display_snapshot);
7247
7248                        if (start_row..end_row).contains(&newest_selection_head.row()) {
7249                            self.layout_cursor_popovers(
7250                                line_height,
7251                                &text_hitbox,
7252                                content_origin,
7253                                start_row,
7254                                scroll_pixel_position,
7255                                &line_layouts,
7256                                newest_selection_head,
7257                                newest_selection_point,
7258                                &style,
7259                                window,
7260                                cx,
7261                            );
7262
7263                            let show_code_actions = snapshot
7264                                .show_code_actions
7265                                .unwrap_or(gutter_settings.code_actions);
7266                            if show_code_actions {
7267                                let newest_selection_point =
7268                                    newest_selection_head.to_point(&snapshot.display_snapshot);
7269                                if !snapshot
7270                                    .is_line_folded(MultiBufferRow(newest_selection_point.row))
7271                                {
7272                                    let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
7273                                        MultiBufferRow(newest_selection_point.row),
7274                                    );
7275                                    if let Some((buffer, range)) = buffer {
7276                                        let buffer_id = buffer.remote_id();
7277                                        let row = range.start.row;
7278                                        let has_test_indicator = self
7279                                            .editor
7280                                            .read(cx)
7281                                            .tasks
7282                                            .contains_key(&(buffer_id, row));
7283
7284                                        if !has_test_indicator {
7285                                            code_actions_indicator = self
7286                                                .layout_code_actions_indicator(
7287                                                    line_height,
7288                                                    newest_selection_head,
7289                                                    scroll_pixel_position,
7290                                                    &gutter_dimensions,
7291                                                    &gutter_hitbox,
7292                                                    &rows_with_hunk_bounds,
7293                                                    window,
7294                                                    cx,
7295                                                );
7296                                        }
7297                                    }
7298                                }
7299                            }
7300                        }
7301                    }
7302
7303                    self.layout_gutter_menu(
7304                        line_height,
7305                        &text_hitbox,
7306                        content_origin,
7307                        scroll_pixel_position,
7308                        gutter_dimensions.width - gutter_dimensions.left_padding,
7309                        window,
7310                        cx,
7311                    );
7312
7313                    let test_indicators = if gutter_settings.runnables {
7314                        self.layout_run_indicators(
7315                            line_height,
7316                            start_row..end_row,
7317                            scroll_pixel_position,
7318                            &gutter_dimensions,
7319                            &gutter_hitbox,
7320                            &rows_with_hunk_bounds,
7321                            &snapshot,
7322                            window,
7323                            cx,
7324                        )
7325                    } else {
7326                        Vec::new()
7327                    };
7328
7329                    self.layout_signature_help(
7330                        &hitbox,
7331                        content_origin,
7332                        scroll_pixel_position,
7333                        newest_selection_head,
7334                        start_row,
7335                        &line_layouts,
7336                        line_height,
7337                        em_width,
7338                        window,
7339                        cx,
7340                    );
7341
7342                    if !cx.has_active_drag() {
7343                        self.layout_hover_popovers(
7344                            &snapshot,
7345                            &hitbox,
7346                            &text_hitbox,
7347                            start_row..end_row,
7348                            content_origin,
7349                            scroll_pixel_position,
7350                            &line_layouts,
7351                            line_height,
7352                            em_width,
7353                            window,
7354                            cx,
7355                        );
7356                    }
7357
7358                    let inline_completion_popover = self.layout_inline_completion_popover(
7359                        &text_hitbox.bounds,
7360                        &snapshot,
7361                        start_row..end_row,
7362                        scroll_position.y,
7363                        scroll_position.y + height_in_lines,
7364                        &line_layouts,
7365                        line_height,
7366                        scroll_pixel_position,
7367                        newest_selection_head,
7368                        editor_width,
7369                        &style,
7370                        window,
7371                        cx,
7372                    );
7373
7374                    let mouse_context_menu = self.layout_mouse_context_menu(
7375                        &snapshot,
7376                        start_row..end_row,
7377                        content_origin,
7378                        window,
7379                        cx,
7380                    );
7381
7382                    window.with_element_namespace("crease_toggles", |window| {
7383                        self.prepaint_crease_toggles(
7384                            &mut crease_toggles,
7385                            line_height,
7386                            &gutter_dimensions,
7387                            gutter_settings,
7388                            scroll_pixel_position,
7389                            &gutter_hitbox,
7390                            window,
7391                            cx,
7392                        )
7393                    });
7394
7395                    let invisible_symbol_font_size = font_size / 2.;
7396                    let tab_invisible = window
7397                        .text_system()
7398                        .shape_line(
7399                            "".into(),
7400                            invisible_symbol_font_size,
7401                            &[TextRun {
7402                                len: "".len(),
7403                                font: self.style.text.font(),
7404                                color: cx.theme().colors().editor_invisible,
7405                                background_color: None,
7406                                underline: None,
7407                                strikethrough: None,
7408                            }],
7409                        )
7410                        .unwrap();
7411                    let space_invisible = window
7412                        .text_system()
7413                        .shape_line(
7414                            "".into(),
7415                            invisible_symbol_font_size,
7416                            &[TextRun {
7417                                len: "".len(),
7418                                font: self.style.text.font(),
7419                                color: cx.theme().colors().editor_invisible,
7420                                background_color: None,
7421                                underline: None,
7422                                strikethrough: None,
7423                            }],
7424                        )
7425                        .unwrap();
7426
7427                    let mode = snapshot.mode;
7428
7429                    let position_map = Rc::new(PositionMap {
7430                        size: bounds.size,
7431                        scroll_pixel_position,
7432                        scroll_max,
7433                        line_layouts,
7434                        line_height,
7435                        em_width,
7436                        em_advance,
7437                        snapshot,
7438                        gutter_hitbox: gutter_hitbox.clone(),
7439                        text_hitbox: text_hitbox.clone(),
7440                    });
7441
7442                    self.editor.update(cx, |editor, _| {
7443                        editor.last_position_map = Some(position_map.clone())
7444                    });
7445
7446                    let hunk_controls = self.layout_diff_hunk_controls(
7447                        start_row..end_row,
7448                        &row_infos,
7449                        &text_hitbox,
7450                        &position_map,
7451                        newest_selection_head,
7452                        line_height,
7453                        scroll_pixel_position,
7454                        &display_hunks,
7455                        self.editor.clone(),
7456                        window,
7457                        cx,
7458                    );
7459
7460                    EditorLayout {
7461                        mode,
7462                        position_map,
7463                        visible_display_row_range: start_row..end_row,
7464                        wrap_guides,
7465                        indent_guides,
7466                        hitbox,
7467                        gutter_hitbox,
7468                        display_hunks,
7469                        content_origin,
7470                        scrollbars_layout,
7471                        active_rows,
7472                        highlighted_rows,
7473                        highlighted_ranges,
7474                        highlighted_gutter_ranges,
7475                        redacted_ranges,
7476                        line_elements,
7477                        line_numbers,
7478                        blamed_display_rows,
7479                        inline_blame,
7480                        blocks,
7481                        cursors,
7482                        visible_cursors,
7483                        selections,
7484                        inline_completion_popover,
7485                        diff_hunk_controls: hunk_controls,
7486                        mouse_context_menu,
7487                        test_indicators,
7488                        code_actions_indicator,
7489                        crease_toggles,
7490                        crease_trailers,
7491                        tab_invisible,
7492                        space_invisible,
7493                        sticky_buffer_header,
7494                    }
7495                })
7496            })
7497        })
7498    }
7499
7500    fn paint(
7501        &mut self,
7502        _: Option<&GlobalElementId>,
7503        bounds: Bounds<gpui::Pixels>,
7504        _: &mut Self::RequestLayoutState,
7505        layout: &mut Self::PrepaintState,
7506        window: &mut Window,
7507        cx: &mut App,
7508    ) {
7509        let focus_handle = self.editor.focus_handle(cx);
7510        let key_context = self
7511            .editor
7512            .update(cx, |editor, cx| editor.key_context(window, cx));
7513
7514        window.set_key_context(key_context);
7515        window.handle_input(
7516            &focus_handle,
7517            ElementInputHandler::new(bounds, self.editor.clone()),
7518            cx,
7519        );
7520        self.register_actions(window, cx);
7521        self.register_key_listeners(window, cx, layout);
7522
7523        let text_style = TextStyleRefinement {
7524            font_size: Some(self.style.text.font_size),
7525            line_height: Some(self.style.text.line_height),
7526            ..Default::default()
7527        };
7528        let rem_size = self.rem_size(cx);
7529        window.with_rem_size(rem_size, |window| {
7530            window.with_text_style(Some(text_style), |window| {
7531                window.with_content_mask(Some(ContentMask { bounds }), |window| {
7532                    self.paint_mouse_listeners(layout, window, cx);
7533                    self.paint_background(layout, window, cx);
7534                    self.paint_indent_guides(layout, window, cx);
7535
7536                    if layout.gutter_hitbox.size.width > Pixels::ZERO {
7537                        self.paint_blamed_display_rows(layout, window, cx);
7538                        self.paint_line_numbers(layout, window, cx);
7539                    }
7540
7541                    self.paint_text(layout, window, cx);
7542
7543                    if layout.gutter_hitbox.size.width > Pixels::ZERO {
7544                        self.paint_gutter_highlights(layout, window, cx);
7545                        self.paint_gutter_indicators(layout, window, cx);
7546                    }
7547
7548                    if !layout.blocks.is_empty() {
7549                        window.with_element_namespace("blocks", |window| {
7550                            self.paint_blocks(layout, window, cx);
7551                        });
7552                    }
7553
7554                    window.with_element_namespace("blocks", |window| {
7555                        if let Some(mut sticky_header) = layout.sticky_buffer_header.take() {
7556                            sticky_header.paint(window, cx)
7557                        }
7558                    });
7559
7560                    self.paint_scrollbars(layout, window, cx);
7561                    self.paint_inline_completion_popover(layout, window, cx);
7562                    self.paint_mouse_context_menu(layout, window, cx);
7563                });
7564            })
7565        })
7566    }
7567}
7568
7569pub(super) fn gutter_bounds(
7570    editor_bounds: Bounds<Pixels>,
7571    gutter_dimensions: GutterDimensions,
7572) -> Bounds<Pixels> {
7573    Bounds {
7574        origin: editor_bounds.origin,
7575        size: size(gutter_dimensions.width, editor_bounds.size.height),
7576    }
7577}
7578
7579struct ScrollbarRangeData {
7580    scrollbar_bounds: Bounds<Pixels>,
7581    scroll_range: Bounds<Pixels>,
7582    letter_size: Size<Pixels>,
7583}
7584
7585impl ScrollbarRangeData {
7586    pub fn new(
7587        scrollbar_bounds: Bounds<Pixels>,
7588        letter_size: Size<Pixels>,
7589        snapshot: &EditorSnapshot,
7590        longest_line_width: Pixels,
7591        longest_line_blame_width: Pixels,
7592        style: &EditorStyle,
7593
7594        cx: &mut App,
7595    ) -> ScrollbarRangeData {
7596        // TODO: Simplify this function down, it requires a lot of parameters
7597        let max_row = snapshot.max_point().row();
7598        let text_bounds_size = size(longest_line_width, max_row.0 as f32 * letter_size.height);
7599
7600        let scrollbar_width = style.scrollbar_width;
7601
7602        let settings = EditorSettings::get_global(cx);
7603        let scroll_beyond_last_line: Pixels = match settings.scroll_beyond_last_line {
7604            ScrollBeyondLastLine::OnePage => px(scrollbar_bounds.size.height / letter_size.height),
7605            ScrollBeyondLastLine::Off => px(1.),
7606            ScrollBeyondLastLine::VerticalScrollMargin => px(1.0 + settings.vertical_scroll_margin),
7607        };
7608
7609        let overscroll = size(
7610            scrollbar_width + (letter_size.width / 2.0) + longest_line_blame_width,
7611            letter_size.height * scroll_beyond_last_line,
7612        );
7613
7614        let scroll_range = Bounds {
7615            origin: scrollbar_bounds.origin,
7616            size: text_bounds_size + overscroll,
7617        };
7618
7619        ScrollbarRangeData {
7620            scrollbar_bounds,
7621            scroll_range,
7622            letter_size,
7623        }
7624    }
7625}
7626
7627impl IntoElement for EditorElement {
7628    type Element = Self;
7629
7630    fn into_element(self) -> Self::Element {
7631        self
7632    }
7633}
7634
7635pub struct EditorLayout {
7636    position_map: Rc<PositionMap>,
7637    hitbox: Hitbox,
7638    gutter_hitbox: Hitbox,
7639    content_origin: gpui::Point<Pixels>,
7640    scrollbars_layout: AxisPair<Option<ScrollbarLayout>>,
7641    mode: EditorMode,
7642    wrap_guides: SmallVec<[(Pixels, bool); 2]>,
7643    indent_guides: Option<Vec<IndentGuideLayout>>,
7644    visible_display_row_range: Range<DisplayRow>,
7645    active_rows: BTreeMap<DisplayRow, bool>,
7646    highlighted_rows: BTreeMap<DisplayRow, Hsla>,
7647    line_elements: SmallVec<[AnyElement; 1]>,
7648    line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
7649    display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
7650    blamed_display_rows: Option<Vec<AnyElement>>,
7651    inline_blame: Option<AnyElement>,
7652    blocks: Vec<BlockLayout>,
7653    highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
7654    highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
7655    redacted_ranges: Vec<Range<DisplayPoint>>,
7656    cursors: Vec<(DisplayPoint, Hsla)>,
7657    visible_cursors: Vec<CursorLayout>,
7658    selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
7659    code_actions_indicator: Option<AnyElement>,
7660    test_indicators: Vec<AnyElement>,
7661    crease_toggles: Vec<Option<AnyElement>>,
7662    diff_hunk_controls: Vec<AnyElement>,
7663    crease_trailers: Vec<Option<CreaseTrailerLayout>>,
7664    inline_completion_popover: Option<AnyElement>,
7665    mouse_context_menu: Option<AnyElement>,
7666    tab_invisible: ShapedLine,
7667    space_invisible: ShapedLine,
7668    sticky_buffer_header: Option<AnyElement>,
7669}
7670
7671impl EditorLayout {
7672    fn line_end_overshoot(&self) -> Pixels {
7673        0.15 * self.position_map.line_height
7674    }
7675}
7676
7677struct LineNumberLayout {
7678    shaped_line: ShapedLine,
7679    hitbox: Option<Hitbox>,
7680    display_row: DisplayRow,
7681}
7682
7683struct ColoredRange<T> {
7684    start: T,
7685    end: T,
7686    color: Hsla,
7687}
7688
7689#[derive(Clone)]
7690struct ScrollbarLayout {
7691    hitbox: Hitbox,
7692    visible_range: Range<f32>,
7693    visible: bool,
7694    text_unit_size: Pixels,
7695    thumb_size: Pixels,
7696    axis: Axis,
7697}
7698
7699impl ScrollbarLayout {
7700    const BORDER_WIDTH: Pixels = px(1.0);
7701    const LINE_MARKER_HEIGHT: Pixels = px(2.0);
7702    const MIN_MARKER_HEIGHT: Pixels = px(5.0);
7703    // const MIN_THUMB_HEIGHT: Pixels = px(20.0);
7704
7705    fn thumb_bounds(&self) -> Bounds<Pixels> {
7706        match self.axis {
7707            Axis::Vertical => {
7708                let thumb_top = self.y_for_row(self.visible_range.start);
7709                let thumb_bottom = thumb_top + self.thumb_size;
7710                Bounds::from_corners(
7711                    point(self.hitbox.left(), thumb_top),
7712                    point(self.hitbox.right(), thumb_bottom),
7713                )
7714            }
7715            Axis::Horizontal => {
7716                let thumb_left =
7717                    self.hitbox.left() + self.visible_range.start * self.text_unit_size;
7718                let thumb_right = thumb_left + self.thumb_size;
7719                Bounds::from_corners(
7720                    point(thumb_left, self.hitbox.top()),
7721                    point(thumb_right, self.hitbox.bottom()),
7722                )
7723            }
7724        }
7725    }
7726
7727    fn y_for_row(&self, row: f32) -> Pixels {
7728        self.hitbox.top() + row * self.text_unit_size
7729    }
7730
7731    fn marker_quads_for_ranges(
7732        &self,
7733        row_ranges: impl IntoIterator<Item = ColoredRange<DisplayRow>>,
7734        column: Option<usize>,
7735    ) -> Vec<PaintQuad> {
7736        struct MinMax {
7737            min: Pixels,
7738            max: Pixels,
7739        }
7740        let (x_range, height_limit) = if let Some(column) = column {
7741            let column_width = px(((self.hitbox.size.width - Self::BORDER_WIDTH).0 / 3.0).floor());
7742            let start = Self::BORDER_WIDTH + (column as f32 * column_width);
7743            let end = start + column_width;
7744            (
7745                Range { start, end },
7746                MinMax {
7747                    min: Self::MIN_MARKER_HEIGHT,
7748                    max: px(f32::MAX),
7749                },
7750            )
7751        } else {
7752            (
7753                Range {
7754                    start: Self::BORDER_WIDTH,
7755                    end: self.hitbox.size.width,
7756                },
7757                MinMax {
7758                    min: Self::LINE_MARKER_HEIGHT,
7759                    max: Self::LINE_MARKER_HEIGHT,
7760                },
7761            )
7762        };
7763
7764        let row_to_y = |row: DisplayRow| row.as_f32() * self.text_unit_size;
7765        let mut pixel_ranges = row_ranges
7766            .into_iter()
7767            .map(|range| {
7768                let start_y = row_to_y(range.start);
7769                let end_y = row_to_y(range.end)
7770                    + self
7771                        .text_unit_size
7772                        .max(height_limit.min)
7773                        .min(height_limit.max);
7774                ColoredRange {
7775                    start: start_y,
7776                    end: end_y,
7777                    color: range.color,
7778                }
7779            })
7780            .peekable();
7781
7782        let mut quads = Vec::new();
7783        while let Some(mut pixel_range) = pixel_ranges.next() {
7784            while let Some(next_pixel_range) = pixel_ranges.peek() {
7785                if pixel_range.end >= next_pixel_range.start - px(1.0)
7786                    && pixel_range.color == next_pixel_range.color
7787                {
7788                    pixel_range.end = next_pixel_range.end.max(pixel_range.end);
7789                    pixel_ranges.next();
7790                } else {
7791                    break;
7792                }
7793            }
7794
7795            let bounds = Bounds::from_corners(
7796                point(x_range.start, pixel_range.start),
7797                point(x_range.end, pixel_range.end),
7798            );
7799            quads.push(quad(
7800                bounds,
7801                Corners::default(),
7802                pixel_range.color,
7803                Edges::default(),
7804                Hsla::transparent_black(),
7805            ));
7806        }
7807
7808        quads
7809    }
7810}
7811
7812struct CreaseTrailerLayout {
7813    element: AnyElement,
7814    bounds: Bounds<Pixels>,
7815}
7816
7817pub(crate) struct PositionMap {
7818    pub size: Size<Pixels>,
7819    pub line_height: Pixels,
7820    pub scroll_pixel_position: gpui::Point<Pixels>,
7821    pub scroll_max: gpui::Point<f32>,
7822    pub em_width: Pixels,
7823    pub em_advance: Pixels,
7824    pub line_layouts: Vec<LineWithInvisibles>,
7825    pub snapshot: EditorSnapshot,
7826    pub text_hitbox: Hitbox,
7827    pub gutter_hitbox: Hitbox,
7828}
7829
7830#[derive(Debug, Copy, Clone)]
7831pub struct PointForPosition {
7832    pub previous_valid: DisplayPoint,
7833    pub next_valid: DisplayPoint,
7834    pub exact_unclipped: DisplayPoint,
7835    pub column_overshoot_after_line_end: u32,
7836}
7837
7838impl PointForPosition {
7839    pub fn as_valid(&self) -> Option<DisplayPoint> {
7840        if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
7841            Some(self.previous_valid)
7842        } else {
7843            None
7844        }
7845    }
7846}
7847
7848impl PositionMap {
7849    pub(crate) fn point_for_position(&self, position: gpui::Point<Pixels>) -> PointForPosition {
7850        let text_bounds = self.text_hitbox.bounds;
7851        let scroll_position = self.snapshot.scroll_position();
7852        let position = position - text_bounds.origin;
7853        let y = position.y.max(px(0.)).min(self.size.height);
7854        let x = position.x + (scroll_position.x * self.em_width);
7855        let row = ((y / self.line_height) + scroll_position.y) as u32;
7856
7857        let (column, x_overshoot_after_line_end) = if let Some(line) = self
7858            .line_layouts
7859            .get(row as usize - scroll_position.y as usize)
7860        {
7861            if let Some(ix) = line.index_for_x(x) {
7862                (ix as u32, px(0.))
7863            } else {
7864                (line.len as u32, px(0.).max(x - line.width))
7865            }
7866        } else {
7867            (0, x)
7868        };
7869
7870        let mut exact_unclipped = DisplayPoint::new(DisplayRow(row), column);
7871        let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
7872        let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
7873
7874        let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
7875        *exact_unclipped.column_mut() += column_overshoot_after_line_end;
7876        PointForPosition {
7877            previous_valid,
7878            next_valid,
7879            exact_unclipped,
7880            column_overshoot_after_line_end,
7881        }
7882    }
7883}
7884
7885struct BlockLayout {
7886    id: BlockId,
7887    row: Option<DisplayRow>,
7888    element: AnyElement,
7889    available_space: Size<AvailableSpace>,
7890    style: BlockStyle,
7891}
7892
7893fn layout_line(
7894    row: DisplayRow,
7895    snapshot: &EditorSnapshot,
7896    style: &EditorStyle,
7897    text_width: Pixels,
7898    is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
7899    window: &mut Window,
7900    cx: &mut App,
7901) -> LineWithInvisibles {
7902    let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
7903    LineWithInvisibles::from_chunks(
7904        chunks,
7905        &style,
7906        MAX_LINE_LEN,
7907        1,
7908        snapshot.mode,
7909        text_width,
7910        is_row_soft_wrapped,
7911        window,
7912        cx,
7913    )
7914    .pop()
7915    .unwrap()
7916}
7917
7918#[derive(Debug)]
7919pub struct IndentGuideLayout {
7920    origin: gpui::Point<Pixels>,
7921    length: Pixels,
7922    single_indent_width: Pixels,
7923    depth: u32,
7924    active: bool,
7925    settings: IndentGuideSettings,
7926}
7927
7928pub struct CursorLayout {
7929    origin: gpui::Point<Pixels>,
7930    block_width: Pixels,
7931    line_height: Pixels,
7932    color: Hsla,
7933    shape: CursorShape,
7934    block_text: Option<ShapedLine>,
7935    cursor_name: Option<AnyElement>,
7936}
7937
7938#[derive(Debug)]
7939pub struct CursorName {
7940    string: SharedString,
7941    color: Hsla,
7942    is_top_row: bool,
7943}
7944
7945impl CursorLayout {
7946    pub fn new(
7947        origin: gpui::Point<Pixels>,
7948        block_width: Pixels,
7949        line_height: Pixels,
7950        color: Hsla,
7951        shape: CursorShape,
7952        block_text: Option<ShapedLine>,
7953    ) -> CursorLayout {
7954        CursorLayout {
7955            origin,
7956            block_width,
7957            line_height,
7958            color,
7959            shape,
7960            block_text,
7961            cursor_name: None,
7962        }
7963    }
7964
7965    pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
7966        Bounds {
7967            origin: self.origin + origin,
7968            size: size(self.block_width, self.line_height),
7969        }
7970    }
7971
7972    fn bounds(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
7973        match self.shape {
7974            CursorShape::Bar => Bounds {
7975                origin: self.origin + origin,
7976                size: size(px(2.0), self.line_height),
7977            },
7978            CursorShape::Block | CursorShape::Hollow => Bounds {
7979                origin: self.origin + origin,
7980                size: size(self.block_width, self.line_height),
7981            },
7982            CursorShape::Underline => Bounds {
7983                origin: self.origin
7984                    + origin
7985                    + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
7986                size: size(self.block_width, px(2.0)),
7987            },
7988        }
7989    }
7990
7991    pub fn layout(
7992        &mut self,
7993        origin: gpui::Point<Pixels>,
7994        cursor_name: Option<CursorName>,
7995        window: &mut Window,
7996        cx: &mut App,
7997    ) {
7998        if let Some(cursor_name) = cursor_name {
7999            let bounds = self.bounds(origin);
8000            let text_size = self.line_height / 1.5;
8001
8002            let name_origin = if cursor_name.is_top_row {
8003                point(bounds.right() - px(1.), bounds.top())
8004            } else {
8005                match self.shape {
8006                    CursorShape::Bar => point(
8007                        bounds.right() - px(2.),
8008                        bounds.top() - text_size / 2. - px(1.),
8009                    ),
8010                    _ => point(
8011                        bounds.right() - px(1.),
8012                        bounds.top() - text_size / 2. - px(1.),
8013                    ),
8014                }
8015            };
8016            let mut name_element = div()
8017                .bg(self.color)
8018                .text_size(text_size)
8019                .px_0p5()
8020                .line_height(text_size + px(2.))
8021                .text_color(cursor_name.color)
8022                .child(cursor_name.string.clone())
8023                .into_any_element();
8024
8025            name_element.prepaint_as_root(name_origin, AvailableSpace::min_size(), window, cx);
8026
8027            self.cursor_name = Some(name_element);
8028        }
8029    }
8030
8031    pub fn paint(&mut self, origin: gpui::Point<Pixels>, window: &mut Window, cx: &mut App) {
8032        let bounds = self.bounds(origin);
8033
8034        //Draw background or border quad
8035        let cursor = if matches!(self.shape, CursorShape::Hollow) {
8036            outline(bounds, self.color)
8037        } else {
8038            fill(bounds, self.color)
8039        };
8040
8041        if let Some(name) = &mut self.cursor_name {
8042            name.paint(window, cx);
8043        }
8044
8045        window.paint_quad(cursor);
8046
8047        if let Some(block_text) = &self.block_text {
8048            block_text
8049                .paint(self.origin + origin, self.line_height, window, cx)
8050                .log_err();
8051        }
8052    }
8053
8054    pub fn shape(&self) -> CursorShape {
8055        self.shape
8056    }
8057}
8058
8059#[derive(Debug)]
8060pub struct HighlightedRange {
8061    pub start_y: Pixels,
8062    pub line_height: Pixels,
8063    pub lines: Vec<HighlightedRangeLine>,
8064    pub color: Hsla,
8065    pub corner_radius: Pixels,
8066}
8067
8068#[derive(Debug)]
8069pub struct HighlightedRangeLine {
8070    pub start_x: Pixels,
8071    pub end_x: Pixels,
8072}
8073
8074impl HighlightedRange {
8075    pub fn paint(&self, bounds: Bounds<Pixels>, window: &mut Window) {
8076        if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
8077            self.paint_lines(self.start_y, &self.lines[0..1], bounds, window);
8078            self.paint_lines(
8079                self.start_y + self.line_height,
8080                &self.lines[1..],
8081                bounds,
8082                window,
8083            );
8084        } else {
8085            self.paint_lines(self.start_y, &self.lines, bounds, window);
8086        }
8087    }
8088
8089    fn paint_lines(
8090        &self,
8091        start_y: Pixels,
8092        lines: &[HighlightedRangeLine],
8093        _bounds: Bounds<Pixels>,
8094        window: &mut Window,
8095    ) {
8096        if lines.is_empty() {
8097            return;
8098        }
8099
8100        let first_line = lines.first().unwrap();
8101        let last_line = lines.last().unwrap();
8102
8103        let first_top_left = point(first_line.start_x, start_y);
8104        let first_top_right = point(first_line.end_x, start_y);
8105
8106        let curve_height = point(Pixels::ZERO, self.corner_radius);
8107        let curve_width = |start_x: Pixels, end_x: Pixels| {
8108            let max = (end_x - start_x) / 2.;
8109            let width = if max < self.corner_radius {
8110                max
8111            } else {
8112                self.corner_radius
8113            };
8114
8115            point(width, Pixels::ZERO)
8116        };
8117
8118        let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
8119        let mut builder = gpui::PathBuilder::fill();
8120        builder.move_to(first_top_right - top_curve_width);
8121        builder.curve_to(first_top_right + curve_height, first_top_right);
8122
8123        let mut iter = lines.iter().enumerate().peekable();
8124        while let Some((ix, line)) = iter.next() {
8125            let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
8126
8127            if let Some((_, next_line)) = iter.peek() {
8128                let next_top_right = point(next_line.end_x, bottom_right.y);
8129
8130                match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
8131                    Ordering::Equal => {
8132                        builder.line_to(bottom_right);
8133                    }
8134                    Ordering::Less => {
8135                        let curve_width = curve_width(next_top_right.x, bottom_right.x);
8136                        builder.line_to(bottom_right - curve_height);
8137                        if self.corner_radius > Pixels::ZERO {
8138                            builder.curve_to(bottom_right - curve_width, bottom_right);
8139                        }
8140                        builder.line_to(next_top_right + curve_width);
8141                        if self.corner_radius > Pixels::ZERO {
8142                            builder.curve_to(next_top_right + curve_height, next_top_right);
8143                        }
8144                    }
8145                    Ordering::Greater => {
8146                        let curve_width = curve_width(bottom_right.x, next_top_right.x);
8147                        builder.line_to(bottom_right - curve_height);
8148                        if self.corner_radius > Pixels::ZERO {
8149                            builder.curve_to(bottom_right + curve_width, bottom_right);
8150                        }
8151                        builder.line_to(next_top_right - curve_width);
8152                        if self.corner_radius > Pixels::ZERO {
8153                            builder.curve_to(next_top_right + curve_height, next_top_right);
8154                        }
8155                    }
8156                }
8157            } else {
8158                let curve_width = curve_width(line.start_x, line.end_x);
8159                builder.line_to(bottom_right - curve_height);
8160                if self.corner_radius > Pixels::ZERO {
8161                    builder.curve_to(bottom_right - curve_width, bottom_right);
8162                }
8163
8164                let bottom_left = point(line.start_x, bottom_right.y);
8165                builder.line_to(bottom_left + curve_width);
8166                if self.corner_radius > Pixels::ZERO {
8167                    builder.curve_to(bottom_left - curve_height, bottom_left);
8168                }
8169            }
8170        }
8171
8172        if first_line.start_x > last_line.start_x {
8173            let curve_width = curve_width(last_line.start_x, first_line.start_x);
8174            let second_top_left = point(last_line.start_x, start_y + self.line_height);
8175            builder.line_to(second_top_left + curve_height);
8176            if self.corner_radius > Pixels::ZERO {
8177                builder.curve_to(second_top_left + curve_width, second_top_left);
8178            }
8179            let first_bottom_left = point(first_line.start_x, second_top_left.y);
8180            builder.line_to(first_bottom_left - curve_width);
8181            if self.corner_radius > Pixels::ZERO {
8182                builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
8183            }
8184        }
8185
8186        builder.line_to(first_top_left + curve_height);
8187        if self.corner_radius > Pixels::ZERO {
8188            builder.curve_to(first_top_left + top_curve_width, first_top_left);
8189        }
8190        builder.line_to(first_top_right - top_curve_width);
8191
8192        if let Ok(path) = builder.build() {
8193            window.paint_path(path, self.color);
8194        }
8195    }
8196}
8197
8198enum CursorPopoverType {
8199    CodeContextMenu,
8200    EditPrediction,
8201}
8202
8203pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
8204    (delta.pow(1.5) / 100.0).into()
8205}
8206
8207fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
8208    (delta.pow(1.2) / 300.0).into()
8209}
8210
8211pub fn register_action<T: Action>(
8212    editor: &Entity<Editor>,
8213    window: &mut Window,
8214    listener: impl Fn(&mut Editor, &T, &mut Window, &mut Context<Editor>) + 'static,
8215) {
8216    let editor = editor.clone();
8217    window.on_action(TypeId::of::<T>(), move |action, phase, window, cx| {
8218        let action = action.downcast_ref().unwrap();
8219        if phase == DispatchPhase::Bubble {
8220            editor.update(cx, |editor, cx| {
8221                listener(editor, action, window, cx);
8222            })
8223        }
8224    })
8225}
8226
8227fn compute_auto_height_layout(
8228    editor: &mut Editor,
8229    max_lines: usize,
8230    max_line_number_width: Pixels,
8231    known_dimensions: Size<Option<Pixels>>,
8232    available_width: AvailableSpace,
8233    window: &mut Window,
8234    cx: &mut Context<Editor>,
8235) -> Option<Size<Pixels>> {
8236    let width = known_dimensions.width.or({
8237        if let AvailableSpace::Definite(available_width) = available_width {
8238            Some(available_width)
8239        } else {
8240            None
8241        }
8242    })?;
8243    if let Some(height) = known_dimensions.height {
8244        return Some(size(width, height));
8245    }
8246
8247    let style = editor.style.as_ref().unwrap();
8248    let font_id = window.text_system().resolve_font(&style.text.font());
8249    let font_size = style.text.font_size.to_pixels(window.rem_size());
8250    let line_height = style.text.line_height_in_pixels(window.rem_size());
8251    let em_width = window.text_system().em_width(font_id, font_size).unwrap();
8252
8253    let mut snapshot = editor.snapshot(window, cx);
8254    let gutter_dimensions = snapshot
8255        .gutter_dimensions(font_id, font_size, max_line_number_width, cx)
8256        .unwrap_or_default();
8257
8258    editor.gutter_dimensions = gutter_dimensions;
8259    let text_width = width - gutter_dimensions.width;
8260    let overscroll = size(em_width, px(0.));
8261
8262    let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
8263    if editor.set_wrap_width(Some(editor_width), cx) {
8264        snapshot = editor.snapshot(window, cx);
8265    }
8266
8267    let scroll_height = Pixels::from(snapshot.max_point().row().next_row().0) * line_height;
8268    let height = scroll_height
8269        .max(line_height)
8270        .min(line_height * max_lines as f32);
8271
8272    Some(size(width, height))
8273}
8274
8275#[cfg(test)]
8276mod tests {
8277    use super::*;
8278    use crate::{
8279        display_map::{BlockPlacement, BlockProperties},
8280        editor_tests::{init_test, update_test_language_settings},
8281        Editor, MultiBuffer,
8282    };
8283    use gpui::{TestAppContext, VisualTestContext};
8284    use language::language_settings;
8285    use log::info;
8286    use similar::DiffableStr;
8287    use std::num::NonZeroU32;
8288    use util::test::sample_text;
8289
8290    #[gpui::test]
8291    fn test_shape_line_numbers(cx: &mut TestAppContext) {
8292        init_test(cx, |_| {});
8293        let window = cx.add_window(|window, cx| {
8294            let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
8295            Editor::new(EditorMode::Full, buffer, None, true, window, cx)
8296        });
8297
8298        let editor = window.root(cx).unwrap();
8299        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
8300        let line_height = window
8301            .update(cx, |_, window, _| {
8302                style.text.line_height_in_pixels(window.rem_size())
8303            })
8304            .unwrap();
8305        let element = EditorElement::new(&editor, style);
8306        let snapshot = window
8307            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
8308            .unwrap();
8309
8310        let layouts = cx
8311            .update_window(*window, |_, window, cx| {
8312                element.layout_line_numbers(
8313                    None,
8314                    GutterDimensions {
8315                        left_padding: Pixels::ZERO,
8316                        right_padding: Pixels::ZERO,
8317                        width: px(30.0),
8318                        margin: Pixels::ZERO,
8319                        git_blame_entries_width: None,
8320                    },
8321                    line_height,
8322                    gpui::Point::default(),
8323                    DisplayRow(0)..DisplayRow(6),
8324                    &(0..6)
8325                        .map(|row| RowInfo {
8326                            buffer_row: Some(row),
8327                            ..Default::default()
8328                        })
8329                        .collect::<Vec<_>>(),
8330                    Some(DisplayPoint::new(DisplayRow(0), 0)),
8331                    &snapshot,
8332                    window,
8333                    cx,
8334                )
8335            })
8336            .unwrap();
8337        assert_eq!(layouts.len(), 6);
8338
8339        let relative_rows = window
8340            .update(cx, |editor, window, cx| {
8341                let snapshot = editor.snapshot(window, cx);
8342                element.calculate_relative_line_numbers(
8343                    &snapshot,
8344                    &(DisplayRow(0)..DisplayRow(6)),
8345                    Some(DisplayRow(3)),
8346                )
8347            })
8348            .unwrap();
8349        assert_eq!(relative_rows[&DisplayRow(0)], 3);
8350        assert_eq!(relative_rows[&DisplayRow(1)], 2);
8351        assert_eq!(relative_rows[&DisplayRow(2)], 1);
8352        // current line has no relative number
8353        assert_eq!(relative_rows[&DisplayRow(4)], 1);
8354        assert_eq!(relative_rows[&DisplayRow(5)], 2);
8355
8356        // works if cursor is before screen
8357        let relative_rows = window
8358            .update(cx, |editor, window, cx| {
8359                let snapshot = editor.snapshot(window, cx);
8360                element.calculate_relative_line_numbers(
8361                    &snapshot,
8362                    &(DisplayRow(3)..DisplayRow(6)),
8363                    Some(DisplayRow(1)),
8364                )
8365            })
8366            .unwrap();
8367        assert_eq!(relative_rows.len(), 3);
8368        assert_eq!(relative_rows[&DisplayRow(3)], 2);
8369        assert_eq!(relative_rows[&DisplayRow(4)], 3);
8370        assert_eq!(relative_rows[&DisplayRow(5)], 4);
8371
8372        // works if cursor is after screen
8373        let relative_rows = window
8374            .update(cx, |editor, window, cx| {
8375                let snapshot = editor.snapshot(window, cx);
8376                element.calculate_relative_line_numbers(
8377                    &snapshot,
8378                    &(DisplayRow(0)..DisplayRow(3)),
8379                    Some(DisplayRow(6)),
8380                )
8381            })
8382            .unwrap();
8383        assert_eq!(relative_rows.len(), 3);
8384        assert_eq!(relative_rows[&DisplayRow(0)], 5);
8385        assert_eq!(relative_rows[&DisplayRow(1)], 4);
8386        assert_eq!(relative_rows[&DisplayRow(2)], 3);
8387    }
8388
8389    #[gpui::test]
8390    async fn test_vim_visual_selections(cx: &mut TestAppContext) {
8391        init_test(cx, |_| {});
8392
8393        let window = cx.add_window(|window, cx| {
8394            let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
8395            Editor::new(EditorMode::Full, buffer, None, true, window, cx)
8396        });
8397        let cx = &mut VisualTestContext::from_window(*window, cx);
8398        let editor = window.root(cx).unwrap();
8399        let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
8400
8401        window
8402            .update(cx, |editor, window, cx| {
8403                editor.cursor_shape = CursorShape::Block;
8404                editor.change_selections(None, window, cx, |s| {
8405                    s.select_ranges([
8406                        Point::new(0, 0)..Point::new(1, 0),
8407                        Point::new(3, 2)..Point::new(3, 3),
8408                        Point::new(5, 6)..Point::new(6, 0),
8409                    ]);
8410                });
8411            })
8412            .unwrap();
8413
8414        let (_, state) = cx.draw(
8415            point(px(500.), px(500.)),
8416            size(px(500.), px(500.)),
8417            |_, _| EditorElement::new(&editor, style),
8418        );
8419
8420        assert_eq!(state.selections.len(), 1);
8421        let local_selections = &state.selections[0].1;
8422        assert_eq!(local_selections.len(), 3);
8423        // moves cursor back one line
8424        assert_eq!(
8425            local_selections[0].head,
8426            DisplayPoint::new(DisplayRow(0), 6)
8427        );
8428        assert_eq!(
8429            local_selections[0].range,
8430            DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0)
8431        );
8432
8433        // moves cursor back one column
8434        assert_eq!(
8435            local_selections[1].range,
8436            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 3)
8437        );
8438        assert_eq!(
8439            local_selections[1].head,
8440            DisplayPoint::new(DisplayRow(3), 2)
8441        );
8442
8443        // leaves cursor on the max point
8444        assert_eq!(
8445            local_selections[2].range,
8446            DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(6), 0)
8447        );
8448        assert_eq!(
8449            local_selections[2].head,
8450            DisplayPoint::new(DisplayRow(6), 0)
8451        );
8452
8453        // active lines does not include 1 (even though the range of the selection does)
8454        assert_eq!(
8455            state.active_rows.keys().cloned().collect::<Vec<_>>(),
8456            vec![DisplayRow(0), DisplayRow(3), DisplayRow(5), DisplayRow(6)]
8457        );
8458
8459        // multi-buffer support
8460        // in DisplayPoint coordinates, this is what we're dealing with:
8461        //  0: [[file
8462        //  1:   header
8463        //  2:   section]]
8464        //  3: aaaaaa
8465        //  4: bbbbbb
8466        //  5: cccccc
8467        //  6:
8468        //  7: [[footer]]
8469        //  8: [[header]]
8470        //  9: ffffff
8471        // 10: gggggg
8472        // 11: hhhhhh
8473        // 12:
8474        // 13: [[footer]]
8475        // 14: [[file
8476        // 15:   header
8477        // 16:   section]]
8478        // 17: bbbbbb
8479        // 18: cccccc
8480        // 19: dddddd
8481        // 20: [[footer]]
8482        let window = cx.add_window(|window, cx| {
8483            let buffer = MultiBuffer::build_multi(
8484                [
8485                    (
8486                        &(sample_text(8, 6, 'a') + "\n"),
8487                        vec![
8488                            Point::new(0, 0)..Point::new(3, 0),
8489                            Point::new(4, 0)..Point::new(7, 0),
8490                        ],
8491                    ),
8492                    (
8493                        &(sample_text(8, 6, 'a') + "\n"),
8494                        vec![Point::new(1, 0)..Point::new(3, 0)],
8495                    ),
8496                ],
8497                cx,
8498            );
8499            Editor::new(EditorMode::Full, buffer, None, true, window, cx)
8500        });
8501        let editor = window.root(cx).unwrap();
8502        let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
8503        let _state = window.update(cx, |editor, window, cx| {
8504            editor.cursor_shape = CursorShape::Block;
8505            editor.change_selections(None, window, cx, |s| {
8506                s.select_display_ranges([
8507                    DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0),
8508                    DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0),
8509                ]);
8510            });
8511        });
8512
8513        let (_, state) = cx.draw(
8514            point(px(500.), px(500.)),
8515            size(px(500.), px(500.)),
8516            |_, _| EditorElement::new(&editor, style),
8517        );
8518        assert_eq!(state.selections.len(), 1);
8519        let local_selections = &state.selections[0].1;
8520        assert_eq!(local_selections.len(), 2);
8521
8522        // moves cursor on excerpt boundary back a line
8523        // and doesn't allow selection to bleed through
8524        assert_eq!(
8525            local_selections[0].range,
8526            DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(7), 0)
8527        );
8528        assert_eq!(
8529            local_selections[0].head,
8530            DisplayPoint::new(DisplayRow(6), 0)
8531        );
8532        // moves cursor on buffer boundary back two lines
8533        // and doesn't allow selection to bleed through
8534        assert_eq!(
8535            local_selections[1].range,
8536            DisplayPoint::new(DisplayRow(10), 0)..DisplayPoint::new(DisplayRow(13), 0)
8537        );
8538        assert_eq!(
8539            local_selections[1].head,
8540            DisplayPoint::new(DisplayRow(12), 0)
8541        );
8542    }
8543
8544    #[gpui::test]
8545    fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
8546        init_test(cx, |_| {});
8547
8548        let window = cx.add_window(|window, cx| {
8549            let buffer = MultiBuffer::build_simple("", cx);
8550            Editor::new(EditorMode::Full, buffer, None, true, window, cx)
8551        });
8552        let cx = &mut VisualTestContext::from_window(*window, cx);
8553        let editor = window.root(cx).unwrap();
8554        let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
8555        window
8556            .update(cx, |editor, window, cx| {
8557                editor.set_placeholder_text("hello", cx);
8558                editor.insert_blocks(
8559                    [BlockProperties {
8560                        style: BlockStyle::Fixed,
8561                        placement: BlockPlacement::Above(Anchor::min()),
8562                        height: 3,
8563                        render: Arc::new(|cx| div().h(3. * cx.window.line_height()).into_any()),
8564                        priority: 0,
8565                    }],
8566                    None,
8567                    cx,
8568                );
8569
8570                // Blur the editor so that it displays placeholder text.
8571                window.blur();
8572            })
8573            .unwrap();
8574
8575        let (_, state) = cx.draw(
8576            point(px(500.), px(500.)),
8577            size(px(500.), px(500.)),
8578            |_, _| EditorElement::new(&editor, style),
8579        );
8580        assert_eq!(state.position_map.line_layouts.len(), 4);
8581        assert_eq!(state.line_numbers.len(), 1);
8582        assert_eq!(
8583            state
8584                .line_numbers
8585                .get(&MultiBufferRow(0))
8586                .and_then(|line_number| line_number.shaped_line.text.as_str()),
8587            Some("1")
8588        );
8589    }
8590
8591    #[gpui::test]
8592    fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
8593        const TAB_SIZE: u32 = 4;
8594
8595        let input_text = "\t \t|\t| a b";
8596        let expected_invisibles = vec![
8597            Invisible::Tab {
8598                line_start_offset: 0,
8599                line_end_offset: TAB_SIZE as usize,
8600            },
8601            Invisible::Whitespace {
8602                line_offset: TAB_SIZE as usize,
8603            },
8604            Invisible::Tab {
8605                line_start_offset: TAB_SIZE as usize + 1,
8606                line_end_offset: TAB_SIZE as usize * 2,
8607            },
8608            Invisible::Tab {
8609                line_start_offset: TAB_SIZE as usize * 2 + 1,
8610                line_end_offset: TAB_SIZE as usize * 3,
8611            },
8612            Invisible::Whitespace {
8613                line_offset: TAB_SIZE as usize * 3 + 1,
8614            },
8615            Invisible::Whitespace {
8616                line_offset: TAB_SIZE as usize * 3 + 3,
8617            },
8618        ];
8619        assert_eq!(
8620            expected_invisibles.len(),
8621            input_text
8622                .chars()
8623                .filter(|initial_char| initial_char.is_whitespace())
8624                .count(),
8625            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
8626        );
8627
8628        for show_line_numbers in [true, false] {
8629            init_test(cx, |s| {
8630                s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
8631                s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
8632            });
8633
8634            let actual_invisibles = collect_invisibles_from_new_editor(
8635                cx,
8636                EditorMode::Full,
8637                input_text,
8638                px(500.0),
8639                show_line_numbers,
8640            );
8641
8642            assert_eq!(expected_invisibles, actual_invisibles);
8643        }
8644    }
8645
8646    #[gpui::test]
8647    fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
8648        init_test(cx, |s| {
8649            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
8650            s.defaults.tab_size = NonZeroU32::new(4);
8651        });
8652
8653        for editor_mode_without_invisibles in [
8654            EditorMode::SingleLine { auto_width: false },
8655            EditorMode::AutoHeight { max_lines: 100 },
8656        ] {
8657            for show_line_numbers in [true, false] {
8658                let invisibles = collect_invisibles_from_new_editor(
8659                    cx,
8660                    editor_mode_without_invisibles,
8661                    "\t\t\t| | a b",
8662                    px(500.0),
8663                    show_line_numbers,
8664                );
8665                assert!(invisibles.is_empty(),
8666                    "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
8667            }
8668        }
8669    }
8670
8671    #[gpui::test]
8672    fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
8673        let tab_size = 4;
8674        let input_text = "a\tbcd     ".repeat(9);
8675        let repeated_invisibles = [
8676            Invisible::Tab {
8677                line_start_offset: 1,
8678                line_end_offset: tab_size as usize,
8679            },
8680            Invisible::Whitespace {
8681                line_offset: tab_size as usize + 3,
8682            },
8683            Invisible::Whitespace {
8684                line_offset: tab_size as usize + 4,
8685            },
8686            Invisible::Whitespace {
8687                line_offset: tab_size as usize + 5,
8688            },
8689            Invisible::Whitespace {
8690                line_offset: tab_size as usize + 6,
8691            },
8692            Invisible::Whitespace {
8693                line_offset: tab_size as usize + 7,
8694            },
8695        ];
8696        let expected_invisibles = std::iter::once(repeated_invisibles)
8697            .cycle()
8698            .take(9)
8699            .flatten()
8700            .collect::<Vec<_>>();
8701        assert_eq!(
8702            expected_invisibles.len(),
8703            input_text
8704                .chars()
8705                .filter(|initial_char| initial_char.is_whitespace())
8706                .count(),
8707            "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
8708        );
8709        info!("Expected invisibles: {expected_invisibles:?}");
8710
8711        init_test(cx, |_| {});
8712
8713        // Put the same string with repeating whitespace pattern into editors of various size,
8714        // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
8715        let resize_step = 10.0;
8716        let mut editor_width = 200.0;
8717        while editor_width <= 1000.0 {
8718            for show_line_numbers in [true, false] {
8719                update_test_language_settings(cx, |s| {
8720                    s.defaults.tab_size = NonZeroU32::new(tab_size);
8721                    s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
8722                    s.defaults.preferred_line_length = Some(editor_width as u32);
8723                    s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
8724                });
8725
8726                let actual_invisibles = collect_invisibles_from_new_editor(
8727                    cx,
8728                    EditorMode::Full,
8729                    &input_text,
8730                    px(editor_width),
8731                    show_line_numbers,
8732                );
8733
8734                // Whatever the editor size is, ensure it has the same invisible kinds in the same order
8735                // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
8736                let mut i = 0;
8737                for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
8738                    i = actual_index;
8739                    match expected_invisibles.get(i) {
8740                        Some(expected_invisible) => match (expected_invisible, actual_invisible) {
8741                            (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
8742                            | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
8743                            _ => {
8744                                panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
8745                            }
8746                        },
8747                        None => {
8748                            panic!("Unexpected extra invisible {actual_invisible:?} at index {i}")
8749                        }
8750                    }
8751                }
8752                let missing_expected_invisibles = &expected_invisibles[i + 1..];
8753                assert!(
8754                    missing_expected_invisibles.is_empty(),
8755                    "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
8756                );
8757
8758                editor_width += resize_step;
8759            }
8760        }
8761    }
8762
8763    fn collect_invisibles_from_new_editor(
8764        cx: &mut TestAppContext,
8765        editor_mode: EditorMode,
8766        input_text: &str,
8767        editor_width: Pixels,
8768        show_line_numbers: bool,
8769    ) -> Vec<Invisible> {
8770        info!(
8771            "Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
8772            editor_width.0
8773        );
8774        let window = cx.add_window(|window, cx| {
8775            let buffer = MultiBuffer::build_simple(input_text, cx);
8776            Editor::new(editor_mode, buffer, None, true, window, cx)
8777        });
8778        let cx = &mut VisualTestContext::from_window(*window, cx);
8779        let editor = window.root(cx).unwrap();
8780
8781        let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
8782        window
8783            .update(cx, |editor, _, cx| {
8784                editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
8785                editor.set_wrap_width(Some(editor_width), cx);
8786                editor.set_show_line_numbers(show_line_numbers, cx);
8787            })
8788            .unwrap();
8789        let (_, state) = cx.draw(
8790            point(px(500.), px(500.)),
8791            size(px(500.), px(500.)),
8792            |_, _| EditorElement::new(&editor, style),
8793        );
8794        state
8795            .position_map
8796            .line_layouts
8797            .iter()
8798            .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles)
8799            .cloned()
8800            .collect()
8801    }
8802}
8803
8804fn diff_hunk_controls(
8805    row: u32,
8806    hunk_range: Range<Anchor>,
8807    line_height: Pixels,
8808    editor: &Entity<Editor>,
8809    cx: &mut App,
8810) -> AnyElement {
8811    h_flex()
8812        .h(line_height)
8813        .mr_1()
8814        .gap_1()
8815        .px_1()
8816        .pb_1()
8817        .border_b_1()
8818        .border_color(cx.theme().colors().border_variant)
8819        .rounded_b_lg()
8820        .bg(cx.theme().colors().editor_background)
8821        .gap_1()
8822        .child(
8823            IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
8824                .shape(IconButtonShape::Square)
8825                .icon_size(IconSize::Small)
8826                // .disabled(!has_multiple_hunks)
8827                .tooltip({
8828                    let focus_handle = editor.focus_handle(cx);
8829                    move |window, cx| {
8830                        Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, window, cx)
8831                    }
8832                })
8833                .on_click({
8834                    let editor = editor.clone();
8835                    move |_event, window, cx| {
8836                        editor.update(cx, |editor, cx| {
8837                            let snapshot = editor.snapshot(window, cx);
8838                            let position = hunk_range.end.to_point(&snapshot.buffer_snapshot);
8839                            editor.go_to_hunk_after_position(&snapshot, position, window, cx);
8840                            editor.expand_selected_diff_hunks(cx);
8841                        });
8842                    }
8843                }),
8844        )
8845        .child(
8846            IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
8847                .shape(IconButtonShape::Square)
8848                .icon_size(IconSize::Small)
8849                // .disabled(!has_multiple_hunks)
8850                .tooltip({
8851                    let focus_handle = editor.focus_handle(cx);
8852                    move |window, cx| {
8853                        Tooltip::for_action_in(
8854                            "Previous Hunk",
8855                            &GoToPrevHunk,
8856                            &focus_handle,
8857                            window,
8858                            cx,
8859                        )
8860                    }
8861                })
8862                .on_click({
8863                    let editor = editor.clone();
8864                    move |_event, window, cx| {
8865                        editor.update(cx, |editor, cx| {
8866                            let snapshot = editor.snapshot(window, cx);
8867                            let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
8868                            editor.go_to_hunk_before_position(&snapshot, point, window, cx);
8869                            editor.expand_selected_diff_hunks(cx);
8870                        });
8871                    }
8872                }),
8873        )
8874        .child(
8875            IconButton::new("discard", IconName::Undo)
8876                .shape(IconButtonShape::Square)
8877                .icon_size(IconSize::Small)
8878                .tooltip({
8879                    let focus_handle = editor.focus_handle(cx);
8880                    move |window, cx| {
8881                        Tooltip::for_action_in(
8882                            "Discard Hunk",
8883                            &RevertSelectedHunks,
8884                            &focus_handle,
8885                            window,
8886                            cx,
8887                        )
8888                    }
8889                })
8890                .on_click({
8891                    let editor = editor.clone();
8892                    move |_event, window, cx| {
8893                        editor.update(cx, |editor, cx| {
8894                            let snapshot = editor.snapshot(window, cx);
8895                            let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
8896                            editor.revert_hunks_in_ranges([point..point].into_iter(), window, cx);
8897                        });
8898                    }
8899                }),
8900        )
8901        .into_any_element()
8902}