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