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