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