element.rs

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